diff --git a/DotnetCliVersion.txt b/DotnetCliVersion.txt index ee186940..f881273c 100644 --- a/DotnetCliVersion.txt +++ b/DotnetCliVersion.txt @@ -1 +1 @@ -5.0.402 \ No newline at end of file +6.0.100 \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index f0592e7d..2bbf7d9c 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -22,414 +22,395 @@ using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.IO.PathConstruction; using static Nuke.Common.Tools.DotNet.DotNetTasks; -namespace LibHacBuild +namespace LibHacBuild; + +partial class Build : NukeBuild { - partial class Build : NukeBuild + public static int Main() => Execute(x => x.Standard); + + [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] + public readonly string Configuration = IsLocalBuild ? "Debug" : "Release"; + + [Parameter("Don't enable any size-reducing settings on native builds.")] + public readonly bool Untrimmed; + + [Parameter("Disable reflection in native builds.")] + public readonly bool NoReflection; + + [Solution("LibHac.sln")] readonly Solution _solution; + + AbsolutePath SourceDirectory => RootDirectory / "src"; + AbsolutePath TestsDirectory => RootDirectory / "tests"; + AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; + AbsolutePath SignedArtifactsDirectory => ArtifactsDirectory / "signed"; + AbsolutePath TempDirectory => RootDirectory / ".tmp"; + AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_net5.0"; + AbsolutePath CliNativeDir => TempDirectory / $"hactoolnet_{HostOsName}"; + AbsolutePath CliNativeExe => CliNativeDir / $"hactoolnet{NativeProgramExtension}"; + AbsolutePath CliCoreZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-netcore.zip"; + AbsolutePath CliNativeZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-{HostOsName}.zip"; + + Project LibHacProject => _solution.GetProject("LibHac").NotNull(); + Project LibHacTestProject => _solution.GetProject("LibHac.Tests").NotNull(); + Project HactoolnetProject => _solution.GetProject("hactoolnet").NotNull(); + + Project CodeGenProject => _solution.GetProject("_buildCodeGen").NotNull(); + + private bool HasGitDir { get; set; } + + private string NativeRuntime { get; set; } + private string HostOsName { get; set; } + private string NativeProgramExtension { get; set; } + + string VersionString { get; set; } + Dictionary VersionProps { get; set; } = new Dictionary(); + + const string CertFileName = "cert.pfx"; + + public Build() { - public static int Main() => Execute(x => x.Standard); - - [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] - public readonly string Configuration = IsLocalBuild ? "Debug" : "Release"; - - [Parameter("Don't enable any size-reducing settings on native builds.")] - public readonly bool Untrimmed; - - [Parameter("Disable reflection in native builds.")] - public readonly bool NoReflection; - - [Solution("LibHac.sln")] readonly Solution _solution; - - AbsolutePath SourceDirectory => RootDirectory / "src"; - AbsolutePath TestsDirectory => RootDirectory / "tests"; - AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; - AbsolutePath SignedArtifactsDirectory => ArtifactsDirectory / "signed"; - AbsolutePath TempDirectory => RootDirectory / ".tmp"; - AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_net5.0"; - AbsolutePath CliNativeDir => TempDirectory / $"hactoolnet_{HostOsName}"; - AbsolutePath CliNativeExe => CliNativeDir / $"hactoolnet{NativeProgramExtension}"; - AbsolutePath CliCoreZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-netcore.zip"; - AbsolutePath CliNativeZip => ArtifactsDirectory / $"hactoolnet-{VersionString}-{HostOsName}.zip"; - - Project LibHacProject => _solution.GetProject("LibHac").NotNull(); - Project LibHacTestProject => _solution.GetProject("LibHac.Tests").NotNull(); - Project HactoolnetProject => _solution.GetProject("hactoolnet").NotNull(); - - Project CodeGenProject => _solution.GetProject("_buildCodeGen").NotNull(); - - private bool HasGitDir { get; set; } - - private string NativeRuntime { get; set; } - private string HostOsName { get; set; } - private string NativeProgramExtension { get; set; } - - string VersionString { get; set; } - Dictionary VersionProps { get; set; } = new Dictionary(); - - const string CertFileName = "cert.pfx"; - - public Build() + if (EnvironmentInfo.IsWin) { - if (EnvironmentInfo.IsWin) - { - NativeRuntime = "win-x64"; - NativeProgramExtension = ".exe"; - HostOsName = "win"; - } - else if (EnvironmentInfo.IsLinux) - { - NativeRuntime = "linux-x64"; - NativeProgramExtension = ""; - HostOsName = "linux"; - } - else if (EnvironmentInfo.IsOsx) - { - NativeRuntime = "osx-x64"; - NativeProgramExtension = ""; - HostOsName = "macos"; - } + NativeRuntime = "win-x64"; + NativeProgramExtension = ".exe"; + HostOsName = "win"; } - - Target SetVersion => _ => _ - .Executes(() => - { - GitRepository gitRepository = null; - GitVersion gitVersion = null; - - try - { - gitRepository = (GitRepository)new GitRepositoryAttribute().GetValue(null, null); - - gitVersion = GitVersionTasks.GitVersion(s => s - .SetFramework("net5.0") - .DisableProcessLogOutput()) - .Result; - } - catch (Exception e) - { - if (!e.Message.Contains("not a git repository", StringComparison.OrdinalIgnoreCase)) - { - Logger.Error(e); - } - } - - if (gitRepository == null || gitVersion == null) - { - Logger.Normal("Unable to read Git version."); - - VersionString = GetCsprojVersion(); - Logger.Normal($"Using version from .csproj: {VersionString}"); - - return; - } - - HasGitDir = true; - - VersionString = $"{gitVersion.MajorMinorPatch}"; - if (!string.IsNullOrWhiteSpace(gitVersion.PreReleaseTag)) - { - VersionString += $"-{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)}"; - } - - if (Host == HostType.AppVeyor) - { - // Workaround GitVersion issue by getting PR info manually https://github.com/GitTools/GitVersion/issues/1927 - string prNumber = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER"); - string branchName = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"); - - if (int.TryParse(prNumber, out int prInt) && branchName != null) - { - string prString = $"PullRequest{prInt:D4}"; - - VersionString = VersionString.Replace(branchName, prString); - suffix = suffix.Replace(branchName, prString); - } - - SetAppVeyorVersion(VersionString); - } - - VersionProps = new Dictionary - { - ["VersionPrefix"] = gitVersion.AssemblySemVer, - ["VersionSuffix"] = suffix - }; - - Logger.Normal($"Building version {VersionString}"); - }); - - Target Clean => _ => _ - .Executes(() => - { - List toDelete = GlobDirectories(SourceDirectory, "**/bin", "**/obj") - .Concat(GlobDirectories(TestsDirectory, "**/bin", "**/obj")).ToList(); - - foreach (string dir in toDelete) - { - DeleteDirectory(dir); - } - - EnsureCleanDirectory(ArtifactsDirectory); - EnsureCleanDirectory(CliCoreDir); - EnsureCleanDirectory(CliNativeDir); - }); - - Target Restore => _ => _ - .DependsOn(Clean) - .Executes(() => - { - DotNetRestoreSettings settings = new DotNetRestoreSettings() - .SetProjectFile(_solution); - - DotNetRestore(s => settings); - }); - - Target Codegen => _ => _ - .Executes(() => - { - ResultCodeGen.Run(); - RunCodegenStage2(); - }); - - Target Compile => _ => _ - .DependsOn(Restore, SetVersion, Codegen) - .Executes(() => - { - DotNetBuildSettings buildSettings = new DotNetBuildSettings() - .SetProjectFile(_solution) - .EnableNoRestore() - .SetConfiguration(Configuration) - .SetProperties(VersionProps) - .SetProperty("BuildType", "Release") - .SetProperty("HasGitDir", HasGitDir); - - DotNetBuild(s => buildSettings); - - DotNetPublishSettings publishSettings = new DotNetPublishSettings() - .EnableNoRestore() - .SetConfiguration(Configuration); - - DotNetPublish(s => publishSettings - .SetProject(HactoolnetProject) - .SetFramework("net5.0") - .SetOutput(CliCoreDir) - .SetNoBuild(true) - .SetProperties(VersionProps)); - - // Hack around OS newline differences - if (EnvironmentInfo.IsUnix) - { - foreach (string filename in Directory.EnumerateFiles(CliCoreDir, "*.json")) - { - ReplaceLineEndings(filename); - } - } - }); - - Target Pack => _ => _ - .DependsOn(Compile) - .Executes(() => - { - DotNetPackSettings settings = new DotNetPackSettings() - .SetProject(LibHacProject) - .EnableNoBuild() - .SetConfiguration(Configuration) - .EnableIncludeSymbols() - .SetSymbolPackageFormat(DotNetSymbolPackageFormat.snupkg) - .SetOutputDirectory(ArtifactsDirectory) - .SetProperties(VersionProps); - - DotNetPack(s => settings); - - foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg")) - { - RepackNugetPackage(filename); - } - - if (Host != HostType.AppVeyor) return; - - foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg")) - { - PushArtifact(filename); - } - }); - - Target Test => _ => _ - .DependsOn(Compile) - .Executes(() => - { - DotNetTestSettings settings = new DotNetTestSettings() - .SetProjectFile(LibHacTestProject) - .EnableNoBuild() - .SetConfiguration(Configuration); - - if (EnvironmentInfo.IsUnix) settings = settings.SetProperty("TargetFramework", "net5.0"); - - DotNetTest(s => settings); - }); - - Target Zip => _ => _ - .DependsOn(Pack) - .After(Native) - .Executes(() => - { - string[] namesCore = Directory.EnumerateFiles(CliCoreDir, "*.json") - .Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll")) - .ToArray(); - - EnsureExistingDirectory(ArtifactsDirectory); - - ZipFiles(CliCoreZip, namesCore); - Logger.Normal($"Created {CliCoreZip}"); - - if (Host == HostType.AppVeyor) - { - PushArtifact(CliCoreZip); - } - }); - - Target Publish => _ => _ - .DependsOn(Test, Pack) - .OnlyWhenStatic(() => AppVeyor.Instance != null && AppVeyor.Instance.PullRequestTitle == null) - .Executes(() => - { - AbsolutePath nupkgFile = ArtifactsDirectory.GlobFiles("*.nupkg").Single(); - - string apiKey = EnvironmentInfo.GetVariable("myget_api_key"); - DotNetNuGetPushSettings settings = new DotNetNuGetPushSettings() - .SetApiKey(apiKey) - .SetSource("https://www.myget.org/F/libhac/api/v3/index.json"); - - DotNetNuGetPush(settings.SetTargetPath(nupkgFile)); - }); - - Target Sign => _ => _ - .DependsOn(Test, Zip) - .OnlyWhenStatic(() => File.Exists(CertFileName)) - .OnlyWhenStatic(() => EnvironmentInfo.IsWin) - .Executes(() => - { - string pwd = ReadPassword(); - - if (pwd == string.Empty) - { - Logger.Normal("Skipping sign task"); - return; - } - - SignAndReZip(pwd); - }); - - Target Native => _ => _ - .DependsOn(SetVersion) - .After(Compile) - .Executes(BuildNative); - - // ReSharper disable once UnusedMember.Local - Target AppVeyorBuild => _ => _ - .DependsOn(Zip, Native, Publish) - .Unlisted() - .Executes(PrintResults); - - Target Standard => _ => _ - .DependsOn(Test, Zip) - .Executes(PrintResults); - - // ReSharper disable once UnusedMember.Local - Target Full => _ => _ - .DependsOn(Sign, Native) - .Executes(PrintResults); - - public void PrintResults() + else if (EnvironmentInfo.IsLinux) { - Logger.Normal("SHA-1:"); - using (var 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)}"); - } - } - } + NativeRuntime = "linux-x64"; + NativeProgramExtension = ""; + HostOsName = "linux"; } - - public void BuildNative() + else if (EnvironmentInfo.IsOsx) { - string buildType = Untrimmed ? "native-untrimmed" : "native"; + NativeRuntime = "osx-x64"; + NativeProgramExtension = ""; + HostOsName = "macos"; + } + } - if (NoReflection) + Target SetVersion => _ => _ + .Executes(() => + { + GitRepository gitRepository = null; + GitVersion gitVersion = null; + + try { - buildType = "native-noreflection"; + gitRepository = (GitRepository)new GitRepositoryAttribute().GetValue(null, null); + + gitVersion = GitVersionTasks.GitVersion(s => s + .SetFramework("net5.0") + .DisableProcessLogOutput()) + .Result; + } + catch (Exception e) + { + if (!e.Message.Contains("not a git repository", StringComparison.OrdinalIgnoreCase)) + { + Logger.Error(e); + } } - DotNetPublishSettings publishSettings = new DotNetPublishSettings() - .SetConfiguration(Configuration) - .SetProject(HactoolnetProject) - .SetRuntime(NativeRuntime) - .SetOutput(CliNativeDir) - .SetProperties(VersionProps) - .AddProperty("BuildType", buildType); - - DotNetPublish(publishSettings); - - if (EnvironmentInfo.IsUnix && !Untrimmed) + if (gitRepository == null || gitVersion == null) { - File.Copy(CliNativeExe, CliNativeExe + "_unstripped", true); - ProcessTasks.StartProcess("strip", CliNativeExe).AssertZeroExitCode(); + Logger.Normal("Unable to read Git version."); + + VersionString = GetCsprojVersion(); + Logger.Normal($"Using version from .csproj: {VersionString}"); + + return; } - EnsureExistingDirectory(ArtifactsDirectory); + HasGitDir = true; - ZipFile(CliNativeZip, CliNativeExe, $"hactoolnet{NativeProgramExtension}"); - Logger.Normal($"Created {CliNativeZip}"); + VersionString = $"{gitVersion.MajorMinorPatch}"; + if (!string.IsNullOrWhiteSpace(gitVersion.PreReleaseTag)) + { + VersionString += $"-{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)}"; + } if (Host == HostType.AppVeyor) { - PushArtifact(CliNativeZip); - } - } + // Workaround GitVersion issue by getting PR info manually https://github.com/GitTools/GitVersion/issues/1927 + string prNumber = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER"); + string branchName = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"); - public static void ZipFiles(string outFile, IEnumerable files) - { - using (var s = new ZipOutputStream(File.Create(outFile))) - { - s.SetLevel(9); - - foreach (string file in files) + if (int.TryParse(prNumber, out int prInt) && branchName != null) { - var entry = new ZipEntry(Path.GetFileName(file)); - entry.DateTime = DateTime.UnixEpoch; + string prString = $"PullRequest{prInt:D4}"; - using (FileStream fs = File.OpenRead(file)) - { - entry.Size = fs.Length; - s.PutNextEntry(entry); - fs.CopyTo(s); - } + VersionString = VersionString.Replace(branchName, prString); + suffix = suffix.Replace(branchName, prString); + } + + SetAppVeyorVersion(VersionString); + } + + VersionProps = new Dictionary + { + ["VersionPrefix"] = gitVersion.AssemblySemVer, + ["VersionSuffix"] = suffix + }; + + Logger.Normal($"Building version {VersionString}"); + }); + + Target Clean => _ => _ + .Executes(() => + { + List toDelete = GlobDirectories(SourceDirectory, "**/bin", "**/obj") + .Concat(GlobDirectories(TestsDirectory, "**/bin", "**/obj")).ToList(); + + foreach (string dir in toDelete) + { + DeleteDirectory(dir); + } + + EnsureCleanDirectory(ArtifactsDirectory); + EnsureCleanDirectory(CliCoreDir); + EnsureCleanDirectory(CliNativeDir); + }); + + Target Restore => _ => _ + .DependsOn(Clean) + .Executes(() => + { + DotNetRestoreSettings settings = new DotNetRestoreSettings() + .SetProjectFile(_solution); + + DotNetRestore(s => settings); + }); + + Target Codegen => _ => _ + .Executes(() => + { + ResultCodeGen.Run(); + RunCodegenStage2(); + }); + + Target Compile => _ => _ + .DependsOn(Restore, SetVersion, Codegen) + .Executes(() => + { + DotNetBuildSettings buildSettings = new DotNetBuildSettings() + .SetProjectFile(_solution) + .EnableNoRestore() + .SetConfiguration(Configuration) + .SetProperties(VersionProps) + .SetProperty("BuildType", "Release") + .SetProperty("HasGitDir", HasGitDir); + + DotNetBuild(s => buildSettings); + + DotNetPublishSettings publishSettings = new DotNetPublishSettings() + .EnableNoRestore() + .SetConfiguration(Configuration); + + DotNetPublish(s => publishSettings + .SetProject(HactoolnetProject) + .SetFramework("net5.0") + .SetOutput(CliCoreDir) + .SetNoBuild(true) + .SetProperties(VersionProps)); + + // Hack around OS newline differences + if (EnvironmentInfo.IsUnix) + { + foreach (string filename in Directory.EnumerateFiles(CliCoreDir, "*.json")) + { + ReplaceLineEndings(filename); + } + } + }); + + Target Pack => _ => _ + .DependsOn(Compile) + .Executes(() => + { + DotNetPackSettings settings = new DotNetPackSettings() + .SetProject(LibHacProject) + .EnableNoBuild() + .SetConfiguration(Configuration) + .EnableIncludeSymbols() + .SetSymbolPackageFormat(DotNetSymbolPackageFormat.snupkg) + .SetOutputDirectory(ArtifactsDirectory) + .SetProperties(VersionProps); + + DotNetPack(s => settings); + + foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg")) + { + RepackNugetPackage(filename); + } + + if (Host != HostType.AppVeyor) return; + + foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.*nupkg")) + { + PushArtifact(filename); + } + }); + + Target Test => _ => _ + .DependsOn(Compile) + .Executes(() => + { + DotNetTestSettings settings = new DotNetTestSettings() + .SetProjectFile(LibHacTestProject) + .EnableNoBuild() + .SetConfiguration(Configuration); + + if (EnvironmentInfo.IsUnix) settings = settings.SetProperty("TargetFramework", "net5.0"); + + DotNetTest(s => settings); + }); + + Target Zip => _ => _ + .DependsOn(Pack) + .After(Native) + .Executes(() => + { + string[] namesCore = Directory.EnumerateFiles(CliCoreDir, "*.json") + .Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll")) + .ToArray(); + + EnsureExistingDirectory(ArtifactsDirectory); + + ZipFiles(CliCoreZip, namesCore); + Logger.Normal($"Created {CliCoreZip}"); + + if (Host == HostType.AppVeyor) + { + PushArtifact(CliCoreZip); + } + }); + + Target Publish => _ => _ + .DependsOn(Test, Pack) + .OnlyWhenStatic(() => AppVeyor.Instance != null && AppVeyor.Instance.PullRequestTitle == null) + .Executes(() => + { + AbsolutePath nupkgFile = ArtifactsDirectory.GlobFiles("*.nupkg").Single(); + + string apiKey = EnvironmentInfo.GetVariable("myget_api_key"); + DotNetNuGetPushSettings settings = new DotNetNuGetPushSettings() + .SetApiKey(apiKey) + .SetSource("https://www.myget.org/F/libhac/api/v3/index.json"); + + DotNetNuGetPush(settings.SetTargetPath(nupkgFile)); + }); + + Target Sign => _ => _ + .DependsOn(Test, Zip) + .OnlyWhenStatic(() => File.Exists(CertFileName)) + .OnlyWhenStatic(() => EnvironmentInfo.IsWin) + .Executes(() => + { + string pwd = ReadPassword(); + + if (pwd == string.Empty) + { + Logger.Normal("Skipping sign task"); + return; + } + + SignAndReZip(pwd); + }); + + Target Native => _ => _ + .DependsOn(SetVersion) + .After(Compile) + .Executes(BuildNative); + + // ReSharper disable once UnusedMember.Local + Target AppVeyorBuild => _ => _ + .DependsOn(Zip, Native, Publish) + .Unlisted() + .Executes(PrintResults); + + Target Standard => _ => _ + .DependsOn(Test, Zip) + .Executes(PrintResults); + + // ReSharper disable once UnusedMember.Local + Target Full => _ => _ + .DependsOn(Sign, Native) + .Executes(PrintResults); + + public void PrintResults() + { + Logger.Normal("SHA-1:"); + using (var 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)}"); } } } + } - public static void ZipFile(string outFile, string file, string nameInsideZip) + public void BuildNative() + { + string buildType = Untrimmed ? "native-untrimmed" : "native"; + + if (NoReflection) { - using (var s = new ZipOutputStream(File.Create(outFile))) - { - s.SetLevel(9); + buildType = "native-noreflection"; + } - var entry = new ZipEntry(nameInsideZip); + DotNetPublishSettings publishSettings = new DotNetPublishSettings() + .SetConfiguration(Configuration) + .SetProject(HactoolnetProject) + .SetRuntime(NativeRuntime) + .SetOutput(CliNativeDir) + .SetProperties(VersionProps) + .AddProperty("BuildType", buildType); + + DotNetPublish(publishSettings); + + if (EnvironmentInfo.IsUnix && !Untrimmed) + { + File.Copy(CliNativeExe, CliNativeExe + "_unstripped", true); + ProcessTasks.StartProcess("strip", CliNativeExe).AssertZeroExitCode(); + } + + EnsureExistingDirectory(ArtifactsDirectory); + + ZipFile(CliNativeZip, CliNativeExe, $"hactoolnet{NativeProgramExtension}"); + Logger.Normal($"Created {CliNativeZip}"); + + if (Host == HostType.AppVeyor) + { + PushArtifact(CliNativeZip); + } + } + + public static void ZipFiles(string outFile, IEnumerable files) + { + using (var s = new ZipOutputStream(File.Create(outFile))) + { + s.SetLevel(9); + + foreach (string file in files) + { + var entry = new ZipEntry(Path.GetFileName(file)); entry.DateTime = DateTime.UnixEpoch; using (FileStream fs = File.OpenRead(file)) @@ -440,269 +421,287 @@ namespace LibHacBuild } } } + } - public static void ZipDirectory(string outFile, string directory) + public static void ZipFile(string outFile, string file, string nameInsideZip) + { + using (var s = new ZipOutputStream(File.Create(outFile))) { - 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)) { - 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 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 byte[] DeflateBytes(byte[] data) - { - var s = new Deflater(9, true); - s.SetInput(data); - s.Finish(); - byte[] buffer = new byte[data.Length]; - s.Deflate(buffer); - - Debug.Assert(s.IsFinished); - - byte[] compressed = new byte[s.TotalOut]; - Array.Copy(buffer, compressed, compressed.Length); - return compressed; - } - - public static void PushArtifact(string path) - { - if (!File.Exists(path)) - { - Logger.Warn($"Unable to add artifact {path}"); - } - - var psi = new ProcessStartInfo - { - FileName = "appveyor", - Arguments = $"PushArtifact \"{path}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - var proc = new Process - { - StartInfo = psi - }; - - proc.Start(); - - proc.WaitForExit(); - - Logger.Normal($"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) - { - string text = File.ReadAllText(filename); - 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 coreFxDir = TempDirectory / ("sign_" + Path.GetFileName(CliCoreZip)); - AbsolutePath nativeZipDir = TempDirectory / ("sign_" + Path.GetFileName(CliNativeZip)); - - bool signNative = FileExists(CliNativeExe); - - try - { - UnzipFiles(CliCoreZip, coreFxDir); - List pkgFileList = UnzipPackage(nupkgFile, nupkgDir); - - var toSign = new List(); - toSign.AddRange(nupkgDir.GlobFiles("**/LibHac.dll")); - toSign.Add(coreFxDir / "hactoolnet.dll"); - - if (signNative) - { - UnzipFiles(CliNativeZip, nativeZipDir); - toSign.Add(nativeZipDir / "hactoolnet.exe"); - } - - Directory.CreateDirectory(SignedArtifactsDirectory); - - SignAssemblies(password, toSign.Select(x => x.ToString()).ToArray()); - - // Avoid having multiple signed versions of the same file - File.Copy(nupkgDir / "lib" / "net5.0" / "LibHac.dll", coreFxDir / "LibHac.dll", true); - - ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(nupkgFile), nupkgDir, pkgFileList); - 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); - SignNupkg(SignedArtifactsDirectory / Path.GetFileName(snupkgFile), password); - } - catch (Exception) - { - Directory.Delete(SignedArtifactsDirectory, true); - throw; - } - finally - { - Directory.Delete(nupkgDir, 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(); - } - - public string GetCsprojVersion() - { - return XmlTasks.XmlPeekSingle(LibHacProject.Path, "/Project/PropertyGroup/VersionPrefix", null); - } - - public void RunCodegenStage2() - { - Logger.Normal("\nBuilding stage 2 codegen project."); - - DotNetRunSettings settings = new DotNetRunSettings() - .SetProjectFile(CodeGenProject.Path); - // .SetLogOutput(false); - - try - { - DotNetRun(settings); - Logger.Normal(); - } - catch (ProcessException) - { - Logger.Error("\nError running stage 2 codegen. Skipping...\n"); + 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))) + { + 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 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 byte[] DeflateBytes(byte[] data) + { + var s = new Deflater(9, true); + s.SetInput(data); + s.Finish(); + byte[] buffer = new byte[data.Length]; + s.Deflate(buffer); + + Debug.Assert(s.IsFinished); + + byte[] compressed = new byte[s.TotalOut]; + Array.Copy(buffer, compressed, compressed.Length); + return compressed; + } + + public static void PushArtifact(string path) + { + if (!File.Exists(path)) + { + Logger.Warn($"Unable to add artifact {path}"); + } + + var psi = new ProcessStartInfo + { + FileName = "appveyor", + Arguments = $"PushArtifact \"{path}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var proc = new Process + { + StartInfo = psi + }; + + proc.Start(); + + proc.WaitForExit(); + + Logger.Normal($"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) + { + string text = File.ReadAllText(filename); + 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 coreFxDir = TempDirectory / ("sign_" + Path.GetFileName(CliCoreZip)); + AbsolutePath nativeZipDir = TempDirectory / ("sign_" + Path.GetFileName(CliNativeZip)); + + bool signNative = FileExists(CliNativeExe); + + try + { + UnzipFiles(CliCoreZip, coreFxDir); + List pkgFileList = UnzipPackage(nupkgFile, nupkgDir); + + var toSign = new List(); + toSign.AddRange(nupkgDir.GlobFiles("**/LibHac.dll")); + toSign.Add(coreFxDir / "hactoolnet.dll"); + + if (signNative) + { + UnzipFiles(CliNativeZip, nativeZipDir); + toSign.Add(nativeZipDir / "hactoolnet.exe"); + } + + Directory.CreateDirectory(SignedArtifactsDirectory); + + SignAssemblies(password, toSign.Select(x => x.ToString()).ToArray()); + + // Avoid having multiple signed versions of the same file + File.Copy(nupkgDir / "lib" / "net5.0" / "LibHac.dll", coreFxDir / "LibHac.dll", true); + + ZipDirectory(SignedArtifactsDirectory / Path.GetFileName(nupkgFile), nupkgDir, pkgFileList); + 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); + SignNupkg(SignedArtifactsDirectory / Path.GetFileName(snupkgFile), password); + } + catch (Exception) + { + Directory.Delete(SignedArtifactsDirectory, true); + throw; + } + finally + { + Directory.Delete(nupkgDir, 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(); + } + + public string GetCsprojVersion() + { + return XmlTasks.XmlPeekSingle(LibHacProject.Path, "/Project/PropertyGroup/VersionPrefix", null); + } + + public void RunCodegenStage2() + { + Logger.Normal("\nBuilding stage 2 codegen project."); + + DotNetRunSettings settings = new DotNetRunSettings() + .SetProjectFile(CodeGenProject.Path); + // .SetLogOutput(false); + + try + { + DotNetRun(settings); + Logger.Normal(); + } + catch (ProcessException) + { + Logger.Error("\nError running stage 2 codegen. Skipping...\n"); + } + } } diff --git a/build/CodeGen/Common.cs b/build/CodeGen/Common.cs index d5bd114a..5c25c792 100644 --- a/build/CodeGen/Common.cs +++ b/build/CodeGen/Common.cs @@ -5,105 +5,104 @@ using System.Reflection; using System.Text; using Nuke.Common; -namespace LibHacBuild.CodeGen +namespace LibHacBuild.CodeGen; + +public static class Common { - public static class Common + public static string GetHeader() { - public static string GetHeader() + string nl = Environment.NewLine; + return + "//-----------------------------------------------------------------------------" + nl + + "// This file was automatically generated." + nl + + "// Changes to this file will be lost when the file is regenerated." + nl + + "//" + nl + + "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + + "// repo and run the build script." + nl + + "//" + nl + + "// The script can be run with the \"codegen\" option to run only the" + nl + + "// code generation portion of the build." + nl + + "//-----------------------------------------------------------------------------"; + } + + // Write the file only if it has changed + // Preserve the UTF-8 BOM usage if the file already exists + public static void WriteOutput(string relativePath, string text) + { + if (string.IsNullOrWhiteSpace(relativePath)) + return; + + string rootPath = FindProjectDirectory(); + string fullPath = Path.Combine(rootPath, relativePath); + string directoryName = Path.GetDirectoryName(fullPath); + + if (directoryName == null) + throw new InvalidDataException($"Invalid output path {relativePath}"); + + if (!Directory.Exists(directoryName)) { - string nl = Environment.NewLine; - return - "//-----------------------------------------------------------------------------" + nl + - "// This file was automatically generated." + nl + - "// Changes to this file will be lost when the file is regenerated." + nl + - "//" + nl + - "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + - "// repo and run the build script." + nl + - "//" + nl + - "// The script can be run with the \"codegen\" option to run only the" + nl + - "// code generation portion of the build." + nl + - "//-----------------------------------------------------------------------------"; + Directory.CreateDirectory(directoryName); } - // Write the file only if it has changed - // Preserve the UTF-8 BOM usage if the file already exists - public static void WriteOutput(string relativePath, string text) + // Default is true because Visual Studio saves .cs files with the BOM by default + bool hasBom = true; + byte[] bom = Encoding.UTF8.GetPreamble(); + byte[] oldFile = null; + + if (File.Exists(fullPath)) { - if (string.IsNullOrWhiteSpace(relativePath)) - return; + oldFile = File.ReadAllBytes(fullPath); - string rootPath = FindProjectDirectory(); - string fullPath = Path.Combine(rootPath, relativePath); - string directoryName = Path.GetDirectoryName(fullPath); - - if (directoryName == null) - throw new InvalidDataException($"Invalid output path {relativePath}"); - - if (!Directory.Exists(directoryName)) - { - Directory.CreateDirectory(directoryName); - } - - // Default is true because Visual Studio saves .cs files with the BOM by default - bool hasBom = true; - byte[] bom = Encoding.UTF8.GetPreamble(); - byte[] oldFile = null; - - if (File.Exists(fullPath)) - { - oldFile = File.ReadAllBytes(fullPath); - - if (oldFile.Length >= 3) - hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); - } - - // Make line endings the same on Windows and Unix - if (Environment.NewLine == "\n") - { - text = text.Replace("\n", "\r\n"); - } - - byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); - - if (oldFile?.SequenceEqual(newFile) == true) - { - Logger.Normal($"{relativePath} is already up-to-date"); - return; - } - - Logger.Normal($"Generated file {relativePath}"); - File.WriteAllBytes(fullPath, newFile); + if (oldFile.Length >= 3) + hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); } - public static Stream GetResource(string name) + // Make line endings the same on Windows and Unix + if (Environment.NewLine == "\n") { - var assembly = Assembly.GetExecutingAssembly(); - string path = $"LibHacBuild.CodeGen.{name}"; - - Stream stream = assembly.GetManifestResourceStream(path); - if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); - - return stream; + text = text.Replace("\n", "\r\n"); } - public static string FindProjectDirectory() + byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); + + if (oldFile?.SequenceEqual(newFile) == true) { - string currentDir = Environment.CurrentDirectory; + Logger.Normal($"{relativePath} is already up-to-date"); + return; + } - while (currentDir != null) + Logger.Normal($"Generated file {relativePath}"); + File.WriteAllBytes(fullPath, newFile); + } + + public static Stream GetResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + string path = $"LibHacBuild.CodeGen.{name}"; + + Stream stream = assembly.GetManifestResourceStream(path); + if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); + + return stream; + } + + public static string FindProjectDirectory() + { + string currentDir = Environment.CurrentDirectory; + + while (currentDir != null) + { + if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) { - if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) - { - break; - } - - currentDir = Path.GetDirectoryName(currentDir); + break; } - if (currentDir == null) - throw new DirectoryNotFoundException("Unable to find project directory."); - - return Path.Combine(currentDir, "src"); + currentDir = Path.GetDirectoryName(currentDir); } + + if (currentDir == null) + throw new DirectoryNotFoundException("Unable to find project directory."); + + return Path.Combine(currentDir, "src"); } } diff --git a/build/CodeGen/IndentingStringBuilder.cs b/build/CodeGen/IndentingStringBuilder.cs index 2d0e8ab3..b734566a 100644 --- a/build/CodeGen/IndentingStringBuilder.cs +++ b/build/CodeGen/IndentingStringBuilder.cs @@ -1,89 +1,88 @@ using System; using System.Text; -namespace LibHacBuild.CodeGen +namespace LibHacBuild.CodeGen; + +public class IndentingStringBuilder { - public class IndentingStringBuilder + public int LevelSize { get; set; } = 4; + public int Level { get; private set; } + + private StringBuilder _sb = new StringBuilder(); + private string _indentation = string.Empty; + private bool _hasIndentedCurrentLine; + private bool _lastLineWasEmpty; + + public IndentingStringBuilder() { } + public IndentingStringBuilder(int levelSize) => LevelSize = levelSize; + + public void SetLevel(int level) { - public int LevelSize { get; set; } = 4; - public int Level { get; private set; } + Level = Math.Max(level, 0); + _indentation = new string(' ', Level * LevelSize); + } - private StringBuilder _sb = new StringBuilder(); - private string _indentation = string.Empty; - private bool _hasIndentedCurrentLine; - private bool _lastLineWasEmpty; + public void IncreaseLevel() => SetLevel(Level + 1); + public void DecreaseLevel() => SetLevel(Level - 1); - public IndentingStringBuilder() { } - public IndentingStringBuilder(int levelSize) => LevelSize = levelSize; + public IndentingStringBuilder AppendLine() + { + _sb.AppendLine(); + _hasIndentedCurrentLine = false; + _lastLineWasEmpty = true; + return this; + } - public void SetLevel(int level) - { - Level = Math.Max(level, 0); - _indentation = new string(' ', Level * LevelSize); - } - - public void IncreaseLevel() => SetLevel(Level + 1); - public void DecreaseLevel() => SetLevel(Level - 1); - - public IndentingStringBuilder AppendLine() + public IndentingStringBuilder AppendSpacerLine() + { + if (!_lastLineWasEmpty) { _sb.AppendLine(); _hasIndentedCurrentLine = false; _lastLineWasEmpty = true; - return this; } - public IndentingStringBuilder AppendSpacerLine() - { - if (!_lastLineWasEmpty) - { - _sb.AppendLine(); - _hasIndentedCurrentLine = false; - _lastLineWasEmpty = true; - } - - return this; - } - - public IndentingStringBuilder AppendLine(string value) - { - IndentIfNeeded(); - _sb.AppendLine(value); - _hasIndentedCurrentLine = false; - _lastLineWasEmpty = string.IsNullOrWhiteSpace(value); - return this; - } - - public IndentingStringBuilder Append(string value) - { - IndentIfNeeded(); - _sb.Append(value); - return this; - } - - public IndentingStringBuilder AppendLineAndIncrease(string value) - { - AppendLine(value); - IncreaseLevel(); - return this; - } - - public IndentingStringBuilder DecreaseAndAppendLine(string value) - { - DecreaseLevel(); - AppendLine(value); - return this; - } - - private void IndentIfNeeded() - { - if (!_hasIndentedCurrentLine) - { - _sb.Append(_indentation); - _hasIndentedCurrentLine = true; - } - } - - public override string ToString() => _sb.ToString(); + return this; } + + public IndentingStringBuilder AppendLine(string value) + { + IndentIfNeeded(); + _sb.AppendLine(value); + _hasIndentedCurrentLine = false; + _lastLineWasEmpty = string.IsNullOrWhiteSpace(value); + return this; + } + + public IndentingStringBuilder Append(string value) + { + IndentIfNeeded(); + _sb.Append(value); + return this; + } + + public IndentingStringBuilder AppendLineAndIncrease(string value) + { + AppendLine(value); + IncreaseLevel(); + return this; + } + + public IndentingStringBuilder DecreaseAndAppendLine(string value) + { + DecreaseLevel(); + AppendLine(value); + return this; + } + + private void IndentIfNeeded() + { + if (!_hasIndentedCurrentLine) + { + _sb.Append(_indentation); + _hasIndentedCurrentLine = true; + } + } + + public override string ToString() => _sb.ToString(); } diff --git a/build/CodeGen/Stage1/ResultCodegen.cs b/build/CodeGen/Stage1/ResultCodegen.cs index e3f6007c..1dc22964 100644 --- a/build/CodeGen/Stage1/ResultCodegen.cs +++ b/build/CodeGen/Stage1/ResultCodegen.cs @@ -11,705 +11,704 @@ using CsvHelper; using CsvHelper.Configuration; using static LibHacBuild.CodeGen.Common; -namespace LibHacBuild.CodeGen.Stage1 +namespace LibHacBuild.CodeGen.Stage1; + +public static class ResultCodeGen { - public static class ResultCodeGen + // RyuJIT will always be inlined a function if its CIL size is <= 0x10 bytes + private const int InlineThreshold = 0x10; + + public static void Run() { - // RyuJIT will always be inlined a function if its CIL size is <= 0x10 bytes - private const int InlineThreshold = 0x10; + ResultSet modules = ReadResults(); - public static void Run() + SetEmptyResultValues(modules); + ValidateResults(modules); + CheckForDuplicates(modules); + ValidateHierarchy(modules); + CheckIfAggressiveInliningNeeded(modules); + + foreach (NamespaceInfo module in modules.Namespaces.Where(x => + !string.IsNullOrWhiteSpace(x.Path) && x.Results.Any())) { - ResultSet modules = ReadResults(); + string moduleResultFile = PrintModule(module); - SetEmptyResultValues(modules); - ValidateResults(modules); - CheckForDuplicates(modules); - ValidateHierarchy(modules); - CheckIfAggressiveInliningNeeded(modules); - - foreach (NamespaceInfo module in modules.Namespaces.Where(x => - !string.IsNullOrWhiteSpace(x.Path) && x.Results.Any())) - { - string moduleResultFile = PrintModule(module); - - WriteOutput($"LibHac/{module.Path}", moduleResultFile); - } - - byte[] archive = BuildArchive(modules); - byte[] compressedArchive = Build.DeflateBytes(archive); - string archiveStr = PrintArchive(compressedArchive); - WriteOutput("LibHac/ResultNameResolver.Generated.cs", archiveStr); - - string enumStr = PrintEnum(modules); - WriteOutput("../.tmp/result_enums.txt", enumStr); + WriteOutput($"LibHac/{module.Path}", moduleResultFile); } - private static ResultSet ReadResults() + byte[] archive = BuildArchive(modules); + byte[] compressedArchive = Build.DeflateBytes(archive); + string archiveStr = PrintArchive(compressedArchive); + WriteOutput("LibHac/ResultNameResolver.Generated.cs", archiveStr); + + string enumStr = PrintEnum(modules); + WriteOutput("../.tmp/result_enums.txt", enumStr); + } + + private static ResultSet ReadResults() + { + ModuleInfo[] modules = ReadCsv("result_modules.csv"); + NamespaceInfo[] nsInfos = ReadCsv("result_namespaces.csv"); + ResultInfo[] results = ReadCsv("results.csv"); + + Dictionary moduleDict = modules.ToDictionary(m => m.Id); + + // Make sure modules have a default namespace + foreach (ModuleInfo module in modules) { - ModuleInfo[] modules = ReadCsv("result_modules.csv"); - NamespaceInfo[] nsInfos = ReadCsv("result_namespaces.csv"); - ResultInfo[] results = ReadCsv("results.csv"); - - Dictionary moduleDict = modules.ToDictionary(m => m.Id); - - // Make sure modules have a default namespace - foreach (ModuleInfo module in modules) + if (string.IsNullOrWhiteSpace(module.Namespace)) { - if (string.IsNullOrWhiteSpace(module.Namespace)) - { - module.Namespace = module.Name; - } + module.Namespace = module.Name; } - - // Populate result module name and namespace fields if needed - foreach (ResultInfo result in results) - { - result.ModuleName = moduleDict[result.ModuleId].Name; - - if (string.IsNullOrWhiteSpace(result.Namespace)) - { - result.Namespace = moduleDict[result.ModuleId].Namespace; - } - } - - // Group results by namespace - foreach (NamespaceInfo nsInfo in nsInfos) - { - // Sort DescriptionEnd by descending so any abstract ranges are put before an actual result at that description value - nsInfo.Results = results.Where(x => x.Namespace == nsInfo.Name).OrderBy(x => x.DescriptionStart) - .ThenByDescending(x => x.DescriptionEnd).ToArray(); - - if (nsInfo.Results.Length == 0) - continue; - - // Set the namespace's result module name - string moduleName = nsInfo.Results.First().ModuleName; - if (nsInfo.Results.Any(x => x.ModuleName != moduleName)) - { - throw new InvalidDataException( - $"Error with namespace \"{nsInfo.Name}\": All results in a namespace must be from the same module."); - } - - nsInfo.ModuleId = nsInfo.Results.First().ModuleId; - nsInfo.ModuleName = moduleName; - } - - // Group results by module - foreach (ModuleInfo module in modules) - { - // Sort DescriptionEnd by descending so any abstract ranges are put before an actual result at that description value - module.Results = results.Where(x => x.ModuleId == module.Id).OrderBy(x => x.DescriptionStart) - .ThenByDescending(x => x.DescriptionEnd).ToArray(); - } - - return new ResultSet - { - Modules = modules.ToList(), - Namespaces = nsInfos.ToList(), - Results = results.ToList() - }; } - private static void SetEmptyResultValues(ResultSet resultSet) + // Populate result module name and namespace fields if needed + foreach (ResultInfo result in results) { - foreach (ResultInfo result in resultSet.Results) - { - result.FullName = $"Result{result.ModuleName}{result.Name}"; + result.ModuleName = moduleDict[result.ModuleId].Name; - if (string.IsNullOrWhiteSpace(result.Name)) + if (string.IsNullOrWhiteSpace(result.Namespace)) + { + result.Namespace = moduleDict[result.ModuleId].Namespace; + } + } + + // Group results by namespace + foreach (NamespaceInfo nsInfo in nsInfos) + { + // Sort DescriptionEnd by descending so any abstract ranges are put before an actual result at that description value + nsInfo.Results = results.Where(x => x.Namespace == nsInfo.Name).OrderBy(x => x.DescriptionStart) + .ThenByDescending(x => x.DescriptionEnd).ToArray(); + + if (nsInfo.Results.Length == 0) + continue; + + // Set the namespace's result module name + string moduleName = nsInfo.Results.First().ModuleName; + if (nsInfo.Results.Any(x => x.ModuleName != moduleName)) + { + throw new InvalidDataException( + $"Error with namespace \"{nsInfo.Name}\": All results in a namespace must be from the same module."); + } + + nsInfo.ModuleId = nsInfo.Results.First().ModuleId; + nsInfo.ModuleName = moduleName; + } + + // Group results by module + foreach (ModuleInfo module in modules) + { + // Sort DescriptionEnd by descending so any abstract ranges are put before an actual result at that description value + module.Results = results.Where(x => x.ModuleId == module.Id).OrderBy(x => x.DescriptionStart) + .ThenByDescending(x => x.DescriptionEnd).ToArray(); + } + + return new ResultSet + { + Modules = modules.ToList(), + Namespaces = nsInfos.ToList(), + Results = results.ToList() + }; + } + + private static void SetEmptyResultValues(ResultSet resultSet) + { + foreach (ResultInfo result in resultSet.Results) + { + result.FullName = $"Result{result.ModuleName}{result.Name}"; + + if (string.IsNullOrWhiteSpace(result.Name)) + { + if (result.IsRange) { - if (result.IsRange) + result.Name += $"Range{result.DescriptionStart}To{result.DescriptionEnd}"; + } + else + { + result.Name = $"Result{result.DescriptionStart}"; + result.DescriptionEnd = result.DescriptionStart; + } + } + } + } + + private static void ValidateResults(ResultSet resultSet) + { + // Make sure all the result values are in range + foreach (ResultInfo result in resultSet.Results) + { + // Logic should match Result.Base.ctor + Assert(1 <= result.ModuleId && result.ModuleId < 512, "Invalid Module"); + Assert(0 <= result.DescriptionStart && result.DescriptionStart < 8192, "Invalid Description Start"); + Assert(0 <= result.DescriptionEnd && result.DescriptionEnd < 8192, "Invalid Description End"); + Assert(result.DescriptionStart <= result.DescriptionEnd, "descriptionStart must be <= descriptionEnd"); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + void Assert(bool condition, string message) + { + if (!condition) + throw new InvalidDataException($"Result {result.ModuleId}-{result.DescriptionStart}: {message}"); + } + } + + // Make sure all the result namespaces match a known namespace + string[] namespaceNames = resultSet.Namespaces.Select(x => x.Name).ToArray(); + + foreach (string nsName in resultSet.Results.Select(x => x.Namespace).Distinct()) + { + if (!namespaceNames.Contains(nsName)) + { + throw new InvalidDataException($"Invalid result namespace \"{nsName}\""); + } + } + } + + private static void CheckForDuplicates(ResultSet resultSet) + { + var moduleIdSet = new HashSet(); + var moduleNameSet = new HashSet(); + + foreach (ModuleInfo module in resultSet.Modules) + { + if (!moduleIdSet.Add(module.Id)) + { + throw new InvalidDataException($"Duplicate result module index {module.Id}."); + } + + if (!moduleNameSet.Add(module.Name)) + { + throw new InvalidDataException($"Duplicate result module name {module.Name}."); + } + + var descriptionSet = new HashSet(); + var descriptionSetAbstract = new HashSet(); + + foreach (ResultInfo result in module.Results) + { + if (result.IsAbstract) + { + if (!descriptionSetAbstract.Add(result.DescriptionStart)) { - result.Name += $"Range{result.DescriptionStart}To{result.DescriptionEnd}"; + throw new InvalidDataException( + $"Duplicate abstract result {result.ModuleId}-{result.DescriptionStart}-{result.DescriptionEnd}."); } - else + } + else + { + if (!descriptionSet.Add(result.DescriptionStart)) { - result.Name = $"Result{result.DescriptionStart}"; - result.DescriptionEnd = result.DescriptionStart; + throw new InvalidDataException( + $"Duplicate result {result.ModuleId}-{result.DescriptionStart}-{result.DescriptionEnd}."); } } } } + } - private static void ValidateResults(ResultSet resultSet) + private static void ValidateHierarchy(ResultSet resultSet) + { + foreach (ModuleInfo module in resultSet.Modules) { - // Make sure all the result values are in range - foreach (ResultInfo result in resultSet.Results) - { - // Logic should match Result.Base.ctor - Assert(1 <= result.ModuleId && result.ModuleId < 512, "Invalid Module"); - Assert(0 <= result.DescriptionStart && result.DescriptionStart < 8192, "Invalid Description Start"); - Assert(0 <= result.DescriptionEnd && result.DescriptionEnd < 8192, "Invalid Description End"); - Assert(result.DescriptionStart <= result.DescriptionEnd, "descriptionStart must be <= descriptionEnd"); - - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - void Assert(bool condition, string message) - { - if (!condition) - throw new InvalidDataException($"Result {result.ModuleId}-{result.DescriptionStart}: {message}"); - } - } - - // Make sure all the result namespaces match a known namespace - string[] namespaceNames = resultSet.Namespaces.Select(x => x.Name).ToArray(); - - foreach (string nsName in resultSet.Results.Select(x => x.Namespace).Distinct()) - { - if (!namespaceNames.Contains(nsName)) - { - throw new InvalidDataException($"Invalid result namespace \"{nsName}\""); - } - } - } - - private static void CheckForDuplicates(ResultSet resultSet) - { - var moduleIdSet = new HashSet(); - var moduleNameSet = new HashSet(); - - foreach (ModuleInfo module in resultSet.Modules) - { - if (!moduleIdSet.Add(module.Id)) - { - throw new InvalidDataException($"Duplicate result module index {module.Id}."); - } - - if (!moduleNameSet.Add(module.Name)) - { - throw new InvalidDataException($"Duplicate result module name {module.Name}."); - } - - var descriptionSet = new HashSet(); - var descriptionSetAbstract = new HashSet(); - - foreach (ResultInfo result in module.Results) - { - if (result.IsAbstract) - { - if (!descriptionSetAbstract.Add(result.DescriptionStart)) - { - throw new InvalidDataException( - $"Duplicate abstract result {result.ModuleId}-{result.DescriptionStart}-{result.DescriptionEnd}."); - } - } - else - { - if (!descriptionSet.Add(result.DescriptionStart)) - { - throw new InvalidDataException( - $"Duplicate result {result.ModuleId}-{result.DescriptionStart}-{result.DescriptionEnd}."); - } - } - } - } - } - - private static void ValidateHierarchy(ResultSet resultSet) - { - foreach (ModuleInfo module in resultSet.Modules) - { - var hierarchy = new Stack(); - - foreach (ResultInfo result in module.Results) - { - while (hierarchy.Count > 0 && hierarchy.Peek().DescriptionEnd < result.DescriptionStart) - { - hierarchy.Pop(); - } - - if (result.IsRange) - { - if (hierarchy.Count > 0 && result.DescriptionEnd > hierarchy.Peek().DescriptionEnd) - { - throw new InvalidDataException($"Result {result.ModuleId}-{result.DescriptionStart} is not nested properly."); - } - - hierarchy.Push(result); - } - } - } - } - - private static void CheckIfAggressiveInliningNeeded(ResultSet resultSet) - { - foreach (NamespaceInfo ns in resultSet.Namespaces) - { - ns.NeedsAggressiveInlining = ns.Results.Any(x => EstimateCilSize(x) > InlineThreshold); - } - } - - private static string PrintModule(NamespaceInfo ns) - { - var sb = new IndentingStringBuilder(); - - sb.AppendLine(GetHeader()); - sb.AppendLine(); - - if (ns.NeedsAggressiveInlining) - { - sb.AppendLine("using System.Runtime.CompilerServices;"); - sb.AppendLine(); - } - - sb.AppendLine($"namespace LibHac.{ns.Name}"); - sb.AppendLineAndIncrease("{"); - - sb.AppendLine($"public static class Result{ns.ClassName}"); - sb.AppendLineAndIncrease("{"); - - sb.AppendLine($"public const int Module{ns.ModuleName} = {ns.ModuleId};"); - sb.AppendLine(); - var hierarchy = new Stack(); - bool justIndented = false; - foreach (ResultInfo result in ns.Results) + foreach (ResultInfo result in module.Results) { while (hierarchy.Count > 0 && hierarchy.Peek().DescriptionEnd < result.DescriptionStart) { hierarchy.Pop(); - sb.DecreaseLevel(); - sb.AppendSpacerLine(); } - if (!justIndented && result.IsRange) - { - sb.AppendSpacerLine(); - } - - PrintResult(sb, ns.ModuleName, result); - if (result.IsRange) { + if (hierarchy.Count > 0 && result.DescriptionEnd > hierarchy.Peek().DescriptionEnd) + { + throw new InvalidDataException($"Result {result.ModuleId}-{result.DescriptionStart} is not nested properly."); + } + hierarchy.Push(result); - sb.IncreaseLevel(); } - - justIndented = result.IsRange; } + } + } - while (hierarchy.Count > 0) + private static void CheckIfAggressiveInliningNeeded(ResultSet resultSet) + { + foreach (NamespaceInfo ns in resultSet.Namespaces) + { + ns.NeedsAggressiveInlining = ns.Results.Any(x => EstimateCilSize(x) > InlineThreshold); + } + } + + private static string PrintModule(NamespaceInfo ns) + { + var sb = new IndentingStringBuilder(); + + sb.AppendLine(GetHeader()); + sb.AppendLine(); + + if (ns.NeedsAggressiveInlining) + { + sb.AppendLine("using System.Runtime.CompilerServices;"); + sb.AppendLine(); + } + + sb.AppendLine($"namespace LibHac.{ns.Name}"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine($"public static class Result{ns.ClassName}"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine($"public const int Module{ns.ModuleName} = {ns.ModuleId};"); + sb.AppendLine(); + + var hierarchy = new Stack(); + bool justIndented = false; + + foreach (ResultInfo result in ns.Results) + { + while (hierarchy.Count > 0 && hierarchy.Peek().DescriptionEnd < result.DescriptionStart) { hierarchy.Pop(); sb.DecreaseLevel(); + sb.AppendSpacerLine(); } - sb.DecreaseAndAppendLine("}"); - sb.DecreaseAndAppendLine("}"); + if (!justIndented && result.IsRange) + { + sb.AppendSpacerLine(); + } - return sb.ToString(); - } - - private static void PrintResult(IndentingStringBuilder sb, string moduleName, ResultInfo result) - { - string descriptionArgs; + PrintResult(sb, ns.ModuleName, result); if (result.IsRange) { - descriptionArgs = $"{result.DescriptionStart}, {result.DescriptionEnd}"; - } - else - { - descriptionArgs = $"{result.DescriptionStart}"; + hierarchy.Push(result); + sb.IncreaseLevel(); } - sb.AppendLine(GetXmlDoc(result)); + justIndented = result.IsRange; + } - string type = result.IsAbstract ? "Result.Base.Abstract" : "Result.Base"; + while (hierarchy.Count > 0) + { + hierarchy.Pop(); + sb.DecreaseLevel(); + } - string resultCtor = $"new {type}(Module{moduleName}, {descriptionArgs});"; - sb.Append($"public static {type} {result.Name} "); + sb.DecreaseAndAppendLine("}"); + sb.DecreaseAndAppendLine("}"); - if (EstimateCilSize(result) > InlineThreshold) + return sb.ToString(); + } + + private static void PrintResult(IndentingStringBuilder sb, string moduleName, ResultInfo result) + { + string descriptionArgs; + + if (result.IsRange) + { + descriptionArgs = $"{result.DescriptionStart}, {result.DescriptionEnd}"; + } + else + { + descriptionArgs = $"{result.DescriptionStart}"; + } + + sb.AppendLine(GetXmlDoc(result)); + + string type = result.IsAbstract ? "Result.Base.Abstract" : "Result.Base"; + + string resultCtor = $"new {type}(Module{moduleName}, {descriptionArgs});"; + sb.Append($"public static {type} {result.Name} "); + + if (EstimateCilSize(result) > InlineThreshold) + { + sb.AppendLine($"{{ [MethodImpl(MethodImplOptions.AggressiveInlining)] get => {resultCtor} }}"); + } + else + { + sb.AppendLine($"=> {resultCtor}"); + } + } + + private static string GetXmlDoc(ResultInfo result) + { + string doc = "/// "; + + if (!string.IsNullOrWhiteSpace(result.Summary)) + { + doc += $"{result.Summary}
"; + } + + doc += $"Error code: {result.ErrorCode}"; + + if (result.IsRange) + { + doc += $"; Range: {result.DescriptionStart}-{result.DescriptionEnd}"; + } + + if (!result.IsAbstract) + { + doc += $"; Inner value: 0x{result.InnerValue:x}"; + } + + doc += "
"; + + return doc; + } + + private static byte[] BuildArchive(ResultSet resultSet) + { + var builder = new ResultArchiveBuilder(); + + foreach (NamespaceInfo module in resultSet.Namespaces.OrderBy(x => x.ModuleId)) + { + foreach (ResultInfo result in module.Results.OrderBy(x => x.DescriptionStart)) { - sb.AppendLine($"{{ [MethodImpl(MethodImplOptions.AggressiveInlining)] get => {resultCtor} }}"); - } - else - { - sb.AppendLine($"=> {resultCtor}"); + builder.Add(result); } } - private static string GetXmlDoc(ResultInfo result) + return builder.Build(); + } + + private static string PrintArchive(ReadOnlySpan data) + { + var sb = new IndentingStringBuilder(); + + sb.AppendLine(GetHeader()); + sb.AppendLine(); + + sb.AppendLine("using System;"); + sb.AppendLine(); + + sb.AppendLine("namespace LibHac"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine("internal partial class ResultNameResolver"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine("private static ReadOnlySpan ArchiveData => new byte[]"); + sb.AppendLineAndIncrease("{"); + + for (int i = 0; i < data.Length; i++) { - string doc = "/// "; + if (i % 16 != 0) sb.Append(" "); + sb.Append($"0x{data[i]:x2}"); - if (!string.IsNullOrWhiteSpace(result.Summary)) + if (i != data.Length - 1) { - doc += $"{result.Summary}
"; + sb.Append(","); + if (i % 16 == 15) sb.AppendLine(); } - - doc += $"Error code: {result.ErrorCode}"; - - if (result.IsRange) - { - doc += $"; Range: {result.DescriptionStart}-{result.DescriptionEnd}"; - } - - if (!result.IsAbstract) - { - doc += $"; Inner value: 0x{result.InnerValue:x}"; - } - - doc += "
"; - - return doc; } - private static byte[] BuildArchive(ResultSet resultSet) - { - var builder = new ResultArchiveBuilder(); + sb.AppendLine(); + sb.DecreaseAndAppendLine("};"); + sb.DecreaseAndAppendLine("}"); + sb.DecreaseAndAppendLine("}"); - foreach (NamespaceInfo module in resultSet.Namespaces.OrderBy(x => x.ModuleId)) + return sb.ToString(); + } + + private static T[] ReadCsv(string name) + { + var configuration = new CsvConfiguration(CultureInfo.InvariantCulture) + { + AllowComments = true, + DetectColumnCountChanges = true + }; + + using (var csv = new CsvReader(new StreamReader(GetResource(name)), configuration)) + { + csv.Context.RegisterClassMap(); + csv.Context.RegisterClassMap(); + csv.Context.RegisterClassMap(); + + return csv.GetRecords().ToArray(); + } + } + + private static int EstimateCilSize(ResultInfo result) + { + int size = 0; + + size += GetLoadSize(result.ModuleId); + size += GetLoadSize(result.DescriptionStart); + + if (result.IsRange) + size += GetLoadSize(result.DescriptionEnd); + + size += 5; // newobj + size += 1; // ret + + return size; + + static int GetLoadSize(int value) + { + if (value >= -1 && value <= 8) + return 1; // ldc.i4.X + + if (value >= sbyte.MinValue && value <= sbyte.MaxValue) + return 2; // ldc.i4.s XX + + return 5; // ldc.i4 XXXXXXXX + } + } + + public static string PrintEnum(ResultSet resultSet) + { + var sb = new StringBuilder(); + int[] printUnknownResultsForModules = { 2 }; + int[] skipModules = { 428 }; + + foreach (ModuleInfo module in resultSet.Modules.Where(x => !skipModules.Contains(x.Id))) + { + bool printAllResults = printUnknownResultsForModules.Contains(module.Id); + int prevResult = 1; + + foreach (ResultInfo result in module.Results) { - foreach (ResultInfo result in module.Results.OrderBy(x => x.DescriptionStart)) + if (printAllResults && result.DescriptionStart > prevResult + 1) { - builder.Add(result); - } - } - - return builder.Build(); - } - - private static string PrintArchive(ReadOnlySpan data) - { - var sb = new IndentingStringBuilder(); - - sb.AppendLine(GetHeader()); - sb.AppendLine(); - - sb.AppendLine("using System;"); - sb.AppendLine(); - - sb.AppendLine("namespace LibHac"); - sb.AppendLineAndIncrease("{"); - - sb.AppendLine("internal partial class ResultNameResolver"); - sb.AppendLineAndIncrease("{"); - - sb.AppendLine("private static ReadOnlySpan ArchiveData => new byte[]"); - sb.AppendLineAndIncrease("{"); - - for (int i = 0; i < data.Length; i++) - { - if (i % 16 != 0) sb.Append(" "); - sb.Append($"0x{data[i]:x2}"); - - if (i != data.Length - 1) - { - sb.Append(","); - if (i % 16 == 15) sb.AppendLine(); - } - } - - sb.AppendLine(); - sb.DecreaseAndAppendLine("};"); - sb.DecreaseAndAppendLine("}"); - sb.DecreaseAndAppendLine("}"); - - return sb.ToString(); - } - - private static T[] ReadCsv(string name) - { - var configuration = new CsvConfiguration(CultureInfo.InvariantCulture) - { - AllowComments = true, - DetectColumnCountChanges = true - }; - - using (var csv = new CsvReader(new StreamReader(GetResource(name)), configuration)) - { - csv.Context.RegisterClassMap(); - csv.Context.RegisterClassMap(); - csv.Context.RegisterClassMap(); - - return csv.GetRecords().ToArray(); - } - } - - private static int EstimateCilSize(ResultInfo result) - { - int size = 0; - - size += GetLoadSize(result.ModuleId); - size += GetLoadSize(result.DescriptionStart); - - if (result.IsRange) - size += GetLoadSize(result.DescriptionEnd); - - size += 5; // newobj - size += 1; // ret - - return size; - - static int GetLoadSize(int value) - { - if (value >= -1 && value <= 8) - return 1; // ldc.i4.X - - if (value >= sbyte.MinValue && value <= sbyte.MaxValue) - return 2; // ldc.i4.s XX - - return 5; // ldc.i4 XXXXXXXX - } - } - - public static string PrintEnum(ResultSet resultSet) - { - var sb = new StringBuilder(); - int[] printUnknownResultsForModules = { 2 }; - int[] skipModules = { 428 }; - - foreach (ModuleInfo module in resultSet.Modules.Where(x => !skipModules.Contains(x.Id))) - { - bool printAllResults = printUnknownResultsForModules.Contains(module.Id); - int prevResult = 1; - - foreach (ResultInfo result in module.Results) - { - if (printAllResults && result.DescriptionStart > prevResult + 1) - { - for (int i = prevResult + 1; i < result.DescriptionStart; i++) - { - int innerValue = 2 & 0x1ff | ((i & 0x7ffff) << 9); - string unknownResultLine = $"Result_{result.ModuleId}_{i} = {innerValue},"; - sb.AppendLine(unknownResultLine); - } - } - - string name = string.IsNullOrWhiteSpace(result.Name) ? string.Empty : $"_{result.Name}"; - string line = $"Result_{result.ModuleId}_{result.DescriptionStart}{name} = {result.InnerValue},"; - - sb.AppendLine(line); - prevResult = result.DescriptionStart; - } - - if (printAllResults) - { - for (int i = prevResult + 1; i < 8192; i++) + for (int i = prevResult + 1; i < result.DescriptionStart; i++) { int innerValue = 2 & 0x1ff | ((i & 0x7ffff) << 9); - string unknownResultLine = $"Result_{module.Id}_{i} = {innerValue},"; + string unknownResultLine = $"Result_{result.ModuleId}_{i} = {innerValue},"; sb.AppendLine(unknownResultLine); } } + + string name = string.IsNullOrWhiteSpace(result.Name) ? string.Empty : $"_{result.Name}"; + string line = $"Result_{result.ModuleId}_{result.DescriptionStart}{name} = {result.InnerValue},"; + + sb.AppendLine(line); + prevResult = result.DescriptionStart; } - return sb.ToString(); - } - } - - public class ResultArchiveBuilder - { - private List Results = new List(); - - public void Add(ResultInfo result) - { - Results.Add(result); - } - - public byte[] Build() - { - int tableOffset = CalculateNameTableOffset(); - byte[] archive = new byte[tableOffset + CalculateNameTableSize()]; - - ref HeaderStruct header = ref Unsafe.As(ref archive[0]); - Span elements = MemoryMarshal.Cast( - archive.AsSpan(Unsafe.SizeOf(), Results.Count * Unsafe.SizeOf())); - Span nameTable = archive.AsSpan(tableOffset); - - header.ElementCount = Results.Count; - header.NameTableOffset = tableOffset; - - int curNameOffset = 0; - - for (int i = 0; i < Results.Count; i++) + if (printAllResults) { - ResultInfo result = Results[i]; - ref Element element = ref elements[i]; - - element.NameOffset = curNameOffset; - element.Module = (short)result.ModuleId; - element.DescriptionStart = (short)result.DescriptionStart; - element.DescriptionEnd = (short)result.DescriptionEnd; - element.IsAbstract = result.IsAbstract; - - Span utf8Name = Encoding.UTF8.GetBytes(result.FullName); - utf8Name.CopyTo(nameTable.Slice(curNameOffset)); - nameTable[curNameOffset + utf8Name.Length] = 0; - - curNameOffset += utf8Name.Length + 1; - } - - return archive; - } - - private int CalculateNameTableOffset() - { - return Unsafe.SizeOf() + Unsafe.SizeOf() * Results.Count; - } - - private int CalculateNameTableSize() - { - int size = 0; - Encoding encoding = Encoding.UTF8; - - foreach (ResultInfo result in Results) - { - size += encoding.GetByteCount(result.FullName) + 1; - } - - return size; - } - - // ReSharper disable NotAccessedField.Local - private struct HeaderStruct - { - public int ElementCount; - public int NameTableOffset; - } - - private struct Element - { - public int NameOffset; - public short Module; - public short DescriptionStart; - public short DescriptionEnd; - public bool IsAbstract; - } - // ReSharper restore NotAccessedField.Local - } - - public class ModuleInfo - { - public int Id { get; set; } - public string Name { get; set; } - public string Namespace { get; set; } - - public ResultInfo[] Results { get; set; } - } - - [DebuggerDisplay("{" + nameof(ClassName) + ",nq}")] - public class NamespaceInfo - { - public string Name { get; set; } - public string ClassName { get; set; } - public int ModuleId { get; set; } - public string ModuleName { get; set; } - public string Path { get; set; } - - public bool NeedsAggressiveInlining { get; set; } - public ResultInfo[] Results { get; set; } - } - - [DebuggerDisplay("{" + nameof(Name) + ",nq}")] - public class ResultInfo - { - public int ModuleId { get; set; } - public int DescriptionStart { get; set; } - public int DescriptionEnd { get; set; } - public ResultInfoFlags Flags { get; set; } - public string Name { get; set; } - public string ModuleName { get; set; } - public string Namespace { get; set; } - public string FullName { get; set; } - public string Summary { get; set; } - - public bool IsRange => DescriptionStart != DescriptionEnd; - public string ErrorCode => $"{2000 + ModuleId:d4}-{DescriptionStart:d4}"; - public int InnerValue => ModuleId & 0x1ff | ((DescriptionStart & 0x7ffff) << 9); - public bool IsAbstract => Flags.HasFlag(ResultInfoFlags.Abstract); - } - - public class ResultSet - { - public List Modules { get; set; } - public List Namespaces { get; set; } - public List Results { get; set; } - } - - [Flags] - public enum ResultInfoFlags - { - None = 0, - Abstract = 1 << 0 - } - - public sealed class ModuleMap : ClassMap - { - public ModuleMap() - { - Map(m => m.Id); - Map(m => m.Name); - Map(m => m.Namespace).Convert(row => - { - string field = row.Row.GetField("Default Namespace"); - if (string.IsNullOrWhiteSpace(field)) - field = row.Row.GetField("Name"); - - return field; - }); - } - } - - public sealed class NamespaceMap : ClassMap - { - public NamespaceMap() - { - Map(m => m.Name).Name("Namespace"); - Map(m => m.Path); - Map(m => m.ClassName).Convert(row => - { - string field = row.Row.GetField("Class Name"); - if (string.IsNullOrWhiteSpace(field)) - field = row.Row.GetField("Namespace"); - - return field; - }); - } - } - - public sealed class ResultMap : ClassMap - { - public ResultMap() - { - Map(m => m.ModuleId).Name("Module"); - Map(m => m.Namespace); - Map(m => m.Name); - Map(m => m.Summary); - - Map(m => m.DescriptionStart); - Map(m => m.DescriptionEnd).Convert(row => - { - string field = row.Row.GetField("DescriptionEnd"); - if (string.IsNullOrWhiteSpace(field)) - field = row.Row.GetField("DescriptionStart"); - - return int.Parse(field); - }); - - Map(m => m.Flags).Convert(row => - { - string field = row.Row.GetField("Flags"); - var flags = ResultInfoFlags.None; - - foreach (char c in field) + for (int i = prevResult + 1; i < 8192; i++) { - switch (c) - { - case 'a': - flags |= ResultInfoFlags.Abstract; - break; - - default: - throw new InvalidDataException($"Invalid Result flag '{c}'"); - } + int innerValue = 2 & 0x1ff | ((i & 0x7ffff) << 9); + string unknownResultLine = $"Result_{module.Id}_{i} = {innerValue},"; + sb.AppendLine(unknownResultLine); } - - return flags; - }); + } } + + return sb.ToString(); + } +} + +public class ResultArchiveBuilder +{ + private List Results = new List(); + + public void Add(ResultInfo result) + { + Results.Add(result); + } + + public byte[] Build() + { + int tableOffset = CalculateNameTableOffset(); + byte[] archive = new byte[tableOffset + CalculateNameTableSize()]; + + ref HeaderStruct header = ref Unsafe.As(ref archive[0]); + Span elements = MemoryMarshal.Cast( + archive.AsSpan(Unsafe.SizeOf(), Results.Count * Unsafe.SizeOf())); + Span nameTable = archive.AsSpan(tableOffset); + + header.ElementCount = Results.Count; + header.NameTableOffset = tableOffset; + + int curNameOffset = 0; + + for (int i = 0; i < Results.Count; i++) + { + ResultInfo result = Results[i]; + ref Element element = ref elements[i]; + + element.NameOffset = curNameOffset; + element.Module = (short)result.ModuleId; + element.DescriptionStart = (short)result.DescriptionStart; + element.DescriptionEnd = (short)result.DescriptionEnd; + element.IsAbstract = result.IsAbstract; + + Span utf8Name = Encoding.UTF8.GetBytes(result.FullName); + utf8Name.CopyTo(nameTable.Slice(curNameOffset)); + nameTable[curNameOffset + utf8Name.Length] = 0; + + curNameOffset += utf8Name.Length + 1; + } + + return archive; + } + + private int CalculateNameTableOffset() + { + return Unsafe.SizeOf() + Unsafe.SizeOf() * Results.Count; + } + + private int CalculateNameTableSize() + { + int size = 0; + Encoding encoding = Encoding.UTF8; + + foreach (ResultInfo result in Results) + { + size += encoding.GetByteCount(result.FullName) + 1; + } + + return size; + } + + // ReSharper disable NotAccessedField.Local + private struct HeaderStruct + { + public int ElementCount; + public int NameTableOffset; + } + + private struct Element + { + public int NameOffset; + public short Module; + public short DescriptionStart; + public short DescriptionEnd; + public bool IsAbstract; + } + // ReSharper restore NotAccessedField.Local +} + +public class ModuleInfo +{ + public int Id { get; set; } + public string Name { get; set; } + public string Namespace { get; set; } + + public ResultInfo[] Results { get; set; } +} + +[DebuggerDisplay("{" + nameof(ClassName) + ",nq}")] +public class NamespaceInfo +{ + public string Name { get; set; } + public string ClassName { get; set; } + public int ModuleId { get; set; } + public string ModuleName { get; set; } + public string Path { get; set; } + + public bool NeedsAggressiveInlining { get; set; } + public ResultInfo[] Results { get; set; } +} + +[DebuggerDisplay("{" + nameof(Name) + ",nq}")] +public class ResultInfo +{ + public int ModuleId { get; set; } + public int DescriptionStart { get; set; } + public int DescriptionEnd { get; set; } + public ResultInfoFlags Flags { get; set; } + public string Name { get; set; } + public string ModuleName { get; set; } + public string Namespace { get; set; } + public string FullName { get; set; } + public string Summary { get; set; } + + public bool IsRange => DescriptionStart != DescriptionEnd; + public string ErrorCode => $"{2000 + ModuleId:d4}-{DescriptionStart:d4}"; + public int InnerValue => ModuleId & 0x1ff | ((DescriptionStart & 0x7ffff) << 9); + public bool IsAbstract => Flags.HasFlag(ResultInfoFlags.Abstract); +} + +public class ResultSet +{ + public List Modules { get; set; } + public List Namespaces { get; set; } + public List Results { get; set; } +} + +[Flags] +public enum ResultInfoFlags +{ + None = 0, + Abstract = 1 << 0 +} + +public sealed class ModuleMap : ClassMap +{ + public ModuleMap() + { + Map(m => m.Id); + Map(m => m.Name); + Map(m => m.Namespace).Convert(row => + { + string field = row.Row.GetField("Default Namespace"); + if (string.IsNullOrWhiteSpace(field)) + field = row.Row.GetField("Name"); + + return field; + }); + } +} + +public sealed class NamespaceMap : ClassMap +{ + public NamespaceMap() + { + Map(m => m.Name).Name("Namespace"); + Map(m => m.Path); + Map(m => m.ClassName).Convert(row => + { + string field = row.Row.GetField("Class Name"); + if (string.IsNullOrWhiteSpace(field)) + field = row.Row.GetField("Namespace"); + + return field; + }); + } +} + +public sealed class ResultMap : ClassMap +{ + public ResultMap() + { + Map(m => m.ModuleId).Name("Module"); + Map(m => m.Namespace); + Map(m => m.Name); + Map(m => m.Summary); + + Map(m => m.DescriptionStart); + Map(m => m.DescriptionEnd).Convert(row => + { + string field = row.Row.GetField("DescriptionEnd"); + if (string.IsNullOrWhiteSpace(field)) + field = row.Row.GetField("DescriptionStart"); + + return int.Parse(field); + }); + + Map(m => m.Flags).Convert(row => + { + string field = row.Row.GetField("Flags"); + var flags = ResultInfoFlags.None; + + foreach (char c in field) + { + switch (c) + { + case 'a': + flags |= ResultInfoFlags.Abstract; + break; + + default: + throw new InvalidDataException($"Invalid Result flag '{c}'"); + } + } + + return flags; + }); } } diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs index c9eee6e7..a18bc83d 100644 --- a/build/CodeGen/Stage2/KeysCodeGen.cs +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -8,386 +8,385 @@ using LibHac.Common.Keys; using LibHac.Crypto; using static LibHacBuild.CodeGen.Common; -namespace LibHacBuild.CodeGen.Stage2 +namespace LibHacBuild.CodeGen.Stage2; + +public static class KeysCodeGen { - public static class KeysCodeGen + private static string InputMainKeyFileName = "IncludedKeys.txt"; + private static string GeneratedFilePath = "LibHac/Common/Keys/DefaultKeySet.Generated.cs"; + + public static void Run() { - private static string InputMainKeyFileName = "IncludedKeys.txt"; - private static string GeneratedFilePath = "LibHac/Common/Keys/DefaultKeySet.Generated.cs"; + KeySet keySet = CreateKeySet(); - public static void Run() - { - KeySet keySet = CreateKeySet(); - - WriteOutput(GeneratedFilePath, BuildDefaultKeySetFile(keySet)); - } - - private static string BuildDefaultKeySetFile(KeySet keySet) - { - var sb = new IndentingStringBuilder(); - - sb.AppendLine(GetHeader()); - sb.AppendLine(); - - sb.AppendLine("using System;"); - sb.AppendLine(); - - sb.AppendLine("namespace LibHac.Common.Keys"); - sb.AppendLineAndIncrease("{"); - - sb.AppendLine("internal static partial class DefaultKeySet"); - sb.AppendLineAndIncrease("{"); - - BuildArray(sb, "RootKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysDev)); - BuildArray(sb, "RootKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysProd)); - BuildArray(sb, "KeySeeds", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._keySeeds)); - BuildArray(sb, "StoredKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysDev)); - BuildArray(sb, "StoredKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysProd)); - BuildArray(sb, "DerivedKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysDev)); - BuildArray(sb, "DerivedKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysProd)); - BuildArray(sb, "DeviceKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._deviceKeys)); - BuildArray(sb, "RsaSigningKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysDev)); - BuildArray(sb, "RsaSigningKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysProd)); - BuildArray(sb, "RsaKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaKeys)); - - sb.DecreaseAndAppendLine("}"); - sb.DecreaseAndAppendLine("}"); - - return sb.ToString(); - } - - private static void BuildArray(IndentingStringBuilder sb, string name, ReadOnlySpan data) - { - sb.AppendSpacerLine(); - sb.Append($"private static ReadOnlySpan {name} => new byte[]"); - - if (data.IsZeros()) - { - sb.AppendLine(" { };"); - return; - } - - sb.AppendLine(); - sb.AppendLineAndIncrease("{"); - - for (int i = 0; i < data.Length; i++) - { - if (i % 16 != 0) sb.Append(" "); - sb.Append($"0x{data[i]:x2}"); - - if (i != data.Length - 1) - { - sb.Append(","); - if (i % 16 == 15) sb.AppendLine(); - } - } - - sb.AppendLine(); - sb.DecreaseAndAppendLine("};"); - } - - private static KeySet CreateKeySet() - { - var keySet = new KeySet(); - - // Populate the key set with all the keys in IncludedKeys.txt - using (Stream keyFile = GetResource(InputMainKeyFileName)) - { - List list = KeySet.CreateKeyInfoList(); - ExternalKeyReader.ReadMainKeys(keySet, keyFile, list); - } - - // Recover all the RSA key parameters and write the key to the key set - RSAParameters betaNca0Params = - Rsa.RecoverParameters(BetaNca0Modulus, StandardPublicExponent, BetaNca0Exponent); - - betaNca0Params.D.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PrivateExponent.Data); - betaNca0Params.DP.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dp.Data); - betaNca0Params.DQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dq.Data); - betaNca0Params.Exponent.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PublicExponent.Data); - betaNca0Params.InverseQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.InverseQ.Data); - betaNca0Params.Modulus.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Modulus.Data); - betaNca0Params.P.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.P.Data); - betaNca0Params.Q.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Q.Data); - - // First populate the prod RSA keys - keySet.SetMode(KeySet.Mode.Prod); - - StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); - StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); - NcaHdrFixedKeyModulus0Prod.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); - NcaHdrFixedKeyModulus1Prod.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); - - StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); - StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); - AcidFixedKeyModulus0Prod.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); - AcidFixedKeyModulus1Prod.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); - - StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); - Package2FixedKeyModulusProd.CopyTo(keySet.Package2SigningKey.Modulus.Data); - - // Populate the dev RSA keys - keySet.SetMode(KeySet.Mode.Dev); - - StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); - StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); - NcaHdrFixedKeyModulus0Dev.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); - NcaHdrFixedKeyModulus1Dev.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); - - StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); - StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); - AcidFixedKeyModulus0Dev.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); - AcidFixedKeyModulus1Dev.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); - - StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); - Package2FixedKeyModulusDev.CopyTo(keySet.Package2SigningKey.Modulus.Data); - - return keySet; - } - - private static ReadOnlySpan StandardPublicExponent => new byte[] - { - 0x01, 0x00, 0x01 - }; - - private static ReadOnlySpan BetaNca0Modulus => new byte[] - { - 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, - 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, - 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, - 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, - 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, - 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, - 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, - 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, - 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, - 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, - 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, - 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, - 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, - 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, - 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, - 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 - }; - - private static ReadOnlySpan BetaNca0Exponent => new byte[] - { - 0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA, - 0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E, - 0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA, - 0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00, - 0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9, - 0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC, - 0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58, - 0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD, - 0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7, - 0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81, - 0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1, - 0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B, - 0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6, - 0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7, - 0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6, - 0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65 - }; - - private static ReadOnlySpan NcaHdrFixedKeyModulus0Prod => new byte[] - { - 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, - 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, - 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, - 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, - 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, - 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, - 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, - 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, - 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, - 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, - 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, - 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, - 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, - 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, - 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, - 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 - }; - - private static ReadOnlySpan NcaHdrFixedKeyModulus1Prod => new byte[] - { - 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, - 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, - 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, - 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, - 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, - 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, - 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, - 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, - 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, - 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, - 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, - 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, - 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, - 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, - 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, - 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD - }; - - private static ReadOnlySpan AcidFixedKeyModulus0Prod => new byte[] - { - 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, - 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, - 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, - 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, - 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, - 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, - 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, - 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, - 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, - 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, - 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, - 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, - 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, - 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, - 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, - 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD - }; - - private static ReadOnlySpan AcidFixedKeyModulus1Prod => new byte[] - { - 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, - 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, - 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, - 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, - 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, - 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, - 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, - 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, - 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, - 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, - 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, - 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, - 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, - 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, - 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, - 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 - }; - - private static ReadOnlySpan Package2FixedKeyModulusProd => new byte[] - { - 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, - 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, - 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, - 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, - 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, - 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, - 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, - 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, - 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, - 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, - 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, - 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, - 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, - 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, - 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, - 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F - }; - - private static ReadOnlySpan NcaHdrFixedKeyModulus0Dev => new byte[] - { - 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, - 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, - 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, - 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, - 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, - 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, - 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, - 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, - 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, - 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, - 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, - 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, - 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, - 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, - 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, - 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 - }; - - private static ReadOnlySpan NcaHdrFixedKeyModulus1Dev => new byte[] - { - 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, - 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, - 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, - 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, - 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, - 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, - 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, - 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, - 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, - 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, - 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, - 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, - 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, - 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, - 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, - 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 - }; - - private static ReadOnlySpan AcidFixedKeyModulus0Dev => new byte[] - { - 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, - 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, - 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, - 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, - 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, - 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, - 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, - 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, - 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, - 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, - 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, - 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, - 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, - 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, - 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, - 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 - }; - - private static ReadOnlySpan AcidFixedKeyModulus1Dev => new byte[] - { - 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, - 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, - 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, - 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, - 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, - 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, - 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, - 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, - 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, - 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, - 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, - 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, - 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, - 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, - 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, - 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B - }; - - private static ReadOnlySpan Package2FixedKeyModulusDev => new byte[] - { - 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, - 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, - 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, - 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, - 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, - 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, - 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, - 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, - 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, - 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, - 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, - 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, - 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, - 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, - 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, - 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B - }; + WriteOutput(GeneratedFilePath, BuildDefaultKeySetFile(keySet)); } + + private static string BuildDefaultKeySetFile(KeySet keySet) + { + var sb = new IndentingStringBuilder(); + + sb.AppendLine(GetHeader()); + sb.AppendLine(); + + sb.AppendLine("using System;"); + sb.AppendLine(); + + sb.AppendLine("namespace LibHac.Common.Keys"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine("internal static partial class DefaultKeySet"); + sb.AppendLineAndIncrease("{"); + + BuildArray(sb, "RootKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysDev)); + BuildArray(sb, "RootKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysProd)); + BuildArray(sb, "KeySeeds", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._keySeeds)); + BuildArray(sb, "StoredKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysDev)); + BuildArray(sb, "StoredKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysProd)); + BuildArray(sb, "DerivedKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysDev)); + BuildArray(sb, "DerivedKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysProd)); + BuildArray(sb, "DeviceKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._deviceKeys)); + BuildArray(sb, "RsaSigningKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysDev)); + BuildArray(sb, "RsaSigningKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysProd)); + BuildArray(sb, "RsaKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaKeys)); + + sb.DecreaseAndAppendLine("}"); + sb.DecreaseAndAppendLine("}"); + + return sb.ToString(); + } + + private static void BuildArray(IndentingStringBuilder sb, string name, ReadOnlySpan data) + { + sb.AppendSpacerLine(); + sb.Append($"private static ReadOnlySpan {name} => new byte[]"); + + if (data.IsZeros()) + { + sb.AppendLine(" { };"); + return; + } + + sb.AppendLine(); + sb.AppendLineAndIncrease("{"); + + for (int i = 0; i < data.Length; i++) + { + if (i % 16 != 0) sb.Append(" "); + sb.Append($"0x{data[i]:x2}"); + + if (i != data.Length - 1) + { + sb.Append(","); + if (i % 16 == 15) sb.AppendLine(); + } + } + + sb.AppendLine(); + sb.DecreaseAndAppendLine("};"); + } + + private static KeySet CreateKeySet() + { + var keySet = new KeySet(); + + // Populate the key set with all the keys in IncludedKeys.txt + using (Stream keyFile = GetResource(InputMainKeyFileName)) + { + List list = KeySet.CreateKeyInfoList(); + ExternalKeyReader.ReadMainKeys(keySet, keyFile, list); + } + + // Recover all the RSA key parameters and write the key to the key set + RSAParameters betaNca0Params = + Rsa.RecoverParameters(BetaNca0Modulus, StandardPublicExponent, BetaNca0Exponent); + + betaNca0Params.D.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PrivateExponent.Data); + betaNca0Params.DP.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dp.Data); + betaNca0Params.DQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dq.Data); + betaNca0Params.Exponent.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PublicExponent.Data); + betaNca0Params.InverseQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.InverseQ.Data); + betaNca0Params.Modulus.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Modulus.Data); + betaNca0Params.P.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.P.Data); + betaNca0Params.Q.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Q.Data); + + // First populate the prod RSA keys + keySet.SetMode(KeySet.Mode.Prod); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Prod.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Prod.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Prod.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Prod.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusProd.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + // Populate the dev RSA keys + keySet.SetMode(KeySet.Mode.Dev); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Dev.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Dev.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Dev.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Dev.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusDev.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + return keySet; + } + + private static ReadOnlySpan StandardPublicExponent => new byte[] + { + 0x01, 0x00, 0x01 + }; + + private static ReadOnlySpan BetaNca0Modulus => new byte[] + { + 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, + 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, + 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, + 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, + 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, + 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, + 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, + 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, + 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, + 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, + 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, + 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, + 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, + 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, + 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, + 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 + }; + + private static ReadOnlySpan BetaNca0Exponent => new byte[] + { + 0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA, + 0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E, + 0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA, + 0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00, + 0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9, + 0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC, + 0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58, + 0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD, + 0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7, + 0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81, + 0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1, + 0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B, + 0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6, + 0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7, + 0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6, + 0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus0Prod => new byte[] + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Prod => new byte[] + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Prod => new byte[] + { + 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Prod => new byte[] + { + 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 + }; + + private static ReadOnlySpan Package2FixedKeyModulusProd => new byte[] + { + 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, + 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, + 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, + 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, + 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, + 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, + 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, + 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, + 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, + 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, + 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, + 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, + 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, + 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, + 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, + 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus0Dev => new byte[] + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Dev => new byte[] + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Dev => new byte[] + { + 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Dev => new byte[] + { + 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B + }; + + private static ReadOnlySpan Package2FixedKeyModulusDev => new byte[] + { + 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, + 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, + 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, + 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, + 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, + 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, + 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, + 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, + 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, + 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, + 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, + 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, + 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, + 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, + 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, + 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B + }; } diff --git a/build/CodeGen/Stage2/RunStage2.cs b/build/CodeGen/Stage2/RunStage2.cs index b339b91d..49f4d54b 100644 --- a/build/CodeGen/Stage2/RunStage2.cs +++ b/build/CodeGen/Stage2/RunStage2.cs @@ -2,25 +2,24 @@ using System.IO; using Octokit; -namespace LibHacBuild.CodeGen.Stage2 +namespace LibHacBuild.CodeGen.Stage2; + +// Some codegen depends on classes in LibHac. +// The part that does is split out into a separate project so the main build project +// doesn't depend on LibHac. +public static class RunStage2 { - // Some codegen depends on classes in LibHac. - // The part that does is split out into a separate project so the main build project - // doesn't depend on LibHac. - public static class RunStage2 + private const string SolutionFileName = "LibHac.sln"; + public static int Main(string[] args) { - private const string SolutionFileName = "LibHac.sln"; - public static int Main(string[] args) + if (!File.Exists(SolutionFileName)) { - if (!File.Exists(SolutionFileName)) - { - Console.Error.WriteLine($"Could not find the solution file {SolutionFileName}."); - return 1; - } - - KeysCodeGen.Run(); - - return 0; + Console.Error.WriteLine($"Could not find the solution file {SolutionFileName}."); + return 1; } + + KeysCodeGen.Run(); + + return 0; } } diff --git a/build/CodeGen/_buildCodeGen.csproj b/build/CodeGen/_buildCodeGen.csproj index 9fa184c4..4a714f54 100644 --- a/build/CodeGen/_buildCodeGen.csproj +++ b/build/CodeGen/_buildCodeGen.csproj @@ -3,6 +3,7 @@ Exe net5.0 + 10.0 false LibHacBuild.CodeGen False diff --git a/build/RepackNuget.cs b/build/RepackNuget.cs index 2de40ce1..f2f6c57b 100644 --- a/build/RepackNuget.cs +++ b/build/RepackNuget.cs @@ -11,126 +11,125 @@ using Nuke.Common.IO; using Nuke.Common.Tools.NuGet; using static Nuke.Common.IO.FileSystemTasks; -namespace LibHacBuild +namespace LibHacBuild; + +public partial class Build { - public partial class Build + public void RepackNugetPackage(string path) { - public void RepackNugetPackage(string path) + AbsolutePath tempDir = TempDirectory / Path.GetFileName(path); + AbsolutePath libDir = tempDir / "lib"; + AbsolutePath relsFile = tempDir / "_rels" / ".rels"; + + try { - AbsolutePath tempDir = TempDirectory / Path.GetFileName(path); - AbsolutePath libDir = tempDir / "lib"; - AbsolutePath relsFile = tempDir / "_rels" / ".rels"; + EnsureCleanDirectory(tempDir); + List fileList = UnzipPackage(path, tempDir); - try + string newPsmdcpName = CalcPsmdcpName(libDir); + string newPsmdcpPath = RenamePsmdcp(tempDir, newPsmdcpName); + EditManifestRelationships(relsFile, newPsmdcpPath); + + int index = fileList.FindIndex(x => x.Contains(".psmdcp")); + fileList[index] = newPsmdcpPath; + + IEnumerable 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) { - EnsureCleanDirectory(tempDir); - List 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 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); + Console.WriteLine(filename); + ReplaceLineEndings(filename); } - finally + + ZipDirectory(path, tempDir, fileList); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + public List UnzipPackage(string package, string dest) + { + var fileList = new List(); + + UnzipFiles(package, dest); + + using (var s = new ZipInputStream(File.OpenRead(package))) + { + ZipEntry entry; + while ((entry = s.GetNextEntry()) != null) { - Directory.Delete(tempDir, true); + fileList.Add(entry.Name); } } - public List UnzipPackage(string package, string dest) + return fileList; + } + + public static string CalcPsmdcpName(string libDir) + { + using (var sha = SHA256.Create()) { - var fileList = new List(); - - UnzipFiles(package, dest); - - using (var s = new ZipInputStream(File.OpenRead(package))) + foreach (string file in Directory.EnumerateFiles(libDir)) { - ZipEntry entry; - while ((entry = s.GetNextEntry()) != null) - { - fileList.Add(entry.Name); - } + byte[] data = File.ReadAllBytes(file); + sha.TransformBlock(data, 0, data.Length, data, 0); } - return fileList; - } + sha.TransformFinalBlock(new byte[0], 0, 0); - public static string CalcPsmdcpName(string libDir) + 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 (var sha = SHA256.Create()) { - foreach (string file in Directory.EnumerateFiles(libDir)) + if (rs.Attribute("Target").Value.Contains(".psmdcp")) { - byte[] data = File.ReadAllBytes(file); - sha.TransformBlock(data, 0, data.Length, data, 0); + rs.Attribute("Target").Value = "/" + psmdcpPath; } - sha.TransformFinalBlock(new byte[0], 0, 0); - - return ToHexString(sha.Hash).ToLower().Substring(0, 32); + 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; } } - 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); + doc.Save(path); + } - return Path.GetRelativePath(packageDir, newFileName).Replace('\\', '/'); - } + 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")); + } - [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 (var 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("-", ""); - } + public static string ToHexString(byte[] arr) + { + return BitConverter.ToString(arr).ToLower().Replace("-", ""); } } diff --git a/build/_build.csproj b/build/_build.csproj index 76876945..411098b4 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -3,6 +3,7 @@ Exe net5.0 + 10.0 false LibHacBuild False diff --git a/src/LibHac/Account/Uid.cs b/src/LibHac/Account/Uid.cs index 31bece09..dba9c683 100644 --- a/src/LibHac/Account/Uid.cs +++ b/src/LibHac/Account/Uid.cs @@ -3,57 +3,56 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Account +namespace LibHac.Account; + +[DebuggerDisplay("0x{ToString(),nq}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct Uid : IEquatable, IComparable, IComparable { - [DebuggerDisplay("0x{ToString(),nq}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct Uid : IEquatable, IComparable, IComparable + public static Uid Zero => default; + + public readonly Id128 Id; + + public Uid(ulong high, ulong low) { - public static Uid Zero => default; - - public readonly Id128 Id; - - public Uid(ulong high, ulong low) - { - Id = new Id128(high, low); - } - - public Uid(ReadOnlySpan uid) - { - Id = new Id128(uid); - } - - public override string ToString() - { - return $"{Id.High:X16}{Id.Low:X16}"; - } - - public bool Equals(Uid other) => Id == other.Id; - public override bool Equals(object obj) => obj is Uid other && Equals(other); - - public override int GetHashCode() => Id.GetHashCode(); - - public int CompareTo(Uid other) => Id.CompareTo(other.Id); - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is Uid other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Uid)}"); - } - - public void ToBytes(Span output) => Id.ToBytes(output); - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(Uid left, Uid right) => left.Equals(right); - public static bool operator !=(Uid left, Uid right) => !left.Equals(right); - - public static bool operator <(Uid left, Uid right) => left.CompareTo(right) < 0; - public static bool operator >(Uid left, Uid right) => left.CompareTo(right) > 0; - public static bool operator <=(Uid left, Uid right) => left.CompareTo(right) <= 0; - public static bool operator >=(Uid left, Uid right) => left.CompareTo(right) >= 0; + Id = new Id128(high, low); } + + public Uid(ReadOnlySpan uid) + { + Id = new Id128(uid); + } + + public override string ToString() + { + return $"{Id.High:X16}{Id.Low:X16}"; + } + + public bool Equals(Uid other) => Id == other.Id; + public override bool Equals(object obj) => obj is Uid other && Equals(other); + + public override int GetHashCode() => Id.GetHashCode(); + + public int CompareTo(Uid other) => Id.CompareTo(other.Id); + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is Uid other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Uid)}"); + } + + public void ToBytes(Span output) => Id.ToBytes(output); + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(Uid left, Uid right) => left.Equals(right); + public static bool operator !=(Uid left, Uid right) => !left.Equals(right); + + public static bool operator <(Uid left, Uid right) => left.CompareTo(right) < 0; + public static bool operator >(Uid left, Uid right) => left.CompareTo(right) > 0; + public static bool operator <=(Uid left, Uid right) => left.CompareTo(right) <= 0; + public static bool operator >=(Uid left, Uid right) => left.CompareTo(right) >= 0; } diff --git a/src/LibHac/ApplicationId.cs b/src/LibHac/ApplicationId.cs index 4092c364..706dfe78 100644 --- a/src/LibHac/ApplicationId.cs +++ b/src/LibHac/ApplicationId.cs @@ -1,23 +1,22 @@ using System; -namespace LibHac +namespace LibHac; + +public readonly struct ApplicationId : IEquatable { - public readonly struct ApplicationId : IEquatable + public static ApplicationId InvalidId => default; + + public readonly ulong Value; + + public ApplicationId(ulong value) { - public static ApplicationId InvalidId => default; - - public readonly ulong Value; - - public ApplicationId(ulong value) - { - Value = value; - } - - public static bool operator ==(ApplicationId left, ApplicationId right) => left.Value == right.Value; - public static bool operator !=(ApplicationId left, ApplicationId right) => left.Value != right.Value; - - public override bool Equals(object obj) => obj is ApplicationId id && Equals(id); - public bool Equals(ApplicationId other) => Value == other.Value; - public override int GetHashCode() => HashCode.Combine(Value); + Value = value; } + + public static bool operator ==(ApplicationId left, ApplicationId right) => left.Value == right.Value; + public static bool operator !=(ApplicationId left, ApplicationId right) => left.Value != right.Value; + + public override bool Equals(object obj) => obj is ApplicationId id && Equals(id); + public bool Equals(ApplicationId other) => Value == other.Value; + public override int GetHashCode() => HashCode.Combine(Value); } diff --git a/src/LibHac/Arp/ApplicationLaunchProperty.cs b/src/LibHac/Arp/ApplicationLaunchProperty.cs index 57e5ef21..4d73db27 100644 --- a/src/LibHac/Arp/ApplicationLaunchProperty.cs +++ b/src/LibHac/Arp/ApplicationLaunchProperty.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace LibHac.Arp +namespace LibHac.Arp; + +[StructLayout(LayoutKind.Explicit, Size = 0x10)] +public struct ApplicationLaunchProperty { - [StructLayout(LayoutKind.Explicit, Size = 0x10)] - public struct ApplicationLaunchProperty - { - [FieldOffset(0x0)] public ApplicationId ApplicationId; - [FieldOffset(0x8)] public uint Version; - [FieldOffset(0xC)] public Ncm.StorageId BaseStorageId; - [FieldOffset(0xD)] public Ncm.StorageId UpdateStorageId; - } + [FieldOffset(0x0)] public ApplicationId ApplicationId; + [FieldOffset(0x8)] public uint Version; + [FieldOffset(0xC)] public Ncm.StorageId BaseStorageId; + [FieldOffset(0xD)] public Ncm.StorageId UpdateStorageId; } diff --git a/src/LibHac/Arp/ArpClient.cs b/src/LibHac/Arp/ArpClient.cs index 07f2a0cd..0cbca8c8 100644 --- a/src/LibHac/Arp/ArpClient.cs +++ b/src/LibHac/Arp/ArpClient.cs @@ -3,73 +3,72 @@ using LibHac.Arp.Impl; using LibHac.Common; using LibHac.Ns; -namespace LibHac.Arp +namespace LibHac.Arp; + +public class ArpClient : IDisposable { - public class ArpClient : IDisposable + private HorizonClient _hosClient; + private SharedRef _reader; + + private readonly object _readerInitLocker = new object(); + + internal ArpClient(HorizonClient horizonClient) { - private HorizonClient _hosClient; - private SharedRef _reader; + _hosClient = horizonClient; + } - private readonly object _readerInitLocker = new object(); + public void Dispose() + { + _reader.Destroy(); + } - internal ArpClient(HorizonClient horizonClient) - { - _hosClient = horizonClient; - } + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId) + { + EnsureReaderInitialized(); - public void Dispose() - { - _reader.Destroy(); - } + return _reader.Get.GetApplicationLaunchProperty(out launchProperty, processId); + } - public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId) - { - EnsureReaderInitialized(); + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId) + { + EnsureReaderInitialized(); - return _reader.Get.GetApplicationLaunchProperty(out launchProperty, processId); - } + return _reader.Get.GetApplicationLaunchPropertyWithApplicationId(out launchProperty, applicationId); + } - public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId) - { - EnsureReaderInitialized(); + public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId) + { + EnsureReaderInitialized(); - return _reader.Get.GetApplicationLaunchPropertyWithApplicationId(out launchProperty, applicationId); - } + return _reader.Get.GetApplicationControlProperty(out controlProperty, processId); + } - public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId) - { - EnsureReaderInitialized(); + public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ApplicationId applicationId) + { + EnsureReaderInitialized(); - return _reader.Get.GetApplicationControlProperty(out controlProperty, processId); - } + return _reader.Get.GetApplicationControlPropertyWithApplicationId(out controlProperty, applicationId); + } - public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ApplicationId applicationId) - { - EnsureReaderInitialized(); + private void EnsureReaderInitialized() + { + if (_reader.HasValue) + return; - return _reader.Get.GetApplicationControlPropertyWithApplicationId(out controlProperty, applicationId); - } - - private void EnsureReaderInitialized() + lock (_readerInitLocker) { if (_reader.HasValue) return; - lock (_readerInitLocker) + using var reader = new SharedRef(); + Result rc = _hosClient.Sm.GetService(ref reader.Ref(), "arp:r"); + + if (rc.IsFailure()) { - if (_reader.HasValue) - return; - - using var reader = new SharedRef(); - Result rc = _hosClient.Sm.GetService(ref reader.Ref(), "arp:r"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to initialize arp reader."); - } - - _reader.SetByMove(ref reader.Ref()); + throw new HorizonResultException(rc, "Failed to initialize arp reader."); } + + _reader.SetByMove(ref reader.Ref()); } } } diff --git a/src/LibHac/Arp/Impl/IReader.cs b/src/LibHac/Arp/Impl/IReader.cs index 484cd434..b6147877 100644 --- a/src/LibHac/Arp/Impl/IReader.cs +++ b/src/LibHac/Arp/Impl/IReader.cs @@ -1,13 +1,12 @@ using System; using LibHac.Ns; -namespace LibHac.Arp.Impl +namespace LibHac.Arp.Impl; + +public interface IReader : IDisposable { - public interface IReader : IDisposable - { - Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId); - Result GetApplicationLaunchPropertyWithApplicationId(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId); - Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId); - Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, ApplicationId applicationId); - } + Result GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, ulong processId); + Result GetApplicationLaunchPropertyWithApplicationId(out ApplicationLaunchProperty launchProperty, ApplicationId applicationId); + Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId); + Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, ApplicationId applicationId); } diff --git a/src/LibHac/Bcat/BcatServer.cs b/src/LibHac/Bcat/BcatServer.cs index 8a6bb4ba..0d898c4d 100644 --- a/src/LibHac/Bcat/BcatServer.cs +++ b/src/LibHac/Bcat/BcatServer.cs @@ -5,86 +5,85 @@ using LibHac.Bcat.Impl.Service.Core; using LibHac.Common; using LibHac.Fs; -namespace LibHac.Bcat +namespace LibHac.Bcat; + +public class BcatServer { - public class BcatServer + private const int ServiceTypeCount = 4; + + internal HorizonClient Hos { get; } + private SharedRef[] _serviceCreators; + + private readonly object _bcatServiceInitLocker = new object(); + private readonly object _storageManagerInitLocker = new object(); + + private DeliveryCacheStorageManager StorageManager { get; set; } + + public BcatServer(HorizonClient horizonClient) { - private const int ServiceTypeCount = 4; + Hos = horizonClient; + _serviceCreators = new SharedRef[ServiceTypeCount]; - internal HorizonClient Hos { get; } - private SharedRef[] _serviceCreators; + InitBcatService(BcatServiceType.BcatU, "bcat:u", AccessControl.MountOwnDeliveryCacheStorage); + InitBcatService(BcatServiceType.BcatS, "bcat:s", AccessControl.MountOthersDeliveryCacheStorage); + InitBcatService(BcatServiceType.BcatM, "bcat:m", AccessControl.MountOthersDeliveryCacheStorage | AccessControl.DeliveryTaskManagement); + InitBcatService(BcatServiceType.BcatA, "bcat:a", AccessControl.All); + } - private readonly object _bcatServiceInitLocker = new object(); - private readonly object _storageManagerInitLocker = new object(); + private void InitBcatService(BcatServiceType type, string name, AccessControl accessControl) + { + InitServiceCreator(type, name, accessControl); - private DeliveryCacheStorageManager StorageManager { get; set; } + using SharedRef service = GetServiceCreator(type); - public BcatServer(HorizonClient horizonClient) + Result rc = Hos.Sm.RegisterService(new BcatServiceObject(ref service.Ref()), name); + if (rc.IsFailure()) { - Hos = horizonClient; - _serviceCreators = new SharedRef[ServiceTypeCount]; - - InitBcatService(BcatServiceType.BcatU, "bcat:u", AccessControl.MountOwnDeliveryCacheStorage); - InitBcatService(BcatServiceType.BcatS, "bcat:s", AccessControl.MountOthersDeliveryCacheStorage); - InitBcatService(BcatServiceType.BcatM, "bcat:m", AccessControl.MountOthersDeliveryCacheStorage | AccessControl.DeliveryTaskManagement); - InitBcatService(BcatServiceType.BcatA, "bcat:a", AccessControl.All); + throw new HorizonResultException(rc, "Abort"); } + } - private void InitBcatService(BcatServiceType type, string name, AccessControl accessControl) + private void InitServiceCreator(BcatServiceType type, string name, AccessControl accessControl) + { + lock (_bcatServiceInitLocker) { - InitServiceCreator(type, name, accessControl); + Debug.Assert((uint)type < ServiceTypeCount); - using SharedRef service = GetServiceCreator(type); + _serviceCreators[(int)type].Reset(new ServiceCreator(this, name, accessControl)); + } + } - Result rc = Hos.Sm.RegisterService(new BcatServiceObject(ref service.Ref()), name); - if (rc.IsFailure()) + private SharedRef GetServiceCreator(BcatServiceType type) + { + lock (_bcatServiceInitLocker) + { + Debug.Assert((uint)type < ServiceTypeCount); + + return SharedRef.CreateCopy(in _serviceCreators[(int)type]); + } + } + + internal DeliveryCacheStorageManager GetStorageManager() + { + return StorageManager ?? InitStorageManager(); + } + + internal FileSystemClient GetFsClient() + { + return Hos.Fs; + } + + private DeliveryCacheStorageManager InitStorageManager() + { + lock (_storageManagerInitLocker) + { + if (StorageManager != null) { - throw new HorizonResultException(rc, "Abort"); - } - } - - private void InitServiceCreator(BcatServiceType type, string name, AccessControl accessControl) - { - lock (_bcatServiceInitLocker) - { - Debug.Assert((uint)type < ServiceTypeCount); - - _serviceCreators[(int)type].Reset(new ServiceCreator(this, name, accessControl)); - } - } - - private SharedRef GetServiceCreator(BcatServiceType type) - { - lock (_bcatServiceInitLocker) - { - Debug.Assert((uint)type < ServiceTypeCount); - - return SharedRef.CreateCopy(in _serviceCreators[(int)type]); - } - } - - internal DeliveryCacheStorageManager GetStorageManager() - { - return StorageManager ?? InitStorageManager(); - } - - internal FileSystemClient GetFsClient() - { - return Hos.Fs; - } - - private DeliveryCacheStorageManager InitStorageManager() - { - lock (_storageManagerInitLocker) - { - if (StorageManager != null) - { - return StorageManager; - } - - StorageManager = new DeliveryCacheStorageManager(this); return StorageManager; } + + StorageManager = new DeliveryCacheStorageManager(this); + return StorageManager; } } } diff --git a/src/LibHac/Bcat/BcatServiceType.cs b/src/LibHac/Bcat/BcatServiceType.cs index f2882b77..8d040e95 100644 --- a/src/LibHac/Bcat/BcatServiceType.cs +++ b/src/LibHac/Bcat/BcatServiceType.cs @@ -1,10 +1,9 @@ -namespace LibHac.Bcat +namespace LibHac.Bcat; + +public enum BcatServiceType { - public enum BcatServiceType - { - BcatU, - BcatS, - BcatM, - BcatA - } + BcatU, + BcatS, + BcatM, + BcatA } diff --git a/src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs b/src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs index 797027f3..ea81f9ef 100644 --- a/src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs +++ b/src/LibHac/Bcat/DeliveryCacheDirectoryEntry.cs @@ -1,19 +1,18 @@ using System.Runtime.InteropServices; -namespace LibHac.Bcat -{ - [StructLayout(LayoutKind.Explicit, Size = 0x38)] - public struct DeliveryCacheDirectoryEntry - { - [FieldOffset(0x00)] public FileName Name; - [FieldOffset(0x20)] public long Size; - [FieldOffset(0x28)] public Digest Digest; +namespace LibHac.Bcat; - public DeliveryCacheDirectoryEntry(ref FileName name, long size, ref Digest digest) - { - Name = name; - Size = size; - Digest = digest; - } +[StructLayout(LayoutKind.Explicit, Size = 0x38)] +public struct DeliveryCacheDirectoryEntry +{ + [FieldOffset(0x00)] public FileName Name; + [FieldOffset(0x20)] public long Size; + [FieldOffset(0x28)] public Digest Digest; + + public DeliveryCacheDirectoryEntry(ref FileName name, long size, ref Digest digest) + { + Name = name; + Size = size; + Digest = digest; } } diff --git a/src/LibHac/Bcat/Digest.cs b/src/LibHac/Bcat/Digest.cs index a73e26d9..241e970e 100644 --- a/src/LibHac/Bcat/Digest.cs +++ b/src/LibHac/Bcat/Digest.cs @@ -4,26 +4,25 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Bcat +namespace LibHac.Bcat; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 16)] +public struct Digest { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 16)] - public struct Digest + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + + public byte this[int i] { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + get => Bytes[i]; + set => Bytes[i] = value; + } - public byte this[int i] - { - get => Bytes[i]; - set => Bytes[i] = value; - } + public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - - public override string ToString() - { - return Bytes.ToHexString(); - } + public override string ToString() + { + return Bytes.ToHexString(); } } diff --git a/src/LibHac/Bcat/DirectoryName.cs b/src/LibHac/Bcat/DirectoryName.cs index 46ded68b..fb0497cd 100644 --- a/src/LibHac/Bcat/DirectoryName.cs +++ b/src/LibHac/Bcat/DirectoryName.cs @@ -4,50 +4,49 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Bcat +namespace LibHac.Bcat; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = MaxSize)] +public struct DirectoryName { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = MaxSize)] - public struct DirectoryName + private const int MaxSize = 0x20; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; + + public byte this[int i] { - private const int MaxSize = 0x20; + get => Bytes[i]; + set => Bytes[i] = value; + } - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; + public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public byte this[int i] + public bool IsValid() + { + Span name = Bytes; + + int i; + for (i = 0; i < name.Length; i++) { - get => Bytes[i]; - set => Bytes[i] = value; - } + if (name[i] == 0) + break; - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - - public bool IsValid() - { - Span name = Bytes; - - int i; - for (i = 0; i < name.Length; i++) - { - if (name[i] == 0) - break; - - if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '-') - return false; - } - - if (i == 0 || i == MaxSize) + if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '-') return false; - - return name[i] == 0; } - public override string ToString() - { - return StringUtils.Utf8ZToString(Bytes); - } + if (i == 0 || i == MaxSize) + return false; + + return name[i] == 0; + } + + public override string ToString() + { + return StringUtils.Utf8ZToString(Bytes); } } diff --git a/src/LibHac/Bcat/FileName.cs b/src/LibHac/Bcat/FileName.cs index 7a5b66c5..91c2d3a3 100644 --- a/src/LibHac/Bcat/FileName.cs +++ b/src/LibHac/Bcat/FileName.cs @@ -4,53 +4,52 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Bcat +namespace LibHac.Bcat; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = MaxSize)] +public struct FileName { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = MaxSize)] - public struct FileName + private const int MaxSize = 0x20; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; + + public byte this[int i] { - private const int MaxSize = 0x20; - - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; - - public byte this[int i] - { - get => Bytes[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - - public bool IsValid() - { - Span name = Bytes; - - int i; - for (i = 0; i < name.Length; i++) - { - if (name[i] == 0) - break; - - if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '.') - return false; - } - - if (i == 0 || i == MaxSize) - return false; - - if (name[i] != 0) - return false; - - return name[i - 1] != '.'; - } - - public override string ToString() - { - return StringUtils.Utf8ZToString(Bytes); - } + get => Bytes[i]; + set => Bytes[i] = value; } -} \ No newline at end of file + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + public bool IsValid() + { + Span name = Bytes; + + int i; + for (i = 0; i < name.Length; i++) + { + if (name[i] == 0) + break; + + if (!StringUtils.IsDigit(name[i]) && !StringUtils.IsAlpha(name[i]) && name[i] != '_' && name[i] != '.') + return false; + } + + if (i == 0 || i == MaxSize) + return false; + + if (name[i] != 0) + return false; + + return name[i - 1] != '.'; + } + + public override string ToString() + { + return StringUtils.Utf8ZToString(Bytes); + } +} diff --git a/src/LibHac/Bcat/Impl/Ipc/BcatServiceObject.cs b/src/LibHac/Bcat/Impl/Ipc/BcatServiceObject.cs index f0231aab..e399bae7 100644 --- a/src/LibHac/Bcat/Impl/Ipc/BcatServiceObject.cs +++ b/src/LibHac/Bcat/Impl/Ipc/BcatServiceObject.cs @@ -2,26 +2,25 @@ using LibHac.Common; using LibHac.Sm; -namespace LibHac.Bcat.Impl.Ipc +namespace LibHac.Bcat.Impl.Ipc; + +internal class BcatServiceObject : IServiceObject { - internal class BcatServiceObject : IServiceObject + private SharedRef _serviceCreator; + + public BcatServiceObject(ref SharedRef serviceCreator) { - private SharedRef _serviceCreator; + _serviceCreator = SharedRef.CreateMove(ref serviceCreator); + } - public BcatServiceObject(ref SharedRef serviceCreator) - { - _serviceCreator = SharedRef.CreateMove(ref serviceCreator); - } + public void Dispose() + { + _serviceCreator.Destroy(); + } - public void Dispose() - { - _serviceCreator.Destroy(); - } - - public Result GetServiceObject(ref SharedRef serviceObject) - { - serviceObject.SetByCopy(in _serviceCreator); - return Result.Success; - } + public Result GetServiceObject(ref SharedRef serviceObject) + { + serviceObject.SetByCopy(in _serviceCreator); + return Result.Success; } } diff --git a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheDirectoryService.cs b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheDirectoryService.cs index 318748fd..6ba97422 100644 --- a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheDirectoryService.cs +++ b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheDirectoryService.cs @@ -1,11 +1,10 @@ using System; -namespace LibHac.Bcat.Impl.Ipc +namespace LibHac.Bcat.Impl.Ipc; + +public interface IDeliveryCacheDirectoryService : IDisposable { - public interface IDeliveryCacheDirectoryService : IDisposable - { - Result Open(ref DirectoryName name); - Result Read(out int entriesRead, Span entryBuffer); - Result GetCount(out int count); - } + Result Open(ref DirectoryName name); + Result Read(out int entriesRead, Span entryBuffer); + Result GetCount(out int count); } diff --git a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheFileService.cs b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheFileService.cs index bb8cab31..c8cc11bb 100644 --- a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheFileService.cs +++ b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheFileService.cs @@ -1,12 +1,11 @@ using System; -namespace LibHac.Bcat.Impl.Ipc +namespace LibHac.Bcat.Impl.Ipc; + +public interface IDeliveryCacheFileService : IDisposable { - public interface IDeliveryCacheFileService : IDisposable - { - Result Open(ref DirectoryName directoryName, ref FileName fileName); - Result Read(out long bytesRead, long offset, Span destination); - Result GetSize(out long size); - Result GetDigest(out Digest digest); - } + Result Open(ref DirectoryName directoryName, ref FileName fileName); + Result Read(out long bytesRead, long offset, Span destination); + Result GetSize(out long size); + Result GetDigest(out Digest digest); } diff --git a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheStorageService.cs b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheStorageService.cs index 003692c8..aa67318c 100644 --- a/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheStorageService.cs +++ b/src/LibHac/Bcat/Impl/Ipc/IDeliveryCacheStorageService.cs @@ -1,12 +1,11 @@ using System; using LibHac.Common; -namespace LibHac.Bcat.Impl.Ipc +namespace LibHac.Bcat.Impl.Ipc; + +public interface IDeliveryCacheStorageService : IDisposable { - public interface IDeliveryCacheStorageService : IDisposable - { - Result CreateFileService(ref SharedRef outFileService); - Result CreateDirectoryService(ref SharedRef outDirectoryService); - Result EnumerateDeliveryCacheDirectory(out int namesRead, Span nameBuffer); - } + Result CreateFileService(ref SharedRef outFileService); + Result CreateDirectoryService(ref SharedRef outDirectoryService); + Result EnumerateDeliveryCacheDirectory(out int namesRead, Span nameBuffer); } diff --git a/src/LibHac/Bcat/Impl/Ipc/IServiceCreator.cs b/src/LibHac/Bcat/Impl/Ipc/IServiceCreator.cs index fa00d0c4..6bb17193 100644 --- a/src/LibHac/Bcat/Impl/Ipc/IServiceCreator.cs +++ b/src/LibHac/Bcat/Impl/Ipc/IServiceCreator.cs @@ -1,14 +1,13 @@ using System; using LibHac.Common; -namespace LibHac.Bcat.Impl.Ipc -{ - public interface IServiceCreator : IDisposable - { - Result CreateDeliveryCacheStorageService(ref SharedRef outService, - ulong processId); +namespace LibHac.Bcat.Impl.Ipc; - Result CreateDeliveryCacheStorageServiceWithApplicationId( - ref SharedRef outService, ApplicationId applicationId); - } +public interface IServiceCreator : IDisposable +{ + Result CreateDeliveryCacheStorageService(ref SharedRef outService, + ulong processId); + + Result CreateDeliveryCacheStorageServiceWithApplicationId( + ref SharedRef outService, ApplicationId applicationId); } diff --git a/src/LibHac/Bcat/Impl/Service/AccessControl.cs b/src/LibHac/Bcat/Impl/Service/AccessControl.cs index e33d9ace..fce7419e 100644 --- a/src/LibHac/Bcat/Impl/Service/AccessControl.cs +++ b/src/LibHac/Bcat/Impl/Service/AccessControl.cs @@ -1,15 +1,14 @@ using System; -namespace LibHac.Bcat.Impl.Service +namespace LibHac.Bcat.Impl.Service; + +[Flags] +internal enum AccessControl { - [Flags] - internal enum AccessControl - { - None = 0, - MountOwnDeliveryCacheStorage = 1 << 1, - MountOthersDeliveryCacheStorage = 1 << 2, - DeliveryTaskManagement = 1 << 3, - Debug = 1 << 4, - All = ~0 - } + None = 0, + MountOwnDeliveryCacheStorage = 1 << 1, + MountOthersDeliveryCacheStorage = 1 << 2, + DeliveryTaskManagement = 1 << 3, + Debug = 1 << 4, + All = ~0 } diff --git a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaAccessor.cs b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaAccessor.cs index 12b9ec72..2fd7a329 100644 --- a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaAccessor.cs +++ b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaAccessor.cs @@ -5,96 +5,95 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.Bcat.Impl.Service.Core +namespace LibHac.Bcat.Impl.Service.Core; + +internal class DeliveryCacheDirectoryMetaAccessor { - internal class DeliveryCacheDirectoryMetaAccessor + private const int MaxEntryCount = 100; + private const int MetaFileHeaderValue = 1; + + private BcatServer Server { get; } + private object Locker { get; } = new object(); + private DeliveryCacheDirectoryMetaEntry[] Entries { get; } = new DeliveryCacheDirectoryMetaEntry[MaxEntryCount]; + public int Count { get; private set; } + + public DeliveryCacheDirectoryMetaAccessor(BcatServer server) { - private const int MaxEntryCount = 100; - private const int MetaFileHeaderValue = 1; + Server = server; + } - private BcatServer Server { get; } - private object Locker { get; } = new object(); - private DeliveryCacheDirectoryMetaEntry[] Entries { get; } = new DeliveryCacheDirectoryMetaEntry[MaxEntryCount]; - public int Count { get; private set; } + public Result ReadApplicationDirectoryMeta(ulong applicationId, bool allowMissingMetaFile) + { + Span metaPath = stackalloc byte[0x50]; + Server.GetStorageManager().GetDirectoriesMetaPath(metaPath, applicationId); - public DeliveryCacheDirectoryMetaAccessor(BcatServer server) + return Read(new U8Span(metaPath), allowMissingMetaFile); + } + + public Result GetEntry(out DeliveryCacheDirectoryMetaEntry entry, int index) + { + UnsafeHelpers.SkipParamInit(out entry); + + lock (Locker) { - Server = server; - } - - public Result ReadApplicationDirectoryMeta(ulong applicationId, bool allowMissingMetaFile) - { - Span metaPath = stackalloc byte[0x50]; - Server.GetStorageManager().GetDirectoriesMetaPath(metaPath, applicationId); - - return Read(new U8Span(metaPath), allowMissingMetaFile); - } - - public Result GetEntry(out DeliveryCacheDirectoryMetaEntry entry, int index) - { - UnsafeHelpers.SkipParamInit(out entry); - - lock (Locker) + if (index >= Count) { - if (index >= Count) - { - return ResultBcat.NotFound.Log(); - } - - entry = Entries[index]; - return Result.Success; + return ResultBcat.NotFound.Log(); } + + entry = Entries[index]; + return Result.Success; } + } - private Result Read(U8Span path, bool allowMissingMetaFile) + private Result Read(U8Span path, bool allowMissingMetaFile) + { + lock (Locker) { - lock (Locker) + FileSystemClient fs = Server.GetFsClient(); + + Result rc = fs.OpenFile(out FileHandle handle, path, OpenMode.Read); + + if (rc.IsFailure()) { - FileSystemClient fs = Server.GetFsClient(); - - Result rc = fs.OpenFile(out FileHandle handle, path, OpenMode.Read); - - if (rc.IsFailure()) + if (ResultFs.PathNotFound.Includes(rc)) { - if (ResultFs.PathNotFound.Includes(rc)) + if (allowMissingMetaFile) { - if (allowMissingMetaFile) - { - Count = 0; - return Result.Success; - } - - return ResultBcat.NotFound.LogConverted(rc); + Count = 0; + return Result.Success; } - return rc; + return ResultBcat.NotFound.LogConverted(rc); } - try - { - Count = 0; - int header = 0; + return rc; + } - // Verify the header value - rc = fs.ReadFile(out long bytesRead, handle, 0, SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; + try + { + Count = 0; + int header = 0; - if (bytesRead != sizeof(int) || header != MetaFileHeaderValue) - return ResultBcat.InvalidDeliveryCacheStorageFile.Log(); + // Verify the header value + rc = fs.ReadFile(out long bytesRead, handle, 0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; - // Read all the directory entries - Span buffer = MemoryMarshal.Cast(Entries); - rc = fs.ReadFile(out bytesRead, handle, 4, buffer); - if (rc.IsFailure()) return rc; + if (bytesRead != sizeof(int) || header != MetaFileHeaderValue) + return ResultBcat.InvalidDeliveryCacheStorageFile.Log(); - Count = (int)((uint)bytesRead / Unsafe.SizeOf()); + // Read all the directory entries + Span buffer = MemoryMarshal.Cast(Entries); + rc = fs.ReadFile(out bytesRead, handle, 4, buffer); + if (rc.IsFailure()) return rc; - return Result.Success; - } - finally - { - fs.CloseFile(handle); - } + Count = (int)((uint)bytesRead / Unsafe.SizeOf()); + + return Result.Success; + } + finally + { + fs.CloseFile(handle); } } } diff --git a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaEntry.cs b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaEntry.cs index 002d039f..837650bd 100644 --- a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaEntry.cs +++ b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheDirectoryMetaEntry.cs @@ -1,11 +1,10 @@ using System.Runtime.InteropServices; -namespace LibHac.Bcat.Impl.Service.Core +namespace LibHac.Bcat.Impl.Service.Core; + +[StructLayout(LayoutKind.Explicit, Size = 0x40)] +internal struct DeliveryCacheDirectoryMetaEntry { - [StructLayout(LayoutKind.Explicit, Size = 0x40)] - internal struct DeliveryCacheDirectoryMetaEntry - { - [FieldOffset(0x00)] public DirectoryName Name; - [FieldOffset(0x20)] public Digest Digest; - } + [FieldOffset(0x00)] public DirectoryName Name; + [FieldOffset(0x20)] public Digest Digest; } diff --git a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaAccessor.cs b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaAccessor.cs index c45d431d..32efa93c 100644 --- a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaAccessor.cs +++ b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaAccessor.cs @@ -6,116 +6,115 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.Bcat.Impl.Service.Core +namespace LibHac.Bcat.Impl.Service.Core; + +internal class DeliveryCacheFileMetaAccessor { - internal class DeliveryCacheFileMetaAccessor + private const int MaxEntryCount = 100; + private const int MetaFileHeaderValue = 1; + + private BcatServer Server { get; } + private object Locker { get; } = new object(); + private DeliveryCacheFileMetaEntry[] Entries { get; } = new DeliveryCacheFileMetaEntry[MaxEntryCount]; + public int Count { get; private set; } + + public DeliveryCacheFileMetaAccessor(BcatServer server) { - private const int MaxEntryCount = 100; - private const int MetaFileHeaderValue = 1; + Server = server; + } - private BcatServer Server { get; } - private object Locker { get; } = new object(); - private DeliveryCacheFileMetaEntry[] Entries { get; } = new DeliveryCacheFileMetaEntry[MaxEntryCount]; - public int Count { get; private set; } + public Result ReadApplicationFileMeta(ulong applicationId, ref DirectoryName directoryName, + bool allowMissingMetaFile) + { + Span metaPath = stackalloc byte[0x50]; + Server.GetStorageManager().GetFilesMetaPath(metaPath, applicationId, ref directoryName); - public DeliveryCacheFileMetaAccessor(BcatServer server) + return Read(new U8Span(metaPath), allowMissingMetaFile); + } + + public Result GetEntry(out DeliveryCacheFileMetaEntry entry, int index) + { + UnsafeHelpers.SkipParamInit(out entry); + + lock (Locker) { - Server = server; - } - - public Result ReadApplicationFileMeta(ulong applicationId, ref DirectoryName directoryName, - bool allowMissingMetaFile) - { - Span metaPath = stackalloc byte[0x50]; - Server.GetStorageManager().GetFilesMetaPath(metaPath, applicationId, ref directoryName); - - return Read(new U8Span(metaPath), allowMissingMetaFile); - } - - public Result GetEntry(out DeliveryCacheFileMetaEntry entry, int index) - { - UnsafeHelpers.SkipParamInit(out entry); - - lock (Locker) + if (index >= Count) { - if (index >= Count) - { - return ResultBcat.NotFound.Log(); - } - - entry = Entries[index]; - return Result.Success; - } - } - - public Result FindEntry(out DeliveryCacheFileMetaEntry entry, ref FileName fileName) - { - UnsafeHelpers.SkipParamInit(out entry); - - lock (Locker) - { - for (int i = 0; i < Count; i++) - { - if (StringUtils.CompareCaseInsensitive(Entries[i].Name.Bytes, fileName.Bytes) == 0) - { - entry = Entries[i]; - return Result.Success; - } - } - return ResultBcat.NotFound.Log(); } + + entry = Entries[index]; + return Result.Success; } + } - private Result Read(U8Span path, bool allowMissingMetaFile) + public Result FindEntry(out DeliveryCacheFileMetaEntry entry, ref FileName fileName) + { + UnsafeHelpers.SkipParamInit(out entry); + + lock (Locker) { - lock (Locker) + for (int i = 0; i < Count; i++) { - FileSystemClient fs = Server.GetFsClient(); - - Result rc = fs.OpenFile(out FileHandle handle, path, OpenMode.Read); - - if (rc.IsFailure()) + if (StringUtils.CompareCaseInsensitive(Entries[i].Name.Bytes, fileName.Bytes) == 0) { - if (ResultFs.PathNotFound.Includes(rc)) - { - if (allowMissingMetaFile) - { - Count = 0; - return Result.Success; - } - - return ResultBcat.NotFound.LogConverted(rc); - } - - return rc; - } - - try - { - Count = 0; - int header = 0; - - // Verify the header value - rc = fs.ReadFile(out long bytesRead, handle, 0, SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; - - if (bytesRead != sizeof(int) || header != MetaFileHeaderValue) - return ResultBcat.InvalidDeliveryCacheStorageFile.Log(); - - // Read all the file entries - Span buffer = MemoryMarshal.Cast(Entries); - rc = fs.ReadFile(out bytesRead, handle, 4, buffer); - if (rc.IsFailure()) return rc; - - Count = (int)((uint)bytesRead / Unsafe.SizeOf()); - + entry = Entries[i]; return Result.Success; } - finally + } + + return ResultBcat.NotFound.Log(); + } + } + + private Result Read(U8Span path, bool allowMissingMetaFile) + { + lock (Locker) + { + FileSystemClient fs = Server.GetFsClient(); + + Result rc = fs.OpenFile(out FileHandle handle, path, OpenMode.Read); + + if (rc.IsFailure()) + { + if (ResultFs.PathNotFound.Includes(rc)) { - fs.CloseFile(handle); + if (allowMissingMetaFile) + { + Count = 0; + return Result.Success; + } + + return ResultBcat.NotFound.LogConverted(rc); } + + return rc; + } + + try + { + Count = 0; + int header = 0; + + // Verify the header value + rc = fs.ReadFile(out long bytesRead, handle, 0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + if (bytesRead != sizeof(int) || header != MetaFileHeaderValue) + return ResultBcat.InvalidDeliveryCacheStorageFile.Log(); + + // Read all the file entries + Span buffer = MemoryMarshal.Cast(Entries); + rc = fs.ReadFile(out bytesRead, handle, 4, buffer); + if (rc.IsFailure()) return rc; + + Count = (int)((uint)bytesRead / Unsafe.SizeOf()); + + return Result.Success; + } + finally + { + fs.CloseFile(handle); } } } diff --git a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaEntry.cs b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaEntry.cs index 40a391ab..d049f426 100644 --- a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaEntry.cs +++ b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheFileMetaEntry.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace LibHac.Bcat.Impl.Service.Core +namespace LibHac.Bcat.Impl.Service.Core; + +[StructLayout(LayoutKind.Explicit, Size = 0x80)] +internal struct DeliveryCacheFileMetaEntry { - [StructLayout(LayoutKind.Explicit, Size = 0x80)] - internal struct DeliveryCacheFileMetaEntry - { - [FieldOffset(0x00)] public FileName Name; - [FieldOffset(0x20)] public long Id; - [FieldOffset(0x28)] public long Size; - [FieldOffset(0x30)] public Digest Digest; - } + [FieldOffset(0x00)] public FileName Name; + [FieldOffset(0x20)] public long Id; + [FieldOffset(0x28)] public long Size; + [FieldOffset(0x30)] public Digest Digest; } diff --git a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheStorageManager.cs b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheStorageManager.cs index a4741d6a..c2d085d6 100644 --- a/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheStorageManager.cs +++ b/src/LibHac/Bcat/Impl/Service/Core/DeliveryCacheStorageManager.cs @@ -6,394 +6,393 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using static LibHac.Fs.StringTraits; -namespace LibHac.Bcat.Impl.Service.Core +namespace LibHac.Bcat.Impl.Service.Core; + +internal class DeliveryCacheStorageManager { - internal class DeliveryCacheStorageManager + private const int MaxEntryCount = 4; + + private BcatServer Server { get; } + + private readonly object _locker = new object(); + private Entry[] Entries { get; } = new Entry[MaxEntryCount]; + private bool DisableStorage { get; set; } + + private struct Entry { - private const int MaxEntryCount = 4; + public ulong ApplicationId { get; set; } + public long RefCount { get; set; } + } - private BcatServer Server { get; } + public DeliveryCacheStorageManager(BcatServer server) + { + Server = server; + DisableStorage = false; + } - private readonly object _locker = new object(); - private Entry[] Entries { get; } = new Entry[MaxEntryCount]; - private bool DisableStorage { get; set; } - - private struct Entry + public Result Open(ulong applicationId) + { + lock (_locker) { - public ulong ApplicationId { get; set; } - public long RefCount { get; set; } - } + // Find an existing storage entry for this application ID or get an empty one + Result rc = FindOrGetUnusedEntry(out int index, applicationId); + if (rc.IsFailure()) return rc; - public DeliveryCacheStorageManager(BcatServer server) - { - Server = server; - DisableStorage = false; - } + ref Entry entry = ref Entries[index]; - public Result Open(ulong applicationId) - { - lock (_locker) + if (entry.RefCount != 0) { - // Find an existing storage entry for this application ID or get an empty one - Result rc = FindOrGetUnusedEntry(out int index, applicationId); - if (rc.IsFailure()) return rc; + return ResultBcat.TargetLocked.Log(); + } - ref Entry entry = ref Entries[index]; + // Get the mount name + var mountName = new MountName(); - if (entry.RefCount != 0) + var sb = new U8StringBuilder(mountName.Name); + sb.Append(DeliveryCacheMountNamePrefix) + .AppendFormat(index, 'd', 2); + + // Mount the save if enabled + if (!DisableStorage) + { + rc = Server.GetFsClient() + .MountBcatSaveData(new U8Span(mountName.Name), new Ncm.ApplicationId(applicationId)); + + if (rc.IsFailure()) { - return ResultBcat.TargetLocked.Log(); - } + if (ResultFs.TargetNotFound.Includes(rc)) + return ResultBcat.SaveDataNotFound.LogConverted(rc); - // Get the mount name + return rc; + } + } + + // Update the storage entry + entry.ApplicationId = applicationId; + entry.RefCount++; + + return Result.Success; + } + } + + public void Release(ulong applicationId) + { + lock (_locker) + { + int index = FindEntry(applicationId); + ref Entry entry = ref Entries[index]; + + entry.RefCount--; + + // Free the entry if there are no more references + if (entry.RefCount == 0) + { var mountName = new MountName(); var sb = new U8StringBuilder(mountName.Name); sb.Append(DeliveryCacheMountNamePrefix) .AppendFormat(index, 'd', 2); - // Mount the save if enabled + // Unmount the entry's savedata if (!DisableStorage) { - rc = Server.GetFsClient() - .MountBcatSaveData(new U8Span(mountName.Name), new Ncm.ApplicationId(applicationId)); - - if (rc.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(rc)) - return ResultBcat.SaveDataNotFound.LogConverted(rc); - - return rc; - } + Server.GetFsClient().Unmount(new U8Span(mountName.Name)); } - // Update the storage entry - entry.ApplicationId = applicationId; - entry.RefCount++; + // Clear the entry + entry.ApplicationId = 0; + // todo: Call nn::bcat::detail::service::core::PassphraseManager::Remove + } + } + } + + public void Commit(ulong applicationId) + { + lock (_locker) + { + int index = FindEntry(applicationId); + + var mountName = new MountName(); + + var sb = new U8StringBuilder(mountName.Name); + sb.Append(DeliveryCacheMountNamePrefix) + .AppendFormat(index, 'd', 2); + + if (!DisableStorage) + { + Result rc = Server.GetFsClient().Commit(new U8Span(mountName.Name)); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Abort"); + } + } + } + } + + public Result GetFreeSpaceSize(out long size, ulong applicationId) + { + lock (_locker) + { + Span path = stackalloc byte[0x20]; + + var sb = new U8StringBuilder(path); + AppendMountName(ref sb, applicationId); + sb.Append(RootPath); + + Result rc; + + if (DisableStorage) + { + size = 0x4400000; + rc = Result.Success; + } + else + { + rc = Server.GetFsClient().GetFreeSpaceSize(out size, new U8Span(path)); + } + + return rc; + } + } + + public void GetPassphrasePath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/passphrase.bin" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(PassphrasePath); + } + } + + public void GetDeliveryListPath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/list.msgpack" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(DeliveryListPath); + } + } + + public void GetEtagFilePath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/etag.bin" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(EtagPath); + } + } + + public void GetNaRequiredPath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/na_required" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(NaRequiredPath); + } + } + + public void GetIndexLockPath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/index.lock" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(IndexLockPath); + } + } + + public void GetFilePath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName, + ref FileName fileName) + { + // returns "mount:/directories/%s/files/%s", directoryName, fileName + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + + sb.Append(DirectoriesPath) + .Append(DirectorySeparator).Append(directoryName.Bytes) + .Append(DirectorySeparator).Append(FilesDirectoryName) + .Append(DirectorySeparator).Append(fileName.Bytes); + } + } + + public void GetFilesMetaPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) + { + // returns "mount:/directories/%s/files.meta", directoryName + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + + sb.Append(DirectoriesPath) + .Append(DirectorySeparator).Append(directoryName.Bytes) + .Append(DirectorySeparator).Append(FilesMetaFileName); + } + } + + public void GetDirectoriesPath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/directories" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + sb.Append(DirectoriesPath); + } + } + + public void GetDirectoryPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) + { + // returns "mount:/directories/%s", directoryName + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + + sb.Append(DirectoriesPath) + .Append(DirectorySeparator).Append(directoryName.Bytes); + } + } + + public void GetDirectoriesMetaPath(Span pathBuffer, ulong applicationId) + { + // returns "mount:/directories.meta" + lock (_locker) + { + var sb = new U8StringBuilder(pathBuffer); + AppendMountName(ref sb, applicationId); + + sb.Append(DirectoriesMetaPath); + } + } + + private void AppendMountName(ref U8StringBuilder sb, ulong applicationId) + { + int index = FindEntry(applicationId); + + sb.Append(DeliveryCacheMountNamePrefix) + .AppendFormat(index, 'd', 2); + } + + private Result FindOrGetUnusedEntry(out int entryIndex, ulong applicationId) + { + UnsafeHelpers.SkipParamInit(out entryIndex); + + // Try to find an existing entry + for (int i = 0; i < Entries.Length; i++) + { + if (Entries[i].ApplicationId == applicationId) + { + entryIndex = i; return Result.Success; } } - public void Release(ulong applicationId) + // Try to find an unused entry + for (int i = 0; i < Entries.Length; i++) { - lock (_locker) + if (Entries[i].ApplicationId == 0) { - int index = FindEntry(applicationId); - ref Entry entry = ref Entries[index]; - - entry.RefCount--; - - // Free the entry if there are no more references - if (entry.RefCount == 0) - { - var mountName = new MountName(); - - var sb = new U8StringBuilder(mountName.Name); - sb.Append(DeliveryCacheMountNamePrefix) - .AppendFormat(index, 'd', 2); - - // Unmount the entry's savedata - if (!DisableStorage) - { - Server.GetFsClient().Unmount(new U8Span(mountName.Name)); - } - - // Clear the entry - entry.ApplicationId = 0; - - // todo: Call nn::bcat::detail::service::core::PassphraseManager::Remove - } + entryIndex = i; + return Result.Success; } } - public void Commit(ulong applicationId) - { - lock (_locker) - { - int index = FindEntry(applicationId); - - var mountName = new MountName(); - - var sb = new U8StringBuilder(mountName.Name); - sb.Append(DeliveryCacheMountNamePrefix) - .AppendFormat(index, 'd', 2); - - if (!DisableStorage) - { - Result rc = Server.GetFsClient().Commit(new U8Span(mountName.Name)); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Abort"); - } - } - } - } - - public Result GetFreeSpaceSize(out long size, ulong applicationId) - { - lock (_locker) - { - Span path = stackalloc byte[0x20]; - - var sb = new U8StringBuilder(path); - AppendMountName(ref sb, applicationId); - sb.Append(RootPath); - - Result rc; - - if (DisableStorage) - { - size = 0x4400000; - rc = Result.Success; - } - else - { - rc = Server.GetFsClient().GetFreeSpaceSize(out size, new U8Span(path)); - } - - return rc; - } - } - - public void GetPassphrasePath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/passphrase.bin" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(PassphrasePath); - } - } - - public void GetDeliveryListPath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/list.msgpack" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(DeliveryListPath); - } - } - - public void GetEtagFilePath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/etag.bin" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(EtagPath); - } - } - - public void GetNaRequiredPath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/na_required" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(NaRequiredPath); - } - } - - public void GetIndexLockPath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/index.lock" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(IndexLockPath); - } - } - - public void GetFilePath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName, - ref FileName fileName) - { - // returns "mount:/directories/%s/files/%s", directoryName, fileName - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - - sb.Append(DirectoriesPath) - .Append(DirectorySeparator).Append(directoryName.Bytes) - .Append(DirectorySeparator).Append(FilesDirectoryName) - .Append(DirectorySeparator).Append(fileName.Bytes); - } - } - - public void GetFilesMetaPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) - { - // returns "mount:/directories/%s/files.meta", directoryName - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - - sb.Append(DirectoriesPath) - .Append(DirectorySeparator).Append(directoryName.Bytes) - .Append(DirectorySeparator).Append(FilesMetaFileName); - } - } - - public void GetDirectoriesPath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/directories" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - sb.Append(DirectoriesPath); - } - } - - public void GetDirectoryPath(Span pathBuffer, ulong applicationId, ref DirectoryName directoryName) - { - // returns "mount:/directories/%s", directoryName - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - - sb.Append(DirectoriesPath) - .Append(DirectorySeparator).Append(directoryName.Bytes); - } - } - - public void GetDirectoriesMetaPath(Span pathBuffer, ulong applicationId) - { - // returns "mount:/directories.meta" - lock (_locker) - { - var sb = new U8StringBuilder(pathBuffer); - AppendMountName(ref sb, applicationId); - - sb.Append(DirectoriesMetaPath); - } - } - - private void AppendMountName(ref U8StringBuilder sb, ulong applicationId) - { - int index = FindEntry(applicationId); - - sb.Append(DeliveryCacheMountNamePrefix) - .AppendFormat(index, 'd', 2); - } - - private Result FindOrGetUnusedEntry(out int entryIndex, ulong applicationId) - { - UnsafeHelpers.SkipParamInit(out entryIndex); - - // Try to find an existing entry - for (int i = 0; i < Entries.Length; i++) - { - if (Entries[i].ApplicationId == applicationId) - { - entryIndex = i; - return Result.Success; - } - } - - // Try to find an unused entry - for (int i = 0; i < Entries.Length; i++) - { - if (Entries[i].ApplicationId == 0) - { - entryIndex = i; - return Result.Success; - } - } - - return ResultBcat.StorageOpenLimitReached.Log(); - } - - private int FindEntry(ulong applicationId) - { - Entry[] entries = Entries; - - for (int i = 0; i < entries.Length; i++) - { - if (entries[i].ApplicationId == applicationId) - { - return i; - } - } - - // Nintendo uses 1 as the entry index if it wasn't found - Debug.Assert(false, "Entry not found."); - return 1; - } - - private static ReadOnlySpan DeliveryCacheMountNamePrefix => // bcat-dc- - new[] { (byte)'b', (byte)'c', (byte)'a', (byte)'t', (byte)'-', (byte)'d', (byte)'c', (byte)'-' }; - - private static ReadOnlySpan RootPath => // :/ - new[] { (byte)':', (byte)'/' }; - - private static ReadOnlySpan PassphrasePath => // :/passphrase.bin - new[] - { - (byte) ':', (byte) '/', (byte) 'p', (byte) 'a', (byte) 's', (byte) 's', (byte) 'p', (byte) 'h', - (byte) 'r', (byte) 'a', (byte) 's', (byte) 'e', (byte) '.', (byte) 'b', (byte) 'i', (byte) 'n' - }; - - private static ReadOnlySpan DeliveryListPath => // :/list.msgpack - new[] - { - (byte) ':', (byte) '/', (byte) 'l', (byte) 'i', (byte) 's', (byte) 't', (byte) '.', (byte) 'm', - (byte) 's', (byte) 'g', (byte) 'p', (byte) 'a', (byte) 'c', (byte) 'k' - }; - - private static ReadOnlySpan EtagPath => // :/etag.bin - new[] - { - (byte) ':', (byte) '/', (byte) 'e', (byte) 't', (byte) 'a', (byte) 'g', (byte) '.', (byte) 'b', - (byte) 'i', (byte) 'n' - }; - - private static ReadOnlySpan NaRequiredPath => // :/na_required - new[] - { - (byte) ':', (byte) '/', (byte) 'n', (byte) 'a', (byte) '_', (byte) 'r', (byte) 'e', (byte) 'q', - (byte) 'u', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'd' - }; - - private static ReadOnlySpan IndexLockPath => // :/index.lock - new[] - { - (byte) ':', (byte) '/', (byte) 'i', (byte) 'n', (byte) 'd', (byte) 'e', (byte) 'x', (byte) '.', - (byte) 'l', (byte) 'o', (byte) 'c', (byte) 'k' - }; - - private static ReadOnlySpan DirectoriesPath => // :/directories - new[] - { - (byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't', - (byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's' - }; - - private static ReadOnlySpan FilesMetaFileName => // files.meta - new[] - { - (byte) 'f', (byte) 'i', (byte) 'l', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e', - (byte) 't', (byte) 'a' - }; - - private static ReadOnlySpan DirectoriesMetaPath => // :/directories.meta - new[] - { - (byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't', - (byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e', - (byte) 't', (byte) 'a' - }; - - private static ReadOnlySpan FilesDirectoryName => // files - new[] { (byte)'f', (byte)'i', (byte)'l', (byte)'e', (byte)'s' }; + return ResultBcat.StorageOpenLimitReached.Log(); } + + private int FindEntry(ulong applicationId) + { + Entry[] entries = Entries; + + for (int i = 0; i < entries.Length; i++) + { + if (entries[i].ApplicationId == applicationId) + { + return i; + } + } + + // Nintendo uses 1 as the entry index if it wasn't found + Debug.Assert(false, "Entry not found."); + return 1; + } + + private static ReadOnlySpan DeliveryCacheMountNamePrefix => // bcat-dc- + new[] { (byte)'b', (byte)'c', (byte)'a', (byte)'t', (byte)'-', (byte)'d', (byte)'c', (byte)'-' }; + + private static ReadOnlySpan RootPath => // :/ + new[] { (byte)':', (byte)'/' }; + + private static ReadOnlySpan PassphrasePath => // :/passphrase.bin + new[] + { + (byte) ':', (byte) '/', (byte) 'p', (byte) 'a', (byte) 's', (byte) 's', (byte) 'p', (byte) 'h', + (byte) 'r', (byte) 'a', (byte) 's', (byte) 'e', (byte) '.', (byte) 'b', (byte) 'i', (byte) 'n' + }; + + private static ReadOnlySpan DeliveryListPath => // :/list.msgpack + new[] + { + (byte) ':', (byte) '/', (byte) 'l', (byte) 'i', (byte) 's', (byte) 't', (byte) '.', (byte) 'm', + (byte) 's', (byte) 'g', (byte) 'p', (byte) 'a', (byte) 'c', (byte) 'k' + }; + + private static ReadOnlySpan EtagPath => // :/etag.bin + new[] + { + (byte) ':', (byte) '/', (byte) 'e', (byte) 't', (byte) 'a', (byte) 'g', (byte) '.', (byte) 'b', + (byte) 'i', (byte) 'n' + }; + + private static ReadOnlySpan NaRequiredPath => // :/na_required + new[] + { + (byte) ':', (byte) '/', (byte) 'n', (byte) 'a', (byte) '_', (byte) 'r', (byte) 'e', (byte) 'q', + (byte) 'u', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'd' + }; + + private static ReadOnlySpan IndexLockPath => // :/index.lock + new[] + { + (byte) ':', (byte) '/', (byte) 'i', (byte) 'n', (byte) 'd', (byte) 'e', (byte) 'x', (byte) '.', + (byte) 'l', (byte) 'o', (byte) 'c', (byte) 'k' + }; + + private static ReadOnlySpan DirectoriesPath => // :/directories + new[] + { + (byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't', + (byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's' + }; + + private static ReadOnlySpan FilesMetaFileName => // files.meta + new[] + { + (byte) 'f', (byte) 'i', (byte) 'l', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e', + (byte) 't', (byte) 'a' + }; + + private static ReadOnlySpan DirectoriesMetaPath => // :/directories.meta + new[] + { + (byte) ':', (byte) '/', (byte) 'd', (byte) 'i', (byte) 'r', (byte) 'e', (byte) 'c', (byte) 't', + (byte) 'o', (byte) 'r', (byte) 'i', (byte) 'e', (byte) 's', (byte) '.', (byte) 'm', (byte) 'e', + (byte) 't', (byte) 'a' + }; + + private static ReadOnlySpan FilesDirectoryName => // files + new[] { (byte)'f', (byte)'i', (byte)'l', (byte)'e', (byte)'s' }; } diff --git a/src/LibHac/Bcat/Impl/Service/DeliveryCacheDirectoryService.cs b/src/LibHac/Bcat/Impl/Service/DeliveryCacheDirectoryService.cs index 5c4a89ea..59e10aa5 100644 --- a/src/LibHac/Bcat/Impl/Service/DeliveryCacheDirectoryService.cs +++ b/src/LibHac/Bcat/Impl/Service/DeliveryCacheDirectoryService.cs @@ -3,105 +3,104 @@ using LibHac.Bcat.Impl.Ipc; using LibHac.Bcat.Impl.Service.Core; using LibHac.Common; -namespace LibHac.Bcat.Impl.Service +namespace LibHac.Bcat.Impl.Service; + +internal class DeliveryCacheDirectoryService : IDeliveryCacheDirectoryService { - internal class DeliveryCacheDirectoryService : IDeliveryCacheDirectoryService + private BcatServer Server { get; } + private object Locker { get; } = new object(); + private DeliveryCacheStorageService Parent { get; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + private AccessControl Access { get; } + private ulong ApplicationId { get; } + private DirectoryName _name; + private bool IsDirectoryOpen { get; set; } + private int Count { get; set; } + + public DeliveryCacheDirectoryService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId, + AccessControl accessControl) { - private BcatServer Server { get; } - private object Locker { get; } = new object(); - private DeliveryCacheStorageService Parent { get; } + Server = server; + Parent = parent; + ApplicationId = applicationId; + Access = accessControl; + } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - private AccessControl Access { get; } - private ulong ApplicationId { get; } - private DirectoryName _name; - private bool IsDirectoryOpen { get; set; } - private int Count { get; set; } + public Result Open(ref DirectoryName name) + { + if (!name.IsValid()) + return ResultBcat.InvalidArgument.Log(); - public DeliveryCacheDirectoryService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId, - AccessControl accessControl) + lock (Locker) { - Server = server; - Parent = parent; - ApplicationId = applicationId; - Access = accessControl; - } + if (IsDirectoryOpen) + return ResultBcat.AlreadyOpen.Log(); - public Result Open(ref DirectoryName name) - { - if (!name.IsValid()) - return ResultBcat.InvalidArgument.Log(); + var metaReader = new DeliveryCacheFileMetaAccessor(Server); + Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref name, false); + if (rc.IsFailure()) return rc; - lock (Locker) - { - if (IsDirectoryOpen) - return ResultBcat.AlreadyOpen.Log(); + Count = metaReader.Count; + _name = name; + IsDirectoryOpen = true; - var metaReader = new DeliveryCacheFileMetaAccessor(Server); - Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref name, false); - if (rc.IsFailure()) return rc; - - Count = metaReader.Count; - _name = name; - IsDirectoryOpen = true; - - return Result.Success; - } - } - - public Result Read(out int entriesRead, Span entryBuffer) - { - UnsafeHelpers.SkipParamInit(out entriesRead); - - lock (Locker) - { - if (!IsDirectoryOpen) - return ResultBcat.NotOpen.Log(); - - var metaReader = new DeliveryCacheFileMetaAccessor(Server); - Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref _name, true); - if (rc.IsFailure()) return rc; - - int i; - for (i = 0; i < entryBuffer.Length; i++) - { - rc = metaReader.GetEntry(out DeliveryCacheFileMetaEntry entry, i); - - if (rc.IsFailure()) - { - if (!ResultBcat.NotFound.Includes(rc)) - return rc; - - break; - } - - entryBuffer[i] = new DeliveryCacheDirectoryEntry(ref entry.Name, entry.Size, ref entry.Digest); - } - - entriesRead = i; - return Result.Success; - } - } - - public Result GetCount(out int count) - { - UnsafeHelpers.SkipParamInit(out count); - - lock (Locker) - { - if (!IsDirectoryOpen) - { - return ResultBcat.NotOpen.Log(); - } - - count = Count; - return Result.Success; - } - } - - public void Dispose() - { - Parent.NotifyCloseDirectory(); + return Result.Success; } } + + public Result Read(out int entriesRead, Span entryBuffer) + { + UnsafeHelpers.SkipParamInit(out entriesRead); + + lock (Locker) + { + if (!IsDirectoryOpen) + return ResultBcat.NotOpen.Log(); + + var metaReader = new DeliveryCacheFileMetaAccessor(Server); + Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref _name, true); + if (rc.IsFailure()) return rc; + + int i; + for (i = 0; i < entryBuffer.Length; i++) + { + rc = metaReader.GetEntry(out DeliveryCacheFileMetaEntry entry, i); + + if (rc.IsFailure()) + { + if (!ResultBcat.NotFound.Includes(rc)) + return rc; + + break; + } + + entryBuffer[i] = new DeliveryCacheDirectoryEntry(ref entry.Name, entry.Size, ref entry.Digest); + } + + entriesRead = i; + return Result.Success; + } + } + + public Result GetCount(out int count) + { + UnsafeHelpers.SkipParamInit(out count); + + lock (Locker) + { + if (!IsDirectoryOpen) + { + return ResultBcat.NotOpen.Log(); + } + + count = Count; + return Result.Success; + } + } + + public void Dispose() + { + Parent.NotifyCloseDirectory(); + } } diff --git a/src/LibHac/Bcat/Impl/Service/DeliveryCacheFileService.cs b/src/LibHac/Bcat/Impl/Service/DeliveryCacheFileService.cs index 144655af..9954de1e 100644 --- a/src/LibHac/Bcat/Impl/Service/DeliveryCacheFileService.cs +++ b/src/LibHac/Bcat/Impl/Service/DeliveryCacheFileService.cs @@ -5,119 +5,118 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.Bcat.Impl.Service +namespace LibHac.Bcat.Impl.Service; + +internal class DeliveryCacheFileService : IDeliveryCacheFileService { - internal class DeliveryCacheFileService : IDeliveryCacheFileService + private BcatServer Server { get; } + private object Locker { get; } = new object(); + private DeliveryCacheStorageService Parent { get; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + private AccessControl Access { get; } + private ulong ApplicationId { get; } + private FileHandle _handle; + private DeliveryCacheFileMetaEntry _metaEntry; + private bool IsFileOpen { get; set; } + + public DeliveryCacheFileService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId, + AccessControl accessControl) { - private BcatServer Server { get; } - private object Locker { get; } = new object(); - private DeliveryCacheStorageService Parent { get; } + Server = server; + Parent = parent; + ApplicationId = applicationId; + Access = accessControl; + } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - private AccessControl Access { get; } - private ulong ApplicationId { get; } - private FileHandle _handle; - private DeliveryCacheFileMetaEntry _metaEntry; - private bool IsFileOpen { get; set; } + public Result Open(ref DirectoryName directoryName, ref FileName fileName) + { + if (!directoryName.IsValid()) + return ResultBcat.InvalidArgument.Log(); - public DeliveryCacheFileService(BcatServer server, DeliveryCacheStorageService parent, ulong applicationId, - AccessControl accessControl) - { - Server = server; - Parent = parent; - ApplicationId = applicationId; - Access = accessControl; - } + if (!fileName.IsValid()) + return ResultBcat.InvalidArgument.Log(); - public Result Open(ref DirectoryName directoryName, ref FileName fileName) - { - if (!directoryName.IsValid()) - return ResultBcat.InvalidArgument.Log(); - - if (!fileName.IsValid()) - return ResultBcat.InvalidArgument.Log(); - - lock (Locker) - { - if (IsFileOpen) - return ResultBcat.AlreadyOpen.Log(); - - var metaReader = new DeliveryCacheFileMetaAccessor(Server); - Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref directoryName, true); - if (rc.IsFailure()) return rc; - - rc = metaReader.FindEntry(out DeliveryCacheFileMetaEntry entry, ref fileName); - if (rc.IsFailure()) return rc; - - Span filePath = stackalloc byte[0x80]; - Server.GetStorageManager().GetFilePath(filePath, ApplicationId, ref directoryName, ref fileName); - - rc = Server.GetFsClient().OpenFile(out _handle, new U8Span(filePath), OpenMode.Read); - if (rc.IsFailure()) return rc; - - _metaEntry = entry; - IsFileOpen = true; - - return Result.Success; - } - } - - public Result Read(out long bytesRead, long offset, Span destination) - { - lock (Locker) - { - bytesRead = 0; - - if (!IsFileOpen) - return ResultBcat.NotOpen.Log(); - - Result rc = Server.GetFsClient().ReadFile(out long read, _handle, offset, destination); - if (rc.IsFailure()) return rc; - - bytesRead = read; - return Result.Success; - } - } - - public Result GetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - lock (Locker) - { - if (!IsFileOpen) - { - return ResultBcat.NotOpen.Log(); - } - - return Server.GetFsClient().GetFileSize(out size, _handle); - } - } - - public Result GetDigest(out Digest digest) - { - UnsafeHelpers.SkipParamInit(out digest); - - lock (Locker) - { - if (!IsFileOpen) - { - return ResultBcat.NotOpen.Log(); - } - - digest = _metaEntry.Digest; - return Result.Success; - } - } - - public void Dispose() + lock (Locker) { if (IsFileOpen) - { - Server.GetFsClient().CloseFile(_handle); - } + return ResultBcat.AlreadyOpen.Log(); - Parent.NotifyCloseFile(); + var metaReader = new DeliveryCacheFileMetaAccessor(Server); + Result rc = metaReader.ReadApplicationFileMeta(ApplicationId, ref directoryName, true); + if (rc.IsFailure()) return rc; + + rc = metaReader.FindEntry(out DeliveryCacheFileMetaEntry entry, ref fileName); + if (rc.IsFailure()) return rc; + + Span filePath = stackalloc byte[0x80]; + Server.GetStorageManager().GetFilePath(filePath, ApplicationId, ref directoryName, ref fileName); + + rc = Server.GetFsClient().OpenFile(out _handle, new U8Span(filePath), OpenMode.Read); + if (rc.IsFailure()) return rc; + + _metaEntry = entry; + IsFileOpen = true; + + return Result.Success; } } + + public Result Read(out long bytesRead, long offset, Span destination) + { + lock (Locker) + { + bytesRead = 0; + + if (!IsFileOpen) + return ResultBcat.NotOpen.Log(); + + Result rc = Server.GetFsClient().ReadFile(out long read, _handle, offset, destination); + if (rc.IsFailure()) return rc; + + bytesRead = read; + return Result.Success; + } + } + + public Result GetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + lock (Locker) + { + if (!IsFileOpen) + { + return ResultBcat.NotOpen.Log(); + } + + return Server.GetFsClient().GetFileSize(out size, _handle); + } + } + + public Result GetDigest(out Digest digest) + { + UnsafeHelpers.SkipParamInit(out digest); + + lock (Locker) + { + if (!IsFileOpen) + { + return ResultBcat.NotOpen.Log(); + } + + digest = _metaEntry.Digest; + return Result.Success; + } + } + + public void Dispose() + { + if (IsFileOpen) + { + Server.GetFsClient().CloseFile(_handle); + } + + Parent.NotifyCloseFile(); + } } diff --git a/src/LibHac/Bcat/Impl/Service/DeliveryCacheStorageService.cs b/src/LibHac/Bcat/Impl/Service/DeliveryCacheStorageService.cs index c4111ca3..09fe2fb6 100644 --- a/src/LibHac/Bcat/Impl/Service/DeliveryCacheStorageService.cs +++ b/src/LibHac/Bcat/Impl/Service/DeliveryCacheStorageService.cs @@ -5,108 +5,107 @@ using LibHac.Bcat.Impl.Service.Core; using LibHac.Common; using LibHac.Util; -namespace LibHac.Bcat.Impl.Service +namespace LibHac.Bcat.Impl.Service; + +internal class DeliveryCacheStorageService : IDeliveryCacheStorageService { - internal class DeliveryCacheStorageService : IDeliveryCacheStorageService + private const int MaxOpenCount = 8; + private BcatServer Server { get; } + + private object Locker { get; } = new object(); + private AccessControl Access { get; } + private ulong ApplicationId { get; } + private int FileServiceOpenCount { get; set; } + private int DirectoryServiceOpenCount { get; set; } + + public DeliveryCacheStorageService(BcatServer server, ulong applicationId, AccessControl accessControl) { - private const int MaxOpenCount = 8; - private BcatServer Server { get; } + Server = server; + ApplicationId = applicationId; + Access = accessControl; + } - private object Locker { get; } = new object(); - private AccessControl Access { get; } - private ulong ApplicationId { get; } - private int FileServiceOpenCount { get; set; } - private int DirectoryServiceOpenCount { get; set; } - - public DeliveryCacheStorageService(BcatServer server, ulong applicationId, AccessControl accessControl) + public Result CreateFileService(ref SharedRef service) + { + lock (Locker) { - Server = server; - ApplicationId = applicationId; - Access = accessControl; - } + if (FileServiceOpenCount >= MaxOpenCount) + return ResultBcat.ServiceOpenLimitReached.Log(); - public Result CreateFileService(ref SharedRef service) - { - lock (Locker) - { - if (FileServiceOpenCount >= MaxOpenCount) - return ResultBcat.ServiceOpenLimitReached.Log(); + service.Reset(new DeliveryCacheFileService(Server, this, ApplicationId, Access)); - service.Reset(new DeliveryCacheFileService(Server, this, ApplicationId, Access)); - - FileServiceOpenCount++; - return Result.Success; - } - } - - public Result CreateDirectoryService(ref SharedRef service) - { - lock (Locker) - { - if (DirectoryServiceOpenCount >= MaxOpenCount) - return ResultBcat.ServiceOpenLimitReached.Log(); - - service.Reset(new DeliveryCacheDirectoryService(Server, this, ApplicationId, Access)); - - DirectoryServiceOpenCount++; - return Result.Success; - } - } - - public Result EnumerateDeliveryCacheDirectory(out int namesRead, Span nameBuffer) - { - UnsafeHelpers.SkipParamInit(out namesRead); - - lock (Locker) - { - var metaReader = new DeliveryCacheDirectoryMetaAccessor(Server); - Result rc = metaReader.ReadApplicationDirectoryMeta(ApplicationId, true); - if (rc.IsFailure()) return rc; - - int i; - for (i = 0; i < nameBuffer.Length; i++) - { - rc = metaReader.GetEntry(out DeliveryCacheDirectoryMetaEntry entry, i); - - if (rc.IsFailure()) - { - if (!ResultBcat.NotFound.Includes(rc)) - return rc; - - break; - } - - StringUtils.Copy(nameBuffer[i].Bytes, entry.Name.Bytes); - } - - namesRead = i; - return Result.Success; - } - } - - internal void NotifyCloseFile() - { - lock (Locker) - { - FileServiceOpenCount--; - - Debug.Assert(FileServiceOpenCount >= 0); - } - } - - internal void NotifyCloseDirectory() - { - lock (Locker) - { - DirectoryServiceOpenCount--; - - Debug.Assert(DirectoryServiceOpenCount >= 0); - } - } - - public void Dispose() - { - Server.GetStorageManager().Release(ApplicationId); + FileServiceOpenCount++; + return Result.Success; } } + + public Result CreateDirectoryService(ref SharedRef service) + { + lock (Locker) + { + if (DirectoryServiceOpenCount >= MaxOpenCount) + return ResultBcat.ServiceOpenLimitReached.Log(); + + service.Reset(new DeliveryCacheDirectoryService(Server, this, ApplicationId, Access)); + + DirectoryServiceOpenCount++; + return Result.Success; + } + } + + public Result EnumerateDeliveryCacheDirectory(out int namesRead, Span nameBuffer) + { + UnsafeHelpers.SkipParamInit(out namesRead); + + lock (Locker) + { + var metaReader = new DeliveryCacheDirectoryMetaAccessor(Server); + Result rc = metaReader.ReadApplicationDirectoryMeta(ApplicationId, true); + if (rc.IsFailure()) return rc; + + int i; + for (i = 0; i < nameBuffer.Length; i++) + { + rc = metaReader.GetEntry(out DeliveryCacheDirectoryMetaEntry entry, i); + + if (rc.IsFailure()) + { + if (!ResultBcat.NotFound.Includes(rc)) + return rc; + + break; + } + + StringUtils.Copy(nameBuffer[i].Bytes, entry.Name.Bytes); + } + + namesRead = i; + return Result.Success; + } + } + + internal void NotifyCloseFile() + { + lock (Locker) + { + FileServiceOpenCount--; + + Debug.Assert(FileServiceOpenCount >= 0); + } + } + + internal void NotifyCloseDirectory() + { + lock (Locker) + { + DirectoryServiceOpenCount--; + + Debug.Assert(DirectoryServiceOpenCount >= 0); + } + } + + public void Dispose() + { + Server.GetStorageManager().Release(ApplicationId); + } } diff --git a/src/LibHac/Bcat/Impl/Service/ServiceCreator.cs b/src/LibHac/Bcat/Impl/Service/ServiceCreator.cs index 29d58d25..f4cd1e9e 100644 --- a/src/LibHac/Bcat/Impl/Service/ServiceCreator.cs +++ b/src/LibHac/Bcat/Impl/Service/ServiceCreator.cs @@ -2,58 +2,57 @@ using LibHac.Bcat.Impl.Ipc; using LibHac.Common; -namespace LibHac.Bcat.Impl.Service +namespace LibHac.Bcat.Impl.Service; + +// Todo: Update BCAT service object management +internal class ServiceCreator : IServiceCreator { - // Todo: Update BCAT service object management - internal class ServiceCreator : IServiceCreator + private BcatServer Server { get; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + private string ServiceName { get; } + private AccessControl AccessControl { get; } + + public ServiceCreator(BcatServer server, string serviceName, AccessControl accessControl) { - private BcatServer Server { get; } + Server = server; + ServiceName = serviceName; + AccessControl = accessControl; + } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - private string ServiceName { get; } - private AccessControl AccessControl { get; } + public void Dispose() { } - public ServiceCreator(BcatServer server, string serviceName, AccessControl accessControl) - { - Server = server; - ServiceName = serviceName; - AccessControl = accessControl; - } + public Result CreateDeliveryCacheStorageService(ref SharedRef outService, + ulong processId) + { + Result rc = Server.Hos.Arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, + processId); - public void Dispose() { } + if (rc.IsFailure()) + return ResultBcat.NotFound.LogConverted(rc); - public Result CreateDeliveryCacheStorageService(ref SharedRef outService, - ulong processId) - { - Result rc = Server.Hos.Arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty launchProperty, - processId); + return CreateDeliveryCacheStorageServiceImpl(ref outService, launchProperty.ApplicationId); + } - if (rc.IsFailure()) - return ResultBcat.NotFound.LogConverted(rc); + public Result CreateDeliveryCacheStorageServiceWithApplicationId( + ref SharedRef outService, ApplicationId applicationId) + { + if (!AccessControl.HasFlag(AccessControl.MountOthersDeliveryCacheStorage)) + return ResultBcat.PermissionDenied.Log(); - return CreateDeliveryCacheStorageServiceImpl(ref outService, launchProperty.ApplicationId); - } + return CreateDeliveryCacheStorageServiceImpl(ref outService, applicationId); + } - public Result CreateDeliveryCacheStorageServiceWithApplicationId( - ref SharedRef outService, ApplicationId applicationId) - { - if (!AccessControl.HasFlag(AccessControl.MountOthersDeliveryCacheStorage)) - return ResultBcat.PermissionDenied.Log(); + private Result CreateDeliveryCacheStorageServiceImpl(ref SharedRef outService, + ApplicationId applicationId) + { + Result rc = Server.GetStorageManager().Open(applicationId.Value); + if (rc.IsFailure()) return rc; - return CreateDeliveryCacheStorageServiceImpl(ref outService, applicationId); - } + // todo: Check if network account required - private Result CreateDeliveryCacheStorageServiceImpl(ref SharedRef outService, - ApplicationId applicationId) - { - Result rc = Server.GetStorageManager().Open(applicationId.Value); - if (rc.IsFailure()) return rc; + outService.Reset(new DeliveryCacheStorageService(Server, applicationId.Value, AccessControl)); - // todo: Check if network account required - - outService.Reset(new DeliveryCacheStorageService(Server, applicationId.Value, AccessControl)); - - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/Bcat/ResultBcat.cs b/src/LibHac/Bcat/ResultBcat.cs index c91295c1..d8823000 100644 --- a/src/LibHac/Bcat/ResultBcat.cs +++ b/src/LibHac/Bcat/ResultBcat.cs @@ -9,47 +9,46 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Bcat -{ - public static class ResultBcat - { - public const int ModuleBcat = 122; +namespace LibHac.Bcat; - /// Error code: 2122-0001; Inner value: 0x27a - public static Result.Base InvalidArgument => new Result.Base(ModuleBcat, 1); - /// Error code: 2122-0002; Inner value: 0x47a - public static Result.Base NotFound => new Result.Base(ModuleBcat, 2); - /// Error code: 2122-0003; Inner value: 0x67a - public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3); - /// Error code: 2122-0004; Inner value: 0x87a - public static Result.Base TargetAlreadyMounted => new Result.Base(ModuleBcat, 4); - /// Error code: 2122-0005; Inner value: 0xa7a - public static Result.Base TargetNotMounted => new Result.Base(ModuleBcat, 5); - /// Error code: 2122-0006; Inner value: 0xc7a - public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6); - /// Error code: 2122-0007; Inner value: 0xe7a - public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7); - /// Error code: 2122-0008; Inner value: 0x107a - public static Result.Base InternetRequestDenied => new Result.Base(ModuleBcat, 8); - /// Error code: 2122-0009; Inner value: 0x127a - public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9); - /// Error code: 2122-0010; Inner value: 0x147a - public static Result.Base SaveDataNotFound => new Result.Base(ModuleBcat, 10); - /// Error code: 2122-0031; Inner value: 0x3e7a - public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31); - /// Error code: 2122-0080; Inner value: 0xa07a - public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80); - /// Error code: 2122-0081; Inner value: 0xa27a - public static Result.Base DataVerificationFailed => new Result.Base(ModuleBcat, 81); - /// Error code: 2122-0090; Inner value: 0xb47a - public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90); - /// Error code: 2122-0091; Inner value: 0xb67a - public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91); - /// Error code: 2122-0098; Inner value: 0xc47a - public static Result.Base InvalidOperation => new Result.Base(ModuleBcat, 98); - /// Error code: 2122-0204; Inner value: 0x1987a - public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204); - /// Error code: 2122-0205; Inner value: 0x19a7a - public static Result.Base StorageOpenLimitReached => new Result.Base(ModuleBcat, 205); - } +public static class ResultBcat +{ + public const int ModuleBcat = 122; + + /// Error code: 2122-0001; Inner value: 0x27a + public static Result.Base InvalidArgument => new Result.Base(ModuleBcat, 1); + /// Error code: 2122-0002; Inner value: 0x47a + public static Result.Base NotFound => new Result.Base(ModuleBcat, 2); + /// Error code: 2122-0003; Inner value: 0x67a + public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3); + /// Error code: 2122-0004; Inner value: 0x87a + public static Result.Base TargetAlreadyMounted => new Result.Base(ModuleBcat, 4); + /// Error code: 2122-0005; Inner value: 0xa7a + public static Result.Base TargetNotMounted => new Result.Base(ModuleBcat, 5); + /// Error code: 2122-0006; Inner value: 0xc7a + public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6); + /// Error code: 2122-0007; Inner value: 0xe7a + public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7); + /// Error code: 2122-0008; Inner value: 0x107a + public static Result.Base InternetRequestDenied => new Result.Base(ModuleBcat, 8); + /// Error code: 2122-0009; Inner value: 0x127a + public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9); + /// Error code: 2122-0010; Inner value: 0x147a + public static Result.Base SaveDataNotFound => new Result.Base(ModuleBcat, 10); + /// Error code: 2122-0031; Inner value: 0x3e7a + public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31); + /// Error code: 2122-0080; Inner value: 0xa07a + public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80); + /// Error code: 2122-0081; Inner value: 0xa27a + public static Result.Base DataVerificationFailed => new Result.Base(ModuleBcat, 81); + /// Error code: 2122-0090; Inner value: 0xb47a + public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90); + /// Error code: 2122-0091; Inner value: 0xb67a + public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91); + /// Error code: 2122-0098; Inner value: 0xc47a + public static Result.Base InvalidOperation => new Result.Base(ModuleBcat, 98); + /// Error code: 2122-0204; Inner value: 0x1987a + public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204); + /// Error code: 2122-0205; Inner value: 0x19a7a + public static Result.Base StorageOpenLimitReached => new Result.Base(ModuleBcat, 205); } diff --git a/src/LibHac/BitReader.cs b/src/LibHac/BitReader.cs index fdd7986a..7b768675 100644 --- a/src/LibHac/BitReader.cs +++ b/src/LibHac/BitReader.cs @@ -1,133 +1,132 @@ using System; using System.Diagnostics; -namespace LibHac +namespace LibHac; + +public class BitReader { - public class BitReader + public byte[] Buffer { get; private set; } + public int LengthBits { get; private set; } + public int Position { get; set; } + public int Remaining => LengthBits - Position; + + public BitReader(byte[] buffer) => SetBuffer(buffer); + + public void SetBuffer(byte[] buffer) { - public byte[] Buffer { get; private set; } - public int LengthBits { get; private set; } - public int Position { get; set; } - public int Remaining => LengthBits - Position; + Buffer = buffer; + LengthBits = Buffer?.Length * 8 ?? 0; + Position = 0; + } - public BitReader(byte[] buffer) => SetBuffer(buffer); + public int ReadInt(int bitCount) + { + int value = PeekInt(bitCount); + Position += bitCount; + return value; + } - public void SetBuffer(byte[] buffer) + //public int ReadSignedInt(int bitCount) + //{ + // int value = PeekInt(bitCount); + // Position += bitCount; + // return Bit.SignExtend32(value, bitCount); + //} + + public bool ReadBool() => ReadInt(1) == 1; + + public int ReadOffsetBinary(int bitCount, OffsetBias bias) + { + int offset = (1 << (bitCount - 1)) - (int)bias; + int value = PeekInt(bitCount) - offset; + Position += bitCount; + return value; + } + + //public void AlignPosition(int multiple) + //{ + // Position = Helpers.GetNextMultiple(Position, multiple); + //} + + public int PeekInt(int bitCount) + { + Debug.Assert(bitCount >= 0 && bitCount <= 32); + + if (bitCount > Remaining) { - Buffer = buffer; - LengthBits = Buffer?.Length * 8 ?? 0; - Position = 0; + if (Position >= LengthBits) return 0; + + int extraBits = bitCount - Remaining; + return PeekIntFallback(Remaining) << extraBits; } - public int ReadInt(int bitCount) + int byteIndex = Position / 8; + int bitIndex = Position % 8; + + if (bitCount <= 9 && Remaining >= 16) { - int value = PeekInt(bitCount); - Position += bitCount; + int value = Buffer[byteIndex] << 8 | Buffer[byteIndex + 1]; + value &= 0xFFFF >> bitIndex; + value >>= 16 - bitCount - bitIndex; return value; } - //public int ReadSignedInt(int bitCount) - //{ - // int value = PeekInt(bitCount); - // Position += bitCount; - // return Bit.SignExtend32(value, bitCount); - //} - - public bool ReadBool() => ReadInt(1) == 1; - - public int ReadOffsetBinary(int bitCount, OffsetBias bias) + if (bitCount <= 17 && Remaining >= 24) { - int offset = (1 << (bitCount - 1)) - (int)bias; - int value = PeekInt(bitCount) - offset; - Position += bitCount; + int value = Buffer[byteIndex] << 16 | Buffer[byteIndex + 1] << 8 | Buffer[byteIndex + 2]; + value &= 0xFFFFFF >> bitIndex; + value >>= 24 - bitCount - bitIndex; return value; } - //public void AlignPosition(int multiple) - //{ - // Position = Helpers.GetNextMultiple(Position, multiple); - //} - - public int PeekInt(int bitCount) + if (bitCount <= 25 && Remaining >= 32) { - Debug.Assert(bitCount >= 0 && bitCount <= 32); - - if (bitCount > Remaining) - { - if (Position >= LengthBits) return 0; - - int extraBits = bitCount - Remaining; - return PeekIntFallback(Remaining) << extraBits; - } - - int byteIndex = Position / 8; - int bitIndex = Position % 8; - - if (bitCount <= 9 && Remaining >= 16) - { - int value = Buffer[byteIndex] << 8 | Buffer[byteIndex + 1]; - value &= 0xFFFF >> bitIndex; - value >>= 16 - bitCount - bitIndex; - return value; - } - - if (bitCount <= 17 && Remaining >= 24) - { - int value = Buffer[byteIndex] << 16 | Buffer[byteIndex + 1] << 8 | Buffer[byteIndex + 2]; - value &= 0xFFFFFF >> bitIndex; - value >>= 24 - bitCount - bitIndex; - return value; - } - - if (bitCount <= 25 && Remaining >= 32) - { - int value = Buffer[byteIndex] << 24 | Buffer[byteIndex + 1] << 16 | Buffer[byteIndex + 2] << 8 | Buffer[byteIndex + 3]; - value &= (int)(0xFFFFFFFF >> bitIndex); - value >>= 32 - bitCount - bitIndex; - return value; - } - return PeekIntFallback(bitCount); - } - - private int PeekIntFallback(int bitCount) - { - int value = 0; - int byteIndex = Position / 8; - int bitIndex = Position % 8; - - while (bitCount > 0) - { - if (bitIndex >= 8) - { - bitIndex = 0; - byteIndex++; - } - - int bitsToRead = Math.Min(bitCount, 8 - bitIndex); - int mask = 0xFF >> bitIndex; - int currentByte = (mask & Buffer[byteIndex]) >> (8 - bitIndex - bitsToRead); - - value = (value << bitsToRead) | currentByte; - bitIndex += bitsToRead; - bitCount -= bitsToRead; - } + int value = Buffer[byteIndex] << 24 | Buffer[byteIndex + 1] << 16 | Buffer[byteIndex + 2] << 8 | Buffer[byteIndex + 3]; + value &= (int)(0xFFFFFFFF >> bitIndex); + value >>= 32 - bitCount - bitIndex; return value; } + return PeekIntFallback(bitCount); + } - /// - /// Specifies the bias of an offset binary value. A positive bias can represent one more - /// positive value than negative value, and a negative bias can represent one more - /// negative value than positive value. - /// - /// Example: - /// A 4-bit offset binary value with a positive bias can store - /// the values 8 through -7 inclusive. - /// A 4-bit offset binary value with a negative bias can store - /// the values 7 through -8 inclusive. - public enum OffsetBias + private int PeekIntFallback(int bitCount) + { + int value = 0; + int byteIndex = Position / 8; + int bitIndex = Position % 8; + + while (bitCount > 0) { - Positive = 1, - Negative = 0 + if (bitIndex >= 8) + { + bitIndex = 0; + byteIndex++; + } + + int bitsToRead = Math.Min(bitCount, 8 - bitIndex); + int mask = 0xFF >> bitIndex; + int currentByte = (mask & Buffer[byteIndex]) >> (8 - bitIndex - bitsToRead); + + value = (value << bitsToRead) | currentByte; + bitIndex += bitsToRead; + bitCount -= bitsToRead; } + return value; + } + + /// + /// Specifies the bias of an offset binary value. A positive bias can represent one more + /// positive value than negative value, and a negative bias can represent one more + /// negative value than positive value. + /// + /// Example: + /// A 4-bit offset binary value with a positive bias can store + /// the values 8 through -7 inclusive. + /// A 4-bit offset binary value with a negative bias can store + /// the values 7 through -8 inclusive. + public enum OffsetBias + { + Positive = 1, + Negative = 0 } } diff --git a/src/LibHac/BitTools.cs b/src/LibHac/BitTools.cs index ba50248f..2afefc7d 100644 --- a/src/LibHac/BitTools.cs +++ b/src/LibHac/BitTools.cs @@ -1,11 +1,10 @@ -namespace LibHac +namespace LibHac; + +public static class BitTools { - public static class BitTools + public static int SignExtend32(int value, int bits) { - public static int SignExtend32(int value, int bits) - { - int shift = 8 * sizeof(int) - bits; - return (value << shift) >> shift; - } + int shift = 8 * sizeof(int) - bits; + return (value << shift) >> shift; } } diff --git a/src/LibHac/Boot/KeyBlob.cs b/src/LibHac/Boot/KeyBlob.cs index ca1d485a..a5d22911 100644 --- a/src/LibHac/Boot/KeyBlob.cs +++ b/src/LibHac/Boot/KeyBlob.cs @@ -6,89 +6,88 @@ using LibHac.Common; using LibHac.Crypto; using LibHac.Util; -namespace LibHac.Boot +namespace LibHac.Boot; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = 0xB0)] +public struct EncryptedKeyBlob { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = 0xB0)] - public struct EncryptedKeyBlob - { #if DEBUG - [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; - [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; - [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; - [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; - [FieldOffset(0x80)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy5; - [FieldOffset(0xA0)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer16 _dummy6; + [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; + [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; + [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; + [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; + [FieldOffset(0x80)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy5; + [FieldOffset(0xA0)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer16 _dummy6; #endif - [FieldOffset(0x00)] public AesCmac Cmac; - [FieldOffset(0x10)] public AesIv Counter; + [FieldOffset(0x00)] public AesCmac Cmac; + [FieldOffset(0x10)] public AesIv Counter; - public Span Payload => Bytes.Slice(0x20, Unsafe.SizeOf()); + public Span Payload => Bytes.Slice(0x20, Unsafe.SizeOf()); - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() - { - ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); - - for (int i = 0; i < ulongSpan.Length; i++) - { - if (ulongSpan[i] != 0) - return false; - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in EncryptedKeyBlob value) - { - return SpanHelpers.AsReadOnlyByteSpan(in value); - } - - public override readonly string ToString() => ReadOnlyBytes.ToHexString(); - } - - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = 0x90)] - public struct KeyBlob + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() { -#if DEBUG - [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; - [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; - [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; - [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; -#endif + ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); - [FieldOffset(0x00)] public AesKey MasterKek; - [FieldOffset(0x80)] public AesKey Package1Key; - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() + for (int i = 0; i < ulongSpan.Length; i++) { - ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); - - for (int i = 0; i < ulongSpan.Length; i++) - { - if (ulongSpan[i] != 0) - return false; - } - - return true; + if (ulongSpan[i] != 0) + return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in KeyBlob value) - { - return SpanHelpers.AsReadOnlyByteSpan(in value); - } - - public override readonly string ToString() => ReadOnlyBytes.ToHexString(); + return true; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in EncryptedKeyBlob value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + public override readonly string ToString() => ReadOnlyBytes.ToHexString(); +} + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = 0x90)] +public struct KeyBlob +{ +#if DEBUG + [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; + [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; + [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; + [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; +#endif + + [FieldOffset(0x00)] public AesKey MasterKek; + [FieldOffset(0x80)] public AesKey Package1Key; + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() + { + ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); + + for (int i = 0; i < ulongSpan.Length; i++) + { + if (ulongSpan[i] != 0) + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in KeyBlob value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + public override readonly string ToString() => ReadOnlyBytes.ToHexString(); } diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs index b13348d7..628555e2 100644 --- a/src/LibHac/Boot/Package1.cs +++ b/src/LibHac/Boot/Package1.cs @@ -9,546 +9,545 @@ using LibHac.Common.Keys; using LibHac.Diag; using LibHac.Util; -namespace LibHac.Boot +namespace LibHac.Boot; + +[StructLayout(LayoutKind.Explicit, Size = 0x170)] +public struct Package1MarikoOemHeader { - [StructLayout(LayoutKind.Explicit, Size = 0x170)] - public struct Package1MarikoOemHeader + [FieldOffset(0x000)] private byte _aesMac; + [FieldOffset(0x010)] private byte _rsaSig; + [FieldOffset(0x110)] private byte _salt; + [FieldOffset(0x130)] private byte _hash; + [FieldOffset(0x150)] public int Version; + [FieldOffset(0x154)] public int Size; + [FieldOffset(0x158)] public int LoadAddress; + [FieldOffset(0x15C)] public int EntryPoint; + [FieldOffset(0x160)] private byte _reserved; + + public ReadOnlySpan AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10); + public ReadOnlySpan RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100); + public ReadOnlySpan Salt => SpanHelpers.CreateSpan(ref _salt, 0x20); + public ReadOnlySpan Hash => SpanHelpers.CreateSpan(ref _hash, 0x20); + public ReadOnlySpan Reserved => SpanHelpers.CreateSpan(ref _reserved, 0x10); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x20)] +public struct Package1MetaData +{ + [FieldOffset(0x00)] public uint LoaderHash; + [FieldOffset(0x04)] public uint SecureMonitorHash; + [FieldOffset(0x08)] public uint BootloaderHash; + [FieldOffset(0x10)] private byte _buildDate; + [FieldOffset(0x1E)] public byte KeyGeneration; + [FieldOffset(0x1F)] public byte Version; + + public U8Span BuildDate => new U8Span(SpanHelpers.CreateSpan(ref _buildDate, 0xE)); + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _buildDate, 0x10); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x20)] +public struct Package1Stage1Footer +{ + [FieldOffset(0x00)] public int Pk11Size; + [FieldOffset(0x10)] private byte _iv; + + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _iv, 0x10); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x20)] +public struct Package1Pk11Header +{ + public const uint ExpectedMagic = 0x31314B50; // PK11 + + [FieldOffset(0x00)] public uint Magic; + [FieldOffset(0x04)] public int WarmBootSize; + [FieldOffset(0x08)] public int WarmBootOffset; + [FieldOffset(0x10)] public int BootloaderSize; + [FieldOffset(0x14)] public int BootloaderOffset; + [FieldOffset(0x18)] public int SecureMonitorSize; + [FieldOffset(0x1C)] public int SecureMonitorOffset; +} + +public enum Package1Section +{ + Bootloader, + SecureMonitor, + WarmBoot +} + +public class Package1 +{ + private const int LegacyStage1Size = 0x4000; + private const int ModernStage1Size = 0x7000; + private const int MarikoWarmBootPlainTextSectionSize = 0x330; + + private SharedRef _baseStorage; + private SubStorage _pk11Storage; + private SubStorage _bodyStorage; + + private KeySet KeySet { get; set; } + + public bool IsModern { get; private set; } + public bool IsMariko { get; private set; } + + /// + /// Returns if the package1 can be decrypted. + /// + public bool IsDecrypted { get; private set; } + public byte KeyRevision { get; private set; } + + public int Pk11Size { get; private set; } + + private Package1MarikoOemHeader _marikoOemHeader; + private Package1MetaData _metaData; + private Package1Stage1Footer _stage1Footer; + private Package1Pk11Header _pk11Header; + private Buffer16 _pk11Mac; + + public ref readonly Package1MarikoOemHeader MarikoOemHeader => ref _marikoOemHeader; + public ref readonly Package1MetaData MetaData => ref _metaData; + public ref readonly Package1Stage1Footer Stage1Footer => ref _stage1Footer; + public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header; + public ref readonly Buffer16 Pk11Mac => ref _pk11Mac; + + public Result Initialize(KeySet keySet, in SharedRef storage) { - [FieldOffset(0x000)] private byte _aesMac; - [FieldOffset(0x010)] private byte _rsaSig; - [FieldOffset(0x110)] private byte _salt; - [FieldOffset(0x130)] private byte _hash; - [FieldOffset(0x150)] public int Version; - [FieldOffset(0x154)] public int Size; - [FieldOffset(0x158)] public int LoadAddress; - [FieldOffset(0x15C)] public int EntryPoint; - [FieldOffset(0x160)] private byte _reserved; + KeySet = keySet; + _baseStorage.SetByCopy(in storage); - public ReadOnlySpan AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10); - public ReadOnlySpan RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100); - public ReadOnlySpan Salt => SpanHelpers.CreateSpan(ref _salt, 0x20); - public ReadOnlySpan Hash => SpanHelpers.CreateSpan(ref _hash, 0x20); - public ReadOnlySpan Reserved => SpanHelpers.CreateSpan(ref _reserved, 0x10); - } + // Read what might be a mariko header and check if it actually is a mariko header + Result rc = _baseStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _marikoOemHeader)); + if (rc.IsFailure()) return rc; - [StructLayout(LayoutKind.Explicit, Size = 0x20)] - public struct Package1MetaData - { - [FieldOffset(0x00)] public uint LoaderHash; - [FieldOffset(0x04)] public uint SecureMonitorHash; - [FieldOffset(0x08)] public uint BootloaderHash; - [FieldOffset(0x10)] private byte _buildDate; - [FieldOffset(0x1E)] public byte KeyGeneration; - [FieldOffset(0x1F)] public byte Version; + IsMariko = IsMarikoImpl(); - public U8Span BuildDate => new U8Span(SpanHelpers.CreateSpan(ref _buildDate, 0xE)); - public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _buildDate, 0x10); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x20)] - public struct Package1Stage1Footer - { - [FieldOffset(0x00)] public int Pk11Size; - [FieldOffset(0x10)] private byte _iv; - - public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _iv, 0x10); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x20)] - public struct Package1Pk11Header - { - public const uint ExpectedMagic = 0x31314B50; // PK11 - - [FieldOffset(0x00)] public uint Magic; - [FieldOffset(0x04)] public int WarmBootSize; - [FieldOffset(0x08)] public int WarmBootOffset; - [FieldOffset(0x10)] public int BootloaderSize; - [FieldOffset(0x14)] public int BootloaderOffset; - [FieldOffset(0x18)] public int SecureMonitorSize; - [FieldOffset(0x1C)] public int SecureMonitorOffset; - } - - public enum Package1Section - { - Bootloader, - SecureMonitor, - WarmBoot - } - - public class Package1 - { - private const int LegacyStage1Size = 0x4000; - private const int ModernStage1Size = 0x7000; - private const int MarikoWarmBootPlainTextSectionSize = 0x330; - - private SharedRef _baseStorage; - private SubStorage _pk11Storage; - private SubStorage _bodyStorage; - - private KeySet KeySet { get; set; } - - public bool IsModern { get; private set; } - public bool IsMariko { get; private set; } - - /// - /// Returns if the package1 can be decrypted. - /// - public bool IsDecrypted { get; private set; } - public byte KeyRevision { get; private set; } - - public int Pk11Size { get; private set; } - - private Package1MarikoOemHeader _marikoOemHeader; - private Package1MetaData _metaData; - private Package1Stage1Footer _stage1Footer; - private Package1Pk11Header _pk11Header; - private Buffer16 _pk11Mac; - - public ref readonly Package1MarikoOemHeader MarikoOemHeader => ref _marikoOemHeader; - public ref readonly Package1MetaData MetaData => ref _metaData; - public ref readonly Package1Stage1Footer Stage1Footer => ref _stage1Footer; - public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header; - public ref readonly Buffer16 Pk11Mac => ref _pk11Mac; - - public Result Initialize(KeySet keySet, in SharedRef storage) + if (IsMariko) { - KeySet = keySet; - _baseStorage.SetByCopy(in storage); - - // Read what might be a mariko header and check if it actually is a mariko header - Result rc = _baseStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _marikoOemHeader)); + rc = InitMarikoBodyStorage(); if (rc.IsFailure()) return rc; + } + else + { + rc = _baseStorage.Get.GetSize(out long baseStorageSize); + if (rc.IsFailure()) return rc.Miss(); - IsMariko = IsMarikoImpl(); + _bodyStorage = new SubStorage(in _baseStorage, 0, baseStorageSize); + rc = _bodyStorage.Read(0, SpanHelpers.AsByteSpan(ref _metaData)); + if (rc.IsFailure()) return rc; + } - if (IsMariko) + rc = ParseStage1(); + if (rc.IsFailure()) return rc; + + rc = ReadPk11Header(); + if (rc.IsFailure()) return rc; + + if (!IsMariko && IsModern) + { + rc = ReadModernEristaMac(); + if (rc.IsFailure()) return rc; + } + + rc = SetPk11Storage(); + if (rc.IsFailure()) return rc; + + // Make sure the PK11 section sizes add up to the expected size + if (IsDecrypted && !VerifyPk11Sizes()) + { + return ResultLibHac.InvalidPackage1Pk11Size.Log(); + } + + return Result.Success; + } + + /// + /// Read the encrypted section of a Mariko Package1 and try to decrypt it. + /// + /// : The operation was successful.
+ /// : The package1 is + /// too small or the size in the OEM header is incorrect.
+ private Result InitMarikoBodyStorage() + { + // Body must be large enough to hold at least one metadata struct + if (MarikoOemHeader.Size < Unsafe.SizeOf()) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Verify the body storage size is not smaller than the size in the header + Result rc = _baseStorage.Get.GetSize(out long totalSize); + if (rc.IsFailure()) return rc; + + long bodySize = totalSize - Unsafe.SizeOf(); + if (bodySize < MarikoOemHeader.Size) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Create body SubStorage and metadata buffers + var bodySubStorage = new SubStorage(in _baseStorage, Unsafe.SizeOf(), bodySize); + + Span metaData = stackalloc Package1MetaData[2]; + Span metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]); + Span metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]); + + // Read both the plaintext metadata and encrypted metadata + rc = bodySubStorage.Read(0, MemoryMarshal.Cast(metaData)); + if (rc.IsFailure()) return rc; + + // Set the body storage and decrypted metadata + _metaData = metaData[0]; + _bodyStorage = bodySubStorage; + + // The plaintext metadata is followed by an encrypted copy + // If these two match then the body is already decrypted + IsDecrypted = metaData1.SequenceEqual(metaData2); + + if (IsDecrypted) + { + return Result.Success; + } + + // If encrypted, check if the body can be decrypted + Crypto.Aes.DecryptCbc128(metaData2, metaData2, KeySet.MarikoBek, _metaData.Iv); + IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData)); + + // Get a decrypted body storage if we have the correct key + if (IsDecrypted) + { + var decStorage = new AesCbcStorage(bodySubStorage, KeySet.MarikoBek, _metaData.Iv, true); + var cachedStorage = new CachedStorage(decStorage, 0x4000, 1, true); + _bodyStorage = new SubStorage(cachedStorage, 0, bodySize); + } + + return Result.Success; + } + + private Result ParseStage1() + { + // Erista package1ldr is stored unencrypted, so we can always directly read the size + // field at the end of package1ldr. + + // Mariko package1ldr is stored encrypted. If we're able to decrypt it, + // directly read the size field at the end of package1ldr. + + IsModern = !IsLegacyImpl(); + int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; + + if (IsMariko && !IsDecrypted) + { + // If we're not able to decrypt the Mariko package1ldr, calculate the size by subtracting + // the known package1ldr size from the total size in the OEM header. + Pk11Size = MarikoOemHeader.Size - stage1Size; + return Result.Success; + } + + // Read the package1ldr footer + int footerOffset = stage1Size - Unsafe.SizeOf(); + + Result rc = _bodyStorage.Read(footerOffset, SpanHelpers.AsByteSpan(ref _stage1Footer)); + if (rc.IsFailure()) return rc; + + // Get the PK11 size from the field in the unencrypted stage 1 footer + Pk11Size = _stage1Footer.Pk11Size; + + return Result.Success; + } + + private Result ReadPk11Header() + { + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + return _bodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + } + + private Result ReadModernEristaMac() + { + return _baseStorage.Get.Read(ModernStage1Size + Pk11Size, _pk11Mac.Bytes); + } + + private Result SetPk11Storage() + { + // Read the PK11 header from the body storage + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + Result rc = _bodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + if (rc.IsFailure()) return rc; + + // Check if PK11 is already decrypted, creating the PK11 storage if it is + IsDecrypted = _pk11Header.Magic == Package1Pk11Header.ExpectedMagic; + + if (IsDecrypted) + { + _pk11Storage = new SubStorage(_bodyStorage, pk11Offset, Pk11Size); + return Result.Success; + } + + var encPk11Storage = new SubStorage(_bodyStorage, pk11Offset, Pk11Size); + + // See if we have an Erista package1 key that can decrypt this PK11 + if (!IsMariko && TryFindEristaKeyRevision()) + { + IsDecrypted = true; + IStorage decPk11Storage; + + if (IsModern) { - rc = InitMarikoBodyStorage(); - if (rc.IsFailure()) return rc; + decPk11Storage = new AesCbcStorage(encPk11Storage, KeySet.Package1Keys[KeyRevision], + _stage1Footer.Iv, true); } else { - rc = _baseStorage.Get.GetSize(out long baseStorageSize); - if (rc.IsFailure()) return rc.Miss(); - - _bodyStorage = new SubStorage(in _baseStorage, 0, baseStorageSize); - rc = _bodyStorage.Read(0, SpanHelpers.AsByteSpan(ref _metaData)); - if (rc.IsFailure()) return rc; + decPk11Storage = new Aes128CtrStorage(encPk11Storage, + KeySet.Package1Keys[KeyRevision].DataRo.ToArray(), _stage1Footer.Iv.ToArray(), true); } - rc = ParseStage1(); - if (rc.IsFailure()) return rc; - - rc = ReadPk11Header(); - if (rc.IsFailure()) return rc; - - if (!IsMariko && IsModern) - { - rc = ReadModernEristaMac(); - if (rc.IsFailure()) return rc; - } - - rc = SetPk11Storage(); - if (rc.IsFailure()) return rc; - - // Make sure the PK11 section sizes add up to the expected size - if (IsDecrypted && !VerifyPk11Sizes()) - { - return ResultLibHac.InvalidPackage1Pk11Size.Log(); - } + _pk11Storage = new SubStorage(new CachedStorage(decPk11Storage, 0x4000, 1, true), 0, Pk11Size); return Result.Success; } - /// - /// Read the encrypted section of a Mariko Package1 and try to decrypt it. - /// - /// : The operation was successful.
- /// : The package1 is - /// too small or the size in the OEM header is incorrect.
- private Result InitMarikoBodyStorage() + // We can't decrypt the PK11. Set Pk11Storage to the encrypted PK11 storage + _pk11Storage = encPk11Storage; + return Result.Success; + } + + private delegate void Decryptor(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false); + + private bool TryFindEristaKeyRevision() + { + // Package1 has no indication of which key it's encrypted with, + // so we must test each known key to find one that works + + var decHeader = new Package1Pk11Header(); + + int start = IsModern ? 6 : 0; + int end = IsModern ? 0x20 : 6; + Decryptor decryptor = IsModern ? Crypto.Aes.DecryptCbc128 : Crypto.Aes.DecryptCtr128; + + for (int i = start; i < end; i++) { - // Body must be large enough to hold at least one metadata struct - if (MarikoOemHeader.Size < Unsafe.SizeOf()) - return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader), + KeySet.Package1Keys[i], _stage1Footer.Iv); - // Verify the body storage size is not smaller than the size in the header - Result rc = _baseStorage.Get.GetSize(out long totalSize); - if (rc.IsFailure()) return rc; - - long bodySize = totalSize - Unsafe.SizeOf(); - if (bodySize < MarikoOemHeader.Size) - return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); - - // Create body SubStorage and metadata buffers - var bodySubStorage = new SubStorage(in _baseStorage, Unsafe.SizeOf(), bodySize); - - Span metaData = stackalloc Package1MetaData[2]; - Span metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]); - Span metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]); - - // Read both the plaintext metadata and encrypted metadata - rc = bodySubStorage.Read(0, MemoryMarshal.Cast(metaData)); - if (rc.IsFailure()) return rc; - - // Set the body storage and decrypted metadata - _metaData = metaData[0]; - _bodyStorage = bodySubStorage; - - // The plaintext metadata is followed by an encrypted copy - // If these two match then the body is already decrypted - IsDecrypted = metaData1.SequenceEqual(metaData2); - - if (IsDecrypted) + if (decHeader.Magic == Package1Pk11Header.ExpectedMagic) { - return Result.Success; + KeyRevision = (byte)i; + _pk11Header = decHeader; + return true; } - - // If encrypted, check if the body can be decrypted - Crypto.Aes.DecryptCbc128(metaData2, metaData2, KeySet.MarikoBek, _metaData.Iv); - IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData)); - - // Get a decrypted body storage if we have the correct key - if (IsDecrypted) - { - var decStorage = new AesCbcStorage(bodySubStorage, KeySet.MarikoBek, _metaData.Iv, true); - var cachedStorage = new CachedStorage(decStorage, 0x4000, 1, true); - _bodyStorage = new SubStorage(cachedStorage, 0, bodySize); - } - - return Result.Success; } - private Result ParseStage1() + return false; + } + + private bool VerifyPk11Sizes() + { + Assert.SdkRequires(IsDecrypted); + + int pk11Size = Unsafe.SizeOf() + GetSectionSize(Package1Section.WarmBoot) + + GetSectionSize(Package1Section.Bootloader) + GetSectionSize(Package1Section.SecureMonitor); + + pk11Size = Alignment.AlignUp(pk11Size, 0x10); + + return pk11Size == Pk11Size; + } + + // MetaData must be read first + private bool IsLegacyImpl() + { + return _metaData.Version < 0xE || StringUtils.Compare(_metaData.BuildDate, LegacyDateCutoff) < 0; + } + + // MarikoOemHeader must be read first + private bool IsMarikoImpl() + { + return MarikoOemHeader.AesMac.IsZeros() && MarikoOemHeader.Reserved.IsZeros(); + } + + /// + /// Opens an of the entire package1, decrypting any encrypted data. + /// + /// If the package1 can be decrypted, an + /// of the package1, . + public IStorage OpenDecryptedPackage1Storage() + { + if (!IsDecrypted) + return null; + + var storages = new List(); + + if (IsMariko) { - // Erista package1ldr is stored unencrypted, so we can always directly read the size - // field at the end of package1ldr. + int metaSize = Unsafe.SizeOf(); - // Mariko package1ldr is stored encrypted. If we're able to decrypt it, - // directly read the size field at the end of package1ldr. - - IsModern = !IsLegacyImpl(); + // The metadata at the start of the body is unencrypted, so don't take its data from the decrypted + // body storage + storages.Add(new SubStorage(in _baseStorage, 0, Unsafe.SizeOf() + metaSize)); + storages.Add(new SubStorage(_bodyStorage, metaSize, _marikoOemHeader.Size - metaSize)); + } + else + { int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; - if (IsMariko && !IsDecrypted) + storages.Add(new SubStorage(in _baseStorage, 0, stage1Size)); + storages.Add(_pk11Storage); + + if (IsModern) { - // If we're not able to decrypt the Mariko package1ldr, calculate the size by subtracting - // the known package1ldr size from the total size in the OEM header. - Pk11Size = MarikoOemHeader.Size - stage1Size; - return Result.Success; + storages.Add(new MemoryStorage(_pk11Mac.Bytes.ToArray())); } - - // Read the package1ldr footer - int footerOffset = stage1Size - Unsafe.SizeOf(); - - Result rc = _bodyStorage.Read(footerOffset, SpanHelpers.AsByteSpan(ref _stage1Footer)); - if (rc.IsFailure()) return rc; - - // Get the PK11 size from the field in the unencrypted stage 1 footer - Pk11Size = _stage1Footer.Pk11Size; - - return Result.Success; } - private Result ReadPk11Header() - { - int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + return new ConcatenationStorage(storages, true); + } - return _bodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + /// + /// Opens an of the warmboot section. + /// + /// If the section can be decrypted, an of the + /// warmboot section; otherwise, . + public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot); + + /// + /// Opens an of the bootloader section. + /// + /// If the section can be decrypted, an of the + /// bootloader section; otherwise, . + public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader); + + /// + /// Opens an of the secure monitor section. + /// + /// If the section can be decrypted, an of the + /// secure monitor section; otherwise, . + public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor); + + /// + /// Opens an for the specified . + /// + /// The section to open. + /// If the section can be decrypted, an of that + /// section; otherwise, . + public IStorage OpenSectionStorage(Package1Section sectionType) + { + if (!IsDecrypted) + return null; + + int offset = Unsafe.SizeOf() + GetSectionOffset(sectionType); + int size = GetSectionSize(sectionType); + + return new SubStorage(_pk11Storage, offset, size); + } + + + public IStorage OpenDecryptedWarmBootStorage() + { + if (!IsDecrypted) + return null; + + IStorage warmBootStorage = OpenWarmBootStorage(); + + // Only Mariko warmboot storage is encrypted + if (!IsMariko) + { + return warmBootStorage; } - private Result ReadModernEristaMac() + int size = GetSectionSize(Package1Section.WarmBoot); + int encryptedSectionSize = size - MarikoWarmBootPlainTextSectionSize; + + var plainTextSection = new SubStorage(warmBootStorage, 0, MarikoWarmBootPlainTextSectionSize); + var encryptedSubStorage = + new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize); + + var zeroIv = new Buffer16(); + IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, KeySet.MarikoBek, zeroIv.Bytes, true); + + decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true); + + return new ConcatenationStorage(new List { plainTextSection, decryptedSection }, true); + } + + public int GetSectionSize(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + return sectionType switch { - return _baseStorage.Get.Read(ModernStage1Size + Pk11Size, _pk11Mac.Bytes); - } + Package1Section.Bootloader => _pk11Header.BootloaderSize, + Package1Section.SecureMonitor => _pk11Header.SecureMonitorSize, + Package1Section.WarmBoot => _pk11Header.WarmBootSize, + _ => 0 + }; + } - private Result SetPk11Storage() + public int GetSectionOffset(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + switch (GetSectionIndex(sectionType)) { - // Read the PK11 header from the body storage - int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; - - Result rc = _bodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); - if (rc.IsFailure()) return rc; - - // Check if PK11 is already decrypted, creating the PK11 storage if it is - IsDecrypted = _pk11Header.Magic == Package1Pk11Header.ExpectedMagic; - - if (IsDecrypted) - { - _pk11Storage = new SubStorage(_bodyStorage, pk11Offset, Pk11Size); - return Result.Success; - } - - var encPk11Storage = new SubStorage(_bodyStorage, pk11Offset, Pk11Size); - - // See if we have an Erista package1 key that can decrypt this PK11 - if (!IsMariko && TryFindEristaKeyRevision()) - { - IsDecrypted = true; - IStorage decPk11Storage; - - if (IsModern) - { - decPk11Storage = new AesCbcStorage(encPk11Storage, KeySet.Package1Keys[KeyRevision], - _stage1Footer.Iv, true); - } - else - { - decPk11Storage = new Aes128CtrStorage(encPk11Storage, - KeySet.Package1Keys[KeyRevision].DataRo.ToArray(), _stage1Footer.Iv.ToArray(), true); - } - - _pk11Storage = new SubStorage(new CachedStorage(decPk11Storage, 0x4000, 1, true), 0, Pk11Size); - - return Result.Success; - } - - // We can't decrypt the PK11. Set Pk11Storage to the encrypted PK11 storage - _pk11Storage = encPk11Storage; - return Result.Success; - } - - private delegate void Decryptor(ReadOnlySpan input, Span output, ReadOnlySpan key, - ReadOnlySpan iv, bool preferDotNetCrypto = false); - - private bool TryFindEristaKeyRevision() - { - // Package1 has no indication of which key it's encrypted with, - // so we must test each known key to find one that works - - var decHeader = new Package1Pk11Header(); - - int start = IsModern ? 6 : 0; - int end = IsModern ? 0x20 : 6; - Decryptor decryptor = IsModern ? Crypto.Aes.DecryptCbc128 : Crypto.Aes.DecryptCtr128; - - for (int i = start; i < end; i++) - { - decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader), - KeySet.Package1Keys[i], _stage1Footer.Iv); - - if (decHeader.Magic == Package1Pk11Header.ExpectedMagic) - { - KeyRevision = (byte)i; - _pk11Header = decHeader; - return true; - } - } - - return false; - } - - private bool VerifyPk11Sizes() - { - Assert.SdkRequires(IsDecrypted); - - int pk11Size = Unsafe.SizeOf() + GetSectionSize(Package1Section.WarmBoot) + - GetSectionSize(Package1Section.Bootloader) + GetSectionSize(Package1Section.SecureMonitor); - - pk11Size = Alignment.AlignUp(pk11Size, 0x10); - - return pk11Size == Pk11Size; - } - - // MetaData must be read first - private bool IsLegacyImpl() - { - return _metaData.Version < 0xE || StringUtils.Compare(_metaData.BuildDate, LegacyDateCutoff) < 0; - } - - // MarikoOemHeader must be read first - private bool IsMarikoImpl() - { - return MarikoOemHeader.AesMac.IsZeros() && MarikoOemHeader.Reserved.IsZeros(); - } - - /// - /// Opens an of the entire package1, decrypting any encrypted data. - /// - /// If the package1 can be decrypted, an - /// of the package1, . - public IStorage OpenDecryptedPackage1Storage() - { - if (!IsDecrypted) - return null; - - var storages = new List(); - - if (IsMariko) - { - int metaSize = Unsafe.SizeOf(); - - // The metadata at the start of the body is unencrypted, so don't take its data from the decrypted - // body storage - storages.Add(new SubStorage(in _baseStorage, 0, Unsafe.SizeOf() + metaSize)); - storages.Add(new SubStorage(_bodyStorage, metaSize, _marikoOemHeader.Size - metaSize)); - } - else - { - int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; - - storages.Add(new SubStorage(in _baseStorage, 0, stage1Size)); - storages.Add(_pk11Storage); - - if (IsModern) - { - storages.Add(new MemoryStorage(_pk11Mac.Bytes.ToArray())); - } - } - - return new ConcatenationStorage(storages, true); - } - - /// - /// Opens an of the warmboot section. - /// - /// If the section can be decrypted, an of the - /// warmboot section; otherwise, . - public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot); - - /// - /// Opens an of the bootloader section. - /// - /// If the section can be decrypted, an of the - /// bootloader section; otherwise, . - public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader); - - /// - /// Opens an of the secure monitor section. - /// - /// If the section can be decrypted, an of the - /// secure monitor section; otherwise, . - public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor); - - /// - /// Opens an for the specified . - /// - /// The section to open. - /// If the section can be decrypted, an of that - /// section; otherwise, . - public IStorage OpenSectionStorage(Package1Section sectionType) - { - if (!IsDecrypted) - return null; - - int offset = Unsafe.SizeOf() + GetSectionOffset(sectionType); - int size = GetSectionSize(sectionType); - - return new SubStorage(_pk11Storage, offset, size); - } - - - public IStorage OpenDecryptedWarmBootStorage() - { - if (!IsDecrypted) - return null; - - IStorage warmBootStorage = OpenWarmBootStorage(); - - // Only Mariko warmboot storage is encrypted - if (!IsMariko) - { - return warmBootStorage; - } - - int size = GetSectionSize(Package1Section.WarmBoot); - int encryptedSectionSize = size - MarikoWarmBootPlainTextSectionSize; - - var plainTextSection = new SubStorage(warmBootStorage, 0, MarikoWarmBootPlainTextSectionSize); - var encryptedSubStorage = - new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize); - - var zeroIv = new Buffer16(); - IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, KeySet.MarikoBek, zeroIv.Bytes, true); - - decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true); - - return new ConcatenationStorage(new List { plainTextSection, decryptedSection }, true); - } - - public int GetSectionSize(Package1Section sectionType) - { - if (!IsDecrypted) + case 0: return 0; + case 1: + return GetSectionSize(GetSectionType(0)); + case 2: + return GetSectionSize(GetSectionType(0)) + GetSectionSize(GetSectionType(1)); + default: + return -1; + } + } + private int GetSectionIndex(Package1Section sectionType) + { + if (_metaData.Version >= 0x07) + { return sectionType switch { - Package1Section.Bootloader => _pk11Header.BootloaderSize, - Package1Section.SecureMonitor => _pk11Header.SecureMonitorSize, - Package1Section.WarmBoot => _pk11Header.WarmBootSize, - _ => 0 - }; - } - - public int GetSectionOffset(Package1Section sectionType) - { - if (!IsDecrypted) - return 0; - - switch (GetSectionIndex(sectionType)) - { - case 0: - return 0; - case 1: - return GetSectionSize(GetSectionType(0)); - case 2: - return GetSectionSize(GetSectionType(0)) + GetSectionSize(GetSectionType(1)); - default: - return -1; - } - } - - private int GetSectionIndex(Package1Section sectionType) - { - if (_metaData.Version >= 0x07) - { - return sectionType switch - { - Package1Section.Bootloader => 0, - Package1Section.SecureMonitor => 1, - Package1Section.WarmBoot => 2, - _ => -1 - }; - } - - if (_metaData.Version >= 0x02) - { - return sectionType switch - { - Package1Section.Bootloader => 1, - Package1Section.SecureMonitor => 2, - Package1Section.WarmBoot => 0, - _ => -1 - }; - } - - return sectionType switch - { - Package1Section.Bootloader => 1, - Package1Section.SecureMonitor => 0, + Package1Section.Bootloader => 0, + Package1Section.SecureMonitor => 1, Package1Section.WarmBoot => 2, _ => -1 }; } - private Package1Section GetSectionType(int index) + if (_metaData.Version >= 0x02) { - if (GetSectionIndex(Package1Section.Bootloader) == index) - return Package1Section.Bootloader; - - if (GetSectionIndex(Package1Section.SecureMonitor) == index) - return Package1Section.SecureMonitor; - - if (GetSectionIndex(Package1Section.WarmBoot) == index) - return Package1Section.WarmBoot; - - return (Package1Section)(-1); + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 2, + Package1Section.WarmBoot => 0, + _ => -1 + }; } - private static ReadOnlySpan LegacyDateCutoff => // 20181107 - new[] - { - (byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7' - }; + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 0, + Package1Section.WarmBoot => 2, + _ => -1 + }; } + + private Package1Section GetSectionType(int index) + { + if (GetSectionIndex(Package1Section.Bootloader) == index) + return Package1Section.Bootloader; + + if (GetSectionIndex(Package1Section.SecureMonitor) == index) + return Package1Section.SecureMonitor; + + if (GetSectionIndex(Package1Section.WarmBoot) == index) + return Package1Section.WarmBoot; + + return (Package1Section)(-1); + } + + private static ReadOnlySpan LegacyDateCutoff => // 20181107 + new[] + { + (byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7' + }; } diff --git a/src/LibHac/Boot/Package2.cs b/src/LibHac/Boot/Package2.cs index 6099ccd4..7e91c81c 100644 --- a/src/LibHac/Boot/Package2.cs +++ b/src/LibHac/Boot/Package2.cs @@ -8,157 +8,156 @@ using LibHac.Util; using System.Diagnostics; #endif -namespace LibHac.Boot +namespace LibHac.Boot; + +[StructLayout(LayoutKind.Explicit, Size = 0x200)] +public struct Package2Header { - [StructLayout(LayoutKind.Explicit, Size = 0x200)] - public struct Package2Header + internal const int Package2SizeMax = (1024 * 1024 * 8) - (1024 * 16); // 8MB - 16KB + internal const int PayloadAlignment = 4; + internal const int PayloadCount = 3; + + internal const int SignatureSize = 0x100; + private ReadOnlySpan RsaPublicKeyExponent => new byte[] { 0x00, 0x01, 0x00, 0x01 }; + + [FieldOffset(0x00)] private byte _signature; + [FieldOffset(0x100)] public Package2Meta Meta; + + public ReadOnlySpan Signature => SpanHelpers.CreateSpan(ref _signature, SignatureSize); + + public Result VerifySignature(ReadOnlySpan modulus, ReadOnlySpan data) { - internal const int Package2SizeMax = (1024 * 1024 * 8) - (1024 * 16); // 8MB - 16KB - internal const int PayloadAlignment = 4; - internal const int PayloadCount = 3; - - internal const int SignatureSize = 0x100; - private ReadOnlySpan RsaPublicKeyExponent => new byte[] { 0x00, 0x01, 0x00, 0x01 }; - - [FieldOffset(0x00)] private byte _signature; - [FieldOffset(0x100)] public Package2Meta Meta; - - public ReadOnlySpan Signature => SpanHelpers.CreateSpan(ref _signature, SignatureSize); - - public Result VerifySignature(ReadOnlySpan modulus, ReadOnlySpan data) + if (!Rsa.VerifyRsa2048PssSha256(Signature, modulus, RsaPublicKeyExponent, data)) { - if (!Rsa.VerifyRsa2048PssSha256(Signature, modulus, RsaPublicKeyExponent, data)) - { - return ResultLibHac.InvalidPackage2HeaderSignature.Log(); - } - - return Result.Success; + return ResultLibHac.InvalidPackage2HeaderSignature.Log(); } -#if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; -#endif + return Result.Success; } - [StructLayout(LayoutKind.Explicit, Size = 0x100)] - public struct Package2Meta - { - public const uint ExpectedMagicValue = 0x31324B50; // PK21 - - [FieldOffset(0x00)] private Buffer16 _headerIv; - - [FieldOffset(0x00)] private uint _package2Size; - [FieldOffset(0x04)] private byte _keyGeneration; - - [FieldOffset(0x06)] private byte _keyGenerationXor1; - [FieldOffset(0x07)] private byte _keyGenerationXor2; - [FieldOffset(0x08)] private uint _sizeXor1; - [FieldOffset(0x0C)] private uint _sizeXor2; - - [FieldOffset(0x10)] private Buffer16 _payloadIvs; - - [FieldOffset(0x50)] private readonly uint _magic; - [FieldOffset(0x54)] private readonly uint _entryPoint; - [FieldOffset(0x5C)] private readonly byte _package2Version; - [FieldOffset(0x5D)] private readonly byte _bootloaderVersion; - - [FieldOffset(0x60)] private uint _payloadSizes; - [FieldOffset(0x70)] private uint _payloadOffsets; - [FieldOffset(0x80)] private Buffer32 _payloadHashes; - - public uint Magic => _magic; - public uint EntryPoint => _entryPoint; - public byte Package2Version => _package2Version; - public byte BootloaderVersion => _bootloaderVersion; - - public Buffer16 HeaderIv => _headerIv; - public readonly uint Size => _package2Size ^ _sizeXor1 ^ _sizeXor2; - public byte KeyGeneration => (byte)Math.Max(0, (_keyGeneration ^ _keyGenerationXor1 ^ _keyGenerationXor2) - 1); - - public ReadOnlySpan PayloadIvs => SpanHelpers.CreateSpan(ref _payloadIvs, Package2Header.PayloadCount); - public ReadOnlySpan PayloadSizes => SpanHelpers.CreateSpan(ref _payloadSizes, Package2Header.PayloadCount); - public ReadOnlySpan PayloadOffsets => SpanHelpers.CreateSpan(ref _payloadOffsets, Package2Header.PayloadCount); - public ReadOnlySpan PayloadHashes => SpanHelpers.CreateSpan(ref _payloadHashes, Package2Header.PayloadCount); - - public int GetPayloadFileOffset(int index) - { - if ((uint)index >= Package2Header.PayloadCount) - throw new IndexOutOfRangeException("Invalid payload index."); - - int offset = Unsafe.SizeOf(); - - for (int i = 0; i < index; i++) - { - offset += (int)PayloadSizes[i]; - } - - return offset; - } - - public Result Verify() - { - // Get the obfuscated metadata. - uint size = Size; - byte keyGeneration = KeyGeneration; - - // Check that size is big enough for the header. - if (size < Unsafe.SizeOf()) - return ResultLibHac.InvalidPackage2MetaSizeA.Log(); - - // Check that the size isn't larger than what we allow. - if (size > Package2Header.Package2SizeMax) - return ResultLibHac.InvalidPackage2MetaSizeB.Log(); - - // Check that the key generation is one that we can use. - if (keyGeneration >= 0x20) - return ResultLibHac.InvalidPackage2MetaKeyGeneration.Log(); - - // Check the magic number. - if (Magic != ExpectedMagicValue) - return ResultLibHac.InvalidPackage2MetaMagic.Log(); - - // Check the payload alignments. - if (EntryPoint % Package2Header.PayloadAlignment != 0) - return ResultLibHac.InvalidPackage2MetaEntryPointAlignment.Log(); - - for (int i = 0; i < Package2Header.PayloadCount; i++) - { - if (PayloadSizes[i] % Package2Header.PayloadAlignment != 0) - return ResultLibHac.InvalidPackage2MetaPayloadSizeAlignment.Log(); - } - - // Check that the sizes sum to the total. - if (Size != Unsafe.SizeOf() + PayloadSizes[0] + PayloadSizes[1] + PayloadSizes[2]) - return ResultLibHac.InvalidPackage2MetaTotalSize.Log(); - - // Check that the payloads do not overflow. - for (int i = 0; i < Package2Header.PayloadCount; i++) - { - if (PayloadOffsets[i] > PayloadOffsets[i] + PayloadSizes[i]) - return ResultLibHac.InvalidPackage2MetaPayloadSize.Log(); - } - - // Verify that no payloads overlap. - for (int i = 0; i < Package2Header.PayloadCount - 1; i++) - for (int j = i + 1; j < Package2Header.PayloadCount; j++) - { - if (Overlap.HasOverlap(PayloadOffsets[i], PayloadSizes[i], PayloadOffsets[j], PayloadSizes[j])) - return ResultLibHac.InvalidPackage2MetaPayloadsOverlap.Log(); - } - - // Check whether any payload contains the entrypoint. - for (int i = 0; i < Package2Header.PayloadCount; i++) - { - if (Overlap.Contains(PayloadOffsets[i], PayloadSizes[i], EntryPoint)) - return Result.Success; - } - - // No payload contains the entrypoint, so we're not valid. - return ResultLibHac.InvalidPackage2MetaEntryPointNotFound.Log(); - } - #if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; +#endif +} + +[StructLayout(LayoutKind.Explicit, Size = 0x100)] +public struct Package2Meta +{ + public const uint ExpectedMagicValue = 0x31324B50; // PK21 + + [FieldOffset(0x00)] private Buffer16 _headerIv; + + [FieldOffset(0x00)] private uint _package2Size; + [FieldOffset(0x04)] private byte _keyGeneration; + + [FieldOffset(0x06)] private byte _keyGenerationXor1; + [FieldOffset(0x07)] private byte _keyGenerationXor2; + [FieldOffset(0x08)] private uint _sizeXor1; + [FieldOffset(0x0C)] private uint _sizeXor2; + + [FieldOffset(0x10)] private Buffer16 _payloadIvs; + + [FieldOffset(0x50)] private readonly uint _magic; + [FieldOffset(0x54)] private readonly uint _entryPoint; + [FieldOffset(0x5C)] private readonly byte _package2Version; + [FieldOffset(0x5D)] private readonly byte _bootloaderVersion; + + [FieldOffset(0x60)] private uint _payloadSizes; + [FieldOffset(0x70)] private uint _payloadOffsets; + [FieldOffset(0x80)] private Buffer32 _payloadHashes; + + public uint Magic => _magic; + public uint EntryPoint => _entryPoint; + public byte Package2Version => _package2Version; + public byte BootloaderVersion => _bootloaderVersion; + + public Buffer16 HeaderIv => _headerIv; + public readonly uint Size => _package2Size ^ _sizeXor1 ^ _sizeXor2; + public byte KeyGeneration => (byte)Math.Max(0, (_keyGeneration ^ _keyGenerationXor1 ^ _keyGenerationXor2) - 1); + + public ReadOnlySpan PayloadIvs => SpanHelpers.CreateSpan(ref _payloadIvs, Package2Header.PayloadCount); + public ReadOnlySpan PayloadSizes => SpanHelpers.CreateSpan(ref _payloadSizes, Package2Header.PayloadCount); + public ReadOnlySpan PayloadOffsets => SpanHelpers.CreateSpan(ref _payloadOffsets, Package2Header.PayloadCount); + public ReadOnlySpan PayloadHashes => SpanHelpers.CreateSpan(ref _payloadHashes, Package2Header.PayloadCount); + + public int GetPayloadFileOffset(int index) + { + if ((uint)index >= Package2Header.PayloadCount) + throw new IndexOutOfRangeException("Invalid payload index."); + + int offset = Unsafe.SizeOf(); + + for (int i = 0; i < index; i++) + { + offset += (int)PayloadSizes[i]; + } + + return offset; + } + + public Result Verify() + { + // Get the obfuscated metadata. + uint size = Size; + byte keyGeneration = KeyGeneration; + + // Check that size is big enough for the header. + if (size < Unsafe.SizeOf()) + return ResultLibHac.InvalidPackage2MetaSizeA.Log(); + + // Check that the size isn't larger than what we allow. + if (size > Package2Header.Package2SizeMax) + return ResultLibHac.InvalidPackage2MetaSizeB.Log(); + + // Check that the key generation is one that we can use. + if (keyGeneration >= 0x20) + return ResultLibHac.InvalidPackage2MetaKeyGeneration.Log(); + + // Check the magic number. + if (Magic != ExpectedMagicValue) + return ResultLibHac.InvalidPackage2MetaMagic.Log(); + + // Check the payload alignments. + if (EntryPoint % Package2Header.PayloadAlignment != 0) + return ResultLibHac.InvalidPackage2MetaEntryPointAlignment.Log(); + + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (PayloadSizes[i] % Package2Header.PayloadAlignment != 0) + return ResultLibHac.InvalidPackage2MetaPayloadSizeAlignment.Log(); + } + + // Check that the sizes sum to the total. + if (Size != Unsafe.SizeOf() + PayloadSizes[0] + PayloadSizes[1] + PayloadSizes[2]) + return ResultLibHac.InvalidPackage2MetaTotalSize.Log(); + + // Check that the payloads do not overflow. + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (PayloadOffsets[i] > PayloadOffsets[i] + PayloadSizes[i]) + return ResultLibHac.InvalidPackage2MetaPayloadSize.Log(); + } + + // Verify that no payloads overlap. + for (int i = 0; i < Package2Header.PayloadCount - 1; i++) + for (int j = i + 1; j < Package2Header.PayloadCount; j++) + { + if (Overlap.HasOverlap(PayloadOffsets[i], PayloadSizes[i], PayloadOffsets[j], PayloadSizes[j])) + return ResultLibHac.InvalidPackage2MetaPayloadsOverlap.Log(); + } + + // Check whether any payload contains the entrypoint. + for (int i = 0; i < Package2Header.PayloadCount; i++) + { + if (Overlap.Contains(PayloadOffsets[i], PayloadSizes[i], EntryPoint)) + return Result.Success; + } + + // No payload contains the entrypoint, so we're not valid. + return ResultLibHac.InvalidPackage2MetaEntryPointNotFound.Log(); + } + +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] [FieldOffset(0x00)] private readonly Padding100 PaddingForVsDebugging; #endif - } } diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs index 125a0cde..87551c3d 100644 --- a/src/LibHac/Boot/Package2StorageReader.cs +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -8,263 +8,262 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Kernel; -namespace LibHac.Boot +namespace LibHac.Boot; + +/// +/// Parses a package2 file and opens the payloads within. +/// +public class Package2StorageReader : IDisposable { - /// - /// Parses a package2 file and opens the payloads within. - /// - public class Package2StorageReader : IDisposable + private const int KernelPayloadIndex = 0; + private const int IniPayloadIndex = 1; + + private SharedRef _storage; + private Package2Header _header; + private KeySet _keySet; + private Crypto.AesKey _key; + + public ref readonly Package2Header Header => ref _header; + + public void Dispose() { - private const int KernelPayloadIndex = 0; - private const int IniPayloadIndex = 1; + _storage.Destroy(); + } - private SharedRef _storage; - private Package2Header _header; - private KeySet _keySet; - private Crypto.AesKey _key; + /// + /// Initializes the . + /// + /// The keyset to use for decrypting the package. + /// An of the encrypted package2. + /// The of the operation. + public Result Initialize(KeySet keySet, in SharedRef storage) + { + Result rc = storage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; - public ref readonly Package2Header Header => ref _header; + _key = keySet.Package2Keys[_header.Meta.KeyGeneration]; + DecryptHeader(_key, ref _header.Meta, ref _header.Meta); - public void Dispose() + _storage.SetByCopy(in storage); + _keySet = keySet; + return Result.Success; + } + + /// + /// Opens a decrypted of one of the payloads in the package. + /// + /// If the method returns successfully, contains an + /// of the specified payload. + /// The index of the payload to get. Must me less than + /// The of the operation. + public Result OpenPayload(ref UniqueRef outPayloadStorage, int index) + { + if ((uint)index >= Package2Header.PayloadCount) + return ResultLibHac.ArgumentOutOfRange.Log(); + + int offset = _header.Meta.GetPayloadFileOffset(index); + int size = (int)_header.Meta.PayloadSizes[index]; + + var payloadSubStorage = new SubStorage(_storage, offset, size); + + if (size == 0) { - _storage.Destroy(); - } - - /// - /// Initializes the . - /// - /// The keyset to use for decrypting the package. - /// An of the encrypted package2. - /// The of the operation. - public Result Initialize(KeySet keySet, in SharedRef storage) - { - Result rc = storage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); - if (rc.IsFailure()) return rc; - - _key = keySet.Package2Keys[_header.Meta.KeyGeneration]; - DecryptHeader(_key, ref _header.Meta, ref _header.Meta); - - _storage.SetByCopy(in storage); - _keySet = keySet; + outPayloadStorage.Reset(payloadSubStorage); return Result.Success; } - /// - /// Opens a decrypted of one of the payloads in the package. - /// - /// If the method returns successfully, contains an - /// of the specified payload. - /// The index of the payload to get. Must me less than - /// The of the operation. - public Result OpenPayload(ref UniqueRef outPayloadStorage, int index) + byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray(); + outPayloadStorage.Reset(new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true)); + return Result.Success; + } + + /// + /// Opens an of the kernel payload. + /// + /// If the method returns successfully, contains an + /// of the kernel payload. + /// The of the operation. + public Result OpenKernel(ref UniqueRef outKernelStorage) + { + return OpenPayload(ref outKernelStorage, KernelPayloadIndex); + } + + /// + /// Opens an of the initial process binary. If the binary is embedded in + /// the kernel, this method will attempt to locate and return the embedded binary. + /// + /// If the method returns successfully, contains an + /// of the initial process binary. + /// The of the operation. + public Result OpenIni(ref UniqueRef outIniStorage) + { + if (HasIniPayload()) { - if ((uint)index >= Package2Header.PayloadCount) - return ResultLibHac.ArgumentOutOfRange.Log(); - - int offset = _header.Meta.GetPayloadFileOffset(index); - int size = (int)_header.Meta.PayloadSizes[index]; - - var payloadSubStorage = new SubStorage(_storage, offset, size); - - if (size == 0) - { - outPayloadStorage.Reset(payloadSubStorage); - return Result.Success; - } - - byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray(); - outPayloadStorage.Reset(new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true)); - return Result.Success; + return OpenPayload(ref outIniStorage, IniPayloadIndex); } - /// - /// Opens an of the kernel payload. - /// - /// If the method returns successfully, contains an - /// of the kernel payload. - /// The of the operation. - public Result OpenKernel(ref UniqueRef outKernelStorage) + // Ini is embedded in the kernel + using var kernelStorage = new UniqueRef(); + Result rc = OpenKernel(ref kernelStorage.Ref()); + if (rc.IsFailure()) return rc; + + if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage.Get)) { - return OpenPayload(ref outKernelStorage, KernelPayloadIndex); + // Unable to find the ini. Could be a new, unsupported layout. + return ResultLibHac.NotImplemented.Log(); } - /// - /// Opens an of the initial process binary. If the binary is embedded in - /// the kernel, this method will attempt to locate and return the embedded binary. - /// - /// If the method returns successfully, contains an - /// of the initial process binary. - /// The of the operation. - public Result OpenIni(ref UniqueRef outIniStorage) + outIniStorage.Reset(new SubStorage(kernelStorage.Release(), offset, size)); + return Result.Success; + } + + /// + /// Verifies the signature, metadata and payloads in the package. + /// + /// The of the operation. + public Result Verify() + { + Result rc = VerifySignature(); + if (rc.IsFailure()) return rc; + + rc = VerifyMeta(); + if (rc.IsFailure()) return rc; + + return VerifyPayloads(); + } + + /// + /// Verifies the signature of the package. + /// + /// The of the operation. + /// if the signature is valid. + public Result VerifySignature() + { + Unsafe.SkipInit(out Package2Meta meta); + Span metaBytes = SpanHelpers.AsByteSpan(ref meta); + + Result rc = _storage.Get.Read(Package2Header.SignatureSize, metaBytes); + if (rc.IsFailure()) return rc; + + return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes); + } + + /// + /// Verifies the package metadata. + /// + /// The of the operation. + /// if the metadata is valid. + public Result VerifyMeta() => _header.Meta.Verify(); + + /// + /// Verifies the hashes of all the payloads in the metadata. + /// + /// The of the operation. + /// if all the hashes are valid. + public Result VerifyPayloads() + { + using (var buffer = new RentedArray(0x10000)) { - if (HasIniPayload()) - { - return OpenPayload(ref outIniStorage, IniPayloadIndex); - } + byte[] array = buffer.Array; + var hashBuffer = new Buffer32(); + var sha = new Sha256Generator(); - // Ini is embedded in the kernel - using var kernelStorage = new UniqueRef(); - Result rc = OpenKernel(ref kernelStorage.Ref()); - if (rc.IsFailure()) return rc; - - if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage.Get)) - { - // Unable to find the ini. Could be a new, unsupported layout. - return ResultLibHac.NotImplemented.Log(); - } - - outIniStorage.Reset(new SubStorage(kernelStorage.Release(), offset, size)); - return Result.Success; - } - - /// - /// Verifies the signature, metadata and payloads in the package. - /// - /// The of the operation. - public Result Verify() - { - Result rc = VerifySignature(); - if (rc.IsFailure()) return rc; - - rc = VerifyMeta(); - if (rc.IsFailure()) return rc; - - return VerifyPayloads(); - } - - /// - /// Verifies the signature of the package. - /// - /// The of the operation. - /// if the signature is valid. - public Result VerifySignature() - { - Unsafe.SkipInit(out Package2Meta meta); - Span metaBytes = SpanHelpers.AsByteSpan(ref meta); - - Result rc = _storage.Get.Read(Package2Header.SignatureSize, metaBytes); - if (rc.IsFailure()) return rc; - - return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes); - } - - /// - /// Verifies the package metadata. - /// - /// The of the operation. - /// if the metadata is valid. - public Result VerifyMeta() => _header.Meta.Verify(); - - /// - /// Verifies the hashes of all the payloads in the metadata. - /// - /// The of the operation. - /// if all the hashes are valid. - public Result VerifyPayloads() - { - using (var buffer = new RentedArray(0x10000)) - { - byte[] array = buffer.Array; - var hashBuffer = new Buffer32(); - var sha = new Sha256Generator(); - - // Verify hashes match for all payloads. - for (int i = 0; i < Package2Header.PayloadCount; i++) - { - if (_header.Meta.PayloadSizes[i] == 0) - continue; - - int offset = _header.Meta.GetPayloadFileOffset(i); - int size = (int)_header.Meta.PayloadSizes[i]; - - var payloadSubStorage = new SubStorage(_storage, offset, size); - - offset = 0; - sha.Initialize(); - - while (size > 0) - { - int toRead = Math.Min(array.Length, size); - Span span = array.AsSpan(0, toRead); - - Result rc = payloadSubStorage.Read(offset, span); - if (rc.IsFailure()) return rc; - - sha.Update(span); - - offset += toRead; - size -= toRead; - } - - sha.GetHash(hashBuffer); - - if (!CryptoUtil.IsSameBytes(hashBuffer, _header.Meta.PayloadHashes[i], 0x20)) - { - return ResultLibHac.InvalidPackage2PayloadCorrupted.Log(); - } - } - } - - return Result.Success; - } - - /// - /// Opens a decrypted of the entire package. - /// - /// If the method returns successfully, contains a decrypted - /// of the package. - /// The of the operation. - public Result OpenDecryptedPackage(ref UniqueRef outPackageStorage) - { - var storages = new List(4); - - // The signature and IV are unencrypted - int unencryptedHeaderSize = Package2Header.SignatureSize + Unsafe.SizeOf(); - int encryptedHeaderSize = Unsafe.SizeOf() - unencryptedHeaderSize; - - // Get signature and IV - storages.Add(new SubStorage(_storage, 0, unencryptedHeaderSize)); - - // Open decrypted meta - var encMetaStorage = new SubStorage(_storage, unencryptedHeaderSize, encryptedHeaderSize); - - // The counter starts counting at the beginning of the meta struct, but the first block in - // the struct isn't encrypted. Increase the counter by one to skip that block. - byte[] iv = _header.Meta.HeaderIv.Bytes.ToArray(); - Utilities.IncrementByteArray(iv); - - storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key.DataRo.ToArray(), iv, true), 0x100, 1, true)); - - // Open all the payloads + // Verify hashes match for all payloads. for (int i = 0; i < Package2Header.PayloadCount; i++) { if (_header.Meta.PayloadSizes[i] == 0) continue; - using var payloadStorage = new UniqueRef(); - Result rc = OpenPayload(ref payloadStorage.Ref(), i); - if (rc.IsFailure()) return rc.Miss(); + int offset = _header.Meta.GetPayloadFileOffset(i); + int size = (int)_header.Meta.PayloadSizes[i]; - storages.Add(payloadStorage.Release()); + var payloadSubStorage = new SubStorage(_storage, offset, size); + + offset = 0; + sha.Initialize(); + + while (size > 0) + { + int toRead = Math.Min(array.Length, size); + Span span = array.AsSpan(0, toRead); + + Result rc = payloadSubStorage.Read(offset, span); + if (rc.IsFailure()) return rc; + + sha.Update(span); + + offset += toRead; + size -= toRead; + } + + sha.GetHash(hashBuffer); + + if (!CryptoUtil.IsSameBytes(hashBuffer, _header.Meta.PayloadHashes[i], 0x20)) + { + return ResultLibHac.InvalidPackage2PayloadCorrupted.Log(); + } } - - outPackageStorage.Reset(new ConcatenationStorage(storages, true)); - return Result.Success; } - private void DecryptHeader(ReadOnlySpan key, ref Package2Meta source, ref Package2Meta dest) + return Result.Success; + } + + /// + /// Opens a decrypted of the entire package. + /// + /// If the method returns successfully, contains a decrypted + /// of the package. + /// The of the operation. + public Result OpenDecryptedPackage(ref UniqueRef outPackageStorage) + { + var storages = new List(4); + + // The signature and IV are unencrypted + int unencryptedHeaderSize = Package2Header.SignatureSize + Unsafe.SizeOf(); + int encryptedHeaderSize = Unsafe.SizeOf() - unencryptedHeaderSize; + + // Get signature and IV + storages.Add(new SubStorage(_storage, 0, unencryptedHeaderSize)); + + // Open decrypted meta + var encMetaStorage = new SubStorage(_storage, unencryptedHeaderSize, encryptedHeaderSize); + + // The counter starts counting at the beginning of the meta struct, but the first block in + // the struct isn't encrypted. Increase the counter by one to skip that block. + byte[] iv = _header.Meta.HeaderIv.Bytes.ToArray(); + Utilities.IncrementByteArray(iv); + + storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key.DataRo.ToArray(), iv, true), 0x100, 1, true)); + + // Open all the payloads + for (int i = 0; i < Package2Header.PayloadCount; i++) { - Buffer16 iv = source.HeaderIv; + if (_header.Meta.PayloadSizes[i] == 0) + continue; - Aes.DecryptCtr128(SpanHelpers.AsByteSpan(ref source), SpanHelpers.AsByteSpan(ref dest), key, iv); + using var payloadStorage = new UniqueRef(); + Result rc = OpenPayload(ref payloadStorage.Ref(), i); + if (rc.IsFailure()) return rc.Miss(); - // Copy the IV to the output because the IV field will be garbage after "decrypting" it - Unsafe.As(ref dest) = iv; + storages.Add(payloadStorage.Release()); } - private bool HasIniPayload() - { - return _header.Meta.PayloadSizes[IniPayloadIndex] != 0; - } + outPackageStorage.Reset(new ConcatenationStorage(storages, true)); + return Result.Success; + } + + private void DecryptHeader(ReadOnlySpan key, ref Package2Meta source, ref Package2Meta dest) + { + Buffer16 iv = source.HeaderIv; + + Aes.DecryptCtr128(SpanHelpers.AsByteSpan(ref source), SpanHelpers.AsByteSpan(ref dest), key, iv); + + // Copy the IV to the output because the IV field will be garbage after "decrypting" it + Unsafe.As(ref dest) = iv; + } + + private bool HasIniPayload() + { + return _header.Meta.PayloadSizes[IniPayloadIndex] != 0; } } diff --git a/src/LibHac/Calibration.cs b/src/LibHac/Calibration.cs index 887f7fd7..7357529b 100644 --- a/src/LibHac/Calibration.cs +++ b/src/LibHac/Calibration.cs @@ -1,275 +1,274 @@ using System.IO; using System.Text; -namespace LibHac +namespace LibHac; + +public class Calibration { - public class Calibration + public const string ExpectedMagic = "CAL0"; + + public string Magic; + public int Version; + public int CalibDataSize; + public short Model; + public short Revision; + public byte[] CalibDataSha256; + public string ConfigId1; + public byte[] Reserved; + public int WlanCountryCodesNum; + public int WlanCountryCodesLastIdx; + public string[] WlanCountryCodes; + public byte[] WlanMacAddr; + public byte[] BdAddr; + public byte[] AccelerometerOffset; + public byte[] AccelerometerScale; + public byte[] GyroscopeOffset; + public byte[] GyroscopeScale; + public string SerialNumber; + public byte[] DeviceKeyEccP256; + public byte[] DeviceCertEccP256; + public byte[] DeviceKeyEccB233; + public byte[] DeviceCertEccB233; + public byte[] EticketKeyEccP256; + public byte[] EticketCertEccP256; + public byte[] EticketKeyEccB233; + public byte[] EticketCertEccB233; + public byte[] SslKey; + public int SslCertSize; + public byte[] SslCert; + public byte[] SslCertSha256; + public byte[] RandomNumber; + public byte[] RandomNumberSha256; + public byte[] GamecardKey; + public byte[] GamecardCert; + public byte[] GamecardCertSha256; + public byte[] EticketKeyRsa; + public byte[] EticketCertRsa; + public string BatteryLot; + public byte[] SpeakerCalibValue; + public int RegionCode; + public byte[] AmiiboKey; + public byte[] AmiiboCertEcqv; + public byte[] AmiiboCertEcdsa; + public byte[] AmiiboKeyEcqvBls; + public byte[] AmiiboCertEcqvBls; + public byte[] AmiiboRootCertEcqvBls; + public int ProductModel; + public byte[] ColorVariation; + public byte[] LcdBacklightBrightnessMapping; + public byte[] DeviceExtKeyEccB233; + public byte[] EticketExtKeyEccP256; + public byte[] EticketExtKeyEccB233; + public byte[] EticketExtKeyRsa; + public byte[] SslExtKey; + public byte[] GamecardExtKey; + public int LcdVendorId; + public byte[] ExtendedRsa2048DeviceKey; + public byte[] Rsa2048DeviceCertificate; + public byte[] UsbTypeCPowerSourceCircuitVersion; + public int HomeMenuSchemeSubColor; + public int HomeMenuSchemeBezelColor; + public int HomeMenuSchemeMainColor1; + public int HomeMenuSchemeMainColor2; + public int HomeMenuSchemeMainColor3; + public byte[] AnalogStickModuleTypeL; + public byte[] AnalogStickModelParameterL; + public byte[] AnalogStickFactoryCalibrationL; + public byte[] AnalogStickModuleTypeR; + public byte[] AnalogStickModelParameterR; + public byte[] AnalogStickFactoryCalibrationR; + public byte[] ConsoleSixAxisSensorModuleType; + public byte[] ConsoleSixAxisSensorHorizontalOffset; + public byte[] BatteryVersion; + public int HomeMenuSchemeModel; + public byte[] ConsoleSixAxisSensorMountType; + + public Calibration(Stream stream) { - public const string ExpectedMagic = "CAL0"; + using var reader = new BinaryReader(stream, Encoding.Default, true); - public string Magic; - public int Version; - public int CalibDataSize; - public short Model; - public short Revision; - public byte[] CalibDataSha256; - public string ConfigId1; - public byte[] Reserved; - public int WlanCountryCodesNum; - public int WlanCountryCodesLastIdx; - public string[] WlanCountryCodes; - public byte[] WlanMacAddr; - public byte[] BdAddr; - public byte[] AccelerometerOffset; - public byte[] AccelerometerScale; - public byte[] GyroscopeOffset; - public byte[] GyroscopeScale; - public string SerialNumber; - public byte[] DeviceKeyEccP256; - public byte[] DeviceCertEccP256; - public byte[] DeviceKeyEccB233; - public byte[] DeviceCertEccB233; - public byte[] EticketKeyEccP256; - public byte[] EticketCertEccP256; - public byte[] EticketKeyEccB233; - public byte[] EticketCertEccB233; - public byte[] SslKey; - public int SslCertSize; - public byte[] SslCert; - public byte[] SslCertSha256; - public byte[] RandomNumber; - public byte[] RandomNumberSha256; - public byte[] GamecardKey; - public byte[] GamecardCert; - public byte[] GamecardCertSha256; - public byte[] EticketKeyRsa; - public byte[] EticketCertRsa; - public string BatteryLot; - public byte[] SpeakerCalibValue; - public int RegionCode; - public byte[] AmiiboKey; - public byte[] AmiiboCertEcqv; - public byte[] AmiiboCertEcdsa; - public byte[] AmiiboKeyEcqvBls; - public byte[] AmiiboCertEcqvBls; - public byte[] AmiiboRootCertEcqvBls; - public int ProductModel; - public byte[] ColorVariation; - public byte[] LcdBacklightBrightnessMapping; - public byte[] DeviceExtKeyEccB233; - public byte[] EticketExtKeyEccP256; - public byte[] EticketExtKeyEccB233; - public byte[] EticketExtKeyRsa; - public byte[] SslExtKey; - public byte[] GamecardExtKey; - public int LcdVendorId; - public byte[] ExtendedRsa2048DeviceKey; - public byte[] Rsa2048DeviceCertificate; - public byte[] UsbTypeCPowerSourceCircuitVersion; - public int HomeMenuSchemeSubColor; - public int HomeMenuSchemeBezelColor; - public int HomeMenuSchemeMainColor1; - public int HomeMenuSchemeMainColor2; - public int HomeMenuSchemeMainColor3; - public byte[] AnalogStickModuleTypeL; - public byte[] AnalogStickModelParameterL; - public byte[] AnalogStickFactoryCalibrationL; - public byte[] AnalogStickModuleTypeR; - public byte[] AnalogStickModelParameterR; - public byte[] AnalogStickFactoryCalibrationR; - public byte[] ConsoleSixAxisSensorModuleType; - public byte[] ConsoleSixAxisSensorHorizontalOffset; - public byte[] BatteryVersion; - public int HomeMenuSchemeModel; - public byte[] ConsoleSixAxisSensorMountType; + stream.Position = 0x0; + Magic = reader.ReadUtf8(0x4); - public Calibration(Stream stream) + stream.Position = 0x4; + Version = reader.ReadInt32(); + + stream.Position = 0x8; + CalibDataSize = reader.ReadInt32(); + + stream.Position = 0xC; + Model = reader.ReadInt16(); + + stream.Position = 0xE; + Revision = reader.ReadInt16(); + + stream.Position = 0x20; + CalibDataSha256 = reader.ReadBytes(0x20); + + stream.Position = 0x40; + ConfigId1 = reader.ReadUtf8Z(0x1E); + + stream.Position = 0x60; + Reserved = reader.ReadBytes(0x20); + + stream.Position = 0x80; + WlanCountryCodesNum = reader.ReadInt32(); + WlanCountryCodesLastIdx = reader.ReadInt32(); + WlanCountryCodes = new string[WlanCountryCodesNum]; + for (int i = 0; i < WlanCountryCodesNum; i++) { - using var reader = new BinaryReader(stream, Encoding.Default, true); - - stream.Position = 0x0; - Magic = reader.ReadUtf8(0x4); - - stream.Position = 0x4; - Version = reader.ReadInt32(); - - stream.Position = 0x8; - CalibDataSize = reader.ReadInt32(); - - stream.Position = 0xC; - Model = reader.ReadInt16(); - - stream.Position = 0xE; - Revision = reader.ReadInt16(); - - stream.Position = 0x20; - CalibDataSha256 = reader.ReadBytes(0x20); - - stream.Position = 0x40; - ConfigId1 = reader.ReadUtf8Z(0x1E); - - stream.Position = 0x60; - Reserved = reader.ReadBytes(0x20); - - stream.Position = 0x80; - WlanCountryCodesNum = reader.ReadInt32(); - WlanCountryCodesLastIdx = reader.ReadInt32(); - WlanCountryCodes = new string[WlanCountryCodesNum]; - for (int i = 0; i < WlanCountryCodesNum; i++) - { - stream.Position = 0x88 + i * 4; - WlanCountryCodes[i] = reader.ReadUtf8Z(); - } - stream.Position = 0x210; - WlanMacAddr = reader.ReadBytes(0x6); - - stream.Position = 0x220; - BdAddr = reader.ReadBytes(0x6); - stream.Position = 0x230; - AccelerometerOffset = reader.ReadBytes(0x6); - stream.Position = 0x238; - AccelerometerScale = reader.ReadBytes(0x6); - stream.Position = 0x240; - GyroscopeOffset = reader.ReadBytes(0x6); - stream.Position = 0x248; - GyroscopeScale = reader.ReadBytes(0x6); - - stream.Position = 0x250; - SerialNumber = reader.ReadUtf8Z(0x18); - - stream.Position = 0x270; - DeviceKeyEccP256 = reader.ReadBytes(0x30); - stream.Position = 0x2B0; - DeviceCertEccP256 = reader.ReadBytes(0x180); - stream.Position = 0x440; - DeviceKeyEccB233 = reader.ReadBytes(0x30); - stream.Position = 0x480; - DeviceCertEccB233 = reader.ReadBytes(0x180); - stream.Position = 0x610; - EticketKeyEccP256 = reader.ReadBytes(0x30); - stream.Position = 0x650; - EticketCertEccP256 = reader.ReadBytes(0x180); - stream.Position = 0x7E0; - EticketKeyEccB233 = reader.ReadBytes(0x30); - stream.Position = 0x820; - EticketCertEccB233 = reader.ReadBytes(0x180); - - stream.Position = 0x9B0; - SslKey = reader.ReadBytes(0x110); - stream.Position = 0xAD0; - SslCertSize = reader.ReadInt32(); - stream.Position = 0x0AE0; - SslCert = reader.ReadBytes(SslCertSize); - stream.Position = 0x12E0; - SslCertSha256 = reader.ReadBytes(0x20); - - stream.Position = 0x1300; - RandomNumber = reader.ReadBytes(0x1000); - stream.Position = 0x2300; - RandomNumberSha256 = reader.ReadBytes(0x20); - - stream.Position = 0x2320; - GamecardKey = reader.ReadBytes(0x110); - stream.Position = 0x2440; - GamecardCert = reader.ReadBytes(0x400); - stream.Position = 0x2840; - GamecardCertSha256 = reader.ReadBytes(0x20); - - stream.Position = 0x2860; - EticketKeyRsa = reader.ReadBytes(0x220); - stream.Position = 0x2A90; - EticketCertRsa = reader.ReadBytes(0x240); - - stream.Position = 0x2CE0; - BatteryLot = reader.ReadUtf8Z(0x18); - - stream.Position = 0x2D00; - SpeakerCalibValue = reader.ReadBytes(0x800); - - stream.Position = 0x3510; - RegionCode = reader.ReadInt32(); - - stream.Position = 0x3520; - AmiiboKey = reader.ReadBytes(0x50); - stream.Position = 0x3580; - AmiiboCertEcqv = reader.ReadBytes(0x14); - stream.Position = 0x35A0; - AmiiboCertEcdsa = reader.ReadBytes(0x70); - stream.Position = 0x3620; - AmiiboKeyEcqvBls = reader.ReadBytes(0x40); - stream.Position = 0x3670; - AmiiboCertEcqvBls = reader.ReadBytes(0x20); - stream.Position = 0x36A0; - AmiiboRootCertEcqvBls = reader.ReadBytes(0x90); - - stream.Position = 0x3740; - ProductModel = reader.ReadInt32(); - stream.Position = 0x3750; - ColorVariation = reader.ReadBytes(0x06); - stream.Position = 0x3760; - LcdBacklightBrightnessMapping = reader.ReadBytes(0x0C); - - stream.Position = 0x3770; - DeviceExtKeyEccB233 = reader.ReadBytes(0x50); - stream.Position = 0x37D0; - EticketExtKeyEccP256 = reader.ReadBytes(0x50); - stream.Position = 0x3830; - EticketExtKeyEccB233 = reader.ReadBytes(0x50); - stream.Position = 0x3890; - EticketExtKeyRsa = reader.ReadBytes(0x240); - stream.Position = 0x3AE0; - SslExtKey = reader.ReadBytes(0x130); - stream.Position = 0x3C20; - GamecardExtKey = reader.ReadBytes(0x130); - - stream.Position = 0x3D60; - LcdVendorId = reader.ReadInt32(); - - stream.Position = 0x3D70; - ExtendedRsa2048DeviceKey = reader.ReadBytes(0x240); - stream.Position = 0x3FC0; - Rsa2048DeviceCertificate = reader.ReadBytes(0x240); - - stream.Position = 0x4210; - UsbTypeCPowerSourceCircuitVersion = reader.ReadBytes(0x1); - - stream.Position = 0x4220; - HomeMenuSchemeSubColor = reader.ReadInt32(); - stream.Position = 0x4230; - HomeMenuSchemeBezelColor = reader.ReadInt32(); - stream.Position = 0x4240; - HomeMenuSchemeMainColor1 = reader.ReadInt32(); - stream.Position = 0x4250; - HomeMenuSchemeMainColor2 = reader.ReadInt32(); - stream.Position = 0x4260; - HomeMenuSchemeMainColor3 = reader.ReadInt32(); - - stream.Position = 0x4270; - AnalogStickModuleTypeL = reader.ReadBytes(0x1); - stream.Position = 0x4280; - AnalogStickModelParameterL = reader.ReadBytes(0x12); - stream.Position = 0x42A0; - AnalogStickFactoryCalibrationL = reader.ReadBytes(0x9); - stream.Position = 0x42B0; - AnalogStickModuleTypeR = reader.ReadBytes(0x1); - stream.Position = 0x42C0; - AnalogStickModelParameterR = reader.ReadBytes(0x12); - stream.Position = 0x42E0; - AnalogStickFactoryCalibrationR = reader.ReadBytes(0x9); - - stream.Position = 0x42F0; - ConsoleSixAxisSensorModuleType = reader.ReadBytes(0x1); - stream.Position = 0x4300; - ConsoleSixAxisSensorHorizontalOffset = reader.ReadBytes(0x6); - - stream.Position = 0x4310; - BatteryVersion = reader.ReadBytes(0x1); - - stream.Position = 0x4330; - HomeMenuSchemeModel = reader.ReadInt32(); - - stream.Position = 0x4340; - ConsoleSixAxisSensorMountType = reader.ReadBytes(0x1); + stream.Position = 0x88 + i * 4; + WlanCountryCodes[i] = reader.ReadUtf8Z(); } + stream.Position = 0x210; + WlanMacAddr = reader.ReadBytes(0x6); + + stream.Position = 0x220; + BdAddr = reader.ReadBytes(0x6); + stream.Position = 0x230; + AccelerometerOffset = reader.ReadBytes(0x6); + stream.Position = 0x238; + AccelerometerScale = reader.ReadBytes(0x6); + stream.Position = 0x240; + GyroscopeOffset = reader.ReadBytes(0x6); + stream.Position = 0x248; + GyroscopeScale = reader.ReadBytes(0x6); + + stream.Position = 0x250; + SerialNumber = reader.ReadUtf8Z(0x18); + + stream.Position = 0x270; + DeviceKeyEccP256 = reader.ReadBytes(0x30); + stream.Position = 0x2B0; + DeviceCertEccP256 = reader.ReadBytes(0x180); + stream.Position = 0x440; + DeviceKeyEccB233 = reader.ReadBytes(0x30); + stream.Position = 0x480; + DeviceCertEccB233 = reader.ReadBytes(0x180); + stream.Position = 0x610; + EticketKeyEccP256 = reader.ReadBytes(0x30); + stream.Position = 0x650; + EticketCertEccP256 = reader.ReadBytes(0x180); + stream.Position = 0x7E0; + EticketKeyEccB233 = reader.ReadBytes(0x30); + stream.Position = 0x820; + EticketCertEccB233 = reader.ReadBytes(0x180); + + stream.Position = 0x9B0; + SslKey = reader.ReadBytes(0x110); + stream.Position = 0xAD0; + SslCertSize = reader.ReadInt32(); + stream.Position = 0x0AE0; + SslCert = reader.ReadBytes(SslCertSize); + stream.Position = 0x12E0; + SslCertSha256 = reader.ReadBytes(0x20); + + stream.Position = 0x1300; + RandomNumber = reader.ReadBytes(0x1000); + stream.Position = 0x2300; + RandomNumberSha256 = reader.ReadBytes(0x20); + + stream.Position = 0x2320; + GamecardKey = reader.ReadBytes(0x110); + stream.Position = 0x2440; + GamecardCert = reader.ReadBytes(0x400); + stream.Position = 0x2840; + GamecardCertSha256 = reader.ReadBytes(0x20); + + stream.Position = 0x2860; + EticketKeyRsa = reader.ReadBytes(0x220); + stream.Position = 0x2A90; + EticketCertRsa = reader.ReadBytes(0x240); + + stream.Position = 0x2CE0; + BatteryLot = reader.ReadUtf8Z(0x18); + + stream.Position = 0x2D00; + SpeakerCalibValue = reader.ReadBytes(0x800); + + stream.Position = 0x3510; + RegionCode = reader.ReadInt32(); + + stream.Position = 0x3520; + AmiiboKey = reader.ReadBytes(0x50); + stream.Position = 0x3580; + AmiiboCertEcqv = reader.ReadBytes(0x14); + stream.Position = 0x35A0; + AmiiboCertEcdsa = reader.ReadBytes(0x70); + stream.Position = 0x3620; + AmiiboKeyEcqvBls = reader.ReadBytes(0x40); + stream.Position = 0x3670; + AmiiboCertEcqvBls = reader.ReadBytes(0x20); + stream.Position = 0x36A0; + AmiiboRootCertEcqvBls = reader.ReadBytes(0x90); + + stream.Position = 0x3740; + ProductModel = reader.ReadInt32(); + stream.Position = 0x3750; + ColorVariation = reader.ReadBytes(0x06); + stream.Position = 0x3760; + LcdBacklightBrightnessMapping = reader.ReadBytes(0x0C); + + stream.Position = 0x3770; + DeviceExtKeyEccB233 = reader.ReadBytes(0x50); + stream.Position = 0x37D0; + EticketExtKeyEccP256 = reader.ReadBytes(0x50); + stream.Position = 0x3830; + EticketExtKeyEccB233 = reader.ReadBytes(0x50); + stream.Position = 0x3890; + EticketExtKeyRsa = reader.ReadBytes(0x240); + stream.Position = 0x3AE0; + SslExtKey = reader.ReadBytes(0x130); + stream.Position = 0x3C20; + GamecardExtKey = reader.ReadBytes(0x130); + + stream.Position = 0x3D60; + LcdVendorId = reader.ReadInt32(); + + stream.Position = 0x3D70; + ExtendedRsa2048DeviceKey = reader.ReadBytes(0x240); + stream.Position = 0x3FC0; + Rsa2048DeviceCertificate = reader.ReadBytes(0x240); + + stream.Position = 0x4210; + UsbTypeCPowerSourceCircuitVersion = reader.ReadBytes(0x1); + + stream.Position = 0x4220; + HomeMenuSchemeSubColor = reader.ReadInt32(); + stream.Position = 0x4230; + HomeMenuSchemeBezelColor = reader.ReadInt32(); + stream.Position = 0x4240; + HomeMenuSchemeMainColor1 = reader.ReadInt32(); + stream.Position = 0x4250; + HomeMenuSchemeMainColor2 = reader.ReadInt32(); + stream.Position = 0x4260; + HomeMenuSchemeMainColor3 = reader.ReadInt32(); + + stream.Position = 0x4270; + AnalogStickModuleTypeL = reader.ReadBytes(0x1); + stream.Position = 0x4280; + AnalogStickModelParameterL = reader.ReadBytes(0x12); + stream.Position = 0x42A0; + AnalogStickFactoryCalibrationL = reader.ReadBytes(0x9); + stream.Position = 0x42B0; + AnalogStickModuleTypeR = reader.ReadBytes(0x1); + stream.Position = 0x42C0; + AnalogStickModelParameterR = reader.ReadBytes(0x12); + stream.Position = 0x42E0; + AnalogStickFactoryCalibrationR = reader.ReadBytes(0x9); + + stream.Position = 0x42F0; + ConsoleSixAxisSensorModuleType = reader.ReadBytes(0x1); + stream.Position = 0x4300; + ConsoleSixAxisSensorHorizontalOffset = reader.ReadBytes(0x6); + + stream.Position = 0x4310; + BatteryVersion = reader.ReadBytes(0x1); + + stream.Position = 0x4330; + HomeMenuSchemeModel = reader.ReadInt32(); + + stream.Position = 0x4340; + ConsoleSixAxisSensorMountType = reader.ReadBytes(0x1); } -} \ No newline at end of file +} diff --git a/src/LibHac/Cnmt.cs b/src/LibHac/Cnmt.cs index a307762f..8b276415 100644 --- a/src/LibHac/Cnmt.cs +++ b/src/LibHac/Cnmt.cs @@ -4,323 +4,322 @@ using LibHac.FsSystem.NcaUtils; using LibHac.Ncm; using ContentType = LibHac.Ncm.ContentType; -namespace LibHac +namespace LibHac; + +public class Cnmt { - public class Cnmt + public ulong TitleId { get; } + public TitleVersion TitleVersion { get; } + public ContentMetaType Type { get; } + public byte FieldD { get; } + public int TableOffset { get; } + public int ContentEntryCount { get; } + public int MetaEntryCount { get; } + + public CnmtContentEntry[] ContentEntries { get; } + public CnmtContentMetaEntry[] MetaEntries { get; } + + public ulong ApplicationTitleId { get; } + public ulong PatchTitleId { get; } + public TitleVersion MinimumSystemVersion { get; } + public TitleVersion MinimumApplicationVersion { get; } + + public CnmtExtended ExtendedData { get; } + + public byte[] Hash { get; } + + public Cnmt() { } + + public Cnmt(Stream file) { - public ulong TitleId { get; } - public TitleVersion TitleVersion { get; } - public ContentMetaType Type { get; } - public byte FieldD { get; } - public int TableOffset { get; } - public int ContentEntryCount { get; } - public int MetaEntryCount { get; } - - public CnmtContentEntry[] ContentEntries { get; } - public CnmtContentMetaEntry[] MetaEntries { get; } - - public ulong ApplicationTitleId { get; } - public ulong PatchTitleId { get; } - public TitleVersion MinimumSystemVersion { get; } - public TitleVersion MinimumApplicationVersion { get; } - - public CnmtExtended ExtendedData { get; } - - public byte[] Hash { get; } - - public Cnmt() { } - - public Cnmt(Stream file) - { - using (var reader = new BinaryReader(file)) - { - TitleId = reader.ReadUInt64(); - uint version = reader.ReadUInt32(); - Type = (ContentMetaType)reader.ReadByte(); - TitleVersion = new TitleVersion(version, Type < ContentMetaType.Application); - FieldD = reader.ReadByte(); - TableOffset = reader.ReadUInt16(); - ContentEntryCount = reader.ReadUInt16(); - MetaEntryCount = reader.ReadUInt16(); - - // Old, pre-release cnmt files don't have the "required system version" field. - // Try to detect this by reading the padding after that field. - // The old format usually contains hashes there. - file.Position += 8; - int padding = reader.ReadInt32(); - bool isOldCnmtFormat = padding != 0; - - switch (Type) - { - case ContentMetaType.Application: - ApplicationTitleId = TitleId; - PatchTitleId = reader.ReadUInt64(); - MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true); - break; - case ContentMetaType.Patch: - ApplicationTitleId = reader.ReadUInt64(); - MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true); - break; - case ContentMetaType.AddOnContent: - ApplicationTitleId = reader.ReadUInt64(); - MinimumApplicationVersion = new TitleVersion(reader.ReadUInt32()); - break; - } - - int baseOffset = isOldCnmtFormat ? 0x18 : 0x20; - file.Position = baseOffset + TableOffset; - - ContentEntries = new CnmtContentEntry[ContentEntryCount]; - MetaEntries = new CnmtContentMetaEntry[MetaEntryCount]; - - for (int i = 0; i < ContentEntryCount; i++) - { - ContentEntries[i] = new CnmtContentEntry(reader); - } - - for (int i = 0; i < MetaEntryCount; i++) - { - MetaEntries[i] = new CnmtContentMetaEntry(reader); - } - - if (Type == ContentMetaType.Patch) - { - ExtendedData = new CnmtExtended(reader); - } - - Hash = reader.ReadBytes(0x20); - } - } - } - - public class CnmtContentEntry - { - public byte[] Hash { get; set; } - public byte[] NcaId { get; set; } - public long Size { get; set; } - public ContentType Type { get; set; } - - public CnmtContentEntry() { } - - public CnmtContentEntry(BinaryReader reader) - { - Hash = reader.ReadBytes(0x20); - NcaId = reader.ReadBytes(0x10); - Size = reader.ReadUInt32(); - Size |= (long)reader.ReadUInt16() << 32; - Type = (ContentType)reader.ReadByte(); - reader.BaseStream.Position += 1; - } - } - - public class CnmtContentMetaEntry - { - public ulong TitleId { get; } - public TitleVersion Version { get; } - public ContentType Type { get; } - - public CnmtContentMetaEntry() { } - - public CnmtContentMetaEntry(BinaryReader reader) + using (var reader = new BinaryReader(file)) { TitleId = reader.ReadUInt64(); - Version = new TitleVersion(reader.ReadUInt32(), true); - Type = (ContentType)reader.ReadByte(); - reader.BaseStream.Position += 3; - } - } - - public class CnmtExtended - { - public int PrevMetaCount { get; } - public int PrevDeltaSetCount { get; } - public int DeltaSetCount { get; } - public int FragmentSetCount { get; } - public int PrevContentCount { get; } - public int DeltaContentCount { get; } - - public CnmtPrevMetaEntry[] PrevMetas { get; } - public CnmtPrevDelta[] PrevDeltaSets { get; } - public CnmtDeltaSetInfo[] DeltaSets { get; } - public CnmtFragmentSetInfo[] FragmentSets { get; } - public CnmtPrevContent[] PrevContents { get; } - public CnmtContentEntry[] DeltaContents { get; } - public FragmentMapEntry[] FragmentMap { get; } - - public CnmtExtended(BinaryReader reader) - { - PrevMetaCount = reader.ReadInt32(); // Lists all previous content meta files - PrevDeltaSetCount = reader.ReadInt32(); // Lists all previous delta sets - DeltaSetCount = reader.ReadInt32(); // Lists the delta set for the current title version - FragmentSetCount = reader.ReadInt32(); // Groups fragments into full deltas - PrevContentCount = reader.ReadInt32(); // Lists all previous NCAs for the title - DeltaContentCount = reader.ReadInt32(); // Lists all NCAs containing delta fragments - reader.BaseStream.Position += 4; - - PrevMetas = new CnmtPrevMetaEntry[PrevMetaCount]; - PrevDeltaSets = new CnmtPrevDelta[PrevDeltaSetCount]; - DeltaSets = new CnmtDeltaSetInfo[DeltaSetCount]; - FragmentSets = new CnmtFragmentSetInfo[FragmentSetCount]; - PrevContents = new CnmtPrevContent[PrevContentCount]; - DeltaContents = new CnmtContentEntry[DeltaContentCount]; - - for (int i = 0; i < PrevMetaCount; i++) - { - PrevMetas[i] = new CnmtPrevMetaEntry(reader); - } - - for (int i = 0; i < PrevDeltaSetCount; i++) - { - PrevDeltaSets[i] = new CnmtPrevDelta(reader); - } - - for (int i = 0; i < DeltaSetCount; i++) - { - DeltaSets[i] = new CnmtDeltaSetInfo(reader); - } - - for (int i = 0; i < FragmentSetCount; i++) - { - FragmentSets[i] = new CnmtFragmentSetInfo(reader); - } - - for (int i = 0; i < PrevContentCount; i++) - { - PrevContents[i] = new CnmtPrevContent(reader); - } - - for (int i = 0; i < DeltaContentCount; i++) - { - DeltaContents[i] = new CnmtContentEntry(reader); - } - - int fragmentCount = FragmentSets.Sum(x => x.FragmentCount); - FragmentMap = new FragmentMapEntry[fragmentCount]; - - for (int i = 0; i < fragmentCount; i++) - { - FragmentMap[i] = new FragmentMapEntry(reader); - } - } - } - - public class CnmtPrevMetaEntry - { - public ulong TitleId { get; } - public TitleVersion Version { get; } - public ContentMetaType Type { get; } - public byte[] Hash { get; } - public short ContentCount { get; } - public short CnmtPrevMetaEntryField32 { get; } - public int CnmtPrevMetaEntryField34 { get; } - - public CnmtPrevMetaEntry(BinaryReader reader) - { - TitleId = reader.ReadUInt64(); - Version = new TitleVersion(reader.ReadUInt32()); + uint version = reader.ReadUInt32(); Type = (ContentMetaType)reader.ReadByte(); - reader.BaseStream.Position += 3; + TitleVersion = new TitleVersion(version, Type < ContentMetaType.Application); + FieldD = reader.ReadByte(); + TableOffset = reader.ReadUInt16(); + ContentEntryCount = reader.ReadUInt16(); + MetaEntryCount = reader.ReadUInt16(); + + // Old, pre-release cnmt files don't have the "required system version" field. + // Try to detect this by reading the padding after that field. + // The old format usually contains hashes there. + file.Position += 8; + int padding = reader.ReadInt32(); + bool isOldCnmtFormat = padding != 0; + + switch (Type) + { + case ContentMetaType.Application: + ApplicationTitleId = TitleId; + PatchTitleId = reader.ReadUInt64(); + MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true); + break; + case ContentMetaType.Patch: + ApplicationTitleId = reader.ReadUInt64(); + MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true); + break; + case ContentMetaType.AddOnContent: + ApplicationTitleId = reader.ReadUInt64(); + MinimumApplicationVersion = new TitleVersion(reader.ReadUInt32()); + break; + } + + int baseOffset = isOldCnmtFormat ? 0x18 : 0x20; + file.Position = baseOffset + TableOffset; + + ContentEntries = new CnmtContentEntry[ContentEntryCount]; + MetaEntries = new CnmtContentMetaEntry[MetaEntryCount]; + + for (int i = 0; i < ContentEntryCount; i++) + { + ContentEntries[i] = new CnmtContentEntry(reader); + } + + for (int i = 0; i < MetaEntryCount; i++) + { + MetaEntries[i] = new CnmtContentMetaEntry(reader); + } + + if (Type == ContentMetaType.Patch) + { + ExtendedData = new CnmtExtended(reader); + } + Hash = reader.ReadBytes(0x20); - ContentCount = reader.ReadInt16(); - CnmtPrevMetaEntryField32 = reader.ReadInt16(); - CnmtPrevMetaEntryField34 = reader.ReadInt32(); - } - } - - public class CnmtPrevDelta - { - public ulong TitleIdOld { get; } - public ulong TitleIdNew { get; } - public TitleVersion VersionOld { get; } - public TitleVersion VersionNew { get; } - public long Size { get; } - public long CnmtPrevDeltaField20 { get; } - - public CnmtPrevDelta(BinaryReader reader) - { - TitleIdOld = reader.ReadUInt64(); - TitleIdNew = reader.ReadUInt64(); - VersionOld = new TitleVersion(reader.ReadUInt32()); - VersionNew = new TitleVersion(reader.ReadUInt32()); - Size = reader.ReadInt64(); - CnmtPrevDeltaField20 = reader.ReadInt64(); - } - } - - public class CnmtDeltaSetInfo - { - public ulong TitleIdOld { get; } - public ulong TitleIdNew { get; } - public TitleVersion VersionOld { get; } - public TitleVersion VersionNew { get; } - public long FragmentSetCount { get; } - public long DeltaContentCount { get; } - - public CnmtDeltaSetInfo(BinaryReader reader) - { - TitleIdOld = reader.ReadUInt64(); - TitleIdNew = reader.ReadUInt64(); - VersionOld = new TitleVersion(reader.ReadUInt32()); - VersionNew = new TitleVersion(reader.ReadUInt32()); - FragmentSetCount = reader.ReadInt64(); - DeltaContentCount = reader.ReadInt64(); - } - } - - public class CnmtFragmentSetInfo - { - public byte[] NcaIdOld { get; } - public byte[] NcaIdNew { get; } - public long SizeOld { get; } - public long SizeNew { get; } - public short FragmentCount { get; } - public ContentType Type { get; } - public UpdateType DeltaType { get; } - public int FragmentSetInfoField30 { get; } - - - public CnmtFragmentSetInfo(BinaryReader reader) - { - NcaIdOld = reader.ReadBytes(0x10); - NcaIdNew = reader.ReadBytes(0x10); - - SizeOld = reader.ReadUInt32(); - SizeOld |= (long)reader.ReadUInt16() << 32; - SizeNew |= (long)reader.ReadUInt16() << 32; - SizeNew = reader.ReadUInt32(); - - FragmentCount = reader.ReadInt16(); - Type = (ContentType)reader.ReadByte(); - DeltaType = (UpdateType)reader.ReadByte(); - FragmentSetInfoField30 = reader.ReadInt32(); - } - } - - public class CnmtPrevContent - { - public byte[] NcaId { get; } - public long Size { get; } - public ContentType Type { get; } - - public CnmtPrevContent(BinaryReader reader) - { - NcaId = reader.ReadBytes(0x10); - Size = reader.ReadUInt32(); - Size |= (long)reader.ReadUInt16() << 32; - Type = (ContentType)reader.ReadByte(); - reader.BaseStream.Position += 1; - } - } - - public class FragmentMapEntry - { - public short ContentIndex { get; } - public short FragmentIndex { get; } - - public FragmentMapEntry(BinaryReader reader) - { - ContentIndex = reader.ReadInt16(); - FragmentIndex = reader.ReadInt16(); } } } + +public class CnmtContentEntry +{ + public byte[] Hash { get; set; } + public byte[] NcaId { get; set; } + public long Size { get; set; } + public ContentType Type { get; set; } + + public CnmtContentEntry() { } + + public CnmtContentEntry(BinaryReader reader) + { + Hash = reader.ReadBytes(0x20); + NcaId = reader.ReadBytes(0x10); + Size = reader.ReadUInt32(); + Size |= (long)reader.ReadUInt16() << 32; + Type = (ContentType)reader.ReadByte(); + reader.BaseStream.Position += 1; + } +} + +public class CnmtContentMetaEntry +{ + public ulong TitleId { get; } + public TitleVersion Version { get; } + public ContentType Type { get; } + + public CnmtContentMetaEntry() { } + + public CnmtContentMetaEntry(BinaryReader reader) + { + TitleId = reader.ReadUInt64(); + Version = new TitleVersion(reader.ReadUInt32(), true); + Type = (ContentType)reader.ReadByte(); + reader.BaseStream.Position += 3; + } +} + +public class CnmtExtended +{ + public int PrevMetaCount { get; } + public int PrevDeltaSetCount { get; } + public int DeltaSetCount { get; } + public int FragmentSetCount { get; } + public int PrevContentCount { get; } + public int DeltaContentCount { get; } + + public CnmtPrevMetaEntry[] PrevMetas { get; } + public CnmtPrevDelta[] PrevDeltaSets { get; } + public CnmtDeltaSetInfo[] DeltaSets { get; } + public CnmtFragmentSetInfo[] FragmentSets { get; } + public CnmtPrevContent[] PrevContents { get; } + public CnmtContentEntry[] DeltaContents { get; } + public FragmentMapEntry[] FragmentMap { get; } + + public CnmtExtended(BinaryReader reader) + { + PrevMetaCount = reader.ReadInt32(); // Lists all previous content meta files + PrevDeltaSetCount = reader.ReadInt32(); // Lists all previous delta sets + DeltaSetCount = reader.ReadInt32(); // Lists the delta set for the current title version + FragmentSetCount = reader.ReadInt32(); // Groups fragments into full deltas + PrevContentCount = reader.ReadInt32(); // Lists all previous NCAs for the title + DeltaContentCount = reader.ReadInt32(); // Lists all NCAs containing delta fragments + reader.BaseStream.Position += 4; + + PrevMetas = new CnmtPrevMetaEntry[PrevMetaCount]; + PrevDeltaSets = new CnmtPrevDelta[PrevDeltaSetCount]; + DeltaSets = new CnmtDeltaSetInfo[DeltaSetCount]; + FragmentSets = new CnmtFragmentSetInfo[FragmentSetCount]; + PrevContents = new CnmtPrevContent[PrevContentCount]; + DeltaContents = new CnmtContentEntry[DeltaContentCount]; + + for (int i = 0; i < PrevMetaCount; i++) + { + PrevMetas[i] = new CnmtPrevMetaEntry(reader); + } + + for (int i = 0; i < PrevDeltaSetCount; i++) + { + PrevDeltaSets[i] = new CnmtPrevDelta(reader); + } + + for (int i = 0; i < DeltaSetCount; i++) + { + DeltaSets[i] = new CnmtDeltaSetInfo(reader); + } + + for (int i = 0; i < FragmentSetCount; i++) + { + FragmentSets[i] = new CnmtFragmentSetInfo(reader); + } + + for (int i = 0; i < PrevContentCount; i++) + { + PrevContents[i] = new CnmtPrevContent(reader); + } + + for (int i = 0; i < DeltaContentCount; i++) + { + DeltaContents[i] = new CnmtContentEntry(reader); + } + + int fragmentCount = FragmentSets.Sum(x => x.FragmentCount); + FragmentMap = new FragmentMapEntry[fragmentCount]; + + for (int i = 0; i < fragmentCount; i++) + { + FragmentMap[i] = new FragmentMapEntry(reader); + } + } +} + +public class CnmtPrevMetaEntry +{ + public ulong TitleId { get; } + public TitleVersion Version { get; } + public ContentMetaType Type { get; } + public byte[] Hash { get; } + public short ContentCount { get; } + public short CnmtPrevMetaEntryField32 { get; } + public int CnmtPrevMetaEntryField34 { get; } + + public CnmtPrevMetaEntry(BinaryReader reader) + { + TitleId = reader.ReadUInt64(); + Version = new TitleVersion(reader.ReadUInt32()); + Type = (ContentMetaType)reader.ReadByte(); + reader.BaseStream.Position += 3; + Hash = reader.ReadBytes(0x20); + ContentCount = reader.ReadInt16(); + CnmtPrevMetaEntryField32 = reader.ReadInt16(); + CnmtPrevMetaEntryField34 = reader.ReadInt32(); + } +} + +public class CnmtPrevDelta +{ + public ulong TitleIdOld { get; } + public ulong TitleIdNew { get; } + public TitleVersion VersionOld { get; } + public TitleVersion VersionNew { get; } + public long Size { get; } + public long CnmtPrevDeltaField20 { get; } + + public CnmtPrevDelta(BinaryReader reader) + { + TitleIdOld = reader.ReadUInt64(); + TitleIdNew = reader.ReadUInt64(); + VersionOld = new TitleVersion(reader.ReadUInt32()); + VersionNew = new TitleVersion(reader.ReadUInt32()); + Size = reader.ReadInt64(); + CnmtPrevDeltaField20 = reader.ReadInt64(); + } +} + +public class CnmtDeltaSetInfo +{ + public ulong TitleIdOld { get; } + public ulong TitleIdNew { get; } + public TitleVersion VersionOld { get; } + public TitleVersion VersionNew { get; } + public long FragmentSetCount { get; } + public long DeltaContentCount { get; } + + public CnmtDeltaSetInfo(BinaryReader reader) + { + TitleIdOld = reader.ReadUInt64(); + TitleIdNew = reader.ReadUInt64(); + VersionOld = new TitleVersion(reader.ReadUInt32()); + VersionNew = new TitleVersion(reader.ReadUInt32()); + FragmentSetCount = reader.ReadInt64(); + DeltaContentCount = reader.ReadInt64(); + } +} + +public class CnmtFragmentSetInfo +{ + public byte[] NcaIdOld { get; } + public byte[] NcaIdNew { get; } + public long SizeOld { get; } + public long SizeNew { get; } + public short FragmentCount { get; } + public ContentType Type { get; } + public UpdateType DeltaType { get; } + public int FragmentSetInfoField30 { get; } + + + public CnmtFragmentSetInfo(BinaryReader reader) + { + NcaIdOld = reader.ReadBytes(0x10); + NcaIdNew = reader.ReadBytes(0x10); + + SizeOld = reader.ReadUInt32(); + SizeOld |= (long)reader.ReadUInt16() << 32; + SizeNew |= (long)reader.ReadUInt16() << 32; + SizeNew = reader.ReadUInt32(); + + FragmentCount = reader.ReadInt16(); + Type = (ContentType)reader.ReadByte(); + DeltaType = (UpdateType)reader.ReadByte(); + FragmentSetInfoField30 = reader.ReadInt32(); + } +} + +public class CnmtPrevContent +{ + public byte[] NcaId { get; } + public long Size { get; } + public ContentType Type { get; } + + public CnmtPrevContent(BinaryReader reader) + { + NcaId = reader.ReadBytes(0x10); + Size = reader.ReadUInt32(); + Size |= (long)reader.ReadUInt16() << 32; + Type = (ContentType)reader.ReadByte(); + reader.BaseStream.Position += 1; + } +} + +public class FragmentMapEntry +{ + public short ContentIndex { get; } + public short FragmentIndex { get; } + + public FragmentMapEntry(BinaryReader reader) + { + ContentIndex = reader.ReadInt16(); + FragmentIndex = reader.ReadInt16(); + } +} diff --git a/src/LibHac/Common/BlitSpan.cs b/src/LibHac/Common/BlitSpan.cs index 8936b138..a422ff83 100644 --- a/src/LibHac/Common/BlitSpan.cs +++ b/src/LibHac/Common/BlitSpan.cs @@ -3,108 +3,107 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common +namespace LibHac.Common; + +/// +/// Provides a representation of a region of memory as if it were a series of blittable structs +/// of type . Also allows viewing the memory as a of bytes. +/// +/// The element type. +public ref struct BlitSpan where T : unmanaged { + private readonly Span _buffer; + /// - /// Provides a representation of a region of memory as if it were a series of blittable structs - /// of type . Also allows viewing the memory as a of bytes. + /// The number of elements of type in the . /// - /// The element type. - public ref struct BlitSpan where T : unmanaged + public int Length => _buffer.Length; + + /// + /// A reference to the first element in this collection. + /// + public ref T Value { - private readonly Span _buffer; - - /// - /// The number of elements of type in the . - /// - public int Length => _buffer.Length; - - /// - /// A reference to the first element in this collection. - /// - public ref T Value + get { - get - { - Debug.Assert(_buffer.Length > 0); - - return ref MemoryMarshal.GetReference(_buffer); - } - } + Debug.Assert(_buffer.Length > 0); - /// - /// A reference to the element at index . - /// - public ref T this[int index] => ref _buffer[index]; - - /// - /// Creates a new using the provided . - /// - /// The span from which to create the . - /// Must have a length of at least 1. - public BlitSpan(Span data) - { - if (data.Length == 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _buffer = data; - } - - /// - /// Creates a new using the provided of bytes. - /// - /// The byte span from which to create the . - /// Must be long enough to hold at least one struct of type - public BlitSpan(Span data) - { - if (data.Length < Unsafe.SizeOf()) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _buffer = MemoryMarshal.Cast(data); - } - - /// - /// Creates a new over a struct of type . - /// - /// The struct from which to create the . - public BlitSpan(ref T data) - { - _buffer = SpanHelpers.AsSpan(ref data); - } - - /// - /// A of the elements in the . - /// - public Span Span => _buffer; - - /// - /// Returns a view of the as a of bytes. - /// - /// A byte span representation of the . - public Span GetByteSpan() - { - return MemoryMarshal.Cast(_buffer); - } - - /// - /// Returns a view of the element at index as a of bytes. - /// - /// The zero-based index of the element. - /// A byte span representation of the element. - public Span GetByteSpan(int elementIndex) - { - return SpanHelpers.AsByteSpan(ref _buffer[elementIndex]); - } - - /// - /// Calculates the length of memory in bytes that would be needed to store - /// elements of type . - /// - /// The number of elements. - /// The number of bytes required. - public static int QueryByteLength(int elementCount) - { - return Unsafe.SizeOf() * elementCount; + return ref MemoryMarshal.GetReference(_buffer); } } + + /// + /// A reference to the element at index . + /// + public ref T this[int index] => ref _buffer[index]; + + /// + /// Creates a new using the provided . + /// + /// The span from which to create the . + /// Must have a length of at least 1. + public BlitSpan(Span data) + { + if (data.Length == 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _buffer = data; + } + + /// + /// Creates a new using the provided of bytes. + /// + /// The byte span from which to create the . + /// Must be long enough to hold at least one struct of type + public BlitSpan(Span data) + { + if (data.Length < Unsafe.SizeOf()) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _buffer = MemoryMarshal.Cast(data); + } + + /// + /// Creates a new over a struct of type . + /// + /// The struct from which to create the . + public BlitSpan(ref T data) + { + _buffer = SpanHelpers.AsSpan(ref data); + } + + /// + /// A of the elements in the . + /// + public Span Span => _buffer; + + /// + /// Returns a view of the as a of bytes. + /// + /// A byte span representation of the . + public Span GetByteSpan() + { + return MemoryMarshal.Cast(_buffer); + } + + /// + /// Returns a view of the element at index as a of bytes. + /// + /// The zero-based index of the element. + /// A byte span representation of the element. + public Span GetByteSpan(int elementIndex) + { + return SpanHelpers.AsByteSpan(ref _buffer[elementIndex]); + } + + /// + /// Calculates the length of memory in bytes that would be needed to store + /// elements of type . + /// + /// The number of elements. + /// The number of bytes required. + public static int QueryByteLength(int elementCount) + { + return Unsafe.SizeOf() * elementCount; + } } diff --git a/src/LibHac/Common/BlitStruct.cs b/src/LibHac/Common/BlitStruct.cs index e2c6540c..297caf66 100644 --- a/src/LibHac/Common/BlitStruct.cs +++ b/src/LibHac/Common/BlitStruct.cs @@ -3,68 +3,67 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common +namespace LibHac.Common; + +/// +/// Handles storing a blittable struct or a series of blittable structs in a byte array. +/// +/// The element type. +public readonly struct BlitStruct where T : unmanaged { + private readonly byte[] _buffer; + + public int Length => _buffer.Length / Unsafe.SizeOf(); + /// - /// Handles storing a blittable struct or a series of blittable structs in a byte array. + /// A reference to the first element in this collection. /// - /// The element type. - public readonly struct BlitStruct where T : unmanaged + public ref T Value { - private readonly byte[] _buffer; - - public int Length => _buffer.Length / Unsafe.SizeOf(); - - /// - /// A reference to the first element in this collection. - /// - public ref T Value + get { - get - { - Debug.Assert(_buffer.Length >= Unsafe.SizeOf()); + Debug.Assert(_buffer.Length >= Unsafe.SizeOf()); - return ref Unsafe.As(ref _buffer[0]); - } - } - - /// - /// Initializes a new that can hold the specified number - /// of elements of type . - /// - /// The number of elements the will be able to store. - public BlitStruct(int elementCount) - { - if (elementCount <= 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _buffer = new byte[QueryByteLength(elementCount)]; - } - - /// - /// Returns a view of the elements in the current as type . - /// - public Span Span => MemoryMarshal.Cast(_buffer); - - /// - /// Returns a view of the elements in the current as s. - /// - public Span ByteSpan => _buffer; - - /// - /// Creates a from the current . - /// - public BlitSpan BlitSpan => new BlitSpan(_buffer); - - /// - /// Calculates the length of memory in bytes that would be needed to store - /// elements of type . - /// - /// The number of elements. - /// The number of bytes required. - public static int QueryByteLength(int elementCount) - { - return Unsafe.SizeOf() * elementCount; + return ref Unsafe.As(ref _buffer[0]); } } + + /// + /// Initializes a new that can hold the specified number + /// of elements of type . + /// + /// The number of elements the will be able to store. + public BlitStruct(int elementCount) + { + if (elementCount <= 0) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + _buffer = new byte[QueryByteLength(elementCount)]; + } + + /// + /// Returns a view of the elements in the current as type . + /// + public Span Span => MemoryMarshal.Cast(_buffer); + + /// + /// Returns a view of the elements in the current as s. + /// + public Span ByteSpan => _buffer; + + /// + /// Creates a from the current . + /// + public BlitSpan BlitSpan => new BlitSpan(_buffer); + + /// + /// Calculates the length of memory in bytes that would be needed to store + /// elements of type . + /// + /// The number of elements. + /// The number of bytes required. + public static int QueryByteLength(int elementCount) + { + return Unsafe.SizeOf() * elementCount; + } } diff --git a/src/LibHac/Common/Buffer.cs b/src/LibHac/Common/Buffer.cs index 81ff725b..7f3846f4 100644 --- a/src/LibHac/Common/Buffer.cs +++ b/src/LibHac/Common/Buffer.cs @@ -4,125 +4,124 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +/// +/// Represents a buffer of 16 bytes. +/// Contains functions that assist with common operations on small buffers. +/// +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 16)] +public struct Buffer16 { - /// - /// Represents a buffer of 16 bytes. - /// Contains functions that assist with common operations on small buffers. - /// - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 16)] - public struct Buffer16 + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + + public byte this[int i] { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; - - public byte this[int i] - { - get => Bytes[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - - // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Span(in Buffer16 value) - { - return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Buffer16 value) - { - return SpanHelpers.AsReadOnlyByteSpan(in value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T As() where T : unmanaged - { - if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) - { - throw new ArgumentException(); - } - - return ref MemoryMarshal.GetReference(AsSpan()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AsSpan() where T : unmanaged - { - return SpanHelpers.AsSpan(ref this); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged - { - return SpanHelpers.AsReadOnlySpan(in this); - } - - public override string ToString() - { - return Bytes.ToHexString(); - } + get => Bytes[i]; + set => Bytes[i] = value; } - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 32)] - public struct Buffer32 + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(in Buffer16 value) { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; - - public byte this[int i] - { - get => Bytes[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - - // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Span(in Buffer32 value) - { - return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Buffer32 value) - { - return SpanHelpers.AsReadOnlyByteSpan(in value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T As() where T : unmanaged - { - if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) - { - throw new ArgumentException(); - } - - return ref MemoryMarshal.GetReference(AsSpan()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AsSpan() where T : unmanaged - { - return SpanHelpers.AsSpan(ref this); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged - { - return SpanHelpers.AsReadOnlySpan(in this); - } - - public override string ToString() - { - return Bytes.ToHexString(); - } + return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); } -} \ No newline at end of file + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Buffer16 value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T As() where T : unmanaged + { + if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) + { + throw new ArgumentException(); + } + + return ref MemoryMarshal.GetReference(AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() where T : unmanaged + { + return SpanHelpers.AsSpan(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged + { + return SpanHelpers.AsReadOnlySpan(in this); + } + + public override string ToString() + { + return Bytes.ToHexString(); + } +} + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 32)] +public struct Buffer32 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; + + public byte this[int i] + { + get => Bytes[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(in Buffer32 value) + { + return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Buffer32 value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T As() where T : unmanaged + { + if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) + { + throw new ArgumentException(); + } + + return ref MemoryMarshal.GetReference(AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() where T : unmanaged + { + return SpanHelpers.AsSpan(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged + { + return SpanHelpers.AsReadOnlySpan(in this); + } + + public override string ToString() + { + return Bytes.ToHexString(); + } +} diff --git a/src/LibHac/Common/FixedArrays/Array1.cs b/src/LibHac/Common/FixedArrays/Array1.cs index 48b4ec24..34d094ba 100644 --- a/src/LibHac/Common/FixedArrays/Array1.cs +++ b/src/LibHac/Common/FixedArrays/Array1.cs @@ -2,21 +2,20 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array1 { - [StructLayout(LayoutKind.Sequential)] - public struct Array1 - { - public const int Length = 1; + public const int Length = 1; - private T _1; + private T _1; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array1 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array1 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array12.cs b/src/LibHac/Common/FixedArrays/Array12.cs index 284df870..102178bd 100644 --- a/src/LibHac/Common/FixedArrays/Array12.cs +++ b/src/LibHac/Common/FixedArrays/Array12.cs @@ -2,32 +2,31 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array12 { - [StructLayout(LayoutKind.Sequential)] - public struct Array12 - { - public const int Length = 12; + public const int Length = 12; - private T _1; - private T _2; - private T _3; - private T _4; - private T _5; - private T _6; - private T _7; - private T _8; - private T _9; - private T _10; - private T _11; - private T _12; + private T _1; + private T _2; + private T _3; + private T _4; + private T _5; + private T _6; + private T _7; + private T _8; + private T _9; + private T _10; + private T _11; + private T _12; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array12 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array12 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array128.cs b/src/LibHac/Common/FixedArrays/Array128.cs index ea4f03d9..9ad3d4ce 100644 --- a/src/LibHac/Common/FixedArrays/Array128.cs +++ b/src/LibHac/Common/FixedArrays/Array128.cs @@ -2,31 +2,30 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array128 { - [StructLayout(LayoutKind.Sequential)] - public struct Array128 + public const int Length = 128; + + private Array64 _0; + private Array64 _64; + + public ref T this[int i] => ref Items[i]; + + public Span Items { - public const int Length = 128; - - private Array64 _0; - private Array64 _64; - - public ref T this[int i] => ref Items[i]; - - public Span Items - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); - } - - public readonly ReadOnlySpan ItemsRo - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array128 value) => value.ItemsRo; + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); } -} \ No newline at end of file + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array128 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array16.cs b/src/LibHac/Common/FixedArrays/Array16.cs index ae8c501e..b8622a94 100644 --- a/src/LibHac/Common/FixedArrays/Array16.cs +++ b/src/LibHac/Common/FixedArrays/Array16.cs @@ -2,31 +2,30 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array16 { - [StructLayout(LayoutKind.Sequential)] - public struct Array16 + public const int Length = 16; + + private Array8 _0; + private Array8 _8; + + public ref T this[int i] => ref Items[i]; + + public Span Items { - public const int Length = 16; - - private Array8 _0; - private Array8 _8; - - public ref T this[int i] => ref Items[i]; - - public Span Items - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); - } - - public readonly ReadOnlySpan ItemsRo - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array16 value) => value.ItemsRo; + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); } -} \ No newline at end of file + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array16 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array2.cs b/src/LibHac/Common/FixedArrays/Array2.cs index f4f06719..f5b59cdb 100644 --- a/src/LibHac/Common/FixedArrays/Array2.cs +++ b/src/LibHac/Common/FixedArrays/Array2.cs @@ -2,22 +2,21 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array2 { - [StructLayout(LayoutKind.Sequential)] - public struct Array2 - { - public const int Length = 2; + public const int Length = 2; - private T _1; - private T _2; + private T _1; + private T _2; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array2 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array2 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array256.cs b/src/LibHac/Common/FixedArrays/Array256.cs index ee8f84ca..2c9a42e7 100644 --- a/src/LibHac/Common/FixedArrays/Array256.cs +++ b/src/LibHac/Common/FixedArrays/Array256.cs @@ -2,31 +2,30 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array256 { - [StructLayout(LayoutKind.Sequential)] - public struct Array256 + public const int Length = 256; + + private Array128 _0; + private Array128 _128; + + public ref T this[int i] => ref Items[i]; + + public Span Items { - public const int Length = 256; - - private Array128 _0; - private Array128 _128; - - public ref T this[int i] => ref Items[i]; - - public Span Items - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); - } - - public readonly ReadOnlySpan ItemsRo - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array256 value) => value.ItemsRo; + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); } -} \ No newline at end of file + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array256 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array3.cs b/src/LibHac/Common/FixedArrays/Array3.cs index 80c1e2c9..c075228f 100644 --- a/src/LibHac/Common/FixedArrays/Array3.cs +++ b/src/LibHac/Common/FixedArrays/Array3.cs @@ -2,23 +2,22 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array3 { - [StructLayout(LayoutKind.Sequential)] - public struct Array3 - { - public const int Length = 3; + public const int Length = 3; - private T _1; - private T _2; - private T _3; + private T _1; + private T _2; + private T _3; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array3 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array3 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array32.cs b/src/LibHac/Common/FixedArrays/Array32.cs index 02be3611..ae73b597 100644 --- a/src/LibHac/Common/FixedArrays/Array32.cs +++ b/src/LibHac/Common/FixedArrays/Array32.cs @@ -2,31 +2,30 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array32 { - [StructLayout(LayoutKind.Sequential)] - public struct Array32 + public const int Length = 32; + + private Array16 _0; + private Array16 _16; + + public ref T this[int i] => ref Items[i]; + + public Span Items { - public const int Length = 32; - - private Array16 _0; - private Array16 _16; - - public ref T this[int i] => ref Items[i]; - - public Span Items - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); - } - - public readonly ReadOnlySpan ItemsRo - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array32 value) => value.ItemsRo; + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); } -} \ No newline at end of file + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array32 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array4.cs b/src/LibHac/Common/FixedArrays/Array4.cs index 7a15d3d3..22a9aad6 100644 --- a/src/LibHac/Common/FixedArrays/Array4.cs +++ b/src/LibHac/Common/FixedArrays/Array4.cs @@ -2,24 +2,23 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array4 { - [StructLayout(LayoutKind.Sequential)] - public struct Array4 - { - public const int Length = 4; + public const int Length = 4; - private T _1; - private T _2; - private T _3; - private T _4; + private T _1; + private T _2; + private T _3; + private T _4; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array4 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array4 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array5.cs b/src/LibHac/Common/FixedArrays/Array5.cs index 45294cc3..0fa75f3c 100644 --- a/src/LibHac/Common/FixedArrays/Array5.cs +++ b/src/LibHac/Common/FixedArrays/Array5.cs @@ -2,25 +2,24 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array5 { - [StructLayout(LayoutKind.Sequential)] - public struct Array5 - { - public const int Length = 5; + public const int Length = 5; - private T _1; - private T _2; - private T _3; - private T _4; - private T _5; + private T _1; + private T _2; + private T _3; + private T _4; + private T _5; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array5 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array5 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array64.cs b/src/LibHac/Common/FixedArrays/Array64.cs index 5660f5d2..9c49f837 100644 --- a/src/LibHac/Common/FixedArrays/Array64.cs +++ b/src/LibHac/Common/FixedArrays/Array64.cs @@ -2,31 +2,30 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array64 { - [StructLayout(LayoutKind.Sequential)] - public struct Array64 + public const int Length = 64; + + private Array32 _0; + private Array32 _32; + + public ref T this[int i] => ref Items[i]; + + public Span Items { - public const int Length = 64; - - private Array32 _0; - private Array32 _32; - - public ref T this[int i] => ref Items[i]; - - public Span Items - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); - } - - public readonly ReadOnlySpan ItemsRo - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array64 value) => value.ItemsRo; + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); } -} \ No newline at end of file + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array64 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/FixedArrays/Array8.cs b/src/LibHac/Common/FixedArrays/Array8.cs index 743496a4..60e39447 100644 --- a/src/LibHac/Common/FixedArrays/Array8.cs +++ b/src/LibHac/Common/FixedArrays/Array8.cs @@ -2,28 +2,27 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common.FixedArrays +namespace LibHac.Common.FixedArrays; + +[StructLayout(LayoutKind.Sequential)] +public struct Array8 { - [StructLayout(LayoutKind.Sequential)] - public struct Array8 - { - public const int Length = 8; + public const int Length = 8; - private T _1; - private T _2; - private T _3; - private T _4; - private T _5; - private T _6; - private T _7; - private T _8; + private T _1; + private T _2; + private T _3; + private T _4; + private T _5; + private T _6; + private T _7; + private T _8; - public ref T this[int i] => ref Items[i]; + public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _1, Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array8 value) => value.ItemsRo; - } -} \ No newline at end of file + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array8 value) => value.ItemsRo; +} diff --git a/src/LibHac/Common/HResult.cs b/src/LibHac/Common/HResult.cs index 5b834ae6..3f0c3439 100644 --- a/src/LibHac/Common/HResult.cs +++ b/src/LibHac/Common/HResult.cs @@ -1,41 +1,40 @@ using System.Diagnostics.CodeAnalysis; using LibHac.Fs; -namespace LibHac.Common -{ - [SuppressMessage("ReSharper", "InconsistentNaming")] - internal static class HResult - { - public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); - public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); - public const int ERROR_ACCESS_DENIED = unchecked((int)0x80070005); - public const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020); - public const int ERROR_HANDLE_EOF = unchecked((int)0x80070026); - public const int ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027); - public const int ERROR_FILE_EXISTS = unchecked((int)0x80070050); - public const int ERROR_DISK_FULL = unchecked((int)0x80070070); - public const int ERROR_INVALID_NAME = unchecked((int)0x8007007B); - public const int ERROR_DIR_NOT_EMPTY = unchecked((int)0x80070091); - public const int ERROR_ALREADY_EXISTS = unchecked((int)0x800700B7); - public const int ERROR_DIRECTORY = unchecked((int)0x8007010B); - public const int ERROR_SPACES_NOT_ENOUGH_DRIVES = unchecked((int)0x80E7000B); +namespace LibHac.Common; - public static Result HResultToHorizonResult(int hResult) => hResult switch - { - ERROR_FILE_NOT_FOUND => ResultFs.PathNotFound.Value, - ERROR_PATH_NOT_FOUND => ResultFs.PathNotFound.Value, - ERROR_ACCESS_DENIED => ResultFs.TargetLocked.Value, - ERROR_SHARING_VIOLATION => ResultFs.TargetLocked.Value, - ERROR_HANDLE_EOF => ResultFs.OutOfRange.Value, - ERROR_HANDLE_DISK_FULL => ResultFs.UsableSpaceNotEnough.Value, - ERROR_FILE_EXISTS => ResultFs.PathAlreadyExists.Value, - ERROR_DISK_FULL => ResultFs.UsableSpaceNotEnough.Value, - ERROR_INVALID_NAME => ResultFs.PathNotFound.Value, - ERROR_DIR_NOT_EMPTY => ResultFs.DirectoryNotEmpty.Value, - ERROR_ALREADY_EXISTS => ResultFs.PathAlreadyExists.Value, - ERROR_DIRECTORY => ResultFs.PathNotFound.Value, - ERROR_SPACES_NOT_ENOUGH_DRIVES => ResultFs.UsableSpaceNotEnough.Value, - _ => ResultFs.UnexpectedInLocalFileSystemE.Value - }; - } +[SuppressMessage("ReSharper", "InconsistentNaming")] +internal static class HResult +{ + public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); + public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); + public const int ERROR_ACCESS_DENIED = unchecked((int)0x80070005); + public const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020); + public const int ERROR_HANDLE_EOF = unchecked((int)0x80070026); + public const int ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027); + public const int ERROR_FILE_EXISTS = unchecked((int)0x80070050); + public const int ERROR_DISK_FULL = unchecked((int)0x80070070); + public const int ERROR_INVALID_NAME = unchecked((int)0x8007007B); + public const int ERROR_DIR_NOT_EMPTY = unchecked((int)0x80070091); + public const int ERROR_ALREADY_EXISTS = unchecked((int)0x800700B7); + public const int ERROR_DIRECTORY = unchecked((int)0x8007010B); + public const int ERROR_SPACES_NOT_ENOUGH_DRIVES = unchecked((int)0x80E7000B); + + public static Result HResultToHorizonResult(int hResult) => hResult switch + { + ERROR_FILE_NOT_FOUND => ResultFs.PathNotFound.Value, + ERROR_PATH_NOT_FOUND => ResultFs.PathNotFound.Value, + ERROR_ACCESS_DENIED => ResultFs.TargetLocked.Value, + ERROR_SHARING_VIOLATION => ResultFs.TargetLocked.Value, + ERROR_HANDLE_EOF => ResultFs.OutOfRange.Value, + ERROR_HANDLE_DISK_FULL => ResultFs.UsableSpaceNotEnough.Value, + ERROR_FILE_EXISTS => ResultFs.PathAlreadyExists.Value, + ERROR_DISK_FULL => ResultFs.UsableSpaceNotEnough.Value, + ERROR_INVALID_NAME => ResultFs.PathNotFound.Value, + ERROR_DIR_NOT_EMPTY => ResultFs.DirectoryNotEmpty.Value, + ERROR_ALREADY_EXISTS => ResultFs.PathAlreadyExists.Value, + ERROR_DIRECTORY => ResultFs.PathNotFound.Value, + ERROR_SPACES_NOT_ENOUGH_DRIVES => ResultFs.UsableSpaceNotEnough.Value, + _ => ResultFs.UnexpectedInLocalFileSystemE.Value + }; } diff --git a/src/LibHac/Common/Id128.cs b/src/LibHac/Common/Id128.cs index 3473131f..e470d6b0 100644 --- a/src/LibHac/Common/Id128.cs +++ b/src/LibHac/Common/Id128.cs @@ -3,83 +3,82 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +/// +/// A generic 128-bit ID value. +/// +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct Id128 : IEquatable, IComparable, IComparable { - /// - /// A generic 128-bit ID value. - /// - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct Id128 : IEquatable, IComparable, IComparable + public readonly ulong High; + public readonly ulong Low; + + public static Id128 Zero => default; + + public Id128(ulong high, ulong low) { - public readonly ulong High; - public readonly ulong Low; - - public static Id128 Zero => default; - - public Id128(ulong high, ulong low) - { - High = high; - Low = low; - } - - public Id128(ReadOnlySpan uid) - { - ReadOnlySpan longs = MemoryMarshal.Cast(uid); - - High = longs[0]; - Low = longs[1]; - } - - public override string ToString() => AsBytes().ToHexString(); - - public bool Equals(Id128 other) - { - return High == other.High && Low == other.Low; - } - - public override bool Equals(object obj) - { - return obj is Id128 other && Equals(other); - } - - public override int GetHashCode() - { - return HashCode.Combine(High, Low); - } - - public int CompareTo(Id128 other) - { - int highComparison = High.CompareTo(other.High); - if (highComparison != 0) return highComparison; - return Low.CompareTo(other.Low); - } - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is Id128 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Id128)}"); - } - - public readonly void ToBytes(Span output) - { - Span longs = MemoryMarshal.Cast(output); - - longs[0] = High; - longs[1] = Low; - } - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(Id128 left, Id128 right) => left.Equals(right); - public static bool operator !=(Id128 left, Id128 right) => !left.Equals(right); - - public static bool operator <(Id128 left, Id128 right) => left.CompareTo(right) < 0; - public static bool operator >(Id128 left, Id128 right) => left.CompareTo(right) > 0; - public static bool operator <=(Id128 left, Id128 right) => left.CompareTo(right) <= 0; - public static bool operator >=(Id128 left, Id128 right) => left.CompareTo(right) >= 0; + High = high; + Low = low; } + + public Id128(ReadOnlySpan uid) + { + ReadOnlySpan longs = MemoryMarshal.Cast(uid); + + High = longs[0]; + Low = longs[1]; + } + + public override string ToString() => AsBytes().ToHexString(); + + public bool Equals(Id128 other) + { + return High == other.High && Low == other.Low; + } + + public override bool Equals(object obj) + { + return obj is Id128 other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(High, Low); + } + + public int CompareTo(Id128 other) + { + int highComparison = High.CompareTo(other.High); + if (highComparison != 0) return highComparison; + return Low.CompareTo(other.Low); + } + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is Id128 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Id128)}"); + } + + public readonly void ToBytes(Span output) + { + Span longs = MemoryMarshal.Cast(output); + + longs[0] = High; + longs[1] = Low; + } + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(Id128 left, Id128 right) => left.Equals(right); + public static bool operator !=(Id128 left, Id128 right) => !left.Equals(right); + + public static bool operator <(Id128 left, Id128 right) => left.CompareTo(right) < 0; + public static bool operator >(Id128 left, Id128 right) => left.CompareTo(right) > 0; + public static bool operator <=(Id128 left, Id128 right) => left.CompareTo(right) <= 0; + public static bool operator >=(Id128 left, Id128 right) => left.CompareTo(right) >= 0; } diff --git a/src/LibHac/Common/InitializationGuard.cs b/src/LibHac/Common/InitializationGuard.cs index 8a971cce..98ff0c55 100644 --- a/src/LibHac/Common/InitializationGuard.cs +++ b/src/LibHac/Common/InitializationGuard.cs @@ -2,94 +2,93 @@ using System.Runtime.CompilerServices; using System.Threading; -namespace LibHac.Common +namespace LibHac.Common; + +internal readonly ref struct InitializationGuard { - internal readonly ref struct InitializationGuard + private readonly Ref _guard; + private readonly object _mutex; + public bool IsInitialized => _mutex == null; + + private const byte GuardBitComplete = 1 << 0; + + [Flags] + private enum InitStatus : byte { - private readonly Ref _guard; - private readonly object _mutex; - public bool IsInitialized => _mutex == null; + Complete = 1 << 0, + Pending = 1 << 1, + Waiting = 1 << 2 + } - private const byte GuardBitComplete = 1 << 0; - - [Flags] - private enum InitStatus : byte + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public InitializationGuard(ref nint guard, object mutex) + { + if (IsGuardInitialized(guard) || !AcquireGuard(ref guard, mutex)) { - Complete = 1 << 0, - Pending = 1 << 1, - Waiting = 1 << 2 + this = default; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public InitializationGuard(ref nint guard, object mutex) + _guard = new Ref(ref guard); + _mutex = mutex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (!IsInitialized) { - if (IsGuardInitialized(guard) || !AcquireGuard(ref guard, mutex)) - { - this = default; - return; - } - - _guard = new Ref(ref guard); - _mutex = mutex; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (!IsInitialized) - { - ReleaseGuard(ref _guard.Value, _mutex); - } - } - - public static bool AcquireGuard(ref nint guard, object mutex) - { - if (SpanHelpers.AsByteSpan(ref guard)[0] == GuardBitComplete) - return false; - - return AcquireInitByte(ref Unsafe.As(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex); - } - - public static void ReleaseGuard(ref nint guard, object mutex) - { - SpanHelpers.AsByteSpan(ref guard)[0] = GuardBitComplete; - - ReleaseInitByte(ref Unsafe.As(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex); - } - - private static bool AcquireInitByte(ref InitStatus initByte, object mutex) - { - lock (mutex) - { - while (initByte.HasFlag(InitStatus.Pending)) - { - initByte |= InitStatus.Waiting; - Monitor.Wait(mutex); - } - - if (initByte == InitStatus.Complete) - return false; - - initByte = InitStatus.Pending; - return true; - } - } - - private static void ReleaseInitByte(ref InitStatus initByte, object mutex) - { - lock (mutex) - { - bool hasWaiting = initByte.HasFlag(InitStatus.Waiting); - initByte = InitStatus.Complete; - - if (hasWaiting) - Monitor.PulseAll(mutex); - } - } - - private static bool IsGuardInitialized(nint guard) - { - return (guard & 1) != 0; + ReleaseGuard(ref _guard.Value, _mutex); } } + + public static bool AcquireGuard(ref nint guard, object mutex) + { + if (SpanHelpers.AsByteSpan(ref guard)[0] == GuardBitComplete) + return false; + + return AcquireInitByte(ref Unsafe.As(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex); + } + + public static void ReleaseGuard(ref nint guard, object mutex) + { + SpanHelpers.AsByteSpan(ref guard)[0] = GuardBitComplete; + + ReleaseInitByte(ref Unsafe.As(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex); + } + + private static bool AcquireInitByte(ref InitStatus initByte, object mutex) + { + lock (mutex) + { + while (initByte.HasFlag(InitStatus.Pending)) + { + initByte |= InitStatus.Waiting; + Monitor.Wait(mutex); + } + + if (initByte == InitStatus.Complete) + return false; + + initByte = InitStatus.Pending; + return true; + } + } + + private static void ReleaseInitByte(ref InitStatus initByte, object mutex) + { + lock (mutex) + { + bool hasWaiting = initByte.HasFlag(InitStatus.Waiting); + initByte = InitStatus.Complete; + + if (hasWaiting) + Monitor.PulseAll(mutex); + } + } + + private static bool IsGuardInitialized(nint guard) + { + return (guard & 1) != 0; + } } diff --git a/src/LibHac/Common/InteropWin32.cs b/src/LibHac/Common/InteropWin32.cs index 69f55fae..92d94cb7 100644 --- a/src/LibHac/Common/InteropWin32.cs +++ b/src/LibHac/Common/InteropWin32.cs @@ -4,61 +4,60 @@ using System.Runtime.InteropServices; #pragma warning disable 649 -namespace LibHac.Common +namespace LibHac.Common; + +public static unsafe class InteropWin32 { - public static unsafe class InteropWin32 + [DllImport("kernel32.dll")] + private static extern int MultiByteToWideChar(uint codePage, uint dwFlags, byte* lpMultiByteStr, + int cbMultiByte, char* lpWideCharStr, int cchWideChar); + + public static int MultiByteToWideChar(int codePage, ReadOnlySpan bytes, Span chars) { - [DllImport("kernel32.dll")] - private static extern int MultiByteToWideChar(uint codePage, uint dwFlags, byte* lpMultiByteStr, - int cbMultiByte, char* lpWideCharStr, int cchWideChar); - - public static int MultiByteToWideChar(int codePage, ReadOnlySpan bytes, Span chars) + fixed (byte* pBytes = bytes) + fixed (char* pChars = chars) { - fixed (byte* pBytes = bytes) - fixed (char* pChars = chars) - { - return MultiByteToWideChar((uint)codePage, 0, pBytes, bytes.Length, pChars, chars.Length); - } - } - - [DllImport("kernel32.dll")] - public static extern bool FindClose(IntPtr handle); - - [DllImport("kernel32.dll")] - public static extern IntPtr FindFirstFileW(char* lpFileName, Win32FindData* lpFindFileData); - - public static IntPtr FindFirstFileW(ReadOnlySpan fileName, out Win32FindData findFileData) - { - fixed (char* pfileName = fileName) - { - Unsafe.SkipInit(out findFileData); - return FindFirstFileW(pfileName, (Win32FindData*)Unsafe.AsPointer(ref findFileData)); - } - } - - public struct Win32FindData - { - public uint FileAttributes; - private uint _creationTimeLow; - private uint _creationTimeHigh; - private uint _lastAccessLow; - private uint _lastAccessHigh; - private uint _lastWriteLow; - private uint _lastWriteHigh; - private uint _fileSizeHigh; - private uint _fileSizeLow; - public uint Reserved0; - public uint Reserved1; - private fixed char _fileName[260]; - private fixed char _alternateFileName[14]; - - public long CreationTime => (long)((ulong)_creationTimeHigh << 32 | _creationTimeLow); - public long LastAccessTime => (long)((ulong)_lastAccessHigh << 32 | _lastAccessLow); - public long LastWriteTime => (long)((ulong)_lastWriteHigh << 32 | _lastWriteLow); - public long FileSize => (long)_fileSizeHigh << 32 | _fileSizeLow; - - public Span FileName => MemoryMarshal.CreateSpan(ref _fileName[0], 260); - public Span AlternateFileName => MemoryMarshal.CreateSpan(ref _alternateFileName[0], 14); + return MultiByteToWideChar((uint)codePage, 0, pBytes, bytes.Length, pChars, chars.Length); } } + + [DllImport("kernel32.dll")] + public static extern bool FindClose(IntPtr handle); + + [DllImport("kernel32.dll")] + public static extern IntPtr FindFirstFileW(char* lpFileName, Win32FindData* lpFindFileData); + + public static IntPtr FindFirstFileW(ReadOnlySpan fileName, out Win32FindData findFileData) + { + fixed (char* pfileName = fileName) + { + Unsafe.SkipInit(out findFileData); + return FindFirstFileW(pfileName, (Win32FindData*)Unsafe.AsPointer(ref findFileData)); + } + } + + public struct Win32FindData + { + public uint FileAttributes; + private uint _creationTimeLow; + private uint _creationTimeHigh; + private uint _lastAccessLow; + private uint _lastAccessHigh; + private uint _lastWriteLow; + private uint _lastWriteHigh; + private uint _fileSizeHigh; + private uint _fileSizeLow; + public uint Reserved0; + public uint Reserved1; + private fixed char _fileName[260]; + private fixed char _alternateFileName[14]; + + public long CreationTime => (long)((ulong)_creationTimeHigh << 32 | _creationTimeLow); + public long LastAccessTime => (long)((ulong)_lastAccessHigh << 32 | _lastAccessLow); + public long LastWriteTime => (long)((ulong)_lastWriteHigh << 32 | _lastWriteLow); + public long FileSize => (long)_fileSizeHigh << 32 | _fileSizeLow; + + public Span FileName => MemoryMarshal.CreateSpan(ref _fileName[0], 260); + public Span AlternateFileName => MemoryMarshal.CreateSpan(ref _alternateFileName[0], 14); + } } diff --git a/src/LibHac/Common/Key128.cs b/src/LibHac/Common/Key128.cs index dc211a22..ebc6682e 100644 --- a/src/LibHac/Common/Key128.cs +++ b/src/LibHac/Common/Key128.cs @@ -3,44 +3,43 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct Key128 : IEquatable { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct Key128 : IEquatable + private readonly ulong _dummy1; + private readonly ulong _dummy2; + + public Span Value => SpanHelpers.AsByteSpan(ref this); + + public Key128(ReadOnlySpan bytes) { - private readonly ulong _dummy1; - private readonly ulong _dummy2; + ReadOnlySpan longs = MemoryMarshal.Cast(bytes); - public Span Value => SpanHelpers.AsByteSpan(ref this); - - public Key128(ReadOnlySpan bytes) - { - ReadOnlySpan longs = MemoryMarshal.Cast(bytes); - - _dummy1 = longs[0]; - _dummy2 = longs[1]; - } - - public override string ToString() => Value.ToHexString(); - - public override bool Equals(object obj) - { - return obj is Key128 key && Equals(key); - } - - public bool Equals(Key128 other) - { - return _dummy1 == other._dummy1 && - _dummy2 == other._dummy2; - } - - public override int GetHashCode() - { - return HashCode.Combine(_dummy1, _dummy2); - } - - public static bool operator ==(Key128 left, Key128 right) => left.Equals(right); - public static bool operator !=(Key128 left, Key128 right) => !(left == right); + _dummy1 = longs[0]; + _dummy2 = longs[1]; } + + public override string ToString() => Value.ToHexString(); + + public override bool Equals(object obj) + { + return obj is Key128 key && Equals(key); + } + + public bool Equals(Key128 other) + { + return _dummy1 == other._dummy1 && + _dummy2 == other._dummy2; + } + + public override int GetHashCode() + { + return HashCode.Combine(_dummy1, _dummy2); + } + + public static bool operator ==(Key128 left, Key128 right) => left.Equals(right); + public static bool operator !=(Key128 left, Key128 right) => !(left == right); } diff --git a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs index fda79d5c..c65f2cdd 100644 --- a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs +++ b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs @@ -1,19 +1,18 @@ using System; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +internal static partial class DefaultKeySet { - internal static partial class DefaultKeySet - { - private static ReadOnlySpan RootKeysDev => new byte[] { }; - private static ReadOnlySpan RootKeysProd => new byte[] { }; - private static ReadOnlySpan KeySeeds => new byte[] { }; - private static ReadOnlySpan StoredKeysDev => new byte[] { }; - private static ReadOnlySpan StoredKeysProd => new byte[] { }; - private static ReadOnlySpan DerivedKeysDev => new byte[] { }; - private static ReadOnlySpan DerivedKeysProd => new byte[] { }; - private static ReadOnlySpan DeviceKeys => new byte[] { }; - private static ReadOnlySpan RsaSigningKeysDev => new byte[] { }; - private static ReadOnlySpan RsaSigningKeysProd => new byte[] { }; - private static ReadOnlySpan RsaKeys => new byte[] { }; - } + private static ReadOnlySpan RootKeysDev => new byte[] { }; + private static ReadOnlySpan RootKeysProd => new byte[] { }; + private static ReadOnlySpan KeySeeds => new byte[] { }; + private static ReadOnlySpan StoredKeysDev => new byte[] { }; + private static ReadOnlySpan StoredKeysProd => new byte[] { }; + private static ReadOnlySpan DerivedKeysDev => new byte[] { }; + private static ReadOnlySpan DerivedKeysProd => new byte[] { }; + private static ReadOnlySpan DeviceKeys => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysDev => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysProd => new byte[] { }; + private static ReadOnlySpan RsaKeys => new byte[] { }; } diff --git a/src/LibHac/Common/Keys/DefaultKeySet.cs b/src/LibHac/Common/Keys/DefaultKeySet.cs index 3d9bdca7..25cbd7d9 100644 --- a/src/LibHac/Common/Keys/DefaultKeySet.cs +++ b/src/LibHac/Common/Keys/DefaultKeySet.cs @@ -4,178 +4,177 @@ using System.Runtime.InteropServices; using Type = LibHac.Common.Keys.KeyInfo.KeyType; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +internal static partial class DefaultKeySet { - internal static partial class DefaultKeySet + /// + /// Creates a that contains any keys that have been embedded in the library. + /// + /// The created . + public static KeySet CreateDefaultKeySet() { - /// - /// Creates a that contains any keys that have been embedded in the library. - /// - /// The created . - public static KeySet CreateDefaultKeySet() + var keySet = new KeySet(); + + // Fill the key set with any key structs included in the library. + if (RootKeysDev.Length == Unsafe.SizeOf()) { - var keySet = new KeySet(); - - // Fill the key set with any key structs included in the library. - if (RootKeysDev.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._rootKeysDev = MemoryMarshal.Cast(RootKeysDev)[0]; - } - - if (RootKeysProd.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._rootKeysProd = MemoryMarshal.Cast(RootKeysProd)[0]; - } - - if (KeySeeds.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._keySeeds = MemoryMarshal.Cast(KeySeeds)[0]; - } - - if (StoredKeysDev.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._storedKeysDev = MemoryMarshal.Cast(StoredKeysDev)[0]; - } - - if (StoredKeysProd.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._storedKeysProd = MemoryMarshal.Cast(StoredKeysProd)[0]; - } - - if (DerivedKeysDev.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._derivedKeysDev = MemoryMarshal.Cast(DerivedKeysDev)[0]; - } - - if (DerivedKeysProd.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._derivedKeysProd = MemoryMarshal.Cast(DerivedKeysProd)[0]; - } - - if (DeviceKeys.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._deviceKeys = MemoryMarshal.Cast(DeviceKeys)[0]; - } - - if (RsaSigningKeysDev.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._rsaSigningKeysDev = MemoryMarshal.Cast(RsaSigningKeysDev)[0]; - } - - if (RsaSigningKeysProd.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._rsaSigningKeysProd = MemoryMarshal.Cast(RsaSigningKeysProd)[0]; - } - - if (RsaKeys.Length == Unsafe.SizeOf()) - { - keySet.KeyStruct._rsaKeys = MemoryMarshal.Cast(RsaKeys)[0]; - } - - return keySet; + keySet.KeyStruct._rootKeysDev = MemoryMarshal.Cast(RootKeysDev)[0]; } - /// - /// Creates a of the of all keys that are loadable by default. - /// - /// The created list. - public static List CreateKeyList() + if (RootKeysProd.Length == Unsafe.SizeOf()) { - // Update this value if more keys are added - var keys = new List(70); - - // Keys with a group value of -1 are keys that will be read but not written. - // This is for compatibility since some keys had other names in the past. - - // TSEC secrets aren't public yet, so the TSEC root keys will be treated as - // root keys even though they're derived. - - keys.Add(new KeyInfo(10, Type.DeviceRoot, "secure_boot_key", (set, _) => set.SecureBootKey)); - keys.Add(new KeyInfo(11, Type.DeviceRoot, "tsec_key", (set, _) => set.TsecKey)); - keys.Add(new KeyInfo(12, Type.DeviceDrvd, "device_key", (set, _) => set.DeviceKey)); - - keys.Add(new KeyInfo(20, Type.CommonRoot, "tsec_root_kek", (set, _) => set.TsecRootKek)); - keys.Add(new KeyInfo(21, Type.CommonRoot, "package1_mac_kek", (set, _) => set.Package1MacKek)); - keys.Add(new KeyInfo(22, Type.CommonRoot, "package1_kek", (set, _) => set.Package1Kek)); - - keys.Add(new KeyInfo(30, Type.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i])); - - keys.Add(new KeyInfo(40, Type.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i])); - - keys.Add(new KeyInfo(50, Type.CommonSeed, "keyblob_mac_key_source", (set, _) => set.KeyBlobMacKeySource)); - keys.Add(new KeyInfo(51, Type.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i])); - - keys.Add(new KeyInfo(55, Type.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i])); - - keys.Add(new KeyInfo(60, Type.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i])); - - keys.Add(new KeyInfo(70, Type.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes)); - - keys.Add(new KeyInfo(80, Type.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes)); - - keys.Add(new KeyInfo(90, Type.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i])); - - keys.Add(new KeyInfo(100, Type.CommonRoot, "mariko_bek", (set, _) => set.MarikoBek)); - keys.Add(new KeyInfo(101, Type.CommonRoot, "mariko_kek", (set, _) => set.MarikoKek)); - - keys.Add(new KeyInfo(110, Type.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); - keys.Add(new KeyInfo(120, Type.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); - keys.Add(new KeyInfo(130, Type.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); - keys.Add(new KeyInfo(140, Type.CommonSeed, "master_key_source", (set, _) => set.MasterKeySource)); - keys.Add(new KeyInfo(150, Type.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); - - keys.Add(new KeyInfo(160, Type.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); - keys.Add(new KeyInfo(170, Type.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i])); - keys.Add(new KeyInfo(180, Type.CommonSeed, "package2_key_source", (set, _) => set.Package2KeySource)); - keys.Add(new KeyInfo(190, Type.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i])); - - keys.Add(new KeyInfo(200, Type.CommonSeed, "bis_kek_source", (set, _) => set.BisKekSource)); - keys.Add(new KeyInfo(201, Type.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i])); - - keys.Add(new KeyInfo(205, Type.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i])); - - keys.Add(new KeyInfo(210, Type.CommonSeed, "per_console_key_source", (set, _) => set.PerConsoleKeySource)); - keys.Add(new KeyInfo(211, Type.CommonSeed, "retail_specific_aes_key_source", (set, _) => set.RetailSpecificAesKeySource)); - keys.Add(new KeyInfo(212, Type.CommonSeed, "aes_kek_generation_source", (set, _) => set.AesKekGenerationSource)); - keys.Add(new KeyInfo(213, Type.CommonSeed, "aes_key_generation_source", (set, _) => set.AesKeyGenerationSource)); - keys.Add(new KeyInfo(214, Type.CommonSeed, "titlekek_source", (set, _) => set.TitleKekSource)); - - keys.Add(new KeyInfo(220, Type.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i])); - - keys.Add(new KeyInfo(230, Type.CommonSeed, "header_kek_source", (set, _) => set.HeaderKekSource)); - keys.Add(new KeyInfo(231, Type.CommonSeed, "header_key_source", (set, _) => set.HeaderKeySource)); - keys.Add(new KeyInfo(232, Type.CommonDrvd, "header_key", (set, _) => set.HeaderKey)); - - keys.Add(new KeyInfo(240, Type.CommonSeed, "key_area_key_application_source", (set, _) => set.KeyAreaKeyApplicationSource)); - keys.Add(new KeyInfo(241, Type.CommonSeed, "key_area_key_ocean_source", (set, _) => set.KeyAreaKeyOceanSource)); - keys.Add(new KeyInfo(242, Type.CommonSeed, "key_area_key_system_source", (set, _) => set.KeyAreaKeySystemSource)); - - keys.Add(new KeyInfo(250, Type.CommonSeed, "save_mac_kek_source", (set, _) => set.DeviceUniqueSaveMacKekSource)); - keys.Add(new KeyInfo(251, Type.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i])); - keys.Add(new KeyInfo(252, Type.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i])); - keys.Add(new KeyInfo(-01, Type.CommonSeed, "save_mac_key_source", (set, _) => set.DeviceUniqueSaveMacKeySources[0])); - - keys.Add(new KeyInfo(253, Type.CommonSeed, "save_mac_sd_card_kek_source", (set, _) => set.SeedUniqueSaveMacKekSource)); - keys.Add(new KeyInfo(254, Type.CommonSeed, "save_mac_sd_card_key_source", (set, _) => set.SeedUniqueSaveMacKeySource)); - keys.Add(new KeyInfo(255, Type.DeviceDrvd, "save_mac_sd_card_key", (set, _) => set.SeedUniqueSaveMacKey)); - - keys.Add(new KeyInfo(260, Type.DeviceRoot, "sd_seed", (set, _) => set.SdCardEncryptionSeed)); - - keys.Add(new KeyInfo(261, Type.CommonSeed, "sd_card_kek_source", (set, _) => set.SdCardKekSource)); - keys.Add(new KeyInfo(262, Type.CommonSeed, "sd_card_save_key_source", (set, _) => set.SdCardKeySources[0])); - keys.Add(new KeyInfo(263, Type.CommonSeed, "sd_card_nca_key_source", (set, _) => set.SdCardKeySources[1])); - keys.Add(new KeyInfo(264, Type.CommonSeed, "sd_card_custom_storage_key_source", (set, _) => set.SdCardKeySources[2])); - - keys.Add(new KeyInfo(270, Type.CommonSeedDiff, "xci_header_key", (set, _) => set.XciHeaderKey)); - - keys.Add(new KeyInfo(280, Type.CommonRoot, "eticket_rsa_kek", (set, _) => set.ETicketRsaKek)); - keys.Add(new KeyInfo(281, Type.CommonRoot, "ssl_rsa_kek", (set, _) => set.SslRsaKek)); - - keys.Add(new KeyInfo(290, Type.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); - keys.Add(new KeyInfo(300, Type.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1])); - keys.Add(new KeyInfo(310, Type.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2])); - - return keys; + keySet.KeyStruct._rootKeysProd = MemoryMarshal.Cast(RootKeysProd)[0]; } + + if (KeySeeds.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._keySeeds = MemoryMarshal.Cast(KeySeeds)[0]; + } + + if (StoredKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysDev = MemoryMarshal.Cast(StoredKeysDev)[0]; + } + + if (StoredKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysProd = MemoryMarshal.Cast(StoredKeysProd)[0]; + } + + if (DerivedKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysDev = MemoryMarshal.Cast(DerivedKeysDev)[0]; + } + + if (DerivedKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysProd = MemoryMarshal.Cast(DerivedKeysProd)[0]; + } + + if (DeviceKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._deviceKeys = MemoryMarshal.Cast(DeviceKeys)[0]; + } + + if (RsaSigningKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysDev = MemoryMarshal.Cast(RsaSigningKeysDev)[0]; + } + + if (RsaSigningKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysProd = MemoryMarshal.Cast(RsaSigningKeysProd)[0]; + } + + if (RsaKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaKeys = MemoryMarshal.Cast(RsaKeys)[0]; + } + + return keySet; + } + + /// + /// Creates a of the of all keys that are loadable by default. + /// + /// The created list. + public static List CreateKeyList() + { + // Update this value if more keys are added + var keys = new List(70); + + // Keys with a group value of -1 are keys that will be read but not written. + // This is for compatibility since some keys had other names in the past. + + // TSEC secrets aren't public yet, so the TSEC root keys will be treated as + // root keys even though they're derived. + + keys.Add(new KeyInfo(10, Type.DeviceRoot, "secure_boot_key", (set, _) => set.SecureBootKey)); + keys.Add(new KeyInfo(11, Type.DeviceRoot, "tsec_key", (set, _) => set.TsecKey)); + keys.Add(new KeyInfo(12, Type.DeviceDrvd, "device_key", (set, _) => set.DeviceKey)); + + keys.Add(new KeyInfo(20, Type.CommonRoot, "tsec_root_kek", (set, _) => set.TsecRootKek)); + keys.Add(new KeyInfo(21, Type.CommonRoot, "package1_mac_kek", (set, _) => set.Package1MacKek)); + keys.Add(new KeyInfo(22, Type.CommonRoot, "package1_kek", (set, _) => set.Package1Kek)); + + keys.Add(new KeyInfo(30, Type.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i])); + + keys.Add(new KeyInfo(40, Type.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i])); + + keys.Add(new KeyInfo(50, Type.CommonSeed, "keyblob_mac_key_source", (set, _) => set.KeyBlobMacKeySource)); + keys.Add(new KeyInfo(51, Type.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i])); + + keys.Add(new KeyInfo(55, Type.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i])); + + keys.Add(new KeyInfo(60, Type.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i])); + + keys.Add(new KeyInfo(70, Type.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(80, Type.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(90, Type.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i])); + + keys.Add(new KeyInfo(100, Type.CommonRoot, "mariko_bek", (set, _) => set.MarikoBek)); + keys.Add(new KeyInfo(101, Type.CommonRoot, "mariko_kek", (set, _) => set.MarikoKek)); + + keys.Add(new KeyInfo(110, Type.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); + keys.Add(new KeyInfo(120, Type.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); + keys.Add(new KeyInfo(130, Type.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); + keys.Add(new KeyInfo(140, Type.CommonSeed, "master_key_source", (set, _) => set.MasterKeySource)); + keys.Add(new KeyInfo(150, Type.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); + + keys.Add(new KeyInfo(160, Type.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); + keys.Add(new KeyInfo(170, Type.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i])); + keys.Add(new KeyInfo(180, Type.CommonSeed, "package2_key_source", (set, _) => set.Package2KeySource)); + keys.Add(new KeyInfo(190, Type.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i])); + + keys.Add(new KeyInfo(200, Type.CommonSeed, "bis_kek_source", (set, _) => set.BisKekSource)); + keys.Add(new KeyInfo(201, Type.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i])); + + keys.Add(new KeyInfo(205, Type.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i])); + + keys.Add(new KeyInfo(210, Type.CommonSeed, "per_console_key_source", (set, _) => set.PerConsoleKeySource)); + keys.Add(new KeyInfo(211, Type.CommonSeed, "retail_specific_aes_key_source", (set, _) => set.RetailSpecificAesKeySource)); + keys.Add(new KeyInfo(212, Type.CommonSeed, "aes_kek_generation_source", (set, _) => set.AesKekGenerationSource)); + keys.Add(new KeyInfo(213, Type.CommonSeed, "aes_key_generation_source", (set, _) => set.AesKeyGenerationSource)); + keys.Add(new KeyInfo(214, Type.CommonSeed, "titlekek_source", (set, _) => set.TitleKekSource)); + + keys.Add(new KeyInfo(220, Type.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i])); + + keys.Add(new KeyInfo(230, Type.CommonSeed, "header_kek_source", (set, _) => set.HeaderKekSource)); + keys.Add(new KeyInfo(231, Type.CommonSeed, "header_key_source", (set, _) => set.HeaderKeySource)); + keys.Add(new KeyInfo(232, Type.CommonDrvd, "header_key", (set, _) => set.HeaderKey)); + + keys.Add(new KeyInfo(240, Type.CommonSeed, "key_area_key_application_source", (set, _) => set.KeyAreaKeyApplicationSource)); + keys.Add(new KeyInfo(241, Type.CommonSeed, "key_area_key_ocean_source", (set, _) => set.KeyAreaKeyOceanSource)); + keys.Add(new KeyInfo(242, Type.CommonSeed, "key_area_key_system_source", (set, _) => set.KeyAreaKeySystemSource)); + + keys.Add(new KeyInfo(250, Type.CommonSeed, "save_mac_kek_source", (set, _) => set.DeviceUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(251, Type.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i])); + keys.Add(new KeyInfo(252, Type.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i])); + keys.Add(new KeyInfo(-01, Type.CommonSeed, "save_mac_key_source", (set, _) => set.DeviceUniqueSaveMacKeySources[0])); + + keys.Add(new KeyInfo(253, Type.CommonSeed, "save_mac_sd_card_kek_source", (set, _) => set.SeedUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(254, Type.CommonSeed, "save_mac_sd_card_key_source", (set, _) => set.SeedUniqueSaveMacKeySource)); + keys.Add(new KeyInfo(255, Type.DeviceDrvd, "save_mac_sd_card_key", (set, _) => set.SeedUniqueSaveMacKey)); + + keys.Add(new KeyInfo(260, Type.DeviceRoot, "sd_seed", (set, _) => set.SdCardEncryptionSeed)); + + keys.Add(new KeyInfo(261, Type.CommonSeed, "sd_card_kek_source", (set, _) => set.SdCardKekSource)); + keys.Add(new KeyInfo(262, Type.CommonSeed, "sd_card_save_key_source", (set, _) => set.SdCardKeySources[0])); + keys.Add(new KeyInfo(263, Type.CommonSeed, "sd_card_nca_key_source", (set, _) => set.SdCardKeySources[1])); + keys.Add(new KeyInfo(264, Type.CommonSeed, "sd_card_custom_storage_key_source", (set, _) => set.SdCardKeySources[2])); + + keys.Add(new KeyInfo(270, Type.CommonSeedDiff, "xci_header_key", (set, _) => set.XciHeaderKey)); + + keys.Add(new KeyInfo(280, Type.CommonRoot, "eticket_rsa_kek", (set, _) => set.ETicketRsaKek)); + keys.Add(new KeyInfo(281, Type.CommonRoot, "ssl_rsa_kek", (set, _) => set.SslRsaKek)); + + keys.Add(new KeyInfo(290, Type.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); + keys.Add(new KeyInfo(300, Type.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1])); + keys.Add(new KeyInfo(310, Type.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2])); + + return keys; } } diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 89acb583..cf641250 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -8,581 +8,580 @@ using LibHac.Fs; using LibHac.Spl; using LibHac.Util; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +public static class ExternalKeyReader { - public static class ExternalKeyReader + private const int ReadBufferSize = 1024; + + // Contains info from a specific key being read from a file + [DebuggerDisplay("{" + nameof(Name) + "}")] + private struct SpecificKeyInfo { - private const int ReadBufferSize = 1024; + public KeyInfo Key; + public int Index; + public bool IsDev; - // Contains info from a specific key being read from a file - [DebuggerDisplay("{" + nameof(Name) + "}")] - private struct SpecificKeyInfo + public string Name => Key.Name; + + public SpecificKeyInfo(KeyInfo info, int index, bool isDev) { - public KeyInfo Key; - public int Index; - public bool IsDev; + Key = info; + Index = index; + IsDev = isDev; + } + } - public string Name => Key.Name; + private const int TitleKeySize = 0x10; - public SpecificKeyInfo(KeyInfo info, int index, bool isDev) - { - Key = info; - Index = index; - IsDev = isDev; - } + /// + /// Loads keys from key files into an existing . Missing keys will be + /// derived from existing keys if possible. Any file names will be skipped. + /// + /// The where the loaded keys will be placed. + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, + string consoleKeysFilename = null, IProgressReport logger = null) + { + List keyInfos = DefaultKeySet.CreateKeyList(); + + if (filename != null) + { + using var storage = new FileStream(filename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } - private const int TitleKeySize = 0x10; - - /// - /// Loads keys from key files into an existing . Missing keys will be - /// derived from existing keys if possible. Any file names will be skipped. - /// - /// The where the loaded keys will be placed. - /// The path of the file containing common keys. Can be . - /// The path of the file containing title keys. Can be . - /// The path of the file containing device-unique keys. Can be . - /// An optional logger that key-parsing errors will be written to. - public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, - string consoleKeysFilename = null, IProgressReport logger = null) + if (consoleKeysFilename != null) { - List keyInfos = DefaultKeySet.CreateKeyList(); - - if (filename != null) - { - using var storage = new FileStream(filename, FileMode.Open, FileAccess.Read); - ReadMainKeys(keySet, storage, keyInfos, logger); - } - - if (consoleKeysFilename != null) - { - using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); - ReadMainKeys(keySet, storage, keyInfos, logger); - } - - if (titleKeysFilename != null) - { - using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); - ReadTitleKeys(keySet, storage, logger); - } - - keySet.DeriveKeys(logger); + using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } - /// - /// Loads keys from key files into an existing . Missing keys will be - /// derived from existing keys if possible. Any file names will be skipped. - /// - /// The where the loaded keys will be placed. - /// The path of the file containing common prod keys. Can be . - /// The path of the file containing common dev keys. Can be . - /// The path of the file containing title keys. Can be . - /// The path of the file containing device-unique keys. Can be . - /// An optional logger that key-parsing errors will be written to. - public static void ReadKeyFile(KeySet keySet, string prodKeysFilename = null, string devKeysFilename = null, - string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) + if (titleKeysFilename != null) { - KeySet.Mode originalMode = keySet.CurrentMode; - List keyInfos = DefaultKeySet.CreateKeyList(); - - if (prodKeysFilename != null) - { - keySet.SetMode(KeySet.Mode.Prod); - using var storage = new FileStream(prodKeysFilename, FileMode.Open, FileAccess.Read); - ReadMainKeys(keySet, storage, keyInfos, logger); - } - - if (devKeysFilename != null) - { - keySet.SetMode(KeySet.Mode.Dev); - using var storage = new FileStream(devKeysFilename, FileMode.Open, FileAccess.Read); - ReadMainKeys(keySet, storage, keyInfos, logger); - } - - keySet.SetMode(originalMode); - - if (consoleKeysFilename != null) - { - using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); - ReadMainKeys(keySet, storage, keyInfos, logger); - } - - if (titleKeysFilename != null) - { - using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); - ReadTitleKeys(keySet, storage, logger); - } - - keySet.DeriveKeys(logger); + using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); + ReadTitleKeys(keySet, storage, logger); } - /// - /// Creates a new initialized with the key files specified and any keys included in the library. - /// Missing keys will be derived from existing keys if possible. Any file names will be skipped. - /// - /// The path of the file containing common keys. Can be . - /// The path of the file containing title keys. Can be . - /// The path of the file containing device-unique keys. Can be . - /// An optional logger that key-parsing errors will be written to. - /// Specifies whether the keys being read are dev or prod keys. - /// The created . - public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, - string consoleKeysFilename = null, IProgressReport logger = null, KeySet.Mode mode = KeySet.Mode.Prod) + keySet.DeriveKeys(logger); + } + + /// + /// Loads keys from key files into an existing . Missing keys will be + /// derived from existing keys if possible. Any file names will be skipped. + /// + /// The where the loaded keys will be placed. + /// The path of the file containing common prod keys. Can be . + /// The path of the file containing common dev keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + public static void ReadKeyFile(KeySet keySet, string prodKeysFilename = null, string devKeysFilename = null, + string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) + { + KeySet.Mode originalMode = keySet.CurrentMode; + List keyInfos = DefaultKeySet.CreateKeyList(); + + if (prodKeysFilename != null) { - var keySet = KeySet.CreateDefaultKeySet(); - keySet.SetMode(mode); - - ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); - - return keySet; + keySet.SetMode(KeySet.Mode.Prod); + using var storage = new FileStream(prodKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } - /// - /// Loads non-title keys from a into an existing . - /// Missing keys will not be derived. - /// - /// The where the loaded keys will be placed. - /// A containing the keys to load. - /// A list of all the keys that will be loaded into the key set. - /// will create a list containing all loadable keys. - /// An optional logger that key-parsing errors will be written to. - public static void ReadMainKeys(KeySet keySet, Stream reader, List keyList, - IProgressReport logger = null) + if (devKeysFilename != null) { - if (reader == null) return; - - using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[ReadBufferSize]; - var ctx = new KvPairReaderContext(streamReader, buffer); - - while (true) - { - ReaderStatus status = GetKeyValuePair(ref ctx); - - if (status == ReaderStatus.Error) - { - logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); - } - else if (status == ReaderStatus.ReadKey) - { - if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, ctx.CurrentKey)) - { - logger?.LogMessage($"Failed to match key {ctx.CurrentKey.ToString()}"); - continue; - } - - Span key; - - // Get the dev key in the key set if needed. - if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) - { - keySet.SetMode(KeySet.Mode.Dev); - key = info.Key.Getter(keySet, info.Index); - keySet.SetMode(KeySet.Mode.Prod); - } - else - { - key = info.Key.Getter(keySet, info.Index); - } - - if (ctx.CurrentValue.Length != key.Length * 2) - { - logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentValue.Length}. Must be {key.Length * 2} hex digits."); - continue; - } - - if (!StringUtils.TryFromHexString(ctx.CurrentValue, key)) - { - key.Clear(); - - logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has an invalid value. Must be {key.Length * 2} hex digits."); - } - } - else if (status == ReaderStatus.Finished) - { - break; - } - } + keySet.SetMode(KeySet.Mode.Dev); + using var storage = new FileStream(devKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } - /// - /// Loads title keys from a into an existing . - /// - /// The where the loaded keys will be placed. - /// A containing the keys to load. - /// An optional logger that key-parsing errors will be written to. - public static void ReadTitleKeys(KeySet keySet, Stream reader, IProgressReport logger = null) + keySet.SetMode(originalMode); + + if (consoleKeysFilename != null) { - if (reader == null) return; - - using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[ReadBufferSize]; - var ctx = new KvPairReaderContext(streamReader, buffer); - - // Estimate the number of keys by assuming each line is about 69 bytes. - // Subtract 2 from that so we estimate slightly high. - keySet.ExternalKeySet.EnsureCapacity((int)reader.Length / 67); - - while (true) - { - ReaderStatus status = GetKeyValuePair(ref ctx); - - if (status == ReaderStatus.Error) - { - logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); - Debugger.Break(); - } - else if (status == ReaderStatus.ReadKey) - { - if (ctx.CurrentKey.Length != TitleKeySize * 2) - { - logger?.LogMessage($"Rights ID {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentKey.Length}. (Expected {TitleKeySize * 2})"); - continue; - } - - if (ctx.CurrentValue.Length != TitleKeySize * 2) - { - logger?.LogMessage($"Title key {ctx.CurrentValue.ToString()} has incorrect size {ctx.CurrentValue.Length}. (Expected {TitleKeySize * 2})"); - continue; - } - - var rightsId = new RightsId(); - var titleKey = new AccessKey(); - - if (!StringUtils.TryFromHexString(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId))) - { - logger?.LogMessage($"Invalid rights ID \"{ctx.CurrentKey.ToString()}\" in title key file"); - continue; - } - - if (!StringUtils.TryFromHexString(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey))) - { - logger?.LogMessage($"Invalid title key \"{ctx.CurrentValue.ToString()}\" in title key file"); - continue; - } - - keySet.ExternalKeySet.Add(rightsId, titleKey).ThrowIfFailure(); - } - else if (status == ReaderStatus.Finished) - { - break; - } - } + using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } - private ref struct KvPairReaderContext + if (titleKeysFilename != null) { - public TextReader Reader; - public Span Buffer; - public Span CurrentKey; - public Span CurrentValue; - public int BufferPos; - public bool NeedFillBuffer; - public bool HasReadEndOfFile; - public bool SkipNextLine; - - public KvPairReaderContext(TextReader reader, Span buffer) - { - Reader = reader; - Buffer = buffer; - CurrentKey = default; - CurrentValue = default; - BufferPos = buffer.Length; - NeedFillBuffer = true; - HasReadEndOfFile = false; - SkipNextLine = false; - } + using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); + ReadTitleKeys(keySet, storage, logger); } - private enum ReaderStatus + keySet.DeriveKeys(logger); + } + + /// + /// Creates a new initialized with the key files specified and any keys included in the library. + /// Missing keys will be derived from existing keys if possible. Any file names will be skipped. + /// + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + /// Specifies whether the keys being read are dev or prod keys. + /// The created . + public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, + string consoleKeysFilename = null, IProgressReport logger = null, KeySet.Mode mode = KeySet.Mode.Prod) + { + var keySet = KeySet.CreateDefaultKeySet(); + keySet.SetMode(mode); + + ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); + + return keySet; + } + + /// + /// Loads non-title keys from a into an existing . + /// Missing keys will not be derived. + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// A list of all the keys that will be loaded into the key set. + /// will create a list containing all loadable keys. + /// An optional logger that key-parsing errors will be written to. + public static void ReadMainKeys(KeySet keySet, Stream reader, List keyList, + IProgressReport logger = null) + { + if (reader == null) return; + + using var streamReader = new StreamReader(reader); + Span buffer = stackalloc char[ReadBufferSize]; + var ctx = new KvPairReaderContext(streamReader, buffer); + + while (true) { - ReadKey, - NoKeyRead, - ReadComment, - Finished, - LineTooLong, - Error + ReaderStatus status = GetKeyValuePair(ref ctx); + + if (status == ReaderStatus.Error) + { + logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); + } + else if (status == ReaderStatus.ReadKey) + { + if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, ctx.CurrentKey)) + { + logger?.LogMessage($"Failed to match key {ctx.CurrentKey.ToString()}"); + continue; + } + + Span key; + + // Get the dev key in the key set if needed. + if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + key = info.Key.Getter(keySet, info.Index); + keySet.SetMode(KeySet.Mode.Prod); + } + else + { + key = info.Key.Getter(keySet, info.Index); + } + + if (ctx.CurrentValue.Length != key.Length * 2) + { + logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentValue.Length}. Must be {key.Length * 2} hex digits."); + continue; + } + + if (!StringUtils.TryFromHexString(ctx.CurrentValue, key)) + { + key.Clear(); + + logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has an invalid value. Must be {key.Length * 2} hex digits."); + } + } + else if (status == ReaderStatus.Finished) + { + break; + } + } + } + + /// + /// Loads title keys from a into an existing . + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// An optional logger that key-parsing errors will be written to. + public static void ReadTitleKeys(KeySet keySet, Stream reader, IProgressReport logger = null) + { + if (reader == null) return; + + using var streamReader = new StreamReader(reader); + Span buffer = stackalloc char[ReadBufferSize]; + var ctx = new KvPairReaderContext(streamReader, buffer); + + // Estimate the number of keys by assuming each line is about 69 bytes. + // Subtract 2 from that so we estimate slightly high. + keySet.ExternalKeySet.EnsureCapacity((int)reader.Length / 67); + + while (true) + { + ReaderStatus status = GetKeyValuePair(ref ctx); + + if (status == ReaderStatus.Error) + { + logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); + Debugger.Break(); + } + else if (status == ReaderStatus.ReadKey) + { + if (ctx.CurrentKey.Length != TitleKeySize * 2) + { + logger?.LogMessage($"Rights ID {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentKey.Length}. (Expected {TitleKeySize * 2})"); + continue; + } + + if (ctx.CurrentValue.Length != TitleKeySize * 2) + { + logger?.LogMessage($"Title key {ctx.CurrentValue.ToString()} has incorrect size {ctx.CurrentValue.Length}. (Expected {TitleKeySize * 2})"); + continue; + } + + var rightsId = new RightsId(); + var titleKey = new AccessKey(); + + if (!StringUtils.TryFromHexString(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId))) + { + logger?.LogMessage($"Invalid rights ID \"{ctx.CurrentKey.ToString()}\" in title key file"); + continue; + } + + if (!StringUtils.TryFromHexString(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey))) + { + logger?.LogMessage($"Invalid title key \"{ctx.CurrentValue.ToString()}\" in title key file"); + continue; + } + + keySet.ExternalKeySet.Add(rightsId, titleKey).ThrowIfFailure(); + } + else if (status == ReaderStatus.Finished) + { + break; + } + } + } + + private ref struct KvPairReaderContext + { + public TextReader Reader; + public Span Buffer; + public Span CurrentKey; + public Span CurrentValue; + public int BufferPos; + public bool NeedFillBuffer; + public bool HasReadEndOfFile; + public bool SkipNextLine; + + public KvPairReaderContext(TextReader reader, Span buffer) + { + Reader = reader; + Buffer = buffer; + CurrentKey = default; + CurrentValue = default; + BufferPos = buffer.Length; + NeedFillBuffer = true; + HasReadEndOfFile = false; + SkipNextLine = false; + } + } + + private enum ReaderStatus + { + ReadKey, + NoKeyRead, + ReadComment, + Finished, + LineTooLong, + Error + } + + private enum ReaderState + { + Initial, + Comment, + Key, + WhiteSpace1, + Delimiter, + Value, + WhiteSpace2, + Success, + CommentSuccess, + Error + } + + private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) + { + Span buffer = reader.Buffer; + + if (reader.BufferPos == buffer.Length && reader.HasReadEndOfFile) + { + // There is no more text to parse. Return that we've finished. + return ReaderStatus.Finished; } - private enum ReaderState + if (reader.NeedFillBuffer) { - Initial, - Comment, - Key, - WhiteSpace1, - Delimiter, - Value, - WhiteSpace2, - Success, - CommentSuccess, - Error + // Move unread text to the front of the buffer + buffer.Slice(reader.BufferPos).CopyTo(buffer); + + int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos)); + + // ReadBlock will only read less than the buffer size if there's nothing left to read + if (charsRead != reader.BufferPos) + { + buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); + reader.Buffer = buffer; + reader.HasReadEndOfFile = true; + } + + reader.NeedFillBuffer = false; + reader.BufferPos = 0; } - private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) + if (reader.SkipNextLine) { - Span buffer = reader.Buffer; - - if (reader.BufferPos == buffer.Length && reader.HasReadEndOfFile) - { - // There is no more text to parse. Return that we've finished. - return ReaderStatus.Finished; - } - - if (reader.NeedFillBuffer) - { - // Move unread text to the front of the buffer - buffer.Slice(reader.BufferPos).CopyTo(buffer); - - int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos)); - - // ReadBlock will only read less than the buffer size if there's nothing left to read - if (charsRead != reader.BufferPos) - { - buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); - reader.Buffer = buffer; - reader.HasReadEndOfFile = true; - } - - reader.NeedFillBuffer = false; - reader.BufferPos = 0; - } - - if (reader.SkipNextLine) - { - while (reader.BufferPos < buffer.Length && !IsEndOfLine(buffer[reader.BufferPos])) - { - reader.BufferPos++; - } - - // Stop skipping once we reach a new line - if (reader.BufferPos < buffer.Length) - { - reader.SkipNextLine = false; - } - } - - // Skip any empty lines - while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos])) + while (reader.BufferPos < buffer.Length && !IsEndOfLine(buffer[reader.BufferPos])) { reader.BufferPos++; } - var state = ReaderState.Initial; - int keyOffset = -1; - int keyLength = -1; - int valueOffset = -1; - int valueLength = -1; - int i; - - for (i = reader.BufferPos; i < buffer.Length; i++) + // Stop skipping once we reach a new line + if (reader.BufferPos < buffer.Length) { - char c = buffer[i]; - - switch (state) - { - case ReaderState.Initial when IsWhiteSpace(c): - continue; - case ReaderState.Initial when IsValidNameChar(c): - state = ReaderState.Key; - keyOffset = i; - - // Skip the next few rounds through the state machine since we know we should be - // encountering a string of name characters - do - { - ToLower(ref buffer[i]); - i++; - } while (i < buffer.Length && IsValidNameChar(buffer[i])); - - // Decrement so we can process this character the next round through the state machine - i--; - continue; - case ReaderState.Initial when c == '#': - state = ReaderState.Comment; - keyOffset = i; - continue; - case ReaderState.Initial when IsEndOfLine(c): - // The line was empty. Update the buffer position to indicate a new line - reader.BufferPos = i; - continue; - case ReaderState.Key when IsWhiteSpace(c): - state = ReaderState.WhiteSpace1; - keyLength = i - keyOffset; - continue; - case ReaderState.Key when IsDelimiter(c): - state = ReaderState.Delimiter; - keyLength = i - keyOffset; - continue; - case ReaderState.WhiteSpace1 when IsWhiteSpace(c): - continue; - case ReaderState.WhiteSpace1 when IsDelimiter(c): - state = ReaderState.Delimiter; - continue; - case ReaderState.Delimiter when IsWhiteSpace(c): - continue; - case ReaderState.Delimiter when StringUtils.IsHexDigit((byte)c): - state = ReaderState.Value; - valueOffset = i; - - do - { - i++; - } while (i < buffer.Length && !IsEndOfLine(buffer[i]) && !IsWhiteSpace(buffer[i])); - - i--; - continue; - case ReaderState.Value when IsEndOfLine(c): - state = ReaderState.Success; - valueLength = i - valueOffset; - break; - case ReaderState.Value when IsWhiteSpace(c): - state = ReaderState.WhiteSpace2; - valueLength = i - valueOffset; - continue; - case ReaderState.WhiteSpace2 when IsWhiteSpace(c): - continue; - case ReaderState.WhiteSpace2 when IsEndOfLine(c): - state = ReaderState.Success; - break; - case ReaderState.Comment when IsEndOfLine(c): - keyLength = i - keyOffset; - state = ReaderState.CommentSuccess; - break; - case ReaderState.Comment: - continue; - - // If none of the expected characters were found while in these states, the - // line is considered invalid. - case ReaderState.Initial: - case ReaderState.Key: - case ReaderState.WhiteSpace1: - case ReaderState.Delimiter: - state = ReaderState.Error; - continue; - case ReaderState.Error when !IsEndOfLine(c): - continue; - } - - // We've exited the state machine for one reason or another - break; + reader.SkipNextLine = false; } + } - // First check if hit the end of the buffer or read the entire buffer without seeing a new line - if (i == buffer.Length && !reader.HasReadEndOfFile) + // Skip any empty lines + while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos])) + { + reader.BufferPos++; + } + + var state = ReaderState.Initial; + int keyOffset = -1; + int keyLength = -1; + int valueOffset = -1; + int valueLength = -1; + int i; + + for (i = reader.BufferPos; i < buffer.Length; i++) + { + char c = buffer[i]; + + switch (state) { - reader.NeedFillBuffer = true; + case ReaderState.Initial when IsWhiteSpace(c): + continue; + case ReaderState.Initial when IsValidNameChar(c): + state = ReaderState.Key; + keyOffset = i; - // If the entire buffer is part of a single long line - if (reader.BufferPos == 0 || reader.SkipNextLine) - { + // Skip the next few rounds through the state machine since we know we should be + // encountering a string of name characters + do + { + ToLower(ref buffer[i]); + i++; + } while (i < buffer.Length && IsValidNameChar(buffer[i])); + + // Decrement so we can process this character the next round through the state machine + i--; + continue; + case ReaderState.Initial when c == '#': + state = ReaderState.Comment; + keyOffset = i; + continue; + case ReaderState.Initial when IsEndOfLine(c): + // The line was empty. Update the buffer position to indicate a new line reader.BufferPos = i; + continue; + case ReaderState.Key when IsWhiteSpace(c): + state = ReaderState.WhiteSpace1; + keyLength = i - keyOffset; + continue; + case ReaderState.Key when IsDelimiter(c): + state = ReaderState.Delimiter; + keyLength = i - keyOffset; + continue; + case ReaderState.WhiteSpace1 when IsWhiteSpace(c): + continue; + case ReaderState.WhiteSpace1 when IsDelimiter(c): + state = ReaderState.Delimiter; + continue; + case ReaderState.Delimiter when IsWhiteSpace(c): + continue; + case ReaderState.Delimiter when StringUtils.IsHexDigit((byte)c): + state = ReaderState.Value; + valueOffset = i; - // The line might continue past the end of the current buffer, so skip the - // remainder of the line after the buffer is refilled. - reader.SkipNextLine = true; - return ReaderStatus.LineTooLong; - } + do + { + i++; + } while (i < buffer.Length && !IsEndOfLine(buffer[i]) && !IsWhiteSpace(buffer[i])); - return ReaderStatus.NoKeyRead; - } - - // The only way we should exit the loop in the "Value" or "WhiteSpace2" state is if we reached - // the end of the buffer in that state, meaning i == buffer.Length. - // Running out of buffer when we haven't read the end of the file will have been handled by the - // previous "if" block. If we get to this point in those states, we should be at the very end - // of the file which will be treated as the end of a line. - if (state == ReaderState.Value || state == ReaderState.WhiteSpace2) - { - Assert.SdkEqual(i, buffer.Length); - Assert.SdkAssert(reader.HasReadEndOfFile); - - // WhiteSpace2 will have already set this value - if (state == ReaderState.Value) + i--; + continue; + case ReaderState.Value when IsEndOfLine(c): + state = ReaderState.Success; valueLength = i - valueOffset; + break; + case ReaderState.Value when IsWhiteSpace(c): + state = ReaderState.WhiteSpace2; + valueLength = i - valueOffset; + continue; + case ReaderState.WhiteSpace2 when IsWhiteSpace(c): + continue; + case ReaderState.WhiteSpace2 when IsEndOfLine(c): + state = ReaderState.Success; + break; + case ReaderState.Comment when IsEndOfLine(c): + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + break; + case ReaderState.Comment: + continue; - state = ReaderState.Success; + // If none of the expected characters were found while in these states, the + // line is considered invalid. + case ReaderState.Initial: + case ReaderState.Key: + case ReaderState.WhiteSpace1: + case ReaderState.Delimiter: + state = ReaderState.Error; + continue; + case ReaderState.Error when !IsEndOfLine(c): + continue; } - // Same situation as the two above states - if (state == ReaderState.Comment) + // We've exited the state machine for one reason or another + break; + } + + // First check if hit the end of the buffer or read the entire buffer without seeing a new line + if (i == buffer.Length && !reader.HasReadEndOfFile) + { + reader.NeedFillBuffer = true; + + // If the entire buffer is part of a single long line + if (reader.BufferPos == 0 || reader.SkipNextLine) { - Assert.SdkEqual(i, buffer.Length); - Assert.SdkAssert(reader.HasReadEndOfFile); - - keyLength = i - keyOffset; - state = ReaderState.CommentSuccess; - } - - // Same as the above states except the final line was empty or whitespace. - if (state == ReaderState.Initial) - { - Assert.SdkEqual(i, buffer.Length); - Assert.SdkAssert(reader.HasReadEndOfFile); - - reader.BufferPos = i; - return ReaderStatus.NoKeyRead; - } - - // If we successfully read both the key and value - if (state == ReaderState.Success) - { - reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); - reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength); reader.BufferPos = i; - return ReaderStatus.ReadKey; + // The line might continue past the end of the current buffer, so skip the + // remainder of the line after the buffer is refilled. + reader.SkipNextLine = true; + return ReaderStatus.LineTooLong; } - if (state == ReaderState.CommentSuccess) - { - reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); - reader.BufferPos = i; + return ReaderStatus.NoKeyRead; + } - return ReaderStatus.ReadComment; - } + // The only way we should exit the loop in the "Value" or "WhiteSpace2" state is if we reached + // the end of the buffer in that state, meaning i == buffer.Length. + // Running out of buffer when we haven't read the end of the file will have been handled by the + // previous "if" block. If we get to this point in those states, we should be at the very end + // of the file which will be treated as the end of a line. + if (state == ReaderState.Value || state == ReaderState.WhiteSpace2) + { + Assert.SdkEqual(i, buffer.Length); + Assert.SdkAssert(reader.HasReadEndOfFile); - // A bad line was encountered if we're in any of the other states - // Return the line as "CurrentKey" - reader.CurrentKey = reader.Buffer.Slice(reader.BufferPos, i - reader.BufferPos); + // WhiteSpace2 will have already set this value + if (state == ReaderState.Value) + valueLength = i - valueOffset; + + state = ReaderState.Success; + } + + // Same situation as the two above states + if (state == ReaderState.Comment) + { + Assert.SdkEqual(i, buffer.Length); + Assert.SdkAssert(reader.HasReadEndOfFile); + + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + } + + // Same as the above states except the final line was empty or whitespace. + if (state == ReaderState.Initial) + { + Assert.SdkEqual(i, buffer.Length); + Assert.SdkAssert(reader.HasReadEndOfFile); + + reader.BufferPos = i; + return ReaderStatus.NoKeyRead; + } + + // If we successfully read both the key and value + if (state == ReaderState.Success) + { + reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); + reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength); reader.BufferPos = i; - return ReaderStatus.Error; - - static bool IsWhiteSpace(char c) => c == ' ' || c == '\t'; - static bool IsDelimiter(char c) => c == '=' || c == ','; - static bool IsEndOfLine(char c) => c == '\0' || c == '\r' || c == '\n'; - - static void ToLower(ref char c) - { - // The only characters we need to worry about are underscores and alphanumerics - // Both lowercase and numbers have bit 5 set, so they're both treated the same - if (c != '_') - { - c |= (char)0b100000; - } - } + return ReaderStatus.ReadKey; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsValidNameChar(char c) + if (state == ReaderState.CommentSuccess) { - return (c | 0x20u) - 'a' <= 'z' - 'a' || (uint)(c - '0') <= 9 || c == '_'; + reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); + reader.BufferPos = i; + + return ReaderStatus.ReadComment; } - private static bool TryGetKeyInfo(out SpecificKeyInfo info, List keyList, ReadOnlySpan keyName) + // A bad line was encountered if we're in any of the other states + // Return the line as "CurrentKey" + reader.CurrentKey = reader.Buffer.Slice(reader.BufferPos, i - reader.BufferPos); + reader.BufferPos = i; + + return ReaderStatus.Error; + + static bool IsWhiteSpace(char c) => c == ' ' || c == '\t'; + static bool IsDelimiter(char c) => c == '=' || c == ','; + static bool IsEndOfLine(char c) => c == '\0' || c == '\r' || c == '\n'; + + static void ToLower(ref char c) { - UnsafeHelpers.SkipParamInit(out info); - - for (int i = 0; i < keyList.Count; i++) + // The only characters we need to worry about are underscores and alphanumerics + // Both lowercase and numbers have bit 5 set, so they're both treated the same + if (c != '_') { - if (keyList[i].Matches(keyName, out int keyIndex, out bool isDev)) - { - info = new SpecificKeyInfo(keyList[i], keyIndex, isDev); - return true; - } + c |= (char)0b100000; } - - return false; } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidNameChar(char c) + { + return (c | 0x20u) - 'a' <= 'z' - 'a' || (uint)(c - '0') <= 9 || c == '_'; + } + + private static bool TryGetKeyInfo(out SpecificKeyInfo info, List keyList, ReadOnlySpan keyName) + { + UnsafeHelpers.SkipParamInit(out info); + + for (int i = 0; i < keyList.Count; i++) + { + if (keyList[i].Matches(keyName, out int keyIndex, out bool isDev)) + { + info = new SpecificKeyInfo(keyList[i], keyIndex, isDev); + return true; + } + } + + return false; + } } diff --git a/src/LibHac/Common/Keys/ExternalKeyWriter.cs b/src/LibHac/Common/Keys/ExternalKeyWriter.cs index 0351846c..5ffa5b48 100644 --- a/src/LibHac/Common/Keys/ExternalKeyWriter.cs +++ b/src/LibHac/Common/Keys/ExternalKeyWriter.cs @@ -9,165 +9,164 @@ using LibHac.Util; using Type = LibHac.Common.Keys.KeyInfo.KeyType; using RangeType = LibHac.Common.Keys.KeyInfo.KeyRangeType; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +public static class ExternalKeyWriter { - public static class ExternalKeyWriter + public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, Type filter, bool isDev) { - public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, Type filter, bool isDev) + if (keys.Count == 0) return; + + string devSuffix = isDev ? "_dev" : string.Empty; + int maxNameLength = keys.Max(x => x.NameLength); + int currentGroup = 0; + + // Todo: Better filtering + bool FilterMatches(KeyInfo keyInfo) { - if (keys.Count == 0) return; + Type filter1 = filter & (Type.Common | Type.Device); + Type filter2 = filter & (Type.Root | Type.Seed | Type.Derived); + Type filter3 = filter & Type.DifferentDev; - string devSuffix = isDev ? "_dev" : string.Empty; - int maxNameLength = keys.Max(x => x.NameLength); - int currentGroup = 0; + // Skip sub-filters that have no flags set + return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && + (filter2 == 0 || (filter2 & keyInfo.Type) != 0) && + filter3 == (filter3 & keyInfo.Type) || + isDev && keyInfo.Type.HasFlag(Type.DifferentDev); + } - // Todo: Better filtering - bool FilterMatches(KeyInfo keyInfo) + bool isFirstPrint = true; + + foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches) + .OrderBy(x => x.Group).ThenBy(x => x.Name)) + { + bool isNewGroup = false; + + if (info.Group == currentGroup + 1) { - Type filter1 = filter & (Type.Common | Type.Device); - Type filter2 = filter & (Type.Root | Type.Seed | Type.Derived); - Type filter3 = filter & Type.DifferentDev; - - // Skip sub-filters that have no flags set - return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && - (filter2 == 0 || (filter2 & keyInfo.Type) != 0) && - filter3 == (filter3 & keyInfo.Type) || - isDev && keyInfo.Type.HasFlag(Type.DifferentDev); + currentGroup = info.Group; + } + else if (info.Group > currentGroup) + { + // Don't update the current group yet because if this key is empty and the next key + // is in the same group, we need to be able to know to add a blank line before printing it. + isNewGroup = !isFirstPrint; } - bool isFirstPrint = true; - - foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches) - .OrderBy(x => x.Group).ThenBy(x => x.Name)) + if (info.RangeType == RangeType.Single) { - bool isNewGroup = false; + Span key = info.Getter(keySet, 0); + if (key.IsZeros()) + continue; - if (info.Group == currentGroup + 1) + if (isNewGroup) { - currentGroup = info.Group; - } - else if (info.Group > currentGroup) - { - // Don't update the current group yet because if this key is empty and the next key - // is in the same group, we need to be able to know to add a blank line before printing it. - isNewGroup = !isFirstPrint; + sb.AppendLine(); } - if (info.RangeType == RangeType.Single) + string keyName = $"{info.Name}{devSuffix}"; + + string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; + sb.AppendLine(line); + isFirstPrint = false; + currentGroup = info.Group; + } + else if (info.RangeType == RangeType.Range) + { + bool hasPrintedKey = false; + + for (int i = info.RangeStart; i < info.RangeEnd; i++) { - Span key = info.Getter(keySet, 0); + Span key = info.Getter(keySet, i); if (key.IsZeros()) continue; - if (isNewGroup) + if (hasPrintedKey == false) { - sb.AppendLine(); + if (isNewGroup) + { + sb.AppendLine(); + } + + hasPrintedKey = true; } - string keyName = $"{info.Name}{devSuffix}"; + string keyName = $"{info.Name}{devSuffix}_{i:x2}"; string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; sb.AppendLine(line); isFirstPrint = false; currentGroup = info.Group; } - else if (info.RangeType == RangeType.Range) - { - bool hasPrintedKey = false; - - for (int i = info.RangeStart; i < info.RangeEnd; i++) - { - Span key = info.Getter(keySet, i); - if (key.IsZeros()) - continue; - - if (hasPrintedKey == false) - { - if (isNewGroup) - { - sb.AppendLine(); - } - - hasPrintedKey = true; - } - - string keyName = $"{info.Name}{devSuffix}_{i:x2}"; - - string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; - sb.AppendLine(line); - isFirstPrint = false; - currentGroup = info.Group; - } - } } } - - public static string PrintTitleKeys(KeySet keySet) - { - var sb = new StringBuilder(); - - foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList() - .OrderBy(x => x.rightsId.ToString())) - { - string line = $"{kv.rightsId} = {kv.key}"; - sb.AppendLine(line); - } - - return sb.ToString(); - } - - public static string PrintCommonKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, - false); - return sb.ToString(); - } - - public static string PrintDeviceKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Device, false); - return sb.ToString(); - } - - public static string PrintAllKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), 0, false); - return sb.ToString(); - } - - public static string PrintAllSeeds(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed, false); - - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - sb.AppendLine(); - keySet.SetMode(KeySet.Mode.Dev); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed | Type.DifferentDev, true); - keySet.SetMode(KeySet.Mode.Prod); - } - return sb.ToString(); - } - - public static string PrintCommonKeysWithDev(KeySet keySet) - { - KeySet.Mode originalMode = keySet.CurrentMode; - var sb = new StringBuilder(); - - keySet.SetMode(KeySet.Mode.Prod); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, - false); - - sb.AppendLine(); - keySet.SetMode(KeySet.Mode.Dev); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true); - - keySet.SetMode(originalMode); - return sb.ToString(); - } + } + + public static string PrintTitleKeys(KeySet keySet) + { + var sb = new StringBuilder(); + + foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList() + .OrderBy(x => x.rightsId.ToString())) + { + string line = $"{kv.rightsId} = {kv.key}"; + sb.AppendLine(line); + } + + return sb.ToString(); + } + + public static string PrintCommonKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, + false); + return sb.ToString(); + } + + public static string PrintDeviceKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Device, false); + return sb.ToString(); + } + + public static string PrintAllKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), 0, false); + return sb.ToString(); + } + + public static string PrintAllSeeds(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed, false); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed | Type.DifferentDev, true); + keySet.SetMode(KeySet.Mode.Prod); + } + return sb.ToString(); + } + + public static string PrintCommonKeysWithDev(KeySet keySet) + { + KeySet.Mode originalMode = keySet.CurrentMode; + var sb = new StringBuilder(); + + keySet.SetMode(KeySet.Mode.Prod); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, + false); + + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true); + + keySet.SetMode(originalMode); + return sb.ToString(); } } diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs index 07c8d02d..8c21892b 100644 --- a/src/LibHac/Common/Keys/KeyDerivation.cs +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -2,397 +2,396 @@ using System.Runtime.InteropServices; using LibHac.Crypto; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +internal static class KeyDerivation { - internal static class KeyDerivation + public static void DeriveAllKeys(KeySet keySet, IProgressReport logger = null) { - public static void DeriveAllKeys(KeySet keySet, IProgressReport logger = null) + DeriveKeyBlobKeys(keySet); + DecryptKeyBlobs(keySet, logger); + ReadKeyBlobs(keySet); + + Derive620Keys(keySet); + Derive620MasterKeks(keySet); + DeriveMarikoMasterKeks(keySet); + DeriveMasterKeys(keySet); + PopulateOldMasterKeys(keySet); + + DerivePerConsoleKeys(keySet); + DerivePerGenerationKeys(keySet); + DeriveNcaHeaderKey(keySet); + DeriveSdCardKeys(keySet); + } + + private static void DeriveKeyBlobKeys(KeySet s) + { + if (s.SecureBootKey.IsZeros() || s.TsecKey.IsZeros()) return; + + bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsZeros(); + var temp = new AesKey(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - DeriveKeyBlobKeys(keySet); - DecryptKeyBlobs(keySet, logger); - ReadKeyBlobs(keySet); + if (s.KeyBlobKeySources[i].IsZeros()) continue; - Derive620Keys(keySet); - Derive620MasterKeks(keySet); - DeriveMarikoMasterKeks(keySet); - DeriveMasterKeys(keySet); - PopulateOldMasterKeys(keySet); + Aes.DecryptEcb128(s.KeyBlobKeySources[i], temp, s.TsecKey); + Aes.DecryptEcb128(temp, s.KeyBlobKeys[i], s.SecureBootKey); - DerivePerConsoleKeys(keySet); - DerivePerGenerationKeys(keySet); - DeriveNcaHeaderKey(keySet); - DeriveSdCardKeys(keySet); + if (!haveKeyBlobMacKeySource) continue; + + Aes.DecryptEcb128(s.KeyBlobMacKeySource, s.KeyBlobMacKeys[i], s.KeyBlobKeys[i]); } + } - private static void DeriveKeyBlobKeys(KeySet s) + private static void DecryptKeyBlobs(KeySet s, IProgressReport logger = null) + { + var cmac = new AesCmac(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - if (s.SecureBootKey.IsZeros() || s.TsecKey.IsZeros()) return; - - bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsZeros(); - var temp = new AesKey(); - - for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + if (s.KeyBlobKeys[i].IsZeros() || s.KeyBlobMacKeys[i].IsZeros() || s.EncryptedKeyBlobs[i].IsZeros()) { - if (s.KeyBlobKeySources[i].IsZeros()) continue; - - Aes.DecryptEcb128(s.KeyBlobKeySources[i], temp, s.TsecKey); - Aes.DecryptEcb128(temp, s.KeyBlobKeys[i], s.SecureBootKey); - - if (!haveKeyBlobMacKeySource) continue; - - Aes.DecryptEcb128(s.KeyBlobMacKeySource, s.KeyBlobMacKeys[i], s.KeyBlobKeys[i]); + continue; } + + Aes.CalculateCmac(cmac, s.EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), s.KeyBlobMacKeys[i]); + + if (!Utilities.SpansEqual(cmac, s.EncryptedKeyBlobs[i].Cmac)) + { + logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); + } + + Aes.DecryptCtr128(s.EncryptedKeyBlobs[i].Bytes.Slice(0x20), s.KeyBlobs[i].Bytes, s.KeyBlobKeys[i], + s.EncryptedKeyBlobs[i].Counter); } + } - private static void DecryptKeyBlobs(KeySet s, IProgressReport logger = null) + private static void ReadKeyBlobs(KeySet s) + { + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - var cmac = new AesCmac(); + if (s.KeyBlobs[i].IsZeros()) continue; - for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) - { - if (s.KeyBlobKeys[i].IsZeros() || s.KeyBlobMacKeys[i].IsZeros() || s.EncryptedKeyBlobs[i].IsZeros()) - { - continue; - } - - Aes.CalculateCmac(cmac, s.EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), s.KeyBlobMacKeys[i]); - - if (!Utilities.SpansEqual(cmac, s.EncryptedKeyBlobs[i].Cmac)) - { - logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); - } - - Aes.DecryptCtr128(s.EncryptedKeyBlobs[i].Bytes.Slice(0x20), s.KeyBlobs[i].Bytes, s.KeyBlobKeys[i], - s.EncryptedKeyBlobs[i].Counter); - } + s.MasterKeks[i] = s.KeyBlobs[i].MasterKek; + s.Package1Keys[i] = s.KeyBlobs[i].Package1Key; } + } - private static void ReadKeyBlobs(KeySet s) + private static void Derive620Keys(KeySet s) + { + bool haveTsecRootKek = !s.TsecRootKek.IsZeros(); + bool havePackage1MacKek = !s.Package1MacKek.IsZeros(); + bool havePackage1Kek = !s.Package1Kek.IsZeros(); + + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) { - for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsZeros()) + continue; + + if (haveTsecRootKek) { - if (s.KeyBlobs[i].IsZeros()) continue; - - s.MasterKeks[i] = s.KeyBlobs[i].MasterKek; - s.Package1Keys[i] = s.KeyBlobs[i].Package1Key; - } - } - - private static void Derive620Keys(KeySet s) - { - bool haveTsecRootKek = !s.TsecRootKek.IsZeros(); - bool havePackage1MacKek = !s.Package1MacKek.IsZeros(); - bool havePackage1Kek = !s.Package1Kek.IsZeros(); - - for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) - { - if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsZeros()) - continue; - - if (haveTsecRootKek) - { - Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], - s.TsecRootKeys[i - KeySet.UsedKeyBlobCount], s.TsecRootKek); - } - - if (havePackage1MacKek) - { - Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1MacKeys[i], - s.Package1MacKek); - } - - if (havePackage1Kek) - { - Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1Keys[i], s.Package1Kek); - } - } - } - - private static void Derive620MasterKeks(KeySet s) - { - for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) - { - // Key revisions >= 8 all use the same TSEC root key - int tsecRootKeyIndex = Math.Min(i, 8) - KeySet.UsedKeyBlobCount; - if (s.TsecRootKeys[tsecRootKeyIndex].IsZeros() || s.MasterKekSources[i].IsZeros()) continue; - - Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]); - } - } - - private static void DeriveMarikoMasterKeks(KeySet s) - { - if (s.MarikoKek.IsZeros()) return; - - for (int i = 0; i < KeySet.KeyRevisionCount; i++) - { - if (s.MarikoMasterKekSources[i].IsZeros()) continue; - - Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek); - } - } - - private static void DeriveMasterKeys(KeySet s) - { - if (s.MasterKeySource.IsZeros()) return; - - for (int i = 0; i < KeySet.KeyRevisionCount; i++) - { - if (s.MasterKeks[i].IsZeros()) continue; - - Aes.DecryptEcb128(s.MasterKeySource, s.MasterKeys[i], s.MasterKeks[i]); - } - } - - private static void PopulateOldMasterKeys(KeySet s) - { - ReadOnlySpan keyVectors = MasterKeyVectors(s); - - // Find the newest master key we have - int newestMasterKey = -1; - - for (int i = keyVectors.Length - 1; i >= 0; i--) - { - if (!s.MasterKeys[i].IsZeros()) - { - newestMasterKey = i; - break; - } + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], + s.TsecRootKeys[i - KeySet.UsedKeyBlobCount], s.TsecRootKek); } - if (newestMasterKey == -1) - return; - - // Don't populate old master keys unless the newest master key is valid - if (!TestKeyGeneration(s, newestMasterKey)) - return; - - // Decrypt all previous master keys - for (int i = newestMasterKey; i > 0; i--) + if (havePackage1MacKek) { - Aes.DecryptEcb128(keyVectors[i], s.MasterKeys[i - 1], s.MasterKeys[i]); - } - } - - /// - /// Check if the master key of the specified generation is correct. - /// - /// The to test. - /// The generation to test. - /// if the key is correct. - private static bool TestKeyGeneration(KeySet s, int generation) - { - ReadOnlySpan keyVectors = MasterKeyVectors(s); - - // Decrypt the vector chain until we get Master Key 0 - AesKey key = s.MasterKeys[generation]; - - for (int i = generation; i > 0; i--) - { - Aes.DecryptEcb128(keyVectors[i], key, key); + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1MacKeys[i], + s.Package1MacKek); } - // Decrypt the zeros with Master Key 0 - Aes.DecryptEcb128(keyVectors[0], key, key); - - // If we don't get zeros, MasterKeys[generation] is incorrect - return key.IsZeros(); - } - - private static ReadOnlySpan MasterKeyVectors(KeySet s) => - MemoryMarshal.Cast(s.CurrentMode == KeySet.Mode.Dev - ? MasterKeyVectorsDev - : MasterKeyVectorsProd); - - private static ReadOnlySpan MasterKeyVectorsDev => new byte[] - { - 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. - 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. - 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. - 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. - 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. - 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. - 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. - 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. - 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. - 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. - 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4, // Master key 09 encrypted with Master key 0A. - 0x21, 0x88, 0x6B, 0x10, 0x9E, 0x83, 0xD6, 0x52, 0xAB, 0x08, 0xDB, 0x6D, 0x39, 0xFF, 0x1C, 0x9C // Master key 0A encrypted with Master key 0B. - }; - - private static ReadOnlySpan MasterKeyVectorsProd => new byte[] - { - 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. - 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. - 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. - 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. - 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. - 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. - 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. - 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. - 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. - 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. - 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A, // Master key 09 encrypted with Master key 0A. - 0xC1, 0x8D, 0x16, 0xBB, 0x2A, 0xE4, 0x1D, 0xD4, 0xC2, 0xC1, 0xB6, 0x40, 0x94, 0x35, 0x63, 0x98 // Master key 0A encrypted with Master key 0B. - }; - - private static void DerivePerConsoleKeys(KeySet s) - { - // Todo: Dev and newer key generations - var kek = new AesKey(); - - // Derive the device key - if (!s.PerConsoleKeySource.IsZeros() && !s.KeyBlobKeys[0].IsZeros()) + if (havePackage1Kek) { - Aes.DecryptEcb128(s.PerConsoleKeySource, s.DeviceKey, s.KeyBlobKeys[0]); - } - - // Derive device-unique save keys - for (int i = 0; i < s.DeviceUniqueSaveMacKeySources.Length; i++) - { - if (!s.DeviceUniqueSaveMacKekSource.IsZeros() && !s.DeviceUniqueSaveMacKeySources[i].IsZeros() && - !s.DeviceKey.IsZeros()) - { - GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null); - Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek); - } - } - - // Derive BIS keys - if (s.DeviceKey.IsZeros() - || s.BisKekSource.IsZeros() - || s.AesKekGenerationSource.IsZeros() - || s.AesKeyGenerationSource.IsZeros() - || s.RetailSpecificAesKeySource.IsZeros()) - { - return; - } - - // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 - if (s.BisKeySources[3].IsZeros() && !s.BisKeySources[2].IsZeros()) - { - s.BisKeySources[3] = s.BisKeySources[2]; - } - - Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey); - if (!s.BisKeySources[0].IsZeros()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); - - GenerateKek(s.DeviceKey, s.BisKekSource, kek, s.AesKekGenerationSource, s.AesKeyGenerationSource); - - for (int i = 1; i < 4; i++) - { - if (!s.BisKeySources[i].IsZeros()) - Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek); - } - } - - private static void DerivePerGenerationKeys(KeySet s) - { - bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros(); - bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros(); - bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsZeros(); - bool haveTitleKekSource = !s.TitleKekSource.IsZeros(); - bool havePackage2KeySource = !s.Package2KeySource.IsZeros(); - - for (int i = 0; i < KeySet.KeyRevisionCount; i++) - { - if (s.MasterKeys[i].IsZeros()) - { - continue; - } - - if (haveKakSource0) - { - GenerateKek(s.MasterKeys[i], s.KeyAreaKeyApplicationSource, s.KeyAreaKeys[i][0], - s.AesKekGenerationSource, s.AesKeyGenerationSource); - } - - if (haveKakSource1) - { - GenerateKek(s.MasterKeys[i], s.KeyAreaKeyOceanSource, s.KeyAreaKeys[i][1], s.AesKekGenerationSource, - s.AesKeyGenerationSource); - } - - if (haveKakSource2) - { - GenerateKek(s.MasterKeys[i], s.KeyAreaKeySystemSource, s.KeyAreaKeys[i][2], - s.AesKekGenerationSource, s.AesKeyGenerationSource); - } - - if (haveTitleKekSource) - { - Aes.DecryptEcb128(s.TitleKekSource, s.TitleKeks[i], s.MasterKeys[i]); - } - - if (havePackage2KeySource) - { - Aes.DecryptEcb128(s.Package2KeySource, s.Package2Keys[i], s.MasterKeys[i]); - } - } - } - - private static void DeriveNcaHeaderKey(KeySet s) - { - if (s.HeaderKekSource.IsZeros() || s.HeaderKeySource.IsZeros() || s.MasterKeys[0].IsZeros()) return; - - var headerKek = new AesKey(); - - GenerateKek(s.MasterKeys[0], s.HeaderKekSource, headerKek, s.AesKekGenerationSource, - s.AesKeyGenerationSource); - Aes.DecryptEcb128(s.HeaderKeySource, s.HeaderKey, headerKek); - } - - public static void DeriveSdCardKeys(KeySet s) - { - var sdKek = new AesKey(); - var tempKey = new AesXtsKey(); - GenerateKek(s.MasterKeys[0], s.SdCardKekSource, sdKek, s.AesKekGenerationSource, s.AesKeyGenerationSource); - - for (int k = 0; k < KeySet.SdCardKeyIdCount; k++) - { - for (int i = 0; i < 4; i++) - { - tempKey.Data64[i] = s.SdCardKeySources[k].Data64[i] ^ s.SdCardEncryptionSeed.Data64[i & 1]; - } - - tempKey.Data64[0] = s.SdCardKeySources[k].Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[1] = s.SdCardKeySources[k].Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; - tempKey.Data64[2] = s.SdCardKeySources[k].Data64[2] ^ s.SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[3] = s.SdCardKeySources[k].Data64[3] ^ s.SdCardEncryptionSeed.Data64[1]; - - Aes.DecryptEcb128(tempKey, s.SdCardEncryptionKeys[k], sdKek); - } - - // Derive sd card save key - if (!s.SeedUniqueSaveMacKekSource.IsZeros() && !s.SeedUniqueSaveMacKeySource.IsZeros()) - { - var keySource = new AesKey(); - - keySource.Data64[0] = s.SeedUniqueSaveMacKeySource.Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; - keySource.Data64[1] = s.SeedUniqueSaveMacKeySource.Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; - - GenerateKek(s.MasterKeys[0], s.SeedUniqueSaveMacKekSource, sdKek, s.AesKekGenerationSource, null); - Aes.DecryptEcb128(keySource, s.SeedUniqueSaveMacKey, sdKek); - } - } - - private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, - ReadOnlySpan kekSeed, ReadOnlySpan keySeed) - { - var kek = new AesKey(); - var srcKek = new AesKey(); - - Aes.DecryptEcb128(kekSeed, kek, key); - Aes.DecryptEcb128(src, srcKek, kek); - - if (!keySeed.IsZeros()) - { - Aes.DecryptEcb128(keySeed, dest, srcKek); - } - else - { - srcKek.Data.CopyTo(dest); + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1Keys[i], s.Package1Kek); } } } + + private static void Derive620MasterKeks(KeySet s) + { + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) + { + // Key revisions >= 8 all use the same TSEC root key + int tsecRootKeyIndex = Math.Min(i, 8) - KeySet.UsedKeyBlobCount; + if (s.TsecRootKeys[tsecRootKeyIndex].IsZeros() || s.MasterKekSources[i].IsZeros()) continue; + + Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]); + } + } + + private static void DeriveMarikoMasterKeks(KeySet s) + { + if (s.MarikoKek.IsZeros()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MarikoMasterKekSources[i].IsZeros()) continue; + + Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek); + } + } + + private static void DeriveMasterKeys(KeySet s) + { + if (s.MasterKeySource.IsZeros()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeks[i].IsZeros()) continue; + + Aes.DecryptEcb128(s.MasterKeySource, s.MasterKeys[i], s.MasterKeks[i]); + } + } + + private static void PopulateOldMasterKeys(KeySet s) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Find the newest master key we have + int newestMasterKey = -1; + + for (int i = keyVectors.Length - 1; i >= 0; i--) + { + if (!s.MasterKeys[i].IsZeros()) + { + newestMasterKey = i; + break; + } + } + + if (newestMasterKey == -1) + return; + + // Don't populate old master keys unless the newest master key is valid + if (!TestKeyGeneration(s, newestMasterKey)) + return; + + // Decrypt all previous master keys + for (int i = newestMasterKey; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], s.MasterKeys[i - 1], s.MasterKeys[i]); + } + } + + /// + /// Check if the master key of the specified generation is correct. + /// + /// The to test. + /// The generation to test. + /// if the key is correct. + private static bool TestKeyGeneration(KeySet s, int generation) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Decrypt the vector chain until we get Master Key 0 + AesKey key = s.MasterKeys[generation]; + + for (int i = generation; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], key, key); + } + + // Decrypt the zeros with Master Key 0 + Aes.DecryptEcb128(keyVectors[0], key, key); + + // If we don't get zeros, MasterKeys[generation] is incorrect + return key.IsZeros(); + } + + private static ReadOnlySpan MasterKeyVectors(KeySet s) => + MemoryMarshal.Cast(s.CurrentMode == KeySet.Mode.Dev + ? MasterKeyVectorsDev + : MasterKeyVectorsProd); + + private static ReadOnlySpan MasterKeyVectorsDev => new byte[] + { + 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. + 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. + 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. + 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. + 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. + 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. + 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. + 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. + 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. + 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. + 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4, // Master key 09 encrypted with Master key 0A. + 0x21, 0x88, 0x6B, 0x10, 0x9E, 0x83, 0xD6, 0x52, 0xAB, 0x08, 0xDB, 0x6D, 0x39, 0xFF, 0x1C, 0x9C // Master key 0A encrypted with Master key 0B. + }; + + private static ReadOnlySpan MasterKeyVectorsProd => new byte[] + { + 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. + 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. + 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. + 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. + 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. + 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. + 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. + 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. + 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. + 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. + 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A, // Master key 09 encrypted with Master key 0A. + 0xC1, 0x8D, 0x16, 0xBB, 0x2A, 0xE4, 0x1D, 0xD4, 0xC2, 0xC1, 0xB6, 0x40, 0x94, 0x35, 0x63, 0x98 // Master key 0A encrypted with Master key 0B. + }; + + private static void DerivePerConsoleKeys(KeySet s) + { + // Todo: Dev and newer key generations + var kek = new AesKey(); + + // Derive the device key + if (!s.PerConsoleKeySource.IsZeros() && !s.KeyBlobKeys[0].IsZeros()) + { + Aes.DecryptEcb128(s.PerConsoleKeySource, s.DeviceKey, s.KeyBlobKeys[0]); + } + + // Derive device-unique save keys + for (int i = 0; i < s.DeviceUniqueSaveMacKeySources.Length; i++) + { + if (!s.DeviceUniqueSaveMacKekSource.IsZeros() && !s.DeviceUniqueSaveMacKeySources[i].IsZeros() && + !s.DeviceKey.IsZeros()) + { + GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek); + } + } + + // Derive BIS keys + if (s.DeviceKey.IsZeros() + || s.BisKekSource.IsZeros() + || s.AesKekGenerationSource.IsZeros() + || s.AesKeyGenerationSource.IsZeros() + || s.RetailSpecificAesKeySource.IsZeros()) + { + return; + } + + // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 + if (s.BisKeySources[3].IsZeros() && !s.BisKeySources[2].IsZeros()) + { + s.BisKeySources[3] = s.BisKeySources[2]; + } + + Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey); + if (!s.BisKeySources[0].IsZeros()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); + + GenerateKek(s.DeviceKey, s.BisKekSource, kek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int i = 1; i < 4; i++) + { + if (!s.BisKeySources[i].IsZeros()) + Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek); + } + } + + private static void DerivePerGenerationKeys(KeySet s) + { + bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros(); + bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros(); + bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsZeros(); + bool haveTitleKekSource = !s.TitleKekSource.IsZeros(); + bool havePackage2KeySource = !s.Package2KeySource.IsZeros(); + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeys[i].IsZeros()) + { + continue; + } + + if (haveKakSource0) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyApplicationSource, s.KeyAreaKeys[i][0], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveKakSource1) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyOceanSource, s.KeyAreaKeys[i][1], s.AesKekGenerationSource, + s.AesKeyGenerationSource); + } + + if (haveKakSource2) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeySystemSource, s.KeyAreaKeys[i][2], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveTitleKekSource) + { + Aes.DecryptEcb128(s.TitleKekSource, s.TitleKeks[i], s.MasterKeys[i]); + } + + if (havePackage2KeySource) + { + Aes.DecryptEcb128(s.Package2KeySource, s.Package2Keys[i], s.MasterKeys[i]); + } + } + } + + private static void DeriveNcaHeaderKey(KeySet s) + { + if (s.HeaderKekSource.IsZeros() || s.HeaderKeySource.IsZeros() || s.MasterKeys[0].IsZeros()) return; + + var headerKek = new AesKey(); + + GenerateKek(s.MasterKeys[0], s.HeaderKekSource, headerKek, s.AesKekGenerationSource, + s.AesKeyGenerationSource); + Aes.DecryptEcb128(s.HeaderKeySource, s.HeaderKey, headerKek); + } + + public static void DeriveSdCardKeys(KeySet s) + { + var sdKek = new AesKey(); + var tempKey = new AesXtsKey(); + GenerateKek(s.MasterKeys[0], s.SdCardKekSource, sdKek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int k = 0; k < KeySet.SdCardKeyIdCount; k++) + { + for (int i = 0; i < 4; i++) + { + tempKey.Data64[i] = s.SdCardKeySources[k].Data64[i] ^ s.SdCardEncryptionSeed.Data64[i & 1]; + } + + tempKey.Data64[0] = s.SdCardKeySources[k].Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[1] = s.SdCardKeySources[k].Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + tempKey.Data64[2] = s.SdCardKeySources[k].Data64[2] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[3] = s.SdCardKeySources[k].Data64[3] ^ s.SdCardEncryptionSeed.Data64[1]; + + Aes.DecryptEcb128(tempKey, s.SdCardEncryptionKeys[k], sdKek); + } + + // Derive sd card save key + if (!s.SeedUniqueSaveMacKekSource.IsZeros() && !s.SeedUniqueSaveMacKeySource.IsZeros()) + { + var keySource = new AesKey(); + + keySource.Data64[0] = s.SeedUniqueSaveMacKeySource.Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + keySource.Data64[1] = s.SeedUniqueSaveMacKeySource.Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + + GenerateKek(s.MasterKeys[0], s.SeedUniqueSaveMacKekSource, sdKek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(keySource, s.SeedUniqueSaveMacKey, sdKek); + } + } + + private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, + ReadOnlySpan kekSeed, ReadOnlySpan keySeed) + { + var kek = new AesKey(); + var srcKek = new AesKey(); + + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); + + if (!keySeed.IsZeros()) + { + Aes.DecryptEcb128(keySeed, dest, srcKek); + } + else + { + srcKek.Data.CopyTo(dest); + } + } } diff --git a/src/LibHac/Common/Keys/KeyInfo.cs b/src/LibHac/Common/Keys/KeyInfo.cs index 3fb39804..4ee6630c 100644 --- a/src/LibHac/Common/Keys/KeyInfo.cs +++ b/src/LibHac/Common/Keys/KeyInfo.cs @@ -3,165 +3,164 @@ using System.Diagnostics; using LibHac.Diag; using LibHac.Util; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +[DebuggerDisplay("{" + nameof(Name) + "}")] +public readonly struct KeyInfo { - [DebuggerDisplay("{" + nameof(Name) + "}")] - public readonly struct KeyInfo + public enum KeyRangeType : byte { - public enum KeyRangeType : byte + Single, + Range + } + + [Flags] + public enum KeyType : byte + { + Common = 1 << 0, + Device = 1 << 1, + Root = 1 << 2, + Seed = 1 << 3, + Derived = 1 << 4, + + /// Specifies that a seed is different in prod and dev. + DifferentDev = 1 << 5, + + CommonRoot = Common | Root, + CommonSeed = Common | Seed, + CommonSeedDiff = Common | Seed | DifferentDev, + CommonDrvd = Common | Derived, + DeviceRoot = Device | Root, + DeviceDrvd = Device | Derived + } + + public readonly string Name; + public readonly KeyGetter Getter; + public readonly int Group; + public readonly KeyRangeType RangeType; + public readonly KeyType Type; + public readonly byte RangeStart; + public readonly byte RangeEnd; + + public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0); + + public delegate Span KeyGetter(KeySet keySet, int i); + + public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc) + { + Assert.SdkRequires(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Single; + Type = type; + RangeStart = default; + RangeEnd = default; + Group = group; + Getter = retrieveFunc; + } + + public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc) + { + Assert.SdkRequires(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Range; + Type = type; + RangeStart = rangeStart; + RangeEnd = rangeEnd; + Group = group; + Getter = retrieveFunc; + } + + public bool Matches(ReadOnlySpan keyName, out int keyIndex, out bool isDev) + { + keyIndex = default; + isDev = default; + + return RangeType switch { - Single, - Range - } + KeyRangeType.Single => MatchesSingle(keyName, out isDev), + KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev), + _ => false + }; + } - [Flags] - public enum KeyType : byte + private bool MatchesSingle(ReadOnlySpan keyName, out bool isDev) + { + Assert.SdkRequiresEqual((int)KeyRangeType.Single, (int)RangeType); + + isDev = false; + + if (keyName.Length == NameLength + 4) { - Common = 1 << 0, - Device = 1 << 1, - Root = 1 << 2, - Seed = 1 << 3, - Derived = 1 << 4, - - /// Specifies that a seed is different in prod and dev. - DifferentDev = 1 << 5, - - CommonRoot = Common | Root, - CommonSeed = Common | Seed, - CommonSeedDiff = Common | Seed | DifferentDev, - CommonDrvd = Common | Derived, - DeviceRoot = Device | Root, - DeviceDrvd = Device | Derived - } - - public readonly string Name; - public readonly KeyGetter Getter; - public readonly int Group; - public readonly KeyRangeType RangeType; - public readonly KeyType Type; - public readonly byte RangeStart; - public readonly byte RangeEnd; - - public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0); - - public delegate Span KeyGetter(KeySet keySet, int i); - - public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc) - { - Assert.SdkRequires(IsKeyTypeValid(type)); - - Name = name; - RangeType = KeyRangeType.Single; - Type = type; - RangeStart = default; - RangeEnd = default; - Group = group; - Getter = retrieveFunc; - } - - public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc) - { - Assert.SdkRequires(IsKeyTypeValid(type)); - - Name = name; - RangeType = KeyRangeType.Range; - Type = type; - RangeStart = rangeStart; - RangeEnd = rangeEnd; - Group = group; - Getter = retrieveFunc; - } - - public bool Matches(ReadOnlySpan keyName, out int keyIndex, out bool isDev) - { - keyIndex = default; - isDev = default; - - return RangeType switch - { - KeyRangeType.Single => MatchesSingle(keyName, out isDev), - KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev), - _ => false - }; - } - - private bool MatchesSingle(ReadOnlySpan keyName, out bool isDev) - { - Assert.SdkRequiresEqual((int)KeyRangeType.Single, (int)RangeType); - - isDev = false; - - if (keyName.Length == NameLength + 4) - { - // Might be a dev key. Check if "_dev" comes after the base key name - if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) - return false; - - isDev = true; - } - else if (keyName.Length != NameLength) - { - return false; - } - - // Check if the base name matches - if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) + // Might be a dev key. Check if "_dev" comes after the base key name + if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) return false; - return true; + isDev = true; } - - private bool MatchesRangedKey(ReadOnlySpan keyName, ref int keyIndex, out bool isDev) + else if (keyName.Length != NameLength) { - Assert.SdkRequiresEqual((int)KeyRangeType.Range, (int)RangeType); - - isDev = false; - - // Check if this is an explicit dev key - if (keyName.Length == Name.Length + 7) - { - // Check if "_dev" comes after the base key name - if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) - return false; - - isDev = true; - } - // Not a dev key. Check that the length of the key name with the trailing index matches - else if (keyName.Length != Name.Length + 3) - return false; - - // Check if the name before the "_XX" index matches - if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) - return false; - - // The name should have an underscore before the index value - if (keyName[keyName.Length - 3] != '_') - return false; - - byte index = default; - - // Try to get the index of the key name - if (!StringUtils.TryFromHexString(keyName.Slice(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index))) - return false; - - // Check if the index is in this key's range - if (index < RangeStart || index >= RangeEnd) - return false; - - keyIndex = index; - return true; + return false; } - private static bool IsKeyTypeValid(KeyType type) + // Check if the base name matches + if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) + return false; + + return true; + } + + private bool MatchesRangedKey(ReadOnlySpan keyName, ref int keyIndex, out bool isDev) + { + Assert.SdkRequiresEqual((int)KeyRangeType.Range, (int)RangeType); + + isDev = false; + + // Check if this is an explicit dev key + if (keyName.Length == Name.Length + 7) { - // Make sure the type has exactly one flag set for each type - KeyType type1 = type & (KeyType.Common | KeyType.Device); - KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived); + // Check if "_dev" comes after the base key name + if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) + return false; - bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device; - bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived; - - return isValid1 && isValid2; + isDev = true; } + // Not a dev key. Check that the length of the key name with the trailing index matches + else if (keyName.Length != Name.Length + 3) + return false; + + // Check if the name before the "_XX" index matches + if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) + return false; + + // The name should have an underscore before the index value + if (keyName[keyName.Length - 3] != '_') + return false; + + byte index = default; + + // Try to get the index of the key name + if (!StringUtils.TryFromHexString(keyName.Slice(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index))) + return false; + + // Check if the index is in this key's range + if (index < RangeStart || index >= RangeEnd) + return false; + + keyIndex = index; + return true; + } + + private static bool IsKeyTypeValid(KeyType type) + { + // Make sure the type has exactly one flag set for each type + KeyType type1 = type & (KeyType.Common | KeyType.Device); + KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived); + + bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device; + bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived; + + return isValid1 && isValid2; } } diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index 79b7e32c..532ecc3b 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -8,377 +8,376 @@ using LibHac.Crypto; using LibHac.FsSrv; using LibHac.Util; -namespace LibHac.Common.Keys +namespace LibHac.Common.Keys; + +public class KeySet { - public class KeySet + public enum Mode { - public enum Mode - { - Dev, - Prod - } - - /// - /// The number of keyblobs that were used for < 6.2.0 crypto - /// - internal const int UsedKeyBlobCount = 6; - internal const int SdCardKeyIdCount = 3; - internal const int KeyRevisionCount = 0x20; - - private AllKeys _keys; - private Mode _mode = Mode.Prod; - - public ref AllKeys KeyStruct => ref _keys; - public Mode CurrentMode => _mode; - - private ref RootKeys RootKeys => ref _mode == Mode.Dev ? ref _keys._rootKeysDev : ref _keys._rootKeysProd; - private ref StoredKeys StoredKeys => ref _mode == Mode.Dev ? ref _keys._storedKeysDev : ref _keys._storedKeysProd; - private ref DerivedKeys DerivedKeys => ref _mode == Mode.Dev ? ref _keys._derivedKeysDev : ref _keys._derivedKeysProd; - private ref RsaSigningKeys RsaSigningKeys => ref _mode == Mode.Dev ? ref _keys._rsaSigningKeysDev : ref _keys._rsaSigningKeysProd; - private ref RsaKeys RsaKeys => ref _keys._rsaKeys; - - private ref RsaSigningKeyParameters RsaSigningKeyParams => ref _mode == Mode.Dev - ? ref _rsaSigningKeyParamsDev - : ref _rsaSigningKeyParamsProd; - - public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet(); - - public Span MarikoAesClassKeys => RootKeys.MarikoAesClassKeys.Items; - public ref AesKey MarikoKek => ref RootKeys.MarikoKek; - public ref AesKey MarikoBek => ref RootKeys.MarikoBek; - public Span KeyBlobs => RootKeys.KeyBlobs.Items; - public Span KeyBlobKeySources => _keys._keySeeds.KeyBlobKeySources.Items; - public ref AesKey KeyBlobMacKeySource => ref _keys._keySeeds.KeyBlobMacKeySource; - public ref AesKey TsecRootKek => ref RootKeys.TsecRootKek; - public ref AesKey Package1MacKek => ref RootKeys.Package1MacKek; - public ref AesKey Package1Kek => ref RootKeys.Package1Kek; - public Span TsecAuthSignatures => RootKeys.TsecAuthSignatures.Items; - public Span TsecRootKeys => RootKeys.TsecRootKeys.Items; - public Span MasterKekSources => _keys._keySeeds.MasterKekSources.Items; - - public Span MarikoMasterKekSources => _mode == Mode.Dev - ? _keys._keySeeds.MarikoMasterKekSources_dev.Items - : _keys._keySeeds.MarikoMasterKekSources.Items; - - public Span MasterKeks => DerivedKeys.MasterKeks.Items; - public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource; - public Span MasterKeys => DerivedKeys.MasterKeys.Items; - public Span Package1MacKeys => DerivedKeys.Package1MacKeys.Items; - public Span Package1Keys => DerivedKeys.Package1Keys.Items; - public Span Package2Keys => DerivedKeys.Package2Keys.Items; - public ref AesKey Package2KeySource => ref _keys._keySeeds.Package2KeySource; - public ref AesKey PerConsoleKeySource => ref _keys._keySeeds.PerConsoleKeySource; - public ref AesKey RetailSpecificAesKeySource => ref _keys._keySeeds.RetailSpecificAesKeySource; - public ref AesKey BisKekSource => ref _keys._keySeeds.BisKekSource; - public Span BisKeySources => _keys._keySeeds.BisKeySources.Items; - public ref AesKey AesKekGenerationSource => ref _keys._keySeeds.AesKekGenerationSource; - public ref AesKey AesKeyGenerationSource => ref _keys._keySeeds.AesKeyGenerationSource; - public ref AesKey KeyAreaKeyApplicationSource => ref _keys._keySeeds.KeyAreaKeyApplicationSource; - public ref AesKey KeyAreaKeyOceanSource => ref _keys._keySeeds.KeyAreaKeyOceanSource; - public ref AesKey KeyAreaKeySystemSource => ref _keys._keySeeds.KeyAreaKeySystemSource; - public ref AesKey TitleKekSource => ref _keys._keySeeds.TitleKekSource; - public ref AesKey HeaderKekSource => ref _keys._keySeeds.HeaderKekSource; - public ref AesKey SdCardKekSource => ref _keys._keySeeds.SdCardKekSource; - public Span SdCardKeySources => _keys._keySeeds.SdCardKeySources.Items; - public ref AesKey DeviceUniqueSaveMacKekSource => ref _keys._keySeeds.DeviceUniqueSaveMacKekSource; - public Span DeviceUniqueSaveMacKeySources => _keys._keySeeds.DeviceUniqueSaveMacKeySources.Items; - public ref AesKey SeedUniqueSaveMacKekSource => ref _keys._keySeeds.SeedUniqueSaveMacKekSource; - public ref AesKey SeedUniqueSaveMacKeySource => ref _keys._keySeeds.SeedUniqueSaveMacKeySource; - public ref AesXtsKey HeaderKeySource => ref _keys._keySeeds.HeaderKeySource; - public ref AesXtsKey HeaderKey => ref DerivedKeys.HeaderKey; - public Span TitleKeks => DerivedKeys.TitleKeks.Items; - public Span> KeyAreaKeys => DerivedKeys.KeyAreaKeys.Items; - public ref AesKey XciHeaderKey => ref StoredKeys.XciHeaderKey; - public ref AesKey ETicketRsaKek => ref DerivedKeys.ETicketRsaKek; - public ref AesKey SslRsaKek => ref DerivedKeys.SslRsaKek; - - public ref AesKey SecureBootKey => ref _keys._deviceKeys.SecureBootKey; - public ref AesKey TsecKey => ref _keys._deviceKeys.TsecKey; - public Span KeyBlobKeys => _keys._deviceKeys.KeyBlobKeys.Items; - public Span KeyBlobMacKeys => _keys._deviceKeys.KeyBlobMacKeys.Items; - public Span EncryptedKeyBlobs => _keys._deviceKeys.EncryptedKeyBlobs.Items; - public ref AesKey DeviceKey => ref _keys._deviceKeys.DeviceKey; - public Span BisKeys => _keys._deviceKeys.BisKeys.Items; - public Span DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items; - public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey; - public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed; - - // Todo: Make a separate type? Not actually an AES-XTS key, but it's still the same shape. - public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; - - public Span NcaHeaderSigningKeys => RsaSigningKeys.NcaHeaderSigningKeys.Items; - public Span AcidSigningKeys => RsaSigningKeys.AcidSigningKeys.Items; - public ref RsaKey Package2SigningKey => ref RsaSigningKeys.Package2SigningKey; - public ref RsaFullKey BetaNca0KeyAreaKey => ref RsaKeys.BetaNca0KeyAreaKey; - - private RsaSigningKeyParameters _rsaSigningKeyParamsDev; - private RsaSigningKeyParameters _rsaSigningKeyParamsProd; - private RsaKeyParameters _rsaKeyParams; - - public RSAParameters ETicketExtKeyRsa { get; set; } - - public Span NcaHeaderSigningKeyParams - { - get - { - ref Optional> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; - - if (!keys.HasValue) - { - keys.Set(new Array2()); - keys.Value[0] = CreateRsaParameters(in NcaHeaderSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in NcaHeaderSigningKeys[1]); - } - - return keys.Value.Items; - } - } - - public Span AcidSigningKeyParams - { - get - { - ref Optional> keys = ref RsaSigningKeyParams.AcidSigningKeys; - - if (!keys.HasValue) - { - keys.Set(new Array2()); - keys.Value[0] = CreateRsaParameters(in AcidSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in AcidSigningKeys[1]); - } - - return keys.Value.Items; - } - } - - public ref RSAParameters Package2SigningKeyParams - { - get - { - ref Optional keys = ref RsaSigningKeyParams.Package2SigningKey; - - if (!keys.HasValue) - { - keys.Set(new RSAParameters()); - keys.Value = CreateRsaParameters(in Package2SigningKey); - } - - return ref keys.Value; - } - } - - public ref RSAParameters BetaNca0KeyAreaKeyParams - { - get - { - ref Optional keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; - - if (!keys.HasValue) - { - keys.Set(CreateRsaParameters(in BetaNca0KeyAreaKey)); - } - - return ref keys.Value; - } - } - - public void SetSdSeed(ReadOnlySpan sdSeed) - { - if (sdSeed.Length != 0x10) - throw new ArgumentException("Sd card encryption seed must be 16 bytes long."); - - sdSeed.CopyTo(SdCardEncryptionSeed); - DeriveSdCardKeys(); - } - - public void SetMode(Mode mode) => _mode = mode; - - /// - /// Returns a new containing any keys that have been compiled into the library. - /// - /// The created , - public static KeySet CreateDefaultKeySet() - { - return DefaultKeySet.CreateDefaultKeySet(); - } - - public static List CreateKeyInfoList() - { - return DefaultKeySet.CreateKeyList(); - } - - public void DeriveKeys(IProgressReport logger = null) - { - Mode originalMode = CurrentMode; - - SetMode(Mode.Prod); - KeyDerivation.DeriveAllKeys(this, logger); - - SetMode(Mode.Dev); - KeyDerivation.DeriveAllKeys(this, logger); - - SetMode(originalMode); - } - - public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); - - private static RSAParameters CreateRsaParameters(in RsaKey key) - { - return new RSAParameters - { - Exponent = key.PublicExponent.DataRo.ToArray(), - Modulus = key.Modulus.DataRo.ToArray() - }; - } - - private static RSAParameters CreateRsaParameters(in RsaFullKey key) - { - return new RSAParameters - { - D = key.PrivateExponent.DataRo.ToArray(), - DP = key.Dp.DataRo.ToArray(), - DQ = key.Dq.DataRo.ToArray(), - Exponent = key.PublicExponent.DataRo.ToArray(), - InverseQ = key.InverseQ.DataRo.ToArray(), - Modulus = key.Modulus.DataRo.ToArray(), - P = key.P.DataRo.ToArray(), - Q = key.Q.DataRo.ToArray() - }; - } - - private struct RsaSigningKeyParameters - { - public Optional> NcaHeaderSigningKeys; - public Optional> AcidSigningKeys; - public Optional Package2SigningKey; - } - - private struct RsaKeyParameters - { - public Optional BetaNca0KeyAreaKey; - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct AllKeys - { - public RootKeys _rootKeysDev; - public RootKeys _rootKeysProd; - public KeySeeds _keySeeds; - public StoredKeys _storedKeysDev; - public StoredKeys _storedKeysProd; - public DerivedKeys _derivedKeysDev; - public DerivedKeys _derivedKeysProd; - public DeviceKeys _deviceKeys; - public RsaSigningKeys _rsaSigningKeysDev; - public RsaSigningKeys _rsaSigningKeysProd; - public RsaKeys _rsaKeys; - } - - [StructLayout(LayoutKind.Sequential)] - public struct RootKeys - { - // Mariko keys. The AES class keys are currently unused. - public AesKey MarikoKek; - public AesKey MarikoBek; - public Array12 MarikoAesClassKeys; - - // The key blobs are technically derived from the encrypted key blobs and their keys, - // however those keys are device-unique. The decrypted key blobs are basically the common root - // keys used by pre-6.2.0 Erista. - public Array32 KeyBlobs; - - // Used by TSEC in >= 6.2.0 Erista firmware - public Array32 TsecAuthSignatures; - public AesKey TsecRootKek; - public AesKey Package1MacKek; - public AesKey Package1Kek; - - // Derived by TSEC. This is the first public root key for >= 6.2.0 Erista - public Array32 TsecRootKeys; - } - - [StructLayout(LayoutKind.Sequential)] - public struct KeySeeds - { - public Array32 KeyBlobKeySources; - public AesKey KeyBlobMacKeySource; - public Array32 MasterKekSources; - public Array32 MarikoMasterKekSources; - public Array32 MarikoMasterKekSources_dev; - public AesKey MasterKeySource; - public AesKey Package2KeySource; - public AesKey PerConsoleKeySource; - public AesKey RetailSpecificAesKeySource; - public AesKey BisKekSource; - public Array4 BisKeySources; - public AesKey AesKekGenerationSource; - public AesKey AesKeyGenerationSource; - public AesKey KeyAreaKeyApplicationSource; - public AesKey KeyAreaKeyOceanSource; - public AesKey KeyAreaKeySystemSource; - public AesKey TitleKekSource; - public AesKey HeaderKekSource; - public AesKey SdCardKekSource; - public Array3 SdCardKeySources; - public AesKey DeviceUniqueSaveMacKekSource; - public Array2 DeviceUniqueSaveMacKeySources; - public AesKey SeedUniqueSaveMacKekSource; - public AesKey SeedUniqueSaveMacKeySource; - public AesXtsKey HeaderKeySource; + Dev, + Prod } /// - /// Holds keys that are stored directly in Horizon programs. + /// The number of keyblobs that were used for < 6.2.0 crypto /// - [StructLayout(LayoutKind.Sequential)] - public struct StoredKeys + internal const int UsedKeyBlobCount = 6; + internal const int SdCardKeyIdCount = 3; + internal const int KeyRevisionCount = 0x20; + + private AllKeys _keys; + private Mode _mode = Mode.Prod; + + public ref AllKeys KeyStruct => ref _keys; + public Mode CurrentMode => _mode; + + private ref RootKeys RootKeys => ref _mode == Mode.Dev ? ref _keys._rootKeysDev : ref _keys._rootKeysProd; + private ref StoredKeys StoredKeys => ref _mode == Mode.Dev ? ref _keys._storedKeysDev : ref _keys._storedKeysProd; + private ref DerivedKeys DerivedKeys => ref _mode == Mode.Dev ? ref _keys._derivedKeysDev : ref _keys._derivedKeysProd; + private ref RsaSigningKeys RsaSigningKeys => ref _mode == Mode.Dev ? ref _keys._rsaSigningKeysDev : ref _keys._rsaSigningKeysProd; + private ref RsaKeys RsaKeys => ref _keys._rsaKeys; + + private ref RsaSigningKeyParameters RsaSigningKeyParams => ref _mode == Mode.Dev + ? ref _rsaSigningKeyParamsDev + : ref _rsaSigningKeyParamsProd; + + public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet(); + + public Span MarikoAesClassKeys => RootKeys.MarikoAesClassKeys.Items; + public ref AesKey MarikoKek => ref RootKeys.MarikoKek; + public ref AesKey MarikoBek => ref RootKeys.MarikoBek; + public Span KeyBlobs => RootKeys.KeyBlobs.Items; + public Span KeyBlobKeySources => _keys._keySeeds.KeyBlobKeySources.Items; + public ref AesKey KeyBlobMacKeySource => ref _keys._keySeeds.KeyBlobMacKeySource; + public ref AesKey TsecRootKek => ref RootKeys.TsecRootKek; + public ref AesKey Package1MacKek => ref RootKeys.Package1MacKek; + public ref AesKey Package1Kek => ref RootKeys.Package1Kek; + public Span TsecAuthSignatures => RootKeys.TsecAuthSignatures.Items; + public Span TsecRootKeys => RootKeys.TsecRootKeys.Items; + public Span MasterKekSources => _keys._keySeeds.MasterKekSources.Items; + + public Span MarikoMasterKekSources => _mode == Mode.Dev + ? _keys._keySeeds.MarikoMasterKekSources_dev.Items + : _keys._keySeeds.MarikoMasterKekSources.Items; + + public Span MasterKeks => DerivedKeys.MasterKeks.Items; + public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource; + public Span MasterKeys => DerivedKeys.MasterKeys.Items; + public Span Package1MacKeys => DerivedKeys.Package1MacKeys.Items; + public Span Package1Keys => DerivedKeys.Package1Keys.Items; + public Span Package2Keys => DerivedKeys.Package2Keys.Items; + public ref AesKey Package2KeySource => ref _keys._keySeeds.Package2KeySource; + public ref AesKey PerConsoleKeySource => ref _keys._keySeeds.PerConsoleKeySource; + public ref AesKey RetailSpecificAesKeySource => ref _keys._keySeeds.RetailSpecificAesKeySource; + public ref AesKey BisKekSource => ref _keys._keySeeds.BisKekSource; + public Span BisKeySources => _keys._keySeeds.BisKeySources.Items; + public ref AesKey AesKekGenerationSource => ref _keys._keySeeds.AesKekGenerationSource; + public ref AesKey AesKeyGenerationSource => ref _keys._keySeeds.AesKeyGenerationSource; + public ref AesKey KeyAreaKeyApplicationSource => ref _keys._keySeeds.KeyAreaKeyApplicationSource; + public ref AesKey KeyAreaKeyOceanSource => ref _keys._keySeeds.KeyAreaKeyOceanSource; + public ref AesKey KeyAreaKeySystemSource => ref _keys._keySeeds.KeyAreaKeySystemSource; + public ref AesKey TitleKekSource => ref _keys._keySeeds.TitleKekSource; + public ref AesKey HeaderKekSource => ref _keys._keySeeds.HeaderKekSource; + public ref AesKey SdCardKekSource => ref _keys._keySeeds.SdCardKekSource; + public Span SdCardKeySources => _keys._keySeeds.SdCardKeySources.Items; + public ref AesKey DeviceUniqueSaveMacKekSource => ref _keys._keySeeds.DeviceUniqueSaveMacKekSource; + public Span DeviceUniqueSaveMacKeySources => _keys._keySeeds.DeviceUniqueSaveMacKeySources.Items; + public ref AesKey SeedUniqueSaveMacKekSource => ref _keys._keySeeds.SeedUniqueSaveMacKekSource; + public ref AesKey SeedUniqueSaveMacKeySource => ref _keys._keySeeds.SeedUniqueSaveMacKeySource; + public ref AesXtsKey HeaderKeySource => ref _keys._keySeeds.HeaderKeySource; + public ref AesXtsKey HeaderKey => ref DerivedKeys.HeaderKey; + public Span TitleKeks => DerivedKeys.TitleKeks.Items; + public Span> KeyAreaKeys => DerivedKeys.KeyAreaKeys.Items; + public ref AesKey XciHeaderKey => ref StoredKeys.XciHeaderKey; + public ref AesKey ETicketRsaKek => ref DerivedKeys.ETicketRsaKek; + public ref AesKey SslRsaKek => ref DerivedKeys.SslRsaKek; + + public ref AesKey SecureBootKey => ref _keys._deviceKeys.SecureBootKey; + public ref AesKey TsecKey => ref _keys._deviceKeys.TsecKey; + public Span KeyBlobKeys => _keys._deviceKeys.KeyBlobKeys.Items; + public Span KeyBlobMacKeys => _keys._deviceKeys.KeyBlobMacKeys.Items; + public Span EncryptedKeyBlobs => _keys._deviceKeys.EncryptedKeyBlobs.Items; + public ref AesKey DeviceKey => ref _keys._deviceKeys.DeviceKey; + public Span BisKeys => _keys._deviceKeys.BisKeys.Items; + public Span DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items; + public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey; + public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed; + + // Todo: Make a separate type? Not actually an AES-XTS key, but it's still the same shape. + public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; + + public Span NcaHeaderSigningKeys => RsaSigningKeys.NcaHeaderSigningKeys.Items; + public Span AcidSigningKeys => RsaSigningKeys.AcidSigningKeys.Items; + public ref RsaKey Package2SigningKey => ref RsaSigningKeys.Package2SigningKey; + public ref RsaFullKey BetaNca0KeyAreaKey => ref RsaKeys.BetaNca0KeyAreaKey; + + private RsaSigningKeyParameters _rsaSigningKeyParamsDev; + private RsaSigningKeyParameters _rsaSigningKeyParamsProd; + private RsaKeyParameters _rsaKeyParams; + + public RSAParameters ETicketExtKeyRsa { get; set; } + + public Span NcaHeaderSigningKeyParams { - public AesKey XciHeaderKey; + get + { + ref Optional> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; + + if (!keys.HasValue) + { + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in NcaHeaderSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in NcaHeaderSigningKeys[1]); + } + + return keys.Value.Items; + } } - [StructLayout(LayoutKind.Sequential)] - public struct DerivedKeys + public Span AcidSigningKeyParams { - public Array32 MasterKeks; - public Array32 MasterKeys; - public Array32 Package1MacKeys; - public Array32 Package1Keys; - public Array32 Package2Keys; - public Array32> KeyAreaKeys; - public Array32 TitleKeks; - public AesXtsKey HeaderKey; - public AesKey ETicketRsaKek; - public AesKey SslRsaKek; + get + { + ref Optional> keys = ref RsaSigningKeyParams.AcidSigningKeys; + + if (!keys.HasValue) + { + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in AcidSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in AcidSigningKeys[1]); + } + + return keys.Value.Items; + } } - [StructLayout(LayoutKind.Sequential)] - public struct DeviceKeys + public ref RSAParameters Package2SigningKeyParams { - public AesKey SecureBootKey; - public AesKey TsecKey; - public Array32 KeyBlobKeys; - public Array32 KeyBlobMacKeys; - public Array32 EncryptedKeyBlobs; - public AesKey DeviceKey; - public Array4 BisKeys; - public Array2 DeviceUniqueSaveMacKeys; - public AesKey SeedUniqueSaveMacKey; - public AesKey SdCardEncryptionSeed; - public Array3 SdCardEncryptionKeys; + get + { + ref Optional keys = ref RsaSigningKeyParams.Package2SigningKey; + + if (!keys.HasValue) + { + keys.Set(new RSAParameters()); + keys.Value = CreateRsaParameters(in Package2SigningKey); + } + + return ref keys.Value; + } } - [StructLayout(LayoutKind.Sequential)] - public struct RsaSigningKeys + public ref RSAParameters BetaNca0KeyAreaKeyParams { - public Array2 NcaHeaderSigningKeys; - public Array2 AcidSigningKeys; - public RsaKey Package2SigningKey; + get + { + ref Optional keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; + + if (!keys.HasValue) + { + keys.Set(CreateRsaParameters(in BetaNca0KeyAreaKey)); + } + + return ref keys.Value; + } } - [StructLayout(LayoutKind.Sequential)] - public struct RsaKeys + public void SetSdSeed(ReadOnlySpan sdSeed) { - public RsaFullKey BetaNca0KeyAreaKey; + if (sdSeed.Length != 0x10) + throw new ArgumentException("Sd card encryption seed must be 16 bytes long."); + + sdSeed.CopyTo(SdCardEncryptionSeed); + DeriveSdCardKeys(); + } + + public void SetMode(Mode mode) => _mode = mode; + + /// + /// Returns a new containing any keys that have been compiled into the library. + /// + /// The created , + public static KeySet CreateDefaultKeySet() + { + return DefaultKeySet.CreateDefaultKeySet(); + } + + public static List CreateKeyInfoList() + { + return DefaultKeySet.CreateKeyList(); + } + + public void DeriveKeys(IProgressReport logger = null) + { + Mode originalMode = CurrentMode; + + SetMode(Mode.Prod); + KeyDerivation.DeriveAllKeys(this, logger); + + SetMode(Mode.Dev); + KeyDerivation.DeriveAllKeys(this, logger); + + SetMode(originalMode); + } + + public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); + + private static RSAParameters CreateRsaParameters(in RsaKey key) + { + return new RSAParameters + { + Exponent = key.PublicExponent.DataRo.ToArray(), + Modulus = key.Modulus.DataRo.ToArray() + }; + } + + private static RSAParameters CreateRsaParameters(in RsaFullKey key) + { + return new RSAParameters + { + D = key.PrivateExponent.DataRo.ToArray(), + DP = key.Dp.DataRo.ToArray(), + DQ = key.Dq.DataRo.ToArray(), + Exponent = key.PublicExponent.DataRo.ToArray(), + InverseQ = key.InverseQ.DataRo.ToArray(), + Modulus = key.Modulus.DataRo.ToArray(), + P = key.P.DataRo.ToArray(), + Q = key.Q.DataRo.ToArray() + }; + } + + private struct RsaSigningKeyParameters + { + public Optional> NcaHeaderSigningKeys; + public Optional> AcidSigningKeys; + public Optional Package2SigningKey; + } + + private struct RsaKeyParameters + { + public Optional BetaNca0KeyAreaKey; } } + +[StructLayout(LayoutKind.Sequential)] +public struct AllKeys +{ + public RootKeys _rootKeysDev; + public RootKeys _rootKeysProd; + public KeySeeds _keySeeds; + public StoredKeys _storedKeysDev; + public StoredKeys _storedKeysProd; + public DerivedKeys _derivedKeysDev; + public DerivedKeys _derivedKeysProd; + public DeviceKeys _deviceKeys; + public RsaSigningKeys _rsaSigningKeysDev; + public RsaSigningKeys _rsaSigningKeysProd; + public RsaKeys _rsaKeys; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RootKeys +{ + // Mariko keys. The AES class keys are currently unused. + public AesKey MarikoKek; + public AesKey MarikoBek; + public Array12 MarikoAesClassKeys; + + // The key blobs are technically derived from the encrypted key blobs and their keys, + // however those keys are device-unique. The decrypted key blobs are basically the common root + // keys used by pre-6.2.0 Erista. + public Array32 KeyBlobs; + + // Used by TSEC in >= 6.2.0 Erista firmware + public Array32 TsecAuthSignatures; + public AesKey TsecRootKek; + public AesKey Package1MacKek; + public AesKey Package1Kek; + + // Derived by TSEC. This is the first public root key for >= 6.2.0 Erista + public Array32 TsecRootKeys; +} + +[StructLayout(LayoutKind.Sequential)] +public struct KeySeeds +{ + public Array32 KeyBlobKeySources; + public AesKey KeyBlobMacKeySource; + public Array32 MasterKekSources; + public Array32 MarikoMasterKekSources; + public Array32 MarikoMasterKekSources_dev; + public AesKey MasterKeySource; + public AesKey Package2KeySource; + public AesKey PerConsoleKeySource; + public AesKey RetailSpecificAesKeySource; + public AesKey BisKekSource; + public Array4 BisKeySources; + public AesKey AesKekGenerationSource; + public AesKey AesKeyGenerationSource; + public AesKey KeyAreaKeyApplicationSource; + public AesKey KeyAreaKeyOceanSource; + public AesKey KeyAreaKeySystemSource; + public AesKey TitleKekSource; + public AesKey HeaderKekSource; + public AesKey SdCardKekSource; + public Array3 SdCardKeySources; + public AesKey DeviceUniqueSaveMacKekSource; + public Array2 DeviceUniqueSaveMacKeySources; + public AesKey SeedUniqueSaveMacKekSource; + public AesKey SeedUniqueSaveMacKeySource; + public AesXtsKey HeaderKeySource; +} + +/// +/// Holds keys that are stored directly in Horizon programs. +/// +[StructLayout(LayoutKind.Sequential)] +public struct StoredKeys +{ + public AesKey XciHeaderKey; +} + +[StructLayout(LayoutKind.Sequential)] +public struct DerivedKeys +{ + public Array32 MasterKeks; + public Array32 MasterKeys; + public Array32 Package1MacKeys; + public Array32 Package1Keys; + public Array32 Package2Keys; + public Array32> KeyAreaKeys; + public Array32 TitleKeks; + public AesXtsKey HeaderKey; + public AesKey ETicketRsaKek; + public AesKey SslRsaKek; +} + +[StructLayout(LayoutKind.Sequential)] +public struct DeviceKeys +{ + public AesKey SecureBootKey; + public AesKey TsecKey; + public Array32 KeyBlobKeys; + public Array32 KeyBlobMacKeys; + public Array32 EncryptedKeyBlobs; + public AesKey DeviceKey; + public Array4 BisKeys; + public Array2 DeviceUniqueSaveMacKeys; + public AesKey SeedUniqueSaveMacKey; + public AesKey SdCardEncryptionSeed; + public Array3 SdCardEncryptionKeys; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RsaSigningKeys +{ + public Array2 NcaHeaderSigningKeys; + public Array2 AcidSigningKeys; + public RsaKey Package2SigningKey; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RsaKeys +{ + public RsaFullKey BetaNca0KeyAreaKey; +} diff --git a/src/LibHac/Common/NonCopyableAttribute.cs b/src/LibHac/Common/NonCopyableAttribute.cs index 095568e9..ad920f5a 100644 --- a/src/LibHac/Common/NonCopyableAttribute.cs +++ b/src/LibHac/Common/NonCopyableAttribute.cs @@ -1,10 +1,9 @@ using System; -namespace LibHac.Common -{ - [AttributeUsage(AttributeTargets.Struct)] - public sealed class NonCopyableAttribute : Attribute { } +namespace LibHac.Common; - [AttributeUsage(AttributeTargets.Struct)] - public sealed class NonCopyableDisposableAttribute : Attribute { } -} +[AttributeUsage(AttributeTargets.Struct)] +public sealed class NonCopyableAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Struct)] +public sealed class NonCopyableDisposableAttribute : Attribute { } diff --git a/src/LibHac/Common/PaddingStructs.cs b/src/LibHac/Common/PaddingStructs.cs index 27c23b5e..d50c6435 100644 --- a/src/LibHac/Common/PaddingStructs.cs +++ b/src/LibHac/Common/PaddingStructs.cs @@ -1,52 +1,51 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace LibHac.Common +namespace LibHac.Common; + +// In order for the Visual Studio debugger to accurately display a struct, every offset +// in the struct that is used for the debugger display must be part of a field. +// These padding structs make it easier to accomplish that. +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +internal struct Padding10 { - // In order for the Visual Studio debugger to accurately display a struct, every offset - // in the struct that is used for the debugger display must be part of a field. - // These padding structs make it easier to accomplish that. - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - internal struct Padding10 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding00; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding08; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x20)] - internal struct Padding20 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding00; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding08; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding10; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding18; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x40)] - internal struct Padding40 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding00; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding20; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x80)] - internal struct Padding80 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding00; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding40; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x100)] - internal struct Padding100 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding00; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding80; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x200)] - internal struct Padding200 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding00; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding08; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x20)] +internal struct Padding20 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding00; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding08; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding10; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding18; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x40)] +internal struct Padding40 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding00; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding20; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x80)] +internal struct Padding80 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding00; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding40; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x100)] +internal struct Padding100 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding00; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding80; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x200)] +internal struct Padding200 +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; } diff --git a/src/LibHac/Common/PathBuilder.cs b/src/LibHac/Common/PathBuilder.cs index 35998732..5501db3d 100644 --- a/src/LibHac/Common/PathBuilder.cs +++ b/src/LibHac/Common/PathBuilder.cs @@ -5,120 +5,119 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +internal ref struct PathBuilder { - [DebuggerDisplay("{ToString()}")] - internal ref struct PathBuilder + private Span _buffer; + private int _pos; + + public int Length { - private Span _buffer; - private int _pos; - - public int Length + get => _pos; + set { - get => _pos; - set - { - Debug.Assert(value >= 0); - Debug.Assert(value <= Capacity); - _pos = value; - } - } - - public int Capacity => _buffer.Length - 1; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public PathBuilder(Span buffer) - { - _buffer = buffer; - _pos = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result Append(byte value) - { - int pos = _pos; - if (pos >= Capacity) - { - return ResultFs.TooLongPath.Log(); - } - - _buffer[pos] = value; - _pos = pos + 1; - return Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result Append(ReadOnlySpan value) - { - int pos = _pos; - if (pos + value.Length >= Capacity) - { - return ResultFs.TooLongPath.Log(); - } - - value.CopyTo(_buffer.Slice(pos)); - _pos = pos + value.Length; - return Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result AppendWithPrecedingSeparator(byte value) - { - int pos = _pos; - if (pos + 1 >= Capacity) - { - // Append the separator if there's enough space - if (pos < Capacity) - { - _buffer[pos] = (byte)'/'; - _pos = pos + 1; - } - - return ResultFs.TooLongPath.Log(); - } - - _buffer[pos] = (byte)'/'; - _buffer[pos + 1] = value; - _pos = pos + 2; - return Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result GoUpLevels(int count) - { - Debug.Assert(count > 0); - - int separators = 0; - int pos = _pos - 1; - - for (; pos >= 0; pos--) - { - if (PathTools.IsDirectorySeparator(_buffer[pos])) - { - separators++; - - if (separators == count) break; - } - } - - if (separators != count) return ResultFs.DirectoryUnobtainable.Log(); - - _pos = pos; - return Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Terminate() - { - if (_buffer.Length > _pos) - { - _buffer[_pos] = 0; - } - } - - public override string ToString() - { - return StringUtils.Utf8ZToString(_buffer.Slice(0, Length)); + Debug.Assert(value >= 0); + Debug.Assert(value <= Capacity); + _pos = value; } } + + public int Capacity => _buffer.Length - 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PathBuilder(Span buffer) + { + _buffer = buffer; + _pos = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Append(byte value) + { + int pos = _pos; + if (pos >= Capacity) + { + return ResultFs.TooLongPath.Log(); + } + + _buffer[pos] = value; + _pos = pos + 1; + return Result.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Append(ReadOnlySpan value) + { + int pos = _pos; + if (pos + value.Length >= Capacity) + { + return ResultFs.TooLongPath.Log(); + } + + value.CopyTo(_buffer.Slice(pos)); + _pos = pos + value.Length; + return Result.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result AppendWithPrecedingSeparator(byte value) + { + int pos = _pos; + if (pos + 1 >= Capacity) + { + // Append the separator if there's enough space + if (pos < Capacity) + { + _buffer[pos] = (byte)'/'; + _pos = pos + 1; + } + + return ResultFs.TooLongPath.Log(); + } + + _buffer[pos] = (byte)'/'; + _buffer[pos + 1] = value; + _pos = pos + 2; + return Result.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result GoUpLevels(int count) + { + Debug.Assert(count > 0); + + int separators = 0; + int pos = _pos - 1; + + for (; pos >= 0; pos--) + { + if (PathTools.IsDirectorySeparator(_buffer[pos])) + { + separators++; + + if (separators == count) break; + } + } + + if (separators != count) return ResultFs.DirectoryUnobtainable.Log(); + + _pos = pos; + return Result.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Terminate() + { + if (_buffer.Length > _pos) + { + _buffer[_pos] = 0; + } + } + + public override string ToString() + { + return StringUtils.Utf8ZToString(_buffer.Slice(0, Length)); + } } diff --git a/src/LibHac/Common/Ref.cs b/src/LibHac/Common/Ref.cs index 822245a1..233a1b42 100644 --- a/src/LibHac/Common/Ref.cs +++ b/src/LibHac/Common/Ref.cs @@ -6,129 +6,128 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common +namespace LibHac.Common; + +/// +/// A that can store a reference to a value of a specified type. +/// +/// The type of value to reference. +public readonly ref struct Ref { /// - /// A that can store a reference to a value of a specified type. + /// The 1-length instance used to track the target value. /// - /// The type of value to reference. - public readonly ref struct Ref + private readonly Span _span; + + /// + /// Initializes a new instance of the struct. + /// + /// The reference to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ref(ref T value) { - /// - /// The 1-length instance used to track the target value. - /// - private readonly Span _span; - - /// - /// Initializes a new instance of the struct. - /// - /// The reference to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Ref(ref T value) - { - _span = MemoryMarshal.CreateSpan(ref value, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The pointer to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe Ref(void* pointer) - : this(ref Unsafe.AsRef(pointer)) - { - } - - /// - /// Gets the reference represented by the current instance. - /// - public ref T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref MemoryMarshal.GetReference(_span); - } - - /// - /// Returns a value that indicates whether the current is . - /// - /// if the held reference is ; - /// otherwise . - public bool IsNull - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Unsafe.IsNullRef(ref Value); - } - - /// - /// Implicitly gets the value from a given instance. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T(Ref reference) - { - return reference.Value; - } + _span = MemoryMarshal.CreateSpan(ref value, 1); } /// - /// A that can store a reference to a value of a specified type. + /// Initializes a new instance of the struct. /// - /// The type of value to reference. - public readonly ref struct ReadOnlyRef + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Ref(void* pointer) + : this(ref Unsafe.AsRef(pointer)) { - /// - /// The 1-length instance used to track the target value. - /// - private readonly ReadOnlySpan _span; - - /// - /// Initializes a new instance of the struct. - /// - /// The reference to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyRef(in T value) - { - _span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in value), 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The pointer to the target value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlyRef(void* pointer) - : this(in Unsafe.AsRef(pointer)) - { - } - - /// - /// Gets the reference represented by the current instance. - /// - public ref readonly T Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref MemoryMarshal.GetReference(_span); - } - - /// - /// Returns a value that indicates whether the current is . - /// - /// if the held reference is ; - /// otherwise . - public bool IsNull - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Unsafe.IsNullRef(ref Unsafe.AsRef(in Value)); - } - - /// - /// Implicitly gets the value from a given instance. - /// - /// The input instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T(ReadOnlyRef reference) - { - return reference.Value; - } } -} \ No newline at end of file + + /// + /// Gets the reference represented by the current instance. + /// + public ref T Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref MemoryMarshal.GetReference(_span); + } + + /// + /// Returns a value that indicates whether the current is . + /// + /// if the held reference is ; + /// otherwise . + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unsafe.IsNullRef(ref Value); + } + + /// + /// Implicitly gets the value from a given instance. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T(Ref reference) + { + return reference.Value; + } +} + +/// +/// A that can store a reference to a value of a specified type. +/// +/// The type of value to reference. +public readonly ref struct ReadOnlyRef +{ + /// + /// The 1-length instance used to track the target value. + /// + private readonly ReadOnlySpan _span; + + /// + /// Initializes a new instance of the struct. + /// + /// The reference to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyRef(in T value) + { + _span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in value), 1); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The pointer to the target value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ReadOnlyRef(void* pointer) + : this(in Unsafe.AsRef(pointer)) + { + } + + /// + /// Gets the reference represented by the current instance. + /// + public ref readonly T Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref MemoryMarshal.GetReference(_span); + } + + /// + /// Returns a value that indicates whether the current is . + /// + /// if the held reference is ; + /// otherwise . + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Unsafe.IsNullRef(ref Unsafe.AsRef(in Value)); + } + + /// + /// Implicitly gets the value from a given instance. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T(ReadOnlyRef reference) + { + return reference.Value; + } +} diff --git a/src/LibHac/Common/RentedArray.cs b/src/LibHac/Common/RentedArray.cs index dde343fb..baf29618 100644 --- a/src/LibHac/Common/RentedArray.cs +++ b/src/LibHac/Common/RentedArray.cs @@ -2,40 +2,39 @@ using System.Buffers; using System.Runtime.CompilerServices; -namespace LibHac.Common +namespace LibHac.Common; + +public readonly ref struct RentedArray { - public readonly ref struct RentedArray + // It's faster to create new smaller arrays than rent them + private const int RentThresholdBytes = 512; + private static int RentThresholdElements => RentThresholdBytes / Unsafe.SizeOf(); + + private readonly Span _span; + + public T[] Array { get; } + public Span Span => _span; + + public RentedArray(int minimumSize) { - // It's faster to create new smaller arrays than rent them - private const int RentThresholdBytes = 512; - private static int RentThresholdElements => RentThresholdBytes / Unsafe.SizeOf(); - - private readonly Span _span; - - public T[] Array { get; } - public Span Span => _span; - - public RentedArray(int minimumSize) + if (minimumSize >= RentThresholdElements) { - if (minimumSize >= RentThresholdElements) - { - Array = ArrayPool.Shared.Rent(minimumSize); - } - else - { - Array = new T[minimumSize]; - } - - _span = Array.AsSpan(0, minimumSize); + Array = ArrayPool.Shared.Rent(minimumSize); + } + else + { + Array = new T[minimumSize]; } - public void Dispose() + _span = Array.AsSpan(0, minimumSize); + } + + public void Dispose() + { + // Only return if array was rented + if (_span.Length >= RentThresholdElements) { - // Only return if array was rented - if (_span.Length >= RentThresholdElements) - { - ArrayPool.Shared.Return(Array); - } + ArrayPool.Shared.Return(Array); } } } diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs index 7d783f88..6d1f0787 100644 --- a/src/LibHac/Common/ResultLibHac.cs +++ b/src/LibHac/Common/ResultLibHac.cs @@ -11,86 +11,85 @@ using System.Runtime.CompilerServices; -namespace LibHac.Common +namespace LibHac.Common; + +public static class ResultLibHac { - public static class ResultLibHac - { - public const int ModuleLibHac = 428; + public const int ModuleLibHac = 428; - /// Error code: 2428-0001; Range: 1-49; Inner value: 0x3ac - public static Result.Base InvalidArgument => new Result.Base(ModuleLibHac, 1, 49); - /// Error code: 2428-0002; Inner value: 0x5ac - public static Result.Base NullArgument => new Result.Base(ModuleLibHac, 2); - /// Error code: 2428-0003; Inner value: 0x7ac - public static Result.Base ArgumentOutOfRange => new Result.Base(ModuleLibHac, 3); - /// Error code: 2428-0004; Inner value: 0x9ac - public static Result.Base BufferTooSmall => new Result.Base(ModuleLibHac, 4); + /// Error code: 2428-0001; Range: 1-49; Inner value: 0x3ac + public static Result.Base InvalidArgument => new Result.Base(ModuleLibHac, 1, 49); + /// Error code: 2428-0002; Inner value: 0x5ac + public static Result.Base NullArgument => new Result.Base(ModuleLibHac, 2); + /// Error code: 2428-0003; Inner value: 0x7ac + public static Result.Base ArgumentOutOfRange => new Result.Base(ModuleLibHac, 3); + /// Error code: 2428-0004; Inner value: 0x9ac + public static Result.Base BufferTooSmall => new Result.Base(ModuleLibHac, 4); - /// Error code: 2428-0051; Inner value: 0x67ac - public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51); - /// Error code: 2428-0101; Inner value: 0xcbac - public static Result.Base NotImplemented => new Result.Base(ModuleLibHac, 101); + /// Error code: 2428-0051; Inner value: 0x67ac + public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51); + /// Error code: 2428-0101; Inner value: 0xcbac + public static Result.Base NotImplemented => new Result.Base(ModuleLibHac, 101); - /// Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac - public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); } - /// Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac - public static Result.Base InvalidInitialProcessData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); } - /// Error code: 2428-1002; Range: 1002-1009; Inner value: 0x7d5ac - public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1002, 1009); } - /// The size of the KIP file was smaller than expected.
Error code: 2428-1003; Inner value: 0x7d7ac
- public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1003); - /// The magic value of the KIP file was not KIP1.
Error code: 2428-1004; Inner value: 0x7d9ac
- public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1004); - /// The size of the compressed KIP segment was smaller than expected.
Error code: 2428-1005; Inner value: 0x7dbac
- public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1005); - /// An error occurred while decompressing a KIP segment.
Error code: 2428-1006; Inner value: 0x7ddac
- public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1006); + /// Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac + public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); } + /// Error code: 2428-1001; Range: 1001-1019; Inner value: 0x7d3ac + public static Result.Base InvalidInitialProcessData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1001, 1019); } + /// Error code: 2428-1002; Range: 1002-1009; Inner value: 0x7d5ac + public static Result.Base InvalidKip { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1002, 1009); } + /// The size of the KIP file was smaller than expected.
Error code: 2428-1003; Inner value: 0x7d7ac
+ public static Result.Base InvalidKipFileSize => new Result.Base(ModuleLibHac, 1003); + /// The magic value of the KIP file was not KIP1.
Error code: 2428-1004; Inner value: 0x7d9ac
+ public static Result.Base InvalidKipMagic => new Result.Base(ModuleLibHac, 1004); + /// The size of the compressed KIP segment was smaller than expected.
Error code: 2428-1005; Inner value: 0x7dbac
+ public static Result.Base InvalidKipSegmentSize => new Result.Base(ModuleLibHac, 1005); + /// An error occurred while decompressing a KIP segment.
Error code: 2428-1006; Inner value: 0x7ddac
+ public static Result.Base KipSegmentDecompressionFailed => new Result.Base(ModuleLibHac, 1006); - /// Error code: 2428-1010; Range: 1010-1019; Inner value: 0x7e5ac - public static Result.Base InvalidIni { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1010, 1019); } - /// The size of the INI file was smaller than expected.
Error code: 2428-1011; Inner value: 0x7e7ac
- public static Result.Base InvalidIniFileSize => new Result.Base(ModuleLibHac, 1011); - /// The magic value of the INI file was not INI1.
Error code: 2428-1012; Inner value: 0x7e9ac
- public static Result.Base InvalidIniMagic => new Result.Base(ModuleLibHac, 1012); - /// The INI had an invalid process count.
Error code: 2428-1013; Inner value: 0x7ebac
- public static Result.Base InvalidIniProcessCount => new Result.Base(ModuleLibHac, 1013); + /// Error code: 2428-1010; Range: 1010-1019; Inner value: 0x7e5ac + public static Result.Base InvalidIni { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1010, 1019); } + /// The size of the INI file was smaller than expected.
Error code: 2428-1011; Inner value: 0x7e7ac
+ public static Result.Base InvalidIniFileSize => new Result.Base(ModuleLibHac, 1011); + /// The magic value of the INI file was not INI1.
Error code: 2428-1012; Inner value: 0x7e9ac
+ public static Result.Base InvalidIniMagic => new Result.Base(ModuleLibHac, 1012); + /// The INI had an invalid process count.
Error code: 2428-1013; Inner value: 0x7ebac
+ public static Result.Base InvalidIniProcessCount => new Result.Base(ModuleLibHac, 1013); - /// Error code: 2428-1020; Range: 1020-1039; Inner value: 0x7f9ac - public static Result.Base InvalidPackage2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1020, 1039); } - /// Error code: 2428-1021; Inner value: 0x7fbac - public static Result.Base InvalidPackage2HeaderSignature => new Result.Base(ModuleLibHac, 1021); - /// Error code: 2428-1022; Inner value: 0x7fdac - public static Result.Base InvalidPackage2MetaSizeA => new Result.Base(ModuleLibHac, 1022); - /// Error code: 2428-1023; Inner value: 0x7ffac - public static Result.Base InvalidPackage2MetaSizeB => new Result.Base(ModuleLibHac, 1023); - /// Error code: 2428-1024; Inner value: 0x801ac - public static Result.Base InvalidPackage2MetaKeyGeneration => new Result.Base(ModuleLibHac, 1024); - /// Error code: 2428-1025; Inner value: 0x803ac - public static Result.Base InvalidPackage2MetaMagic => new Result.Base(ModuleLibHac, 1025); - /// Error code: 2428-1026; Inner value: 0x805ac - public static Result.Base InvalidPackage2MetaEntryPointAlignment => new Result.Base(ModuleLibHac, 1026); - /// Error code: 2428-1027; Inner value: 0x807ac - public static Result.Base InvalidPackage2MetaPayloadAlignment => new Result.Base(ModuleLibHac, 1027); - /// Error code: 2428-1028; Inner value: 0x809ac - public static Result.Base InvalidPackage2MetaPayloadSizeAlignment => new Result.Base(ModuleLibHac, 1028); - /// Error code: 2428-1029; Inner value: 0x80bac - public static Result.Base InvalidPackage2MetaTotalSize => new Result.Base(ModuleLibHac, 1029); - /// Error code: 2428-1030; Inner value: 0x80dac - public static Result.Base InvalidPackage2MetaPayloadSize => new Result.Base(ModuleLibHac, 1030); - /// Error code: 2428-1031; Inner value: 0x80fac - public static Result.Base InvalidPackage2MetaPayloadsOverlap => new Result.Base(ModuleLibHac, 1031); - /// Error code: 2428-1032; Inner value: 0x811ac - public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); - /// Error code: 2428-1033; Inner value: 0x813ac - public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); + /// Error code: 2428-1020; Range: 1020-1039; Inner value: 0x7f9ac + public static Result.Base InvalidPackage2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1020, 1039); } + /// Error code: 2428-1021; Inner value: 0x7fbac + public static Result.Base InvalidPackage2HeaderSignature => new Result.Base(ModuleLibHac, 1021); + /// Error code: 2428-1022; Inner value: 0x7fdac + public static Result.Base InvalidPackage2MetaSizeA => new Result.Base(ModuleLibHac, 1022); + /// Error code: 2428-1023; Inner value: 0x7ffac + public static Result.Base InvalidPackage2MetaSizeB => new Result.Base(ModuleLibHac, 1023); + /// Error code: 2428-1024; Inner value: 0x801ac + public static Result.Base InvalidPackage2MetaKeyGeneration => new Result.Base(ModuleLibHac, 1024); + /// Error code: 2428-1025; Inner value: 0x803ac + public static Result.Base InvalidPackage2MetaMagic => new Result.Base(ModuleLibHac, 1025); + /// Error code: 2428-1026; Inner value: 0x805ac + public static Result.Base InvalidPackage2MetaEntryPointAlignment => new Result.Base(ModuleLibHac, 1026); + /// Error code: 2428-1027; Inner value: 0x807ac + public static Result.Base InvalidPackage2MetaPayloadAlignment => new Result.Base(ModuleLibHac, 1027); + /// Error code: 2428-1028; Inner value: 0x809ac + public static Result.Base InvalidPackage2MetaPayloadSizeAlignment => new Result.Base(ModuleLibHac, 1028); + /// Error code: 2428-1029; Inner value: 0x80bac + public static Result.Base InvalidPackage2MetaTotalSize => new Result.Base(ModuleLibHac, 1029); + /// Error code: 2428-1030; Inner value: 0x80dac + public static Result.Base InvalidPackage2MetaPayloadSize => new Result.Base(ModuleLibHac, 1030); + /// Error code: 2428-1031; Inner value: 0x80fac + public static Result.Base InvalidPackage2MetaPayloadsOverlap => new Result.Base(ModuleLibHac, 1031); + /// Error code: 2428-1032; Inner value: 0x811ac + public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); + /// Error code: 2428-1033; Inner value: 0x813ac + public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); - /// Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac - public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); } - /// Error code: 2428-1041; Inner value: 0x823ac - public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041); - /// Error code: 2428-1042; Inner value: 0x825ac - public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042); - /// Error code: 2428-1043; Inner value: 0x827ac - public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043); - } + /// Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac + public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); } + /// Error code: 2428-1041; Inner value: 0x823ac + public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041); + /// Error code: 2428-1042; Inner value: 0x825ac + public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042); + /// Error code: 2428-1043; Inner value: 0x827ac + public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043); } diff --git a/src/LibHac/Common/SharedObjectHelpers.cs b/src/LibHac/Common/SharedObjectHelpers.cs index db0f8097..f4db218d 100644 --- a/src/LibHac/Common/SharedObjectHelpers.cs +++ b/src/LibHac/Common/SharedObjectHelpers.cs @@ -1,22 +1,21 @@ using System.Runtime.CompilerServices; -namespace LibHac.Common -{ - public static class Shared - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Move(ref T value) - { - T tmp = value; - value = default; - return tmp; - } +namespace LibHac.Common; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Move(out T dest, ref T value) - { - dest = value; - value = default; - } +public static class Shared +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Move(ref T value) + { + T tmp = value; + value = default; + return tmp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Move(out T dest, ref T value) + { + dest = value; + value = default; } } diff --git a/src/LibHac/Common/SharedRef.cs b/src/LibHac/Common/SharedRef.cs index 5f1b603f..dfa64010 100644 --- a/src/LibHac/Common/SharedRef.cs +++ b/src/LibHac/Common/SharedRef.cs @@ -6,235 +6,283 @@ using LibHac.Diag; #pragma warning disable LH0001 -namespace LibHac.Common +namespace LibHac.Common; + +public static class SharedRefExtensions { - public static class SharedRefExtensions + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref SharedRef Ref(this in SharedRef value) where T : class, IDisposable { - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref SharedRef Ref(this in SharedRef value) where T : class, IDisposable + IL.Emit.Ldarg(nameof(value)); + IL.Emit.Ret(); + throw IL.Unreachable(); + } + + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref WeakRef Ref(this in WeakRef value) where T : class, IDisposable + { + IL.Emit.Ldarg(nameof(value)); + IL.Emit.Ret(); + throw IL.Unreachable(); + } +} + +internal class RefCount +{ + private int _count; + private int _weakCount; + private IDisposable _value; + + public RefCount(IDisposable value) + { + _count = 1; + _weakCount = 1; + _value = value; + } + + public int UseCount() + { + return _count; + } + + public void Increment() + { + // This function shouldn't ever be called after the ref count is reduced to zero. + // SharedRef clearing their RefCountBase field after decrementing the ref count *should* already + // prevent this if everything works properly. + Assert.SdkRequiresGreater(_count, 0); + + Interlocked.Increment(ref _count); + } + + public void IncrementWeak() + { + Assert.SdkRequiresGreater(_count, 0); + + Interlocked.Increment(ref _weakCount); + } + + public bool IncrementIfNotZero() + { + int count = Volatile.Read(ref _count); + + while (count != 0) { - IL.Emit.Ldarg(nameof(value)); - IL.Emit.Ret(); - throw IL.Unreachable(); + int oldValue = Interlocked.CompareExchange(ref _count, count + 1, count); + if (oldValue == count) + { + return true; + } + + count = oldValue; } - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref WeakRef Ref(this in WeakRef value) where T : class, IDisposable + return false; + } + + public void Decrement() + { + if (Interlocked.Decrement(ref _count) == 0) { - IL.Emit.Ldarg(nameof(value)); - IL.Emit.Ret(); - throw IL.Unreachable(); + Destroy(); + DecrementWeak(); } } - internal class RefCount + public void DecrementWeak() { - private int _count; - private int _weakCount; - private IDisposable _value; - - public RefCount(IDisposable value) + if (Interlocked.Decrement(ref _weakCount) == 0) { - _count = 1; - _weakCount = 1; - _value = value; - } - - public int UseCount() - { - return _count; - } - - public void Increment() - { - // This function shouldn't ever be called after the ref count is reduced to zero. - // SharedRef clearing their RefCountBase field after decrementing the ref count *should* already - // prevent this if everything works properly. - Assert.SdkRequiresGreater(_count, 0); - - Interlocked.Increment(ref _count); - } - - public void IncrementWeak() - { - Assert.SdkRequiresGreater(_count, 0); - - Interlocked.Increment(ref _weakCount); - } - - public bool IncrementIfNotZero() - { - int count = Volatile.Read(ref _count); - - while (count != 0) - { - int oldValue = Interlocked.CompareExchange(ref _count, count + 1, count); - if (oldValue == count) - { - return true; - } - - count = oldValue; - } - - return false; - } - - public void Decrement() - { - if (Interlocked.Decrement(ref _count) == 0) - { - Destroy(); - DecrementWeak(); - } - } - - public void DecrementWeak() - { - if (Interlocked.Decrement(ref _weakCount) == 0) - { - // Deallocate - } - } - - private void Destroy() - { - if (_value is not null) - { - _value.Dispose(); - _value = null; - } + // Deallocate } } - [NonCopyableDisposable] - public struct SharedRef : IDisposable where T : class, IDisposable + private void Destroy() { - // SharedRef and WeakRef should share a base type, but struct inheritance doesn't exist in C#. - // This presents a problem because C# also doesn't have friend classes, these two types need to - // access each other's fields and we'd rather the fields' visibility stay private. Because the - // two types have the same layout we can hack around this with some Unsafe.As shenanigans. - private T _value; - private RefCount _refCount; - - public SharedRef(T value) + if (_value is not null) { - _value = value; - _refCount = new RefCount(value); + _value.Dispose(); + _value = null; + } + } +} + +[NonCopyableDisposable] +public struct SharedRef : IDisposable where T : class, IDisposable +{ + // SharedRef and WeakRef should share a base type, but struct inheritance doesn't exist in C#. + // This presents a problem because C# also doesn't have friend classes, these two types need to + // access each other's fields and we'd rather the fields' visibility stay private. Because the + // two types have the same layout we can hack around this with some Unsafe.As shenanigans. + private T _value; + private RefCount _refCount; + + public SharedRef(T value) + { + _value = value; + _refCount = new RefCount(value); + } + + [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] + public void Dispose() + { + // This function shouldn't be called manually and should always be called at the end of a using block. + // This means we don't need to clear any fields because we're going out of scope anyway. + _refCount?.Decrement(); + } + + /// + /// Used to manually dispose the from the Dispose methods of other types. + /// + public void Destroy() + { + Reset(); + } + + public readonly T Get => _value; + public readonly bool HasValue => Get is not null; + public readonly int UseCount => _refCount?.UseCount() ?? 0; + + public static SharedRef CreateMove(ref SharedRef other) where TFrom : class, T + { + var sharedRef = new SharedRef(); + + sharedRef._value = Unsafe.As(ref other._value); + sharedRef._refCount = other._refCount; + + other._value = null; + other._refCount = null; + + return sharedRef; + } + + public static SharedRef CreateCopy(in SharedRef other) where TFrom : class, T + { + var sharedRef = new SharedRef(); + + sharedRef._value = Unsafe.As(ref other.Ref()._value); + sharedRef._refCount = other._refCount; + + sharedRef._refCount?.Increment(); + + return sharedRef; + } + + public static SharedRef Create(in WeakRef other) where TFrom : class, T + { + ref SharedRef otherShared = ref Unsafe.As, SharedRef>(ref other.Ref()); + + var sharedRef = new SharedRef(); + + if (otherShared._refCount is not null && otherShared._refCount.IncrementIfNotZero()) + { + sharedRef._value = Unsafe.As(ref otherShared._value); + sharedRef._refCount = otherShared._refCount; + } + else + { + ThrowBadWeakPtr(); } - [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] - public void Dispose() + return sharedRef; + } + + public static SharedRef Create(ref UniqueRef other) where TFrom : class, T + { + var sharedRef = new SharedRef(); + TFrom value = other.Get; + + if (value is not null) { - // This function shouldn't be called manually and should always be called at the end of a using block. - // This means we don't need to clear any fields because we're going out of scope anyway. - _refCount?.Decrement(); + sharedRef._value = value; + sharedRef._refCount = new RefCount(value); + other.Release(); + } + else + { + sharedRef._value = null; + sharedRef._refCount = null; } - /// - /// Used to manually dispose the from the Dispose methods of other types. - /// - public void Destroy() + return sharedRef; + } + + public void Swap(ref SharedRef other) + { + (other._value, _value) = (_value, other._value); + (other._refCount, _refCount) = (_refCount, other._refCount); + } + + public void Reset() + { + _value = null; + RefCount oldRefCount = _refCount; + _refCount = null; + + oldRefCount?.Decrement(); + } + + public void Reset(T value) + { + _value = value; + RefCount oldRefCount = _refCount; + _refCount = new RefCount(value); + + oldRefCount?.Decrement(); + } + + public void SetByMove(ref SharedRef other) where TFrom : class, T + { + RefCount oldRefCount = _refCount; + + _value = Unsafe.As(ref other._value); + _refCount = other._refCount; + + other._value = null; + other._refCount = null; + + oldRefCount?.Decrement(); + } + + public void SetByCopy(in SharedRef other) where TFrom : class, T + { + RefCount oldRefCount = _refCount; + RefCount otherRef = other._refCount; + + otherRef?.Increment(); + + _value = Unsafe.As(ref other.Ref()._value); + _refCount = otherRef; + + oldRefCount?.Decrement(); + } + + public void Set(ref UniqueRef other) where TFrom : class, T + { + RefCount oldRefCount = _refCount; + TFrom otherValue = other.Release(); + + if (otherValue is not null) { - Reset(); + _value = Unsafe.As(ref otherValue); + _refCount = new RefCount(otherValue); } - - public readonly T Get => _value; - public readonly bool HasValue => Get is not null; - public readonly int UseCount => _refCount?.UseCount() ?? 0; - - public static SharedRef CreateMove(ref SharedRef other) where TFrom : class, T - { - var sharedRef = new SharedRef(); - - sharedRef._value = Unsafe.As(ref other._value); - sharedRef._refCount = other._refCount; - - other._value = null; - other._refCount = null; - - return sharedRef; - } - - public static SharedRef CreateCopy(in SharedRef other) where TFrom : class, T - { - var sharedRef = new SharedRef(); - - sharedRef._value = Unsafe.As(ref other.Ref()._value); - sharedRef._refCount = other._refCount; - - sharedRef._refCount?.Increment(); - - return sharedRef; - } - - public static SharedRef Create(in WeakRef other) where TFrom : class, T - { - ref SharedRef otherShared = ref Unsafe.As, SharedRef>(ref other.Ref()); - - var sharedRef = new SharedRef(); - - if (otherShared._refCount is not null && otherShared._refCount.IncrementIfNotZero()) - { - sharedRef._value = Unsafe.As(ref otherShared._value); - sharedRef._refCount = otherShared._refCount; - } - else - { - ThrowBadWeakPtr(); - } - - return sharedRef; - } - - public static SharedRef Create(ref UniqueRef other) where TFrom : class, T - { - var sharedRef = new SharedRef(); - TFrom value = other.Get; - - if (value is not null) - { - sharedRef._value = value; - sharedRef._refCount = new RefCount(value); - other.Release(); - } - else - { - sharedRef._value = null; - sharedRef._refCount = null; - } - - return sharedRef; - } - - public void Swap(ref SharedRef other) - { - (other._value, _value) = (_value, other._value); - (other._refCount, _refCount) = (_refCount, other._refCount); - } - - public void Reset() + else { _value = null; - RefCount oldRefCount = _refCount; _refCount = null; - - oldRefCount?.Decrement(); } - public void Reset(T value) + oldRefCount?.Decrement(); + } + + public bool TryCastSet(ref SharedRef other) where TFrom : class, IDisposable + { + RefCount oldRefCount = _refCount; + TFrom otherValue = other.Get; + + if (otherValue is T) { - _value = value; - RefCount oldRefCount = _refCount; - _refCount = new RefCount(value); - - oldRefCount?.Decrement(); - } - - public void SetByMove(ref SharedRef other) where TFrom : class, T - { - RefCount oldRefCount = _refCount; - _value = Unsafe.As(ref other._value); _refCount = other._refCount; @@ -242,200 +290,151 @@ namespace LibHac.Common other._refCount = null; oldRefCount?.Decrement(); + + return true; } - public void SetByCopy(in SharedRef other) where TFrom : class, T - { - RefCount oldRefCount = _refCount; - RefCount otherRef = other._refCount; - - otherRef?.Increment(); - - _value = Unsafe.As(ref other.Ref()._value); - _refCount = otherRef; - - oldRefCount?.Decrement(); - } - - public void Set(ref UniqueRef other) where TFrom : class, T - { - RefCount oldRefCount = _refCount; - TFrom otherValue = other.Release(); - - if (otherValue is not null) - { - _value = Unsafe.As(ref otherValue); - _refCount = new RefCount(otherValue); - } - else - { - _value = null; - _refCount = null; - } - - oldRefCount?.Decrement(); - } - - public bool TryCastSet(ref SharedRef other) where TFrom : class, IDisposable - { - RefCount oldRefCount = _refCount; - TFrom otherValue = other.Get; - - if (otherValue is T) - { - _value = Unsafe.As(ref other._value); - _refCount = other._refCount; - - other._value = null; - other._refCount = null; - - oldRefCount?.Decrement(); - - return true; - } - - return false; - } - - private static void ThrowBadWeakPtr() - { - throw new ObjectDisposedException(string.Empty, "bad_weak_ptr"); - } + return false; } - [NonCopyableDisposable] - public struct WeakRef : IDisposable where T : class, IDisposable + private static void ThrowBadWeakPtr() { - private T _value; - private RefCount _refCount; - - public WeakRef(in SharedRef other) - { - this = Create(in other); - } - - [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] - public void Dispose() - { - _refCount?.DecrementWeak(); - } - - // A copy of Dispose so we can call it ourselves inside the struct - private void DisposeInternal() - { - _refCount?.DecrementWeak(); - } - - /// - /// Used to manually dispose the from the Dispose methods of other types. - /// - public void Destroy() - { - Reset(); - } - - public readonly int UseCount => _refCount?.UseCount() ?? 0; - public readonly bool Expired => UseCount == 0; - - public static WeakRef CreateMove(ref WeakRef other) where TFrom : class, T - { - var weakRef = new WeakRef(); - - weakRef._value = Unsafe.As(ref other._value); - weakRef._refCount = other._refCount; - - other._value = null; - other._refCount = null; - - return weakRef; - } - - public static WeakRef CreateCopy(in WeakRef other) where TFrom : class, T - { - var weakRef = new WeakRef(); - - if (other._refCount is not null) - { - weakRef._refCount = other._refCount; - weakRef._refCount.IncrementWeak(); - - if (weakRef._refCount.IncrementIfNotZero()) - { - weakRef._value = Unsafe.As(ref other.Ref()._value); - weakRef._refCount.Decrement(); - } - } - - return weakRef; - } - - public static WeakRef Create(in SharedRef other) where TFrom : class, T - { - ref WeakRef otherWeak = ref Unsafe.As, WeakRef>(ref other.Ref()); - - var weakRef = new WeakRef(); - - if (otherWeak._refCount is not null) - { - weakRef._value = Unsafe.As(ref otherWeak._value); - weakRef._refCount = otherWeak._refCount; - - weakRef._refCount.IncrementWeak(); - } - else - { - weakRef._value = null; - weakRef._refCount = null; - } - - return weakRef; - } - - public void Swap(ref WeakRef other) - { - (other._value, _value) = (_value, other._value); - (other._refCount, _refCount) = (_refCount, other._refCount); - } - - public void Reset() - { - var temp = new WeakRef(); - Swap(ref temp); - temp.DisposeInternal(); - } - - public void SetMove(ref WeakRef other) where TFrom : class, T - { - WeakRef temp = CreateMove(ref other); - Swap(ref temp); - temp.DisposeInternal(); - } - - public void SetCopy(in WeakRef other) where TFrom : class, T - { - WeakRef temp = CreateCopy(in other); - Swap(ref temp); - temp.DisposeInternal(); - } - - public void Set(in SharedRef other) where TFrom : class, T - { - WeakRef temp = Create(in other); - Swap(ref temp); - temp.DisposeInternal(); - } - - public readonly SharedRef Lock() - { - var sharedRef = new SharedRef(); - - if (_refCount is not null && _refCount.IncrementIfNotZero()) - { - Unsafe.As, WeakRef>(ref sharedRef)._value = _value; - Unsafe.As, WeakRef>(ref sharedRef)._refCount = _refCount; - } - - return sharedRef; - } + throw new ObjectDisposedException(string.Empty, "bad_weak_ptr"); + } +} + +[NonCopyableDisposable] +public struct WeakRef : IDisposable where T : class, IDisposable +{ + private T _value; + private RefCount _refCount; + + public WeakRef(in SharedRef other) + { + this = Create(in other); + } + + [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] + public void Dispose() + { + _refCount?.DecrementWeak(); + } + + // A copy of Dispose so we can call it ourselves inside the struct + private void DisposeInternal() + { + _refCount?.DecrementWeak(); + } + + /// + /// Used to manually dispose the from the Dispose methods of other types. + /// + public void Destroy() + { + Reset(); + } + + public readonly int UseCount => _refCount?.UseCount() ?? 0; + public readonly bool Expired => UseCount == 0; + + public static WeakRef CreateMove(ref WeakRef other) where TFrom : class, T + { + var weakRef = new WeakRef(); + + weakRef._value = Unsafe.As(ref other._value); + weakRef._refCount = other._refCount; + + other._value = null; + other._refCount = null; + + return weakRef; + } + + public static WeakRef CreateCopy(in WeakRef other) where TFrom : class, T + { + var weakRef = new WeakRef(); + + if (other._refCount is not null) + { + weakRef._refCount = other._refCount; + weakRef._refCount.IncrementWeak(); + + if (weakRef._refCount.IncrementIfNotZero()) + { + weakRef._value = Unsafe.As(ref other.Ref()._value); + weakRef._refCount.Decrement(); + } + } + + return weakRef; + } + + public static WeakRef Create(in SharedRef other) where TFrom : class, T + { + ref WeakRef otherWeak = ref Unsafe.As, WeakRef>(ref other.Ref()); + + var weakRef = new WeakRef(); + + if (otherWeak._refCount is not null) + { + weakRef._value = Unsafe.As(ref otherWeak._value); + weakRef._refCount = otherWeak._refCount; + + weakRef._refCount.IncrementWeak(); + } + else + { + weakRef._value = null; + weakRef._refCount = null; + } + + return weakRef; + } + + public void Swap(ref WeakRef other) + { + (other._value, _value) = (_value, other._value); + (other._refCount, _refCount) = (_refCount, other._refCount); + } + + public void Reset() + { + var temp = new WeakRef(); + Swap(ref temp); + temp.DisposeInternal(); + } + + public void SetMove(ref WeakRef other) where TFrom : class, T + { + WeakRef temp = CreateMove(ref other); + Swap(ref temp); + temp.DisposeInternal(); + } + + public void SetCopy(in WeakRef other) where TFrom : class, T + { + WeakRef temp = CreateCopy(in other); + Swap(ref temp); + temp.DisposeInternal(); + } + + public void Set(in SharedRef other) where TFrom : class, T + { + WeakRef temp = Create(in other); + Swap(ref temp); + temp.DisposeInternal(); + } + + public readonly SharedRef Lock() + { + var sharedRef = new SharedRef(); + + if (_refCount is not null && _refCount.IncrementIfNotZero()) + { + Unsafe.As, WeakRef>(ref sharedRef)._value = _value; + Unsafe.As, WeakRef>(ref sharedRef)._refCount = _refCount; + } + + return sharedRef; } } diff --git a/src/LibHac/Common/SpanHelpers.cs b/src/LibHac/Common/SpanHelpers.cs index fd0543ad..6b1334fb 100644 --- a/src/LibHac/Common/SpanHelpers.cs +++ b/src/LibHac/Common/SpanHelpers.cs @@ -2,116 +2,115 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Common -{ - public static class SpanExtensions - { - /// - /// Gets the element at the specified zero-based index or gets 0 if the index is out-of-bounds. - /// - /// The containing the element to get. - /// The zero-based index of the element. - /// The element at the specified index or 0 if out-of-bounds. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte At(in this ReadOnlySpan span, int i) - { - return (uint)i >= (uint)span.Length ? (byte)0 : span[i]; - } +namespace LibHac.Common; - /// - /// Gets the element at the specified zero-based index or gets 0 if the index is out-of-bounds. - /// - /// The containing the element to get. - /// The zero-based index of the element. - /// The element at the specified index or 0 if out-of-bounds. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte At(in this Span span, int i) - { - return (uint)i >= (uint)span.Length ? (byte)0 : span[i]; - } +public static class SpanExtensions +{ + /// + /// Gets the element at the specified zero-based index or gets 0 if the index is out-of-bounds. + /// + /// The containing the element to get. + /// The zero-based index of the element. + /// The element at the specified index or 0 if out-of-bounds. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte At(in this ReadOnlySpan span, int i) + { + return (uint)i >= (uint)span.Length ? (byte)0 : span[i]; } - public static class SpanHelpers + /// + /// Gets the element at the specified zero-based index or gets 0 if the index is out-of-bounds. + /// + /// The containing the element to get. + /// The zero-based index of the element. + /// The element at the specified index or 0 if out-of-bounds. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte At(in this Span span, int i) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span CreateSpan(ref T reference, int length) - { - return MemoryMarshal.CreateSpan(ref reference, length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span AsSpan(ref T reference) where T : unmanaged - { - return CreateSpan(ref reference, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span AsSpan(ref TStruct reference) - where TStruct : unmanaged where TSpan : unmanaged - { - return CreateSpan(ref Unsafe.As(ref reference), - Unsafe.SizeOf() / Unsafe.SizeOf()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span AsByteSpan(ref T reference) where T : unmanaged - { - return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan CreateReadOnlySpan(in T reference, int length) - { - return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan AsReadOnlySpan(in T reference) where T : unmanaged - { - return CreateReadOnlySpan(in reference, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan AsReadOnlySpan(in TStruct reference) - where TStruct : unmanaged where TSpan : unmanaged - { - return CreateReadOnlySpan(in Unsafe.As(ref Unsafe.AsRef(in reference)), - Unsafe.SizeOf() / Unsafe.SizeOf()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan AsReadOnlyByteSpan(in T reference) where T : unmanaged - { - return CreateReadOnlySpan(in Unsafe.As(ref Unsafe.AsRef(in reference)), Unsafe.SizeOf()); - } - - // All AsStruct methods do bounds checks on the input - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T AsStruct(Span span) where T : unmanaged - { - return ref MemoryMarshal.Cast(span)[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref readonly T AsReadOnlyStruct(ReadOnlySpan span) where T : unmanaged - { - return ref MemoryMarshal.Cast(span)[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref TTo AsStruct(Span span) - where TFrom : unmanaged - where TTo : unmanaged - { - return ref MemoryMarshal.Cast(span)[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref readonly TTo AsStruct(ReadOnlySpan span) - where TFrom : unmanaged - where TTo : unmanaged - { - return ref MemoryMarshal.Cast(span)[0]; - } + return (uint)i >= (uint)span.Length ? (byte)0 : span[i]; + } +} + +public static class SpanHelpers +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span CreateSpan(ref T reference, int length) + { + return MemoryMarshal.CreateSpan(ref reference, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(ref T reference) where T : unmanaged + { + return CreateSpan(ref reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(ref TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsByteSpan(ref T reference) where T : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan CreateReadOnlySpan(in T reference, int length) + { + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(in T reference) where T : unmanaged + { + return CreateReadOnlySpan(in reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(in TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateReadOnlySpan(in Unsafe.As(ref Unsafe.AsRef(in reference)), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlyByteSpan(in T reference) where T : unmanaged + { + return CreateReadOnlySpan(in Unsafe.As(ref Unsafe.AsRef(in reference)), Unsafe.SizeOf()); + } + + // All AsStruct methods do bounds checks on the input + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsStruct(Span span) where T : unmanaged + { + return ref MemoryMarshal.Cast(span)[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T AsReadOnlyStruct(ReadOnlySpan span) where T : unmanaged + { + return ref MemoryMarshal.Cast(span)[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo AsStruct(Span span) + where TFrom : unmanaged + where TTo : unmanaged + { + return ref MemoryMarshal.Cast(span)[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly TTo AsStruct(ReadOnlySpan span) + where TFrom : unmanaged + where TTo : unmanaged + { + return ref MemoryMarshal.Cast(span)[0]; } } diff --git a/src/LibHac/Common/U8Span.cs b/src/LibHac/Common/U8Span.cs index 73db3349..dac9b2c5 100644 --- a/src/LibHac/Common/U8Span.cs +++ b/src/LibHac/Common/U8Span.cs @@ -5,99 +5,98 @@ using System.Runtime.InteropServices; using System.Text; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +public readonly ref struct U8Span { - [DebuggerDisplay("{ToString()}")] - public readonly ref struct U8Span + private readonly ReadOnlySpan _buffer; + + public ReadOnlySpan Value => _buffer; + public int Length => _buffer.Length; + + public static U8Span Empty => default; + + public byte this[int i] { - private readonly ReadOnlySpan _buffer; + get => _buffer[i]; + } - public ReadOnlySpan Value => _buffer; - public int Length => _buffer.Length; + public U8Span(ReadOnlySpan value) + { + _buffer = value; + } - public static U8Span Empty => default; + public U8Span(string value) + { + _buffer = Encoding.UTF8.GetBytes(value); + } - public byte this[int i] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetOrNull(int i) + { + byte value = 0; + + if ((uint)i < (uint)_buffer.Length) { - get => _buffer[i]; + value = GetUnsafe(i); } - public U8Span(ReadOnlySpan value) - { - _buffer = value; - } + return value; + } - public U8Span(string value) - { - _buffer = Encoding.UTF8.GetBytes(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetOrNull(int i) - { - byte value = 0; - - if ((uint)i < (uint)_buffer.Length) - { - value = GetUnsafe(i); - } - - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetUnsafe(int i) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetUnsafe(int i) + { #if DEBUG - return _buffer[i]; + return _buffer[i]; #else return Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), i); #endif - } - - public U8Span Slice(int start) - { - return new U8Span(_buffer.Slice(start)); - } - - public U8Span Slice(int start, int length) - { - return new U8Span(_buffer.Slice(start, length)); - } - - public static implicit operator ReadOnlySpan(in U8Span value) => value.Value; - - public static explicit operator string(in U8Span value) => value.ToString(); - public static explicit operator U8Span(string value) => new U8Span(value); - - public override string ToString() - { - return StringUtils.Utf8ZToString(_buffer); - } - - public U8String ToU8String() - { - int length = StringUtils.GetLength(_buffer); - - // Allocate an extra byte for the null terminator - byte[] buffer = new byte[length + 1]; - _buffer.Slice(0, length).CopyTo(buffer); - - return new U8String(buffer); - } - - /// - /// Checks if the has no buffer. - /// - /// if the span has no buffer. - /// Otherwise, . - public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); - - /// - /// Checks if the has no buffer or begins with a null terminator. - /// - /// if the span has no buffer or begins with a null terminator. - /// Otherwise, . - public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0; } + + public U8Span Slice(int start) + { + return new U8Span(_buffer.Slice(start)); + } + + public U8Span Slice(int start, int length) + { + return new U8Span(_buffer.Slice(start, length)); + } + + public static implicit operator ReadOnlySpan(in U8Span value) => value.Value; + + public static explicit operator string(in U8Span value) => value.ToString(); + public static explicit operator U8Span(string value) => new U8Span(value); + + public override string ToString() + { + return StringUtils.Utf8ZToString(_buffer); + } + + public U8String ToU8String() + { + int length = StringUtils.GetLength(_buffer); + + // Allocate an extra byte for the null terminator + byte[] buffer = new byte[length + 1]; + _buffer.Slice(0, length).CopyTo(buffer); + + return new U8String(buffer); + } + + /// + /// Checks if the has no buffer. + /// + /// if the span has no buffer. + /// Otherwise, . + public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the span has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0; } diff --git a/src/LibHac/Common/U8SpanMutable.cs b/src/LibHac/Common/U8SpanMutable.cs index d7457bd2..1dd1629e 100644 --- a/src/LibHac/Common/U8SpanMutable.cs +++ b/src/LibHac/Common/U8SpanMutable.cs @@ -5,95 +5,94 @@ using System.Runtime.InteropServices; using System.Text; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +public readonly ref struct U8SpanMutable { - [DebuggerDisplay("{ToString()}")] - public readonly ref struct U8SpanMutable + private readonly Span _buffer; + + public Span Value => _buffer; + public int Length => _buffer.Length; + + public byte this[int i] { - private readonly Span _buffer; + get => _buffer[i]; + set => _buffer[i] = value; + } - public Span Value => _buffer; - public int Length => _buffer.Length; + public U8SpanMutable(Span value) + { + _buffer = value; + } - public byte this[int i] + public U8SpanMutable(string value) + { + _buffer = Encoding.UTF8.GetBytes(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetOrNull(int i) + { + byte value = 0; + + if ((uint)i < (uint)_buffer.Length) { - get => _buffer[i]; - set => _buffer[i] = value; + value = GetUnsafe(i); } - public U8SpanMutable(Span value) - { - _buffer = value; - } + return value; + } - public U8SpanMutable(string value) - { - _buffer = Encoding.UTF8.GetBytes(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetOrNull(int i) - { - byte value = 0; - - if ((uint)i < (uint)_buffer.Length) - { - value = GetUnsafe(i); - } - - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetUnsafe(int i) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetUnsafe(int i) + { #if DEBUG - return _buffer[i]; + return _buffer[i]; #else return Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), i); #endif - } - - public U8SpanMutable Slice(int start) - { - return new U8SpanMutable(_buffer.Slice(start)); - } - - public U8SpanMutable Slice(int start, int length) - { - return new U8SpanMutable(_buffer.Slice(start, length)); - } - - public static implicit operator U8Span(U8SpanMutable value) => new U8Span(value._buffer); - - public static implicit operator ReadOnlySpan(U8SpanMutable value) => value.Value; - public static implicit operator Span(U8SpanMutable value) => value.Value; - - public static explicit operator string(U8SpanMutable value) => value.ToString(); - public static explicit operator U8SpanMutable(string value) => new U8SpanMutable(value); - - public override string ToString() - { - return StringUtils.Utf8ZToString(_buffer); - } - - public U8StringMutable ToU8String() - { - return new U8StringMutable(_buffer.ToArray()); - } - - /// - /// Checks if the has no buffer. - /// - /// if the span has no buffer. - /// Otherwise, . - public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); - - /// - /// Checks if the has no buffer or begins with a null terminator. - /// - /// if the span has no buffer or begins with a null terminator. - /// Otherwise, . - public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0; } + + public U8SpanMutable Slice(int start) + { + return new U8SpanMutable(_buffer.Slice(start)); + } + + public U8SpanMutable Slice(int start, int length) + { + return new U8SpanMutable(_buffer.Slice(start, length)); + } + + public static implicit operator U8Span(U8SpanMutable value) => new U8Span(value._buffer); + + public static implicit operator ReadOnlySpan(U8SpanMutable value) => value.Value; + public static implicit operator Span(U8SpanMutable value) => value.Value; + + public static explicit operator string(U8SpanMutable value) => value.ToString(); + public static explicit operator U8SpanMutable(string value) => new U8SpanMutable(value); + + public override string ToString() + { + return StringUtils.Utf8ZToString(_buffer); + } + + public U8StringMutable ToU8String() + { + return new U8StringMutable(_buffer.ToArray()); + } + + /// + /// Checks if the has no buffer. + /// + /// if the span has no buffer. + /// Otherwise, . + public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer)); + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the span has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0; } diff --git a/src/LibHac/Common/U8String.cs b/src/LibHac/Common/U8String.cs index af81643c..dbdbae0b 100644 --- a/src/LibHac/Common/U8String.cs +++ b/src/LibHac/Common/U8String.cs @@ -3,62 +3,61 @@ using System.Diagnostics; using System.Text; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +public readonly struct U8String { - [DebuggerDisplay("{ToString()}")] - public readonly struct U8String + private readonly byte[] _buffer; + + public ReadOnlySpan Value => _buffer; + public int Length => _buffer.Length; + + public byte this[int i] => _buffer[i]; + + public U8String(byte[] value) { - private readonly byte[] _buffer; - - public ReadOnlySpan Value => _buffer; - public int Length => _buffer.Length; - - public byte this[int i] => _buffer[i]; - - public U8String(byte[] value) - { - _buffer = value; - } - - public U8String(string value) - { - _buffer = Encoding.UTF8.GetBytes(value); - } - - public U8String Slice(int start) - { - return new U8String(_buffer.AsSpan(start).ToArray()); - } - - public U8String Slice(int start, int length) - { - return new U8String(_buffer.AsSpan(start, length).ToArray()); - } - - public static implicit operator U8Span(U8String value) => new U8Span(value._buffer); - - public static implicit operator ReadOnlySpan(U8String value) => value.Value; - - public static explicit operator string(U8String value) => value.ToString(); - public static explicit operator U8String(string value) => new U8String(value); - - public override string ToString() - { - return StringUtils.Utf8ZToString(_buffer); - } - - /// - /// Checks if the has no buffer. - /// - /// if the string has no buffer. - /// Otherwise, . - public bool IsNull() => _buffer == null; - - /// - /// Checks if the has no buffer or begins with a null terminator. - /// - /// if the string has no buffer or begins with a null terminator. - /// Otherwise, . - public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0; + _buffer = value; } + + public U8String(string value) + { + _buffer = Encoding.UTF8.GetBytes(value); + } + + public U8String Slice(int start) + { + return new U8String(_buffer.AsSpan(start).ToArray()); + } + + public U8String Slice(int start, int length) + { + return new U8String(_buffer.AsSpan(start, length).ToArray()); + } + + public static implicit operator U8Span(U8String value) => new U8Span(value._buffer); + + public static implicit operator ReadOnlySpan(U8String value) => value.Value; + + public static explicit operator string(U8String value) => value.ToString(); + public static explicit operator U8String(string value) => new U8String(value); + + public override string ToString() + { + return StringUtils.Utf8ZToString(_buffer); + } + + /// + /// Checks if the has no buffer. + /// + /// if the string has no buffer. + /// Otherwise, . + public bool IsNull() => _buffer == null; + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the string has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0; } diff --git a/src/LibHac/Common/U8StringBuilder.cs b/src/LibHac/Common/U8StringBuilder.cs index 0cf367a3..7046a651 100644 --- a/src/LibHac/Common/U8StringBuilder.cs +++ b/src/LibHac/Common/U8StringBuilder.cs @@ -6,564 +6,563 @@ using System.Runtime.CompilerServices; using LibHac.Diag; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +public ref struct U8StringBuilder { - [DebuggerDisplay("{ToString()}")] - public ref struct U8StringBuilder + private const int NullTerminatorLength = 1; + + public Span Buffer { get; private set; } + + /// + /// The current length of the string not including the null terminator. + /// + public int Length { get; private set; } + public bool Overflowed { get; private set; } + public bool AutoExpand { get; } + + private enum PadType : byte { - private const int NullTerminatorLength = 1; - - public Span Buffer { get; private set; } - - /// - /// The current length of the string not including the null terminator. - /// - public int Length { get; private set; } - public bool Overflowed { get; private set; } - public bool AutoExpand { get; } - - private enum PadType : byte - { - None, - Left, - Right - } - - private PadType PaddingType { get; set; } - private byte PaddingCharacter { get; set; } - private byte PaddedLength { get; set; } - - private byte[] _rentedArray; - - public readonly int Capacity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Buffer.Length - NullTerminatorLength; - } - - public U8StringBuilder(Span buffer, bool autoExpand = false) - { - Buffer = buffer; - Length = 0; - Overflowed = false; - AutoExpand = autoExpand; - PaddingType = PadType.None; - PaddingCharacter = 0; - PaddedLength = 0; - _rentedArray = null; - - if (autoExpand) - { - TryEnsureAdditionalCapacity(1); - } - else - { - ThrowIfBufferLengthIsZero(); - } - - AddNullTerminator(); - } - - public void Dispose() - { - byte[] toReturn = _rentedArray; - this = default; - if (toReturn is not null) - { - ArrayPool.Shared.Return(_rentedArray); - } - } - - // These functions are internal so they can be called by the extension methods - // in U8StringBuilderExtensions. It's not an ideal setup, but it allows append - // calls to be chained without accidentally creating a copy of the U8StringBuilder. - internal void AppendInternal(ReadOnlySpan value) - { - // Once in the Overflowed state, nothing else is written to the buffer. - if (Overflowed) return; - - int valueLength = StringUtils.GetLength(value); - - // If needed, expand the buffer size if allowed, or set the Overflowed state if not. - if (!TryEnsureAdditionalCapacity(valueLength)) - { - Overflowed = true; - return; - } - - // Because we know the length of the output string beforehand, we can write the - // proper amount of padding bytes before writing the output string to the buffer. - if (PaddingType == PadType.Left && valueLength < PaddedLength) - { - int paddingNeeded = PaddedLength - valueLength; - Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); - PaddingType = PadType.None; - Length += paddingNeeded; - } - - // Copy the string to the buffer and right pad if necessary. - value.Slice(0, valueLength).CopyTo(Buffer.Slice(Length)); - PadOutput(valueLength); - - // Update the length and null-terminate the string. - Length += valueLength; - AddNullTerminator(); - } - - internal void AppendInternal(byte value) - { - if (Overflowed) return; - - if (!TryEnsureAdditionalCapacity(1)) - { - Overflowed = true; - return; - } - - if (PaddingType == PadType.Left && 1 < PaddedLength) - { - int paddingNeeded = PaddedLength - 1; - Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); - PaddingType = PadType.None; - Length += paddingNeeded; - } - - Buffer[Length] = value; - PadOutput(1); - - Length++; - AddNullTerminator(); - } - - private void AppendFormatInt64(long value, ulong mask, char format, byte precision) - { - if (Overflowed) return; - - // Check if we have enough remaining buffer to fit the required padding. - if (!TryEnsureAdditionalCapacity(0)) - { - Overflowed = true; - return; - } - - // Remove possible sign extension if needed - if (mask == 'x' || mask == 'X') - { - value &= (long)mask; - } - - int bytesWritten; - - // If auto-expand is enabled, try to expand the buffer until it can hold the output. - while (true) - { - // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it - Span availableBuffer = Buffer.Slice(Length, Capacity - Length); - - bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, - new StandardFormat(format, precision)); - - // Continue if the buffer is large enough. - if (bufferLargeEnough) - break; - - // We can't expand the buffer. Mark the string builder as Overflowed - if (!AutoExpand) - { - Overflowed = true; - return; - } - - // Grow the buffer and try again. The buffer size will at least double each time it grows, - // so asking for 0x10 additional bytes actually gets us more than that. - Grow(availableBuffer.Length + 0x10); - Assert.SdkGreater(Capacity, Length + availableBuffer.Length); - } - - PadOutput(bytesWritten); - - Length += bytesWritten; - AddNullTerminator(); - } - - private void AppendFormatUInt64(ulong value, char format, byte precision) - { - if (Overflowed) return; - - if (!TryEnsureAdditionalCapacity(0)) - { - Overflowed = true; - return; - } - - int bytesWritten; - - while (true) - { - // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it - Span availableBuffer = Buffer.Slice(Length, Capacity - Length); - - bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, - new StandardFormat(format, precision)); - - if (bufferLargeEnough) - break; - - if (!AutoExpand) - { - Overflowed = true; - return; - } - - Grow(availableBuffer.Length + 0x10); - Assert.SdkGreater(Capacity, Length + availableBuffer.Length); - } - - PadOutput(bytesWritten); - - Length += bytesWritten; - AddNullTerminator(); - } - - private void AppendFormatFloat(float value, char format, byte precision) - { - if (Overflowed) return; - - if (!TryEnsureAdditionalCapacity(0)) - { - Overflowed = true; - return; - } - - int bytesWritten; - - while (true) - { - // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it - Span availableBuffer = Buffer.Slice(Length, Capacity - Length); - - bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, - new StandardFormat(format, precision)); - - if (bufferLargeEnough) - break; - - if (!AutoExpand) - { - Overflowed = true; - return; - } - - Grow(availableBuffer.Length + 0x10); - Assert.SdkGreater(Capacity, Length + availableBuffer.Length); - } - - PadOutput(bytesWritten); - - Length += bytesWritten; - AddNullTerminator(); - } - - private void AppendFormatDouble(double value, char format, byte precision) - { - if (Overflowed) return; - - if (!TryEnsureAdditionalCapacity(0)) - { - Overflowed = true; - return; - } - - int bytesWritten; - - while (true) - { - // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it - Span availableBuffer = Buffer.Slice(Length, Capacity - Length); - - bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, - new StandardFormat(format, precision)); - - if (bufferLargeEnough) - break; - - if (!AutoExpand) - { - Overflowed = true; - return; - } - - Grow(availableBuffer.Length + 0x10); - Assert.SdkGreater(Capacity, Length + availableBuffer.Length); - } - - PadOutput(bytesWritten); - - Length += bytesWritten; - AddNullTerminator(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) => - AppendFormatUInt64(value, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(sbyte value, char format = 'G', byte precision = 255) => - AppendFormatInt64(value, 0xff, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(ushort value, char format = 'G', byte precision = 255) => - AppendFormatUInt64(value, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(short value, char format = 'G', byte precision = 255) => - AppendFormatInt64(value, 0xffff, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(uint value, char format = 'G', byte precision = 255) => - AppendFormatUInt64(value, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(int value, char format = 'G', byte precision = 255) => - AppendFormatInt64(value, 0xffffff, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(ulong value, char format = 'G', byte precision = 255) => - AppendFormatUInt64(value, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(long value, char format = 'G', byte precision = 255) => - AppendFormatInt64(value, 0xffffffff, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(float value, char format = 'G', byte precision = 255) => - AppendFormatFloat(value, format, precision); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) => - AppendFormatDouble(value, format, precision); - - internal void PadLeftInternal(byte paddingCharacter, byte paddedLength) - { - PaddingType = PadType.Left; - PaddingCharacter = paddingCharacter; - PaddedLength = paddedLength; - } - - internal void PadRightInternal(byte paddingCharacter, byte paddedLength) - { - PaddingType = PadType.Right; - PaddingCharacter = paddingCharacter; - PaddedLength = paddedLength; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly bool HasCapacity(int requiredCapacity) - { - return requiredCapacity <= Capacity; - } - - private bool TryEnsureAdditionalCapacity(int requiredAdditionalCapacity) - { - int paddedLength = PaddingType != PadType.None ? PaddedLength : 0; - int requiredAfterPadding = Math.Max(paddedLength, requiredAdditionalCapacity); - bool hasCapacity = HasCapacity(Length + requiredAfterPadding); - - if (!hasCapacity && AutoExpand) - { - Grow(requiredAfterPadding); - Assert.SdkAssert(HasCapacity(Length + requiredAfterPadding)); - hasCapacity = true; - } - - return hasCapacity; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddNullTerminator() - { - Buffer[Length] = 0; - } - - private void Grow(int requiredAdditionalCapacity) - { - byte[] poolArray = - ArrayPool.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2)); - - Buffer.Slice(0, Length).CopyTo(poolArray); - Buffer = poolArray; - - byte[] toReturn = _rentedArray; - _rentedArray = poolArray; - if (toReturn is not null) - { - ArrayPool.Shared.Return(toReturn); - } - } - - private readonly void ThrowIfBufferLengthIsZero() - { - if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0."); - } - - /// - /// Pads the output after being written to the buffer if necessary, - /// resetting the padding type to afterward. - /// - /// The length of the unpadded output string. - /// Because it involves moving the output string when left padding, this function is only used - /// when the length of the output string isn't known before writing it to the buffer. - private void PadOutput(int bytesWritten) - { - if (PaddingType == PadType.Right && bytesWritten < PaddedLength) - { - // Fill the remaining bytes to the right with the padding character - int paddingNeeded = PaddedLength - bytesWritten; - Buffer.Slice(Length + bytesWritten, paddingNeeded).Fill(PaddingCharacter); - PaddingType = PadType.None; - Length += paddingNeeded; - } - else if (PaddingType == PadType.Left && bytesWritten < PaddedLength) - { - int paddingNeeded = PaddedLength - bytesWritten; - - // Move the printed bytes to the right to make room for the padding - Span source = Buffer.Slice(Length, bytesWritten); - Span dest = Buffer.Slice(Length + paddingNeeded, bytesWritten); - source.CopyTo(dest); - - // Fill the leading bytes with the padding character - Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); - PaddingType = PadType.None; - Length += paddingNeeded; - } - } - - public override readonly string ToString() => StringUtils.Utf8ZToString(Buffer); + None, + Left, + Right } - public static class U8StringBuilderExtensions + private PadType PaddingType { get; set; } + private byte PaddingCharacter { get; set; } + private byte PaddedLength { get; set; } + + private byte[] _rentedArray; + + public readonly int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder Append(this ref U8StringBuilder sb, ReadOnlySpan value) + get => Buffer.Length - NullTerminatorLength; + } + + public U8StringBuilder(Span buffer, bool autoExpand = false) + { + Buffer = buffer; + Length = 0; + Overflowed = false; + AutoExpand = autoExpand; + PaddingType = PadType.None; + PaddingCharacter = 0; + PaddedLength = 0; + _rentedArray = null; + + if (autoExpand) { - sb.AppendInternal(value); - return ref sb; + TryEnsureAdditionalCapacity(1); + } + else + { + ThrowIfBufferLengthIsZero(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder Append(this ref U8StringBuilder sb, byte value) + AddNullTerminator(); + } + + public void Dispose() + { + byte[] toReturn = _rentedArray; + this = default; + if (toReturn is not null) { - sb.AppendInternal(value); - return ref sb; + ArrayPool.Shared.Return(_rentedArray); + } + } + + // These functions are internal so they can be called by the extension methods + // in U8StringBuilderExtensions. It's not an ideal setup, but it allows append + // calls to be chained without accidentally creating a copy of the U8StringBuilder. + internal void AppendInternal(ReadOnlySpan value) + { + // Once in the Overflowed state, nothing else is written to the buffer. + if (Overflowed) return; + + int valueLength = StringUtils.GetLength(value); + + // If needed, expand the buffer size if allowed, or set the Overflowed state if not. + if (!TryEnsureAdditionalCapacity(valueLength)) + { + Overflowed = true; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, byte value, char format = 'G', - byte precision = 255) + // Because we know the length of the output string beforehand, we can write the + // proper amount of padding bytes before writing the output string to the buffer. + if (PaddingType == PadType.Left && valueLength < PaddedLength) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + int paddingNeeded = PaddedLength - valueLength; + Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); + PaddingType = PadType.None; + Length += paddingNeeded; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, sbyte value, char format = 'G', - byte precision = 255) + // Copy the string to the buffer and right pad if necessary. + value.Slice(0, valueLength).CopyTo(Buffer.Slice(Length)); + PadOutput(valueLength); + + // Update the length and null-terminate the string. + Length += valueLength; + AddNullTerminator(); + } + + internal void AppendInternal(byte value) + { + if (Overflowed) return; + + if (!TryEnsureAdditionalCapacity(1)) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + Overflowed = true; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ushort value, char format = 'G', - byte precision = 255) + if (PaddingType == PadType.Left && 1 < PaddedLength) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + int paddingNeeded = PaddedLength - 1; + Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); + PaddingType = PadType.None; + Length += paddingNeeded; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, short value, char format = 'G', - byte precision = 255) + Buffer[Length] = value; + PadOutput(1); + + Length++; + AddNullTerminator(); + } + + private void AppendFormatInt64(long value, ulong mask, char format, byte precision) + { + if (Overflowed) return; + + // Check if we have enough remaining buffer to fit the required padding. + if (!TryEnsureAdditionalCapacity(0)) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + Overflowed = true; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, uint value, char format = 'G', - byte precision = 255) + // Remove possible sign extension if needed + if (mask == 'x' || mask == 'X') { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + value &= (long)mask; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, int value, char format = 'G', - byte precision = 255) + int bytesWritten; + + // If auto-expand is enabled, try to expand the buffer until it can hold the output. + while (true) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it + Span availableBuffer = Buffer.Slice(Length, Capacity - Length); + + bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, + new StandardFormat(format, precision)); + + // Continue if the buffer is large enough. + if (bufferLargeEnough) + break; + + // We can't expand the buffer. Mark the string builder as Overflowed + if (!AutoExpand) + { + Overflowed = true; + return; + } + + // Grow the buffer and try again. The buffer size will at least double each time it grows, + // so asking for 0x10 additional bytes actually gets us more than that. + Grow(availableBuffer.Length + 0x10); + Assert.SdkGreater(Capacity, Length + availableBuffer.Length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ulong value, char format = 'G', - byte precision = 255) + PadOutput(bytesWritten); + + Length += bytesWritten; + AddNullTerminator(); + } + + private void AppendFormatUInt64(ulong value, char format, byte precision) + { + if (Overflowed) return; + + if (!TryEnsureAdditionalCapacity(0)) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + Overflowed = true; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, long value, char format = 'G', - byte precision = 255) + int bytesWritten; + + while (true) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it + Span availableBuffer = Buffer.Slice(Length, Capacity - Length); + + bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, + new StandardFormat(format, precision)); + + if (bufferLargeEnough) + break; + + if (!AutoExpand) + { + Overflowed = true; + return; + } + + Grow(availableBuffer.Length + 0x10); + Assert.SdkGreater(Capacity, Length + availableBuffer.Length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, float value, char format = 'G', - byte precision = 255) + PadOutput(bytesWritten); + + Length += bytesWritten; + AddNullTerminator(); + } + + private void AppendFormatFloat(float value, char format, byte precision) + { + if (Overflowed) return; + + if (!TryEnsureAdditionalCapacity(0)) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + Overflowed = true; + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, double value, char format = 'G', - byte precision = 255) + int bytesWritten; + + while (true) { - sb.AppendFormatInternal(value, format, precision); - return ref sb; + // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it + Span availableBuffer = Buffer.Slice(Length, Capacity - Length); + + bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, + new StandardFormat(format, precision)); + + if (bufferLargeEnough) + break; + + if (!AutoExpand) + { + Overflowed = true; + return; + } + + Grow(availableBuffer.Length + 0x10); + Assert.SdkGreater(Capacity, Length + availableBuffer.Length); } - /// - /// Set the parameters used to left-pad the next appended value. - /// - /// The used string builder. - /// The character used to pad the output. - /// The minimum length of the output after padding. - /// The used string builder. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder PadLeft(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength) + PadOutput(bytesWritten); + + Length += bytesWritten; + AddNullTerminator(); + } + + private void AppendFormatDouble(double value, char format, byte precision) + { + if (Overflowed) return; + + if (!TryEnsureAdditionalCapacity(0)) { - sb.PadLeftInternal(paddingCharacter, paddedLength); - return ref sb; + Overflowed = true; + return; } - /// - /// Set the parameters used to right-pad the next appended value. - /// - /// The used string builder. - /// The character used to pad the output. - /// The minimum length of the output after padding. - /// The used string builder. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref U8StringBuilder PadRight(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength) + int bytesWritten; + + while (true) { - sb.PadRightInternal(paddingCharacter, paddedLength); - return ref sb; + // Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it + Span availableBuffer = Buffer.Slice(Length, Capacity - Length); + + bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten, + new StandardFormat(format, precision)); + + if (bufferLargeEnough) + break; + + if (!AutoExpand) + { + Overflowed = true; + return; + } + + Grow(availableBuffer.Length + 0x10); + Assert.SdkGreater(Capacity, Length + availableBuffer.Length); } + + PadOutput(bytesWritten); + + Length += bytesWritten; + AddNullTerminator(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) => + AppendFormatUInt64(value, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(sbyte value, char format = 'G', byte precision = 255) => + AppendFormatInt64(value, 0xff, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(ushort value, char format = 'G', byte precision = 255) => + AppendFormatUInt64(value, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(short value, char format = 'G', byte precision = 255) => + AppendFormatInt64(value, 0xffff, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(uint value, char format = 'G', byte precision = 255) => + AppendFormatUInt64(value, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(int value, char format = 'G', byte precision = 255) => + AppendFormatInt64(value, 0xffffff, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(ulong value, char format = 'G', byte precision = 255) => + AppendFormatUInt64(value, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(long value, char format = 'G', byte precision = 255) => + AppendFormatInt64(value, 0xffffffff, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(float value, char format = 'G', byte precision = 255) => + AppendFormatFloat(value, format, precision); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) => + AppendFormatDouble(value, format, precision); + + internal void PadLeftInternal(byte paddingCharacter, byte paddedLength) + { + PaddingType = PadType.Left; + PaddingCharacter = paddingCharacter; + PaddedLength = paddedLength; + } + + internal void PadRightInternal(byte paddingCharacter, byte paddedLength) + { + PaddingType = PadType.Right; + PaddingCharacter = paddingCharacter; + PaddedLength = paddedLength; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool HasCapacity(int requiredCapacity) + { + return requiredCapacity <= Capacity; + } + + private bool TryEnsureAdditionalCapacity(int requiredAdditionalCapacity) + { + int paddedLength = PaddingType != PadType.None ? PaddedLength : 0; + int requiredAfterPadding = Math.Max(paddedLength, requiredAdditionalCapacity); + bool hasCapacity = HasCapacity(Length + requiredAfterPadding); + + if (!hasCapacity && AutoExpand) + { + Grow(requiredAfterPadding); + Assert.SdkAssert(HasCapacity(Length + requiredAfterPadding)); + hasCapacity = true; + } + + return hasCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddNullTerminator() + { + Buffer[Length] = 0; + } + + private void Grow(int requiredAdditionalCapacity) + { + byte[] poolArray = + ArrayPool.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2)); + + Buffer.Slice(0, Length).CopyTo(poolArray); + Buffer = poolArray; + + byte[] toReturn = _rentedArray; + _rentedArray = poolArray; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + private readonly void ThrowIfBufferLengthIsZero() + { + if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0."); + } + + /// + /// Pads the output after being written to the buffer if necessary, + /// resetting the padding type to afterward. + /// + /// The length of the unpadded output string. + /// Because it involves moving the output string when left padding, this function is only used + /// when the length of the output string isn't known before writing it to the buffer. + private void PadOutput(int bytesWritten) + { + if (PaddingType == PadType.Right && bytesWritten < PaddedLength) + { + // Fill the remaining bytes to the right with the padding character + int paddingNeeded = PaddedLength - bytesWritten; + Buffer.Slice(Length + bytesWritten, paddingNeeded).Fill(PaddingCharacter); + PaddingType = PadType.None; + Length += paddingNeeded; + } + else if (PaddingType == PadType.Left && bytesWritten < PaddedLength) + { + int paddingNeeded = PaddedLength - bytesWritten; + + // Move the printed bytes to the right to make room for the padding + Span source = Buffer.Slice(Length, bytesWritten); + Span dest = Buffer.Slice(Length + paddingNeeded, bytesWritten); + source.CopyTo(dest); + + // Fill the leading bytes with the padding character + Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter); + PaddingType = PadType.None; + Length += paddingNeeded; + } + } + + public override readonly string ToString() => StringUtils.Utf8ZToString(Buffer); +} + +public static class U8StringBuilderExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder Append(this ref U8StringBuilder sb, ReadOnlySpan value) + { + sb.AppendInternal(value); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder Append(this ref U8StringBuilder sb, byte value) + { + sb.AppendInternal(value); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, byte value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, sbyte value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ushort value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, short value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, uint value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, int value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, ulong value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, long value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, float value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder AppendFormat(this ref U8StringBuilder sb, double value, char format = 'G', + byte precision = 255) + { + sb.AppendFormatInternal(value, format, precision); + return ref sb; + } + + /// + /// Set the parameters used to left-pad the next appended value. + /// + /// The used string builder. + /// The character used to pad the output. + /// The minimum length of the output after padding. + /// The used string builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder PadLeft(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength) + { + sb.PadLeftInternal(paddingCharacter, paddedLength); + return ref sb; + } + + /// + /// Set the parameters used to right-pad the next appended value. + /// + /// The used string builder. + /// The character used to pad the output. + /// The minimum length of the output after padding. + /// The used string builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref U8StringBuilder PadRight(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength) + { + sb.PadRightInternal(paddingCharacter, paddedLength); + return ref sb; } } diff --git a/src/LibHac/Common/U8StringHelpers.cs b/src/LibHac/Common/U8StringHelpers.cs index 5dcae1ce..6a8d06eb 100644 --- a/src/LibHac/Common/U8StringHelpers.cs +++ b/src/LibHac/Common/U8StringHelpers.cs @@ -1,15 +1,14 @@ -namespace LibHac.Common -{ - public static class U8StringHelpers - { - public static U8String ToU8String(this string value) - { - return new U8String(value); - } +namespace LibHac.Common; - public static U8Span ToU8Span(this string value) - { - return new U8Span(value); - } +public static class U8StringHelpers +{ + public static U8String ToU8String(this string value) + { + return new U8String(value); + } + + public static U8Span ToU8Span(this string value) + { + return new U8Span(value); } } diff --git a/src/LibHac/Common/U8StringMutable.cs b/src/LibHac/Common/U8StringMutable.cs index b041aeea..dd76847e 100644 --- a/src/LibHac/Common/U8StringMutable.cs +++ b/src/LibHac/Common/U8StringMutable.cs @@ -3,69 +3,68 @@ using System.Diagnostics; using System.Text; using LibHac.Util; -namespace LibHac.Common +namespace LibHac.Common; + +[DebuggerDisplay("{ToString()}")] +public readonly struct U8StringMutable { - [DebuggerDisplay("{ToString()}")] - public readonly struct U8StringMutable + private readonly byte[] _buffer; + + public Span Value => _buffer; + public int Length => _buffer.Length; + + public byte this[int i] { - private readonly byte[] _buffer; - - public Span Value => _buffer; - public int Length => _buffer.Length; - - public byte this[int i] - { - get => _buffer[i]; - set => _buffer[i] = value; - } - - public U8StringMutable(byte[] value) - { - _buffer = value; - } - - public U8StringMutable(string value) - { - _buffer = Encoding.UTF8.GetBytes(value); - } - - public U8StringMutable Slice(int start) - { - return new U8StringMutable(_buffer.AsSpan(start).ToArray()); - } - - public U8StringMutable Slice(int start, int length) - { - return new U8StringMutable(_buffer.AsSpan(start, length).ToArray()); - } - - public static implicit operator U8String(U8StringMutable value) => new U8String(value._buffer); - public static implicit operator U8SpanMutable(U8StringMutable value) => new U8SpanMutable(value._buffer); - public static implicit operator U8Span(U8StringMutable value) => new U8Span(value._buffer); - - public static implicit operator ReadOnlySpan(U8StringMutable value) => value.Value; - public static implicit operator Span(U8StringMutable value) => value.Value; - - public static explicit operator string(U8StringMutable value) => value.ToString(); - public static explicit operator U8StringMutable(string value) => new U8StringMutable(value); - - public override string ToString() - { - return StringUtils.Utf8ZToString(_buffer); - } - - /// - /// Checks if the has no buffer. - /// - /// if the string has no buffer. - /// Otherwise, . - public bool IsNull() => _buffer == null; - - /// - /// Checks if the has no buffer or begins with a null terminator. - /// - /// if the string has no buffer or begins with a null terminator. - /// Otherwise, . - public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0; + get => _buffer[i]; + set => _buffer[i] = value; } + + public U8StringMutable(byte[] value) + { + _buffer = value; + } + + public U8StringMutable(string value) + { + _buffer = Encoding.UTF8.GetBytes(value); + } + + public U8StringMutable Slice(int start) + { + return new U8StringMutable(_buffer.AsSpan(start).ToArray()); + } + + public U8StringMutable Slice(int start, int length) + { + return new U8StringMutable(_buffer.AsSpan(start, length).ToArray()); + } + + public static implicit operator U8String(U8StringMutable value) => new U8String(value._buffer); + public static implicit operator U8SpanMutable(U8StringMutable value) => new U8SpanMutable(value._buffer); + public static implicit operator U8Span(U8StringMutable value) => new U8Span(value._buffer); + + public static implicit operator ReadOnlySpan(U8StringMutable value) => value.Value; + public static implicit operator Span(U8StringMutable value) => value.Value; + + public static explicit operator string(U8StringMutable value) => value.ToString(); + public static explicit operator U8StringMutable(string value) => new U8StringMutable(value); + + public override string ToString() + { + return StringUtils.Utf8ZToString(_buffer); + } + + /// + /// Checks if the has no buffer. + /// + /// if the string has no buffer. + /// Otherwise, . + public bool IsNull() => _buffer == null; + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the string has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0; } diff --git a/src/LibHac/Common/UniqueRef.cs b/src/LibHac/Common/UniqueRef.cs index 66f8cc8a..617f132f 100644 --- a/src/LibHac/Common/UniqueRef.cs +++ b/src/LibHac/Common/UniqueRef.cs @@ -2,90 +2,89 @@ using System.Runtime.CompilerServices; using static InlineIL.IL.Emit; -namespace LibHac.Common +namespace LibHac.Common; + +public static class UniqueRefExtensions { - public static class UniqueRefExtensions + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref UniqueRef Ref(this in UniqueRef value) where T : class, IDisposable { - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref UniqueRef Ref(this in UniqueRef value) where T : class, IDisposable - { - Ldarg(nameof(value)); - Ret(); - throw InlineIL.IL.Unreachable(); - } - } - - [NonCopyableDisposable] - public struct UniqueRef : IDisposable where T : class, IDisposable - { - private T _value; - - public readonly T Get => _value; - public readonly bool HasValue => Get is not null; - - public UniqueRef(T value) - { - _value = value; - } - - public UniqueRef(ref UniqueRef other) - { - _value = other.Release(); - } - - [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] - public void Dispose() - { - _value?.Dispose(); - } - - /// - /// Used to manually dispose the from the Dispose methods of other types. - /// - public void Destroy() - { - Reset(); - } - - public static UniqueRef Create(ref UniqueRef other) where TFrom : class, T - { - return new UniqueRef(other.Release()); - } - - public void Swap(ref UniqueRef other) - { - (other._value, _value) = (_value, other._value); - } - - public void Reset() => Reset(null); - - public void Reset(T value) - { - T oldValue = _value; - _value = value; - - oldValue?.Dispose(); - } - - public void Set(ref UniqueRef other) - { - if (Unsafe.AreSame(ref this, ref other)) - return; - - Reset(other.Release()); - } - - public void Set(ref UniqueRef other) where TFrom : class, T - { - Reset(other.Release()); - } - - public T Release() - { - T oldValue = _value; - _value = null; - - return oldValue; - } + Ldarg(nameof(value)); + Ret(); + throw InlineIL.IL.Unreachable(); + } +} + +[NonCopyableDisposable] +public struct UniqueRef : IDisposable where T : class, IDisposable +{ + private T _value; + + public readonly T Get => _value; + public readonly bool HasValue => Get is not null; + + public UniqueRef(T value) + { + _value = value; + } + + public UniqueRef(ref UniqueRef other) + { + _value = other.Release(); + } + + [Obsolete("This method should never be manually called. Use the Destroy method instead.", true)] + public void Dispose() + { + _value?.Dispose(); + } + + /// + /// Used to manually dispose the from the Dispose methods of other types. + /// + public void Destroy() + { + Reset(); + } + + public static UniqueRef Create(ref UniqueRef other) where TFrom : class, T + { + return new UniqueRef(other.Release()); + } + + public void Swap(ref UniqueRef other) + { + (other._value, _value) = (_value, other._value); + } + + public void Reset() => Reset(null); + + public void Reset(T value) + { + T oldValue = _value; + _value = value; + + oldValue?.Dispose(); + } + + public void Set(ref UniqueRef other) + { + if (Unsafe.AreSame(ref this, ref other)) + return; + + Reset(other.Release()); + } + + public void Set(ref UniqueRef other) where TFrom : class, T + { + Reset(other.Release()); + } + + public T Release() + { + T oldValue = _value; + _value = null; + + return oldValue; } } diff --git a/src/LibHac/Common/UnsafeHelpers.cs b/src/LibHac/Common/UnsafeHelpers.cs index 216fc6e1..da844cea 100644 --- a/src/LibHac/Common/UnsafeHelpers.cs +++ b/src/LibHac/Common/UnsafeHelpers.cs @@ -1,67 +1,66 @@ using System.Runtime.CompilerServices; -namespace LibHac.Common +namespace LibHac.Common; + +public static class UnsafeHelpers { - public static class UnsafeHelpers + /// + /// Bypasses definite assignment rules for a given unmanaged value, + /// or zeros a managed value to avoid having invalid references. + ///
Used in instances where an out value in the original code isn't set due to an error condition. + ///
Behaves the same as , except it zeros managed values. + ///
+ /// The type of the object. + /// The object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipParamInit(out T value) { - /// - /// Bypasses definite assignment rules for a given unmanaged value, - /// or zeros a managed value to avoid having invalid references. - ///
Used in instances where an out value in the original code isn't set due to an error condition. - ///
Behaves the same as , except it zeros managed values. - ///
- /// The type of the object. - /// The object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SkipParamInit(out T value) + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) { - if (!RuntimeHelpers.IsReferenceOrContainsReferences()) - { - Unsafe.SkipInit(out value); - } - else - { - value = default; - } + Unsafe.SkipInit(out value); } - - /// - /// Bypasses definite assignment rules for the given unmanaged values, - /// zeroing any managed values to avoid having invalid references. - ///
Used in instances where out values in the original code aren't set due to an error condition. - ///
Behaves the same as calling - /// on each value, except managed values will be zeroed. - ///
- /// The type of the first object. - /// The type of the second object. - /// The first object. - /// The second object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SkipParamInit(out T1 value1, out T2 value2) + else { - SkipParamInit(out value1); - SkipParamInit(out value2); - } - - /// - /// Bypasses definite assignment rules for the given unmanaged values, - /// zeroing any managed values to avoid having invalid references. - ///
Used in instances where out values in the original code aren't set due to an error condition. - ///
Behaves the same as calling - /// on each value, except managed values will be zeroed. - ///
- /// The type of the first object. - /// The type of the second object. - /// The type of the third object. - /// The first object. - /// The second object. - /// The third object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SkipParamInit(out T1 value1, out T2 value2, out T3 value3) - { - SkipParamInit(out value1); - SkipParamInit(out value2); - SkipParamInit(out value3); + value = default; } } + + /// + /// Bypasses definite assignment rules for the given unmanaged values, + /// zeroing any managed values to avoid having invalid references. + ///
Used in instances where out values in the original code aren't set due to an error condition. + ///
Behaves the same as calling + /// on each value, except managed values will be zeroed. + ///
+ /// The type of the first object. + /// The type of the second object. + /// The first object. + /// The second object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipParamInit(out T1 value1, out T2 value2) + { + SkipParamInit(out value1); + SkipParamInit(out value2); + } + + /// + /// Bypasses definite assignment rules for the given unmanaged values, + /// zeroing any managed values to avoid having invalid references. + ///
Used in instances where out values in the original code aren't set due to an error condition. + ///
Behaves the same as calling + /// on each value, except managed values will be zeroed. + ///
+ /// The type of the first object. + /// The type of the second object. + /// The type of the third object. + /// The first object. + /// The second object. + /// The third object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipParamInit(out T1 value1, out T2 value2, out T3 value3) + { + SkipParamInit(out value1); + SkipParamInit(out value2); + SkipParamInit(out value3); + } } diff --git a/src/LibHac/Crypto/Aes.cs b/src/LibHac/Crypto/Aes.cs index 4e642d90..eb2bcd08 100644 --- a/src/LibHac/Crypto/Aes.cs +++ b/src/LibHac/Crypto/Aes.cs @@ -7,305 +7,304 @@ using LibHac.Diag; using AesNi = System.Runtime.Intrinsics.X86.Aes; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public static class Aes { - public static class Aes + public const int KeySize128 = 0x10; + public const int BlockSize = 0x10; + + public static bool IsAesNiSupported() { - public const int KeySize128 = 0x10; - public const int BlockSize = 0x10; + return AesNi.IsSupported; + } - public static bool IsAesNiSupported() + public static ICipher CreateEcbDecryptor(ReadOnlySpan key, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - return AesNi.IsSupported; + return new AesEcbDecryptorNi(key); } - public static ICipher CreateEcbDecryptor(ReadOnlySpan key, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesEcbDecryptorNi(key); - } + return new AesEcbDecryptor(key); + } - return new AesEcbDecryptor(key); + public static ICipher CreateEcbEncryptor(ReadOnlySpan key, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesEcbEncryptorNi(key); } - public static ICipher CreateEcbEncryptor(ReadOnlySpan key, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesEcbEncryptorNi(key); - } + return new AesEcbEncryptor(key); + } - return new AesEcbEncryptor(key); + public static ICipher CreateCbcDecryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesCbcDecryptorNi(key, iv); } - public static ICipher CreateCbcDecryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesCbcDecryptorNi(key, iv); - } + return new AesCbcDecryptor(key, iv); + } - return new AesCbcDecryptor(key, iv); + public static ICipher CreateCbcEncryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesCbcEncryptorNi(key, iv); } - public static ICipher CreateCbcEncryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesCbcEncryptorNi(key, iv); - } + return new AesCbcEncryptor(key, iv); + } - return new AesCbcEncryptor(key, iv); + public static ICipherWithIv CreateCtrDecryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesCtrCipherNi(key, iv); } - public static ICipherWithIv CreateCtrDecryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesCtrCipherNi(key, iv); - } + // Encryption and decryption in counter mode is the same operation + return CreateCtrEncryptor(key, iv, preferDotNetCrypto); + } - // Encryption and decryption in counter mode is the same operation - return CreateCtrEncryptor(key, iv, preferDotNetCrypto); + public static ICipherWithIv CreateCtrEncryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesCtrCipherNi(key, iv); } - public static ICipherWithIv CreateCtrEncryptor(ReadOnlySpan key, ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesCtrCipherNi(key, iv); - } + return new AesCtrCipher(key, iv); + } - return new AesCtrCipher(key, iv); + public static ICipherWithIv CreateXtsDecryptor(ReadOnlySpan key1, ReadOnlySpan key2, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesXtsDecryptorNi(key1, key2, iv); } - public static ICipherWithIv CreateXtsDecryptor(ReadOnlySpan key1, ReadOnlySpan key2, - ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesXtsDecryptorNi(key1, key2, iv); - } + return new AesXtsDecryptor(key1, key2, iv); + } - return new AesXtsDecryptor(key1, key2, iv); + public static ICipherWithIv CreateXtsEncryptor(ReadOnlySpan key1, ReadOnlySpan key2, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + return new AesXtsEncryptorNi(key1, key2, iv); } - public static ICipherWithIv CreateXtsEncryptor(ReadOnlySpan key1, ReadOnlySpan key2, - ReadOnlySpan iv, bool preferDotNetCrypto = false) - { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - return new AesXtsEncryptorNi(key1, key2, iv); - } + return new AesXtsEncryptor(key1, key2, iv); + } - return new AesXtsEncryptor(key1, key2, iv); + public static void EncryptEcb128(ReadOnlySpan input, Span output, ReadOnlySpan key, + bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) + { + Unsafe.SkipInit(out AesEcbModeNi cipherNi); + + cipherNi.Initialize(key, false); + cipherNi.Encrypt(input, output); + return; } - public static void EncryptEcb128(ReadOnlySpan input, Span output, ReadOnlySpan key, - bool preferDotNetCrypto = false) + ICipher cipher = CreateEcbEncryptor(key, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void DecryptEcb128(ReadOnlySpan input, Span output, ReadOnlySpan key, + bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesEcbModeNi cipherNi); + Unsafe.SkipInit(out AesEcbModeNi cipherNi); - cipherNi.Initialize(key, false); - cipherNi.Encrypt(input, output); - return; - } - - ICipher cipher = CreateEcbEncryptor(key, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key, true); + cipherNi.Decrypt(input, output); + return; } - public static void DecryptEcb128(ReadOnlySpan input, Span output, ReadOnlySpan key, - bool preferDotNetCrypto = false) + ICipher cipher = CreateEcbDecryptor(key, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void EncryptCbc128(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesEcbModeNi cipherNi); + Unsafe.SkipInit(out AesCbcModeNi cipherNi); - cipherNi.Initialize(key, true); - cipherNi.Decrypt(input, output); - return; - } - - ICipher cipher = CreateEcbDecryptor(key, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key, iv, false); + cipherNi.Encrypt(input, output); + return; } - public static void EncryptCbc128(ReadOnlySpan input, Span output, ReadOnlySpan key, - ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateCbcEncryptor(key, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void DecryptCbc128(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesCbcModeNi cipherNi); + Unsafe.SkipInit(out AesCbcModeNi cipherNi); - cipherNi.Initialize(key, iv, false); - cipherNi.Encrypt(input, output); - return; - } - - ICipher cipher = CreateCbcEncryptor(key, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key, iv, true); + cipherNi.Decrypt(input, output); + return; } - public static void DecryptCbc128(ReadOnlySpan input, Span output, ReadOnlySpan key, - ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateCbcDecryptor(key, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void EncryptCtr128(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesCbcModeNi cipherNi); + Unsafe.SkipInit(out AesCtrModeNi cipherNi); - cipherNi.Initialize(key, iv, true); - cipherNi.Decrypt(input, output); - return; - } - - ICipher cipher = CreateCbcDecryptor(key, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key, iv); + cipherNi.Transform(input, output); + return; } - public static void EncryptCtr128(ReadOnlySpan input, Span output, ReadOnlySpan key, - ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateCtrEncryptor(key, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void DecryptCtr128(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesCtrModeNi cipherNi); + Unsafe.SkipInit(out AesCtrModeNi cipherNi); - cipherNi.Initialize(key, iv); - cipherNi.Transform(input, output); - return; - } - - ICipher cipher = CreateCtrEncryptor(key, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key, iv); + cipherNi.Transform(input, output); + return; } - public static void DecryptCtr128(ReadOnlySpan input, Span output, ReadOnlySpan key, - ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateCtrDecryptor(key, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void EncryptXts128(ReadOnlySpan input, Span output, ReadOnlySpan key1, + ReadOnlySpan key2, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesCtrModeNi cipherNi); + Unsafe.SkipInit(out AesXtsModeNi cipherNi); - cipherNi.Initialize(key, iv); - cipherNi.Transform(input, output); - return; - } - - ICipher cipher = CreateCtrDecryptor(key, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key1, key2, iv, false); + cipherNi.Encrypt(input, output); + return; } - public static void EncryptXts128(ReadOnlySpan input, Span output, ReadOnlySpan key1, - ReadOnlySpan key2, ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateXtsEncryptor(key1, key2, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + public static void DecryptXts128(ReadOnlySpan input, Span output, ReadOnlySpan key1, + ReadOnlySpan key2, ReadOnlySpan iv, bool preferDotNetCrypto = false) + { + if (IsAesNiSupported() && !preferDotNetCrypto) { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesXtsModeNi cipherNi); + Unsafe.SkipInit(out AesXtsModeNi cipherNi); - cipherNi.Initialize(key1, key2, iv, false); - cipherNi.Encrypt(input, output); - return; - } - - ICipher cipher = CreateXtsEncryptor(key1, key2, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + cipherNi.Initialize(key1, key2, iv, true); + cipherNi.Decrypt(input, output); + return; } - public static void DecryptXts128(ReadOnlySpan input, Span output, ReadOnlySpan key1, - ReadOnlySpan key2, ReadOnlySpan iv, bool preferDotNetCrypto = false) + ICipher cipher = CreateXtsDecryptor(key1, key2, iv, preferDotNetCrypto); + + cipher.Transform(input, output); + } + + /// + /// Computes the CMAC of the provided data using AES-128. + /// + /// The buffer where the generated MAC will be placed. Must be at least 16 bytes long. + /// The message on which the MAC will be calculated. + /// The 128-bit AES key used to calculate the MAC. + /// https://tools.ietf.org/html/rfc4493 + public static void CalculateCmac(Span mac, ReadOnlySpan data, ReadOnlySpan key) + { + ReadOnlySpan zero = new Buffer16(); + int len = data.Length; + + // Step 1, AES-128 with key K is applied to an all-zero input block. + Span l = stackalloc byte[16]; + EncryptCbc128(zero, l, key, zero); + + // Step 2, K1 is derived through the following operation: + Span k1 = stackalloc byte[16]; + LeftShiftBytes(l, k1); + if ((l[0] & 0x80) == 0x80) // If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit. + k1[15] ^= 0x87; // Otherwise, K1 is the XOR of const_Rb and the left-shift of L by 1 bit. + + // Step 3, K2 is derived through the following operation: + Span k2 = stackalloc byte[16]; + LeftShiftBytes(k1, k2); + if ((k1[0] & 0x80) == 0x80) // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit. + k2[15] ^= 0x87; // Otherwise, K2 is the XOR of const_Rb and the left-shift of K1 by 1 bit. + + // ReSharper disable once RedundantAssignment + Span paddedMessage = l; + + if (len != 0 && len % 16 == 0) // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits), + { // the last block shall be XOR'ed with K1 before processing + paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len]; + data.CopyTo(paddedMessage); + + for (int j = 0; j < k1.Length; j++) + paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j]; + } + else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2. { - if (IsAesNiSupported() && !preferDotNetCrypto) - { - Unsafe.SkipInit(out AesXtsModeNi cipherNi); + int paddedLength = len + (16 - len % 16); + paddedMessage = paddedLength < 0x800 ? stackalloc byte[paddedLength] : new byte[paddedLength]; - cipherNi.Initialize(key1, key2, iv, true); - cipherNi.Decrypt(input, output); - return; - } + paddedMessage.Slice(len).Clear(); + paddedMessage[len] = 0x80; + data.CopyTo(paddedMessage); - ICipher cipher = CreateXtsDecryptor(key1, key2, iv, preferDotNetCrypto); - - cipher.Transform(input, output); + for (int j = 0; j < k2.Length; j++) + paddedMessage[paddedMessage.Length - 16 + j] ^= k2[j]; } - /// - /// Computes the CMAC of the provided data using AES-128. - /// - /// The buffer where the generated MAC will be placed. Must be at least 16 bytes long. - /// The message on which the MAC will be calculated. - /// The 128-bit AES key used to calculate the MAC. - /// https://tools.ietf.org/html/rfc4493 - public static void CalculateCmac(Span mac, ReadOnlySpan data, ReadOnlySpan key) + EncryptCbc128(paddedMessage, paddedMessage, key, zero); // The result of the previous process will be the input of the last encryption. + paddedMessage.Slice(paddedMessage.Length - 0x10).CopyTo(mac); + } + + private static void LeftShiftBytes(ReadOnlySpan input, Span output) + { + Assert.SdkRequiresGreaterEqual(output.Length, input.Length); + + byte carry = 0; + + for (int i = input.Length - 1; i >= 0; i--) { - ReadOnlySpan zero = new Buffer16(); - int len = data.Length; - - // Step 1, AES-128 with key K is applied to an all-zero input block. - Span l = stackalloc byte[16]; - EncryptCbc128(zero, l, key, zero); - - // Step 2, K1 is derived through the following operation: - Span k1 = stackalloc byte[16]; - LeftShiftBytes(l, k1); - if ((l[0] & 0x80) == 0x80) // If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit. - k1[15] ^= 0x87; // Otherwise, K1 is the XOR of const_Rb and the left-shift of L by 1 bit. - - // Step 3, K2 is derived through the following operation: - Span k2 = stackalloc byte[16]; - LeftShiftBytes(k1, k2); - if ((k1[0] & 0x80) == 0x80) // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit. - k2[15] ^= 0x87; // Otherwise, K2 is the XOR of const_Rb and the left-shift of K1 by 1 bit. - - // ReSharper disable once RedundantAssignment - Span paddedMessage = l; - - if (len != 0 && len % 16 == 0) // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits), - { // the last block shall be XOR'ed with K1 before processing - paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len]; - data.CopyTo(paddedMessage); - - for (int j = 0; j < k1.Length; j++) - paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j]; - } - else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2. - { - int paddedLength = len + (16 - len % 16); - paddedMessage = paddedLength < 0x800 ? stackalloc byte[paddedLength] : new byte[paddedLength]; - - paddedMessage.Slice(len).Clear(); - paddedMessage[len] = 0x80; - data.CopyTo(paddedMessage); - - for (int j = 0; j < k2.Length; j++) - paddedMessage[paddedMessage.Length - 16 + j] ^= k2[j]; - } - - EncryptCbc128(paddedMessage, paddedMessage, key, zero); // The result of the previous process will be the input of the last encryption. - paddedMessage.Slice(paddedMessage.Length - 0x10).CopyTo(mac); - } - - private static void LeftShiftBytes(ReadOnlySpan input, Span output) - { - Assert.SdkRequiresGreaterEqual(output.Length, input.Length); - - byte carry = 0; - - for (int i = input.Length - 1; i >= 0; i--) - { - ushort b = (ushort)(input[i] << 1); - output[i] = (byte)((b & 0xff) + carry); - carry = (byte)((b & 0xff00) >> 8); - } + ushort b = (ushort)(input[i] << 1); + output[i] = (byte)((b & 0xff) + carry); + carry = (byte)((b & 0xff00) >> 8); } } } diff --git a/src/LibHac/Crypto/AesCbcCipher.cs b/src/LibHac/Crypto/AesCbcCipher.cs index 7e246066..4502d6de 100644 --- a/src/LibHac/Crypto/AesCbcCipher.cs +++ b/src/LibHac/Crypto/AesCbcCipher.cs @@ -1,37 +1,36 @@ using System; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesCbcEncryptor : ICipher { - public class AesCbcEncryptor : ICipher + private AesCbcMode _baseCipher; + + public AesCbcEncryptor(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCbcMode _baseCipher; - - public AesCbcEncryptor(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCbcMode(); - _baseCipher.Initialize(key, iv, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesCbcMode(); + _baseCipher.Initialize(key, iv, false); } - public class AesCbcDecryptor : ICipher + public void Transform(ReadOnlySpan input, Span output) { - private AesCbcMode _baseCipher; - - public AesCbcDecryptor(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCbcMode(); - _baseCipher.Initialize(key, iv, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesCbcDecryptor : ICipher +{ + private AesCbcMode _baseCipher; + + public AesCbcDecryptor(ReadOnlySpan key, ReadOnlySpan iv) + { + _baseCipher = new AesCbcMode(); + _baseCipher.Initialize(key, iv, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/AesCbcCipherNi.cs b/src/LibHac/Crypto/AesCbcCipherNi.cs index 99ac5675..418bb3e0 100644 --- a/src/LibHac/Crypto/AesCbcCipherNi.cs +++ b/src/LibHac/Crypto/AesCbcCipherNi.cs @@ -4,41 +4,40 @@ using System.Runtime.Intrinsics; using LibHac.Common; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesCbcEncryptorNi : ICipherWithIv { - public class AesCbcEncryptorNi : ICipherWithIv + private AesCbcModeNi _baseCipher; + + public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); + + public AesCbcEncryptorNi(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCbcModeNi _baseCipher; - - public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); - - public AesCbcEncryptorNi(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCbcModeNi(); - _baseCipher.Initialize(key, iv, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesCbcModeNi(); + _baseCipher.Initialize(key, iv, false); } - public class AesCbcDecryptorNi : ICipherWithIv + public void Transform(ReadOnlySpan input, Span output) { - private AesCbcModeNi _baseCipher; - - public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); - - public AesCbcDecryptorNi(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCbcModeNi(); - _baseCipher.Initialize(key, iv, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesCbcDecryptorNi : ICipherWithIv +{ + private AesCbcModeNi _baseCipher; + + public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); + + public AesCbcDecryptorNi(ReadOnlySpan key, ReadOnlySpan iv) + { + _baseCipher = new AesCbcModeNi(); + _baseCipher.Initialize(key, iv, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/AesCtrCipher.cs b/src/LibHac/Crypto/AesCtrCipher.cs index 35a9454b..6661b53e 100644 --- a/src/LibHac/Crypto/AesCtrCipher.cs +++ b/src/LibHac/Crypto/AesCtrCipher.cs @@ -2,23 +2,22 @@ using LibHac.Common; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesCtrCipher : ICipherWithIv { - public class AesCtrCipher : ICipherWithIv + private AesCtrMode _baseCipher; + + public ref Buffer16 Iv => ref _baseCipher.Iv; + + public AesCtrCipher(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCtrMode _baseCipher; + _baseCipher = new AesCtrMode(); + _baseCipher.Initialize(key, iv); + } - public ref Buffer16 Iv => ref _baseCipher.Iv; - - public AesCtrCipher(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCtrMode(); - _baseCipher.Initialize(key, iv); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Transform(input, output); - } + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Transform(input, output); } } diff --git a/src/LibHac/Crypto/AesCtrCipherNi.cs b/src/LibHac/Crypto/AesCtrCipherNi.cs index f8222cd1..6474b533 100644 --- a/src/LibHac/Crypto/AesCtrCipherNi.cs +++ b/src/LibHac/Crypto/AesCtrCipherNi.cs @@ -4,23 +4,22 @@ using System.Runtime.Intrinsics; using LibHac.Common; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesCtrCipherNi : ICipherWithIv { - public class AesCtrCipherNi : ICipherWithIv + private AesCtrModeNi _baseCipher; + + public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); + + public AesCtrCipherNi(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCtrModeNi _baseCipher; + _baseCipher = new AesCtrModeNi(); + _baseCipher.Initialize(key, iv); + } - public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); - - public AesCtrCipherNi(ReadOnlySpan key, ReadOnlySpan iv) - { - _baseCipher = new AesCtrModeNi(); - _baseCipher.Initialize(key, iv); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Transform(input, output); - } + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Transform(input, output); } } diff --git a/src/LibHac/Crypto/AesEcbCipher.cs b/src/LibHac/Crypto/AesEcbCipher.cs index 25bd1fe1..887acf27 100644 --- a/src/LibHac/Crypto/AesEcbCipher.cs +++ b/src/LibHac/Crypto/AesEcbCipher.cs @@ -1,37 +1,36 @@ using System; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesEcbEncryptor : ICipher { - public class AesEcbEncryptor : ICipher + private AesEcbMode _baseCipher; + + public AesEcbEncryptor(ReadOnlySpan key) { - private AesEcbMode _baseCipher; - - public AesEcbEncryptor(ReadOnlySpan key) - { - _baseCipher = new AesEcbMode(); - _baseCipher.Initialize(key, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesEcbMode(); + _baseCipher.Initialize(key, false); } - public class AesEcbDecryptor : ICipher + public void Transform(ReadOnlySpan input, Span output) { - private AesEcbMode _baseCipher; - - public AesEcbDecryptor(ReadOnlySpan key) - { - _baseCipher = new AesEcbMode(); - _baseCipher.Initialize(key, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesEcbDecryptor : ICipher +{ + private AesEcbMode _baseCipher; + + public AesEcbDecryptor(ReadOnlySpan key) + { + _baseCipher = new AesEcbMode(); + _baseCipher.Initialize(key, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/AesEcbCipherNi.cs b/src/LibHac/Crypto/AesEcbCipherNi.cs index 8adf0f62..17a9f43e 100644 --- a/src/LibHac/Crypto/AesEcbCipherNi.cs +++ b/src/LibHac/Crypto/AesEcbCipherNi.cs @@ -1,37 +1,36 @@ using System; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesEcbEncryptorNi : ICipher { - public class AesEcbEncryptorNi : ICipher + private AesEcbModeNi _baseCipher; + + public AesEcbEncryptorNi(ReadOnlySpan key) { - private AesEcbModeNi _baseCipher; - - public AesEcbEncryptorNi(ReadOnlySpan key) - { - _baseCipher = new AesEcbModeNi(); - _baseCipher.Initialize(key, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesEcbModeNi(); + _baseCipher.Initialize(key, false); } - public class AesEcbDecryptorNi : ICipher + public void Transform(ReadOnlySpan input, Span output) { - private AesEcbModeNi _baseCipher; - - public AesEcbDecryptorNi(ReadOnlySpan key) - { - _baseCipher = new AesEcbModeNi(); - _baseCipher.Initialize(key, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesEcbDecryptorNi : ICipher +{ + private AesEcbModeNi _baseCipher; + + public AesEcbDecryptorNi(ReadOnlySpan key) + { + _baseCipher = new AesEcbModeNi(); + _baseCipher.Initialize(key, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/AesXtsCipher.cs b/src/LibHac/Crypto/AesXtsCipher.cs index e8096260..57ddb18d 100644 --- a/src/LibHac/Crypto/AesXtsCipher.cs +++ b/src/LibHac/Crypto/AesXtsCipher.cs @@ -2,41 +2,40 @@ using LibHac.Common; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesXtsEncryptor : ICipherWithIv { - public class AesXtsEncryptor : ICipherWithIv + private AesXtsMode _baseCipher; + + public ref Buffer16 Iv => ref _baseCipher.Iv; + + public AesXtsEncryptor(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) { - private AesXtsMode _baseCipher; - - public ref Buffer16 Iv => ref _baseCipher.Iv; - - public AesXtsEncryptor(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) - { - _baseCipher = new AesXtsMode(); - _baseCipher.Initialize(key1, key2, iv, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesXtsMode(); + _baseCipher.Initialize(key1, key2, iv, false); } - public class AesXtsDecryptor : ICipherWithIv + public void Transform(ReadOnlySpan input, Span output) { - private AesXtsMode _baseCipher; - - public ref Buffer16 Iv => ref _baseCipher.Iv; - - public AesXtsDecryptor(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) - { - _baseCipher = new AesXtsMode(); - _baseCipher.Initialize(key1, key2, iv, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesXtsDecryptor : ICipherWithIv +{ + private AesXtsMode _baseCipher; + + public ref Buffer16 Iv => ref _baseCipher.Iv; + + public AesXtsDecryptor(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) + { + _baseCipher = new AesXtsMode(); + _baseCipher.Initialize(key1, key2, iv, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/AesXtsCipherNi.cs b/src/LibHac/Crypto/AesXtsCipherNi.cs index 476c0a81..6799378f 100644 --- a/src/LibHac/Crypto/AesXtsCipherNi.cs +++ b/src/LibHac/Crypto/AesXtsCipherNi.cs @@ -4,41 +4,40 @@ using System.Runtime.Intrinsics; using LibHac.Common; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class AesXtsEncryptorNi : ICipherWithIv { - public class AesXtsEncryptorNi : ICipherWithIv + private AesXtsModeNi _baseCipher; + + public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); + + public AesXtsEncryptorNi(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) { - private AesXtsModeNi _baseCipher; - - public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); - - public AesXtsEncryptorNi(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) - { - _baseCipher = new AesXtsModeNi(); - _baseCipher.Initialize(key1, key2, iv, false); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Encrypt(input, output); - } + _baseCipher = new AesXtsModeNi(); + _baseCipher.Initialize(key1, key2, iv, false); } - public class AesXtsDecryptorNi : ICipherWithIv + public void Transform(ReadOnlySpan input, Span output) { - private AesXtsModeNi _baseCipher; - - public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); - - public AesXtsDecryptorNi(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) - { - _baseCipher = new AesXtsModeNi(); - _baseCipher.Initialize(key1, key2, iv, true); - } - - public void Transform(ReadOnlySpan input, Span output) - { - _baseCipher.Decrypt(input, output); - } + _baseCipher.Encrypt(input, output); + } +} + +public class AesXtsDecryptorNi : ICipherWithIv +{ + private AesXtsModeNi _baseCipher; + + public ref Buffer16 Iv => ref Unsafe.As, Buffer16>(ref _baseCipher.Iv); + + public AesXtsDecryptorNi(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv) + { + _baseCipher = new AesXtsModeNi(); + _baseCipher.Initialize(key1, key2, iv, true); + } + + public void Transform(ReadOnlySpan input, Span output) + { + _baseCipher.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/CryptoUtil.cs b/src/LibHac/Crypto/CryptoUtil.cs index fc91d7ea..9301c7ef 100644 --- a/src/LibHac/Crypto/CryptoUtil.cs +++ b/src/LibHac/Crypto/CryptoUtil.cs @@ -2,29 +2,28 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +internal static class CryptoUtil { - internal static class CryptoUtil + public static bool IsSameBytes(ReadOnlySpan buffer1, ReadOnlySpan buffer2, int length) { - public static bool IsSameBytes(ReadOnlySpan buffer1, ReadOnlySpan buffer2, int length) - { - if (buffer1.Length < (uint)length || buffer2.Length < (uint)length) - throw new ArgumentOutOfRangeException(nameof(length)); + if (buffer1.Length < (uint)length || buffer2.Length < (uint)length) + throw new ArgumentOutOfRangeException(nameof(length)); - return IsSameBytes(ref MemoryMarshal.GetReference(buffer1), ref MemoryMarshal.GetReference(buffer2), length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSameBytes(ref byte p1, ref byte p2, int length) - { - int result = 0; - - for (int i = 0; i < length; i++) - { - result |= Unsafe.Add(ref p1, i) ^ Unsafe.Add(ref p2, i); - } - - return result == 0; - } + return IsSameBytes(ref MemoryMarshal.GetReference(buffer1), ref MemoryMarshal.GetReference(buffer2), length); } -} \ No newline at end of file + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSameBytes(ref byte p1, ref byte p2, int length) + { + int result = 0; + + for (int i = 0; i < length; i++) + { + result |= Unsafe.Add(ref p1, i) ^ Unsafe.Add(ref p2, i); + } + + return result == 0; + } +} diff --git a/src/LibHac/Crypto/ICipher.cs b/src/LibHac/Crypto/ICipher.cs index c9df0a06..1ee75e81 100644 --- a/src/LibHac/Crypto/ICipher.cs +++ b/src/LibHac/Crypto/ICipher.cs @@ -1,15 +1,14 @@ using System; using LibHac.Common; -namespace LibHac.Crypto -{ - public interface ICipher - { - void Transform(ReadOnlySpan input, Span output); - } +namespace LibHac.Crypto; - public interface ICipherWithIv : ICipher - { - ref Buffer16 Iv { get; } - } -} \ No newline at end of file +public interface ICipher +{ + void Transform(ReadOnlySpan input, Span output); +} + +public interface ICipherWithIv : ICipher +{ + ref Buffer16 Iv { get; } +} diff --git a/src/LibHac/Crypto/IHash.cs b/src/LibHac/Crypto/IHash.cs index 56ea6857..73295058 100644 --- a/src/LibHac/Crypto/IHash.cs +++ b/src/LibHac/Crypto/IHash.cs @@ -1,11 +1,10 @@ using System; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public interface IHash { - public interface IHash - { - void Initialize(); - void Update(ReadOnlySpan data); - void GetHash(Span hashBuffer); - } -} \ No newline at end of file + void Initialize(); + void Update(ReadOnlySpan data); + void GetHash(Span hashBuffer); +} diff --git a/src/LibHac/Crypto/Impl/AesCbcMode.cs b/src/LibHac/Crypto/Impl/AesCbcMode.cs index e66e7944..09c7e1ea 100644 --- a/src/LibHac/Crypto/Impl/AesCbcMode.cs +++ b/src/LibHac/Crypto/Impl/AesCbcMode.cs @@ -1,26 +1,25 @@ using System; using System.Security.Cryptography; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesCbcMode { - public struct AesCbcMode + private AesCore _aesCore; + + public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, bool isDecrypting) { - private AesCore _aesCore; + _aesCore = new AesCore(); + _aesCore.Initialize(key, iv, CipherMode.CBC, isDecrypting); + } - public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, bool isDecrypting) - { - _aesCore = new AesCore(); - _aesCore.Initialize(key, iv, CipherMode.CBC, isDecrypting); - } + public void Encrypt(ReadOnlySpan input, Span output) + { + _aesCore.Encrypt(input, output); + } - public void Encrypt(ReadOnlySpan input, Span output) - { - _aesCore.Encrypt(input, output); - } - - public void Decrypt(ReadOnlySpan input, Span output) - { - _aesCore.Decrypt(input, output); - } + public void Decrypt(ReadOnlySpan input, Span output) + { + _aesCore.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/Impl/AesCbcModeNi.cs b/src/LibHac/Crypto/Impl/AesCbcModeNi.cs index cd880774..7c61894e 100644 --- a/src/LibHac/Crypto/Impl/AesCbcModeNi.cs +++ b/src/LibHac/Crypto/Impl/AesCbcModeNi.cs @@ -5,105 +5,104 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesCbcModeNi { - public struct AesCbcModeNi + private AesCoreNi _aesCore; + + public Vector128 Iv; + + public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, bool isDecrypting) { - private AesCoreNi _aesCore; + Debug.Assert(iv.Length == Aes.BlockSize); - public Vector128 Iv; + _aesCore.Initialize(key, isDecrypting); - public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, bool isDecrypting) + Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + } + + public void Encrypt(ReadOnlySpan input, Span output) + { + int blockCount = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + Vector128 iv = Iv; + + for (int i = 0; i < blockCount; i++) { - Debug.Assert(iv.Length == Aes.BlockSize); + iv = _aesCore.EncryptBlock(Sse2.Xor(iv, inBlock)); - _aesCore.Initialize(key, isDecrypting); + outBlock = iv; - Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); } - public void Encrypt(ReadOnlySpan input, Span output) + Iv = iv; + } + + public void Decrypt(ReadOnlySpan input, Span output) + { + int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + Vector128 iv = Iv; + + while (remainingBlocks > 7) { - int blockCount = Math.Min(input.Length, output.Length) >> 4; + Vector128 in0 = Unsafe.Add(ref inBlock, 0); + Vector128 in1 = Unsafe.Add(ref inBlock, 1); + Vector128 in2 = Unsafe.Add(ref inBlock, 2); + Vector128 in3 = Unsafe.Add(ref inBlock, 3); + Vector128 in4 = Unsafe.Add(ref inBlock, 4); + Vector128 in5 = Unsafe.Add(ref inBlock, 5); + Vector128 in6 = Unsafe.Add(ref inBlock, 6); + Vector128 in7 = Unsafe.Add(ref inBlock, 7); - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + _aesCore.DecryptBlocks8(in0, in1, in2, in3, in4, in5, in6, in7, + out Vector128 b0, + out Vector128 b1, + out Vector128 b2, + out Vector128 b3, + out Vector128 b4, + out Vector128 b5, + out Vector128 b6, + out Vector128 b7); - Vector128 iv = Iv; + Unsafe.Add(ref outBlock, 0) = Sse2.Xor(iv, b0); + Unsafe.Add(ref outBlock, 1) = Sse2.Xor(in0, b1); + Unsafe.Add(ref outBlock, 2) = Sse2.Xor(in1, b2); + Unsafe.Add(ref outBlock, 3) = Sse2.Xor(in2, b3); + Unsafe.Add(ref outBlock, 4) = Sse2.Xor(in3, b4); + Unsafe.Add(ref outBlock, 5) = Sse2.Xor(in4, b5); + Unsafe.Add(ref outBlock, 6) = Sse2.Xor(in5, b6); + Unsafe.Add(ref outBlock, 7) = Sse2.Xor(in6, b7); - for (int i = 0; i < blockCount; i++) - { - iv = _aesCore.EncryptBlock(Sse2.Xor(iv, inBlock)); + iv = in7; - outBlock = iv; - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - } - - Iv = iv; + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remainingBlocks -= 8; } - public void Decrypt(ReadOnlySpan input, Span output) + while (remainingBlocks > 0) { - int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; + Vector128 currentBlock = inBlock; + Vector128 decBeforeIv = _aesCore.DecryptBlock(currentBlock); + outBlock = Sse2.Xor(decBeforeIv, iv); - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + iv = currentBlock; - Vector128 iv = Iv; - - while (remainingBlocks > 7) - { - Vector128 in0 = Unsafe.Add(ref inBlock, 0); - Vector128 in1 = Unsafe.Add(ref inBlock, 1); - Vector128 in2 = Unsafe.Add(ref inBlock, 2); - Vector128 in3 = Unsafe.Add(ref inBlock, 3); - Vector128 in4 = Unsafe.Add(ref inBlock, 4); - Vector128 in5 = Unsafe.Add(ref inBlock, 5); - Vector128 in6 = Unsafe.Add(ref inBlock, 6); - Vector128 in7 = Unsafe.Add(ref inBlock, 7); - - _aesCore.DecryptBlocks8(in0, in1, in2, in3, in4, in5, in6, in7, - out Vector128 b0, - out Vector128 b1, - out Vector128 b2, - out Vector128 b3, - out Vector128 b4, - out Vector128 b5, - out Vector128 b6, - out Vector128 b7); - - Unsafe.Add(ref outBlock, 0) = Sse2.Xor(iv, b0); - Unsafe.Add(ref outBlock, 1) = Sse2.Xor(in0, b1); - Unsafe.Add(ref outBlock, 2) = Sse2.Xor(in1, b2); - Unsafe.Add(ref outBlock, 3) = Sse2.Xor(in2, b3); - Unsafe.Add(ref outBlock, 4) = Sse2.Xor(in3, b4); - Unsafe.Add(ref outBlock, 5) = Sse2.Xor(in4, b5); - Unsafe.Add(ref outBlock, 6) = Sse2.Xor(in5, b6); - Unsafe.Add(ref outBlock, 7) = Sse2.Xor(in6, b7); - - iv = in7; - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remainingBlocks -= 8; - } - - while (remainingBlocks > 0) - { - Vector128 currentBlock = inBlock; - Vector128 decBeforeIv = _aesCore.DecryptBlock(currentBlock); - outBlock = Sse2.Xor(decBeforeIv, iv); - - iv = currentBlock; - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remainingBlocks -= 1; - } - - Iv = iv; + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remainingBlocks -= 1; } + + Iv = iv; } } diff --git a/src/LibHac/Crypto/Impl/AesCore.cs b/src/LibHac/Crypto/Impl/AesCore.cs index 32023b68..548c5153 100644 --- a/src/LibHac/Crypto/Impl/AesCore.cs +++ b/src/LibHac/Crypto/Impl/AesCore.cs @@ -3,72 +3,71 @@ using System.Diagnostics; using System.Security.Cryptography; using LibHac.Common; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesCore { - public struct AesCore + private ICryptoTransform _transform; + private bool _isDecrypting; + + public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, CipherMode mode, bool isDecrypting) { - private ICryptoTransform _transform; - private bool _isDecrypting; + Debug.Assert(key.Length == Aes.KeySize128); + Debug.Assert(iv.IsEmpty || iv.Length == Aes.BlockSize); - public void Initialize(ReadOnlySpan key, ReadOnlySpan iv, CipherMode mode, bool isDecrypting) + var aes = System.Security.Cryptography.Aes.Create(); + + if (aes == null) throw new CryptographicException("Unable to create AES object"); + aes.Key = key.ToArray(); + aes.Mode = mode; + aes.Padding = PaddingMode.None; + + if (!iv.IsEmpty) { - Debug.Assert(key.Length == Aes.KeySize128); - Debug.Assert(iv.IsEmpty || iv.Length == Aes.BlockSize); - - var aes = System.Security.Cryptography.Aes.Create(); - - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Key = key.ToArray(); - aes.Mode = mode; - aes.Padding = PaddingMode.None; - - if (!iv.IsEmpty) - { - aes.IV = iv.ToArray(); - } - - _transform = isDecrypting ? aes.CreateDecryptor() : aes.CreateEncryptor(); - _isDecrypting = isDecrypting; + aes.IV = iv.ToArray(); } - public void Encrypt(ReadOnlySpan input, Span output) - { - Debug.Assert(!_isDecrypting); - Transform(input, output); - } + _transform = isDecrypting ? aes.CreateDecryptor() : aes.CreateEncryptor(); + _isDecrypting = isDecrypting; + } - public void Decrypt(ReadOnlySpan input, Span output) - { - Debug.Assert(_isDecrypting); - Transform(input, output); - } + public void Encrypt(ReadOnlySpan input, Span output) + { + Debug.Assert(!_isDecrypting); + Transform(input, output); + } - public void Encrypt(byte[] input, byte[] output, int length) - { - Debug.Assert(!_isDecrypting); - Transform(input, output, length); - } + public void Decrypt(ReadOnlySpan input, Span output) + { + Debug.Assert(_isDecrypting); + Transform(input, output); + } - public void Decrypt(byte[] input, byte[] output, int length) - { - Debug.Assert(_isDecrypting); - Transform(input, output, length); - } + public void Encrypt(byte[] input, byte[] output, int length) + { + Debug.Assert(!_isDecrypting); + Transform(input, output, length); + } - private void Transform(ReadOnlySpan input, Span output) - { - using var rented = new RentedArray(input.Length); + public void Decrypt(byte[] input, byte[] output, int length) + { + Debug.Assert(_isDecrypting); + Transform(input, output, length); + } - input.CopyTo(rented.Array); + private void Transform(ReadOnlySpan input, Span output) + { + using var rented = new RentedArray(input.Length); - Transform(rented.Array, rented.Array, input.Length); + input.CopyTo(rented.Array); - rented.Array.CopyTo(output); - } + Transform(rented.Array, rented.Array, input.Length); - private void Transform(byte[] input, byte[] output, int length) - { - _transform.TransformBlock(input, 0, length, output, 0); - } + rented.Array.CopyTo(output); + } + + private void Transform(byte[] input, byte[] output, int length) + { + _transform.TransformBlock(input, 0, length, output, 0); } } diff --git a/src/LibHac/Crypto/Impl/AesCoreNi.cs b/src/LibHac/Crypto/Impl/AesCoreNi.cs index dbfd04a6..24b34e9f 100644 --- a/src/LibHac/Crypto/Impl/AesCoreNi.cs +++ b/src/LibHac/Crypto/Impl/AesCoreNi.cs @@ -6,573 +6,572 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using AesNi = System.Runtime.Intrinsics.X86.Aes; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +[StructLayout(LayoutKind.Sequential, Size = RoundKeyCount * RoundKeySize)] +public struct AesCoreNi { - [StructLayout(LayoutKind.Sequential, Size = RoundKeyCount * RoundKeySize)] - public struct AesCoreNi + private const int RoundKeyCount = 11; + private const int RoundKeySize = 0x10; + + private Vector128 _roundKeys; + + // An Initialize method is used instead of a constructor because it prevents the runtime + // from zeroing out the structure's memory when creating it. + // When processing a single block, doing this can increase performance by 20-40% + // depending on the context. + public void Initialize(ReadOnlySpan key, bool isDecrypting) { - private const int RoundKeyCount = 11; - private const int RoundKeySize = 0x10; + Debug.Assert(key.Length == Aes.KeySize128); - private Vector128 _roundKeys; + KeyExpansion(key, isDecrypting); + } - // An Initialize method is used instead of a constructor because it prevents the runtime - // from zeroing out the structure's memory when creating it. - // When processing a single block, doing this can increase performance by 20-40% - // depending on the context. - public void Initialize(ReadOnlySpan key, bool isDecrypting) + public readonly ReadOnlySpan> RoundKeys => + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _roundKeys), RoundKeyCount); + + public readonly void Encrypt(ReadOnlySpan input, Span output) + { + int blockCount = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + for (int i = 0; i < blockCount; i++) { - Debug.Assert(key.Length == Aes.KeySize128); + outBlock = EncryptBlock(inBlock); - KeyExpansion(key, isDecrypting); - } - - public readonly ReadOnlySpan> RoundKeys => - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _roundKeys), RoundKeyCount); - - public readonly void Encrypt(ReadOnlySpan input, Span output) - { - int blockCount = Math.Min(input.Length, output.Length) >> 4; - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - for (int i = 0; i < blockCount; i++) - { - outBlock = EncryptBlock(inBlock); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - } - } - - public readonly void Decrypt(ReadOnlySpan input, Span output) - { - int blockCount = Math.Min(input.Length, output.Length) >> 4; - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - for (int i = 0; i < blockCount; i++) - { - outBlock = DecryptBlock(inBlock); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector128 EncryptBlock(Vector128 input) - { - ReadOnlySpan> keys = RoundKeys; - - Vector128 b = Sse2.Xor(input, keys[0]); - b = AesNi.Encrypt(b, keys[1]); - b = AesNi.Encrypt(b, keys[2]); - b = AesNi.Encrypt(b, keys[3]); - b = AesNi.Encrypt(b, keys[4]); - b = AesNi.Encrypt(b, keys[5]); - b = AesNi.Encrypt(b, keys[6]); - b = AesNi.Encrypt(b, keys[7]); - b = AesNi.Encrypt(b, keys[8]); - b = AesNi.Encrypt(b, keys[9]); - return AesNi.EncryptLast(b, keys[10]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector128 DecryptBlock(Vector128 input) - { - ReadOnlySpan> keys = RoundKeys; - - Vector128 b = Sse2.Xor(input, keys[10]); - b = AesNi.Decrypt(b, keys[9]); - b = AesNi.Decrypt(b, keys[8]); - b = AesNi.Decrypt(b, keys[7]); - b = AesNi.Decrypt(b, keys[6]); - b = AesNi.Decrypt(b, keys[5]); - b = AesNi.Decrypt(b, keys[4]); - b = AesNi.Decrypt(b, keys[3]); - b = AesNi.Decrypt(b, keys[2]); - b = AesNi.Decrypt(b, keys[1]); - return AesNi.DecryptLast(b, keys[0]); - } - - public readonly void EncryptInterleaved8(ReadOnlySpan input, Span output) - { - int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - while (remainingBlocks > 7) - { - EncryptBlocks8( - Unsafe.Add(ref inBlock, 0), - Unsafe.Add(ref inBlock, 1), - Unsafe.Add(ref inBlock, 2), - Unsafe.Add(ref inBlock, 3), - Unsafe.Add(ref inBlock, 4), - Unsafe.Add(ref inBlock, 5), - Unsafe.Add(ref inBlock, 6), - Unsafe.Add(ref inBlock, 7), - out Unsafe.Add(ref outBlock, 0), - out Unsafe.Add(ref outBlock, 1), - out Unsafe.Add(ref outBlock, 2), - out Unsafe.Add(ref outBlock, 3), - out Unsafe.Add(ref outBlock, 4), - out Unsafe.Add(ref outBlock, 5), - out Unsafe.Add(ref outBlock, 6), - out Unsafe.Add(ref outBlock, 7)); - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remainingBlocks -= 8; - } - - while (remainingBlocks > 0) - { - outBlock = EncryptBlock(inBlock); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remainingBlocks -= 1; - } - } - - public readonly void DecryptInterleaved8(ReadOnlySpan input, Span output) - { - int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - while (remainingBlocks > 7) - { - DecryptBlocks8( - Unsafe.Add(ref inBlock, 0), - Unsafe.Add(ref inBlock, 1), - Unsafe.Add(ref inBlock, 2), - Unsafe.Add(ref inBlock, 3), - Unsafe.Add(ref inBlock, 4), - Unsafe.Add(ref inBlock, 5), - Unsafe.Add(ref inBlock, 6), - Unsafe.Add(ref inBlock, 7), - out Unsafe.Add(ref outBlock, 0), - out Unsafe.Add(ref outBlock, 1), - out Unsafe.Add(ref outBlock, 2), - out Unsafe.Add(ref outBlock, 3), - out Unsafe.Add(ref outBlock, 4), - out Unsafe.Add(ref outBlock, 5), - out Unsafe.Add(ref outBlock, 6), - out Unsafe.Add(ref outBlock, 7)); - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remainingBlocks -= 8; - } - - while (remainingBlocks > 0) - { - outBlock = DecryptBlock(inBlock); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remainingBlocks -= 1; - } - } - - // When inlining this function, RyuJIT will almost make the - // generated code the same as if it were manually inlined - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void EncryptBlocks8( - Vector128 in0, - Vector128 in1, - Vector128 in2, - Vector128 in3, - Vector128 in4, - Vector128 in5, - Vector128 in6, - Vector128 in7, - out Vector128 out0, - out Vector128 out1, - out Vector128 out2, - out Vector128 out3, - out Vector128 out4, - out Vector128 out5, - out Vector128 out6, - out Vector128 out7) - { - ReadOnlySpan> keys = RoundKeys; - - Vector128 key = keys[0]; - Vector128 b0 = Sse2.Xor(in0, key); - Vector128 b1 = Sse2.Xor(in1, key); - Vector128 b2 = Sse2.Xor(in2, key); - Vector128 b3 = Sse2.Xor(in3, key); - Vector128 b4 = Sse2.Xor(in4, key); - Vector128 b5 = Sse2.Xor(in5, key); - Vector128 b6 = Sse2.Xor(in6, key); - Vector128 b7 = Sse2.Xor(in7, key); - - key = keys[1]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[2]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[3]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[4]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[5]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[6]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[7]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[8]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[9]; - b0 = AesNi.Encrypt(b0, key); - b1 = AesNi.Encrypt(b1, key); - b2 = AesNi.Encrypt(b2, key); - b3 = AesNi.Encrypt(b3, key); - b4 = AesNi.Encrypt(b4, key); - b5 = AesNi.Encrypt(b5, key); - b6 = AesNi.Encrypt(b6, key); - b7 = AesNi.Encrypt(b7, key); - - key = keys[10]; - out0 = AesNi.EncryptLast(b0, key); - out1 = AesNi.EncryptLast(b1, key); - out2 = AesNi.EncryptLast(b2, key); - out3 = AesNi.EncryptLast(b3, key); - out4 = AesNi.EncryptLast(b4, key); - out5 = AesNi.EncryptLast(b5, key); - out6 = AesNi.EncryptLast(b6, key); - out7 = AesNi.EncryptLast(b7, key); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void DecryptBlocks8( - Vector128 in0, - Vector128 in1, - Vector128 in2, - Vector128 in3, - Vector128 in4, - Vector128 in5, - Vector128 in6, - Vector128 in7, - out Vector128 out0, - out Vector128 out1, - out Vector128 out2, - out Vector128 out3, - out Vector128 out4, - out Vector128 out5, - out Vector128 out6, - out Vector128 out7) - { - ReadOnlySpan> keys = RoundKeys; - - Vector128 key = keys[10]; - Vector128 b0 = Sse2.Xor(in0, key); - Vector128 b1 = Sse2.Xor(in1, key); - Vector128 b2 = Sse2.Xor(in2, key); - Vector128 b3 = Sse2.Xor(in3, key); - Vector128 b4 = Sse2.Xor(in4, key); - Vector128 b5 = Sse2.Xor(in5, key); - Vector128 b6 = Sse2.Xor(in6, key); - Vector128 b7 = Sse2.Xor(in7, key); - - key = keys[9]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[8]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[7]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[6]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[5]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[4]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[3]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[2]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[1]; - b0 = AesNi.Decrypt(b0, key); - b1 = AesNi.Decrypt(b1, key); - b2 = AesNi.Decrypt(b2, key); - b3 = AesNi.Decrypt(b3, key); - b4 = AesNi.Decrypt(b4, key); - b5 = AesNi.Decrypt(b5, key); - b6 = AesNi.Decrypt(b6, key); - b7 = AesNi.Decrypt(b7, key); - - key = keys[0]; - out0 = AesNi.DecryptLast(b0, key); - out1 = AesNi.DecryptLast(b1, key); - out2 = AesNi.DecryptLast(b2, key); - out3 = AesNi.DecryptLast(b3, key); - out4 = AesNi.DecryptLast(b4, key); - out5 = AesNi.DecryptLast(b5, key); - out6 = AesNi.DecryptLast(b6, key); - out7 = AesNi.DecryptLast(b7, key); - } - - public static Vector128 EncryptBlock(Vector128 input, Vector128 key) - { - Vector128 curKey = key; - Vector128 b = Sse2.Xor(input, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x01)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x02)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x04)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x08)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x10)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x20)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x40)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x80)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x1b)); - b = AesNi.Encrypt(b, curKey); - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x36)); - return AesNi.EncryptLast(b, curKey); - } - - public static Vector128 DecryptBlock(Vector128 input, Vector128 key) - { - Vector128 key0 = key; - Vector128 key1 = KeyExpansion(key0, AesNi.KeygenAssist(key0, 0x01)); - Vector128 key2 = KeyExpansion(key1, AesNi.KeygenAssist(key1, 0x02)); - Vector128 key3 = KeyExpansion(key2, AesNi.KeygenAssist(key2, 0x04)); - Vector128 key4 = KeyExpansion(key3, AesNi.KeygenAssist(key3, 0x08)); - Vector128 key5 = KeyExpansion(key4, AesNi.KeygenAssist(key4, 0x10)); - Vector128 key6 = KeyExpansion(key5, AesNi.KeygenAssist(key5, 0x20)); - Vector128 key7 = KeyExpansion(key6, AesNi.KeygenAssist(key6, 0x40)); - Vector128 key8 = KeyExpansion(key7, AesNi.KeygenAssist(key7, 0x80)); - Vector128 key9 = KeyExpansion(key8, AesNi.KeygenAssist(key8, 0x1b)); - Vector128 key10 = KeyExpansion(key9, AesNi.KeygenAssist(key9, 0x36)); - - Vector128 b = input; - - b = Sse2.Xor(b, key10); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key9)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key8)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key7)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key6)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key5)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key4)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key3)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key2)); - b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key1)); - return AesNi.DecryptLast(b, key0); - } - - private void KeyExpansion(ReadOnlySpan key, bool isDecrypting) - { - Span> roundKeys = MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount); - - var curKey = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(key)); - roundKeys[0] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x01)); - roundKeys[1] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x02)); - roundKeys[2] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x04)); - roundKeys[3] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x08)); - roundKeys[4] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x10)); - roundKeys[5] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x20)); - roundKeys[6] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x40)); - roundKeys[7] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x80)); - roundKeys[8] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x1b)); - roundKeys[9] = curKey; - - curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x36)); - roundKeys[10] = curKey; - - if (isDecrypting) - { - roundKeys[1] = AesNi.InverseMixColumns(roundKeys[1]); - roundKeys[2] = AesNi.InverseMixColumns(roundKeys[2]); - roundKeys[3] = AesNi.InverseMixColumns(roundKeys[3]); - roundKeys[4] = AesNi.InverseMixColumns(roundKeys[4]); - roundKeys[5] = AesNi.InverseMixColumns(roundKeys[5]); - roundKeys[6] = AesNi.InverseMixColumns(roundKeys[6]); - roundKeys[7] = AesNi.InverseMixColumns(roundKeys[7]); - roundKeys[8] = AesNi.InverseMixColumns(roundKeys[8]); - roundKeys[9] = AesNi.InverseMixColumns(roundKeys[9]); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 KeyExpansion(Vector128 s, Vector128 t) - { - t = Sse2.Shuffle(t.AsUInt32(), 0xFF).AsByte(); - - s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 4)); - s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 8)); - - return Sse2.Xor(s, t); + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); } } + + public readonly void Decrypt(ReadOnlySpan input, Span output) + { + int blockCount = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + for (int i = 0; i < blockCount; i++) + { + outBlock = DecryptBlock(inBlock); + + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector128 EncryptBlock(Vector128 input) + { + ReadOnlySpan> keys = RoundKeys; + + Vector128 b = Sse2.Xor(input, keys[0]); + b = AesNi.Encrypt(b, keys[1]); + b = AesNi.Encrypt(b, keys[2]); + b = AesNi.Encrypt(b, keys[3]); + b = AesNi.Encrypt(b, keys[4]); + b = AesNi.Encrypt(b, keys[5]); + b = AesNi.Encrypt(b, keys[6]); + b = AesNi.Encrypt(b, keys[7]); + b = AesNi.Encrypt(b, keys[8]); + b = AesNi.Encrypt(b, keys[9]); + return AesNi.EncryptLast(b, keys[10]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector128 DecryptBlock(Vector128 input) + { + ReadOnlySpan> keys = RoundKeys; + + Vector128 b = Sse2.Xor(input, keys[10]); + b = AesNi.Decrypt(b, keys[9]); + b = AesNi.Decrypt(b, keys[8]); + b = AesNi.Decrypt(b, keys[7]); + b = AesNi.Decrypt(b, keys[6]); + b = AesNi.Decrypt(b, keys[5]); + b = AesNi.Decrypt(b, keys[4]); + b = AesNi.Decrypt(b, keys[3]); + b = AesNi.Decrypt(b, keys[2]); + b = AesNi.Decrypt(b, keys[1]); + return AesNi.DecryptLast(b, keys[0]); + } + + public readonly void EncryptInterleaved8(ReadOnlySpan input, Span output) + { + int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + while (remainingBlocks > 7) + { + EncryptBlocks8( + Unsafe.Add(ref inBlock, 0), + Unsafe.Add(ref inBlock, 1), + Unsafe.Add(ref inBlock, 2), + Unsafe.Add(ref inBlock, 3), + Unsafe.Add(ref inBlock, 4), + Unsafe.Add(ref inBlock, 5), + Unsafe.Add(ref inBlock, 6), + Unsafe.Add(ref inBlock, 7), + out Unsafe.Add(ref outBlock, 0), + out Unsafe.Add(ref outBlock, 1), + out Unsafe.Add(ref outBlock, 2), + out Unsafe.Add(ref outBlock, 3), + out Unsafe.Add(ref outBlock, 4), + out Unsafe.Add(ref outBlock, 5), + out Unsafe.Add(ref outBlock, 6), + out Unsafe.Add(ref outBlock, 7)); + + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remainingBlocks -= 8; + } + + while (remainingBlocks > 0) + { + outBlock = EncryptBlock(inBlock); + + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remainingBlocks -= 1; + } + } + + public readonly void DecryptInterleaved8(ReadOnlySpan input, Span output) + { + int remainingBlocks = Math.Min(input.Length, output.Length) >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + while (remainingBlocks > 7) + { + DecryptBlocks8( + Unsafe.Add(ref inBlock, 0), + Unsafe.Add(ref inBlock, 1), + Unsafe.Add(ref inBlock, 2), + Unsafe.Add(ref inBlock, 3), + Unsafe.Add(ref inBlock, 4), + Unsafe.Add(ref inBlock, 5), + Unsafe.Add(ref inBlock, 6), + Unsafe.Add(ref inBlock, 7), + out Unsafe.Add(ref outBlock, 0), + out Unsafe.Add(ref outBlock, 1), + out Unsafe.Add(ref outBlock, 2), + out Unsafe.Add(ref outBlock, 3), + out Unsafe.Add(ref outBlock, 4), + out Unsafe.Add(ref outBlock, 5), + out Unsafe.Add(ref outBlock, 6), + out Unsafe.Add(ref outBlock, 7)); + + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remainingBlocks -= 8; + } + + while (remainingBlocks > 0) + { + outBlock = DecryptBlock(inBlock); + + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remainingBlocks -= 1; + } + } + + // When inlining this function, RyuJIT will almost make the + // generated code the same as if it were manually inlined + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void EncryptBlocks8( + Vector128 in0, + Vector128 in1, + Vector128 in2, + Vector128 in3, + Vector128 in4, + Vector128 in5, + Vector128 in6, + Vector128 in7, + out Vector128 out0, + out Vector128 out1, + out Vector128 out2, + out Vector128 out3, + out Vector128 out4, + out Vector128 out5, + out Vector128 out6, + out Vector128 out7) + { + ReadOnlySpan> keys = RoundKeys; + + Vector128 key = keys[0]; + Vector128 b0 = Sse2.Xor(in0, key); + Vector128 b1 = Sse2.Xor(in1, key); + Vector128 b2 = Sse2.Xor(in2, key); + Vector128 b3 = Sse2.Xor(in3, key); + Vector128 b4 = Sse2.Xor(in4, key); + Vector128 b5 = Sse2.Xor(in5, key); + Vector128 b6 = Sse2.Xor(in6, key); + Vector128 b7 = Sse2.Xor(in7, key); + + key = keys[1]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[2]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[3]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[4]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[5]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[6]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[7]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[8]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[9]; + b0 = AesNi.Encrypt(b0, key); + b1 = AesNi.Encrypt(b1, key); + b2 = AesNi.Encrypt(b2, key); + b3 = AesNi.Encrypt(b3, key); + b4 = AesNi.Encrypt(b4, key); + b5 = AesNi.Encrypt(b5, key); + b6 = AesNi.Encrypt(b6, key); + b7 = AesNi.Encrypt(b7, key); + + key = keys[10]; + out0 = AesNi.EncryptLast(b0, key); + out1 = AesNi.EncryptLast(b1, key); + out2 = AesNi.EncryptLast(b2, key); + out3 = AesNi.EncryptLast(b3, key); + out4 = AesNi.EncryptLast(b4, key); + out5 = AesNi.EncryptLast(b5, key); + out6 = AesNi.EncryptLast(b6, key); + out7 = AesNi.EncryptLast(b7, key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void DecryptBlocks8( + Vector128 in0, + Vector128 in1, + Vector128 in2, + Vector128 in3, + Vector128 in4, + Vector128 in5, + Vector128 in6, + Vector128 in7, + out Vector128 out0, + out Vector128 out1, + out Vector128 out2, + out Vector128 out3, + out Vector128 out4, + out Vector128 out5, + out Vector128 out6, + out Vector128 out7) + { + ReadOnlySpan> keys = RoundKeys; + + Vector128 key = keys[10]; + Vector128 b0 = Sse2.Xor(in0, key); + Vector128 b1 = Sse2.Xor(in1, key); + Vector128 b2 = Sse2.Xor(in2, key); + Vector128 b3 = Sse2.Xor(in3, key); + Vector128 b4 = Sse2.Xor(in4, key); + Vector128 b5 = Sse2.Xor(in5, key); + Vector128 b6 = Sse2.Xor(in6, key); + Vector128 b7 = Sse2.Xor(in7, key); + + key = keys[9]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[8]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[7]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[6]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[5]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[4]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[3]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[2]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[1]; + b0 = AesNi.Decrypt(b0, key); + b1 = AesNi.Decrypt(b1, key); + b2 = AesNi.Decrypt(b2, key); + b3 = AesNi.Decrypt(b3, key); + b4 = AesNi.Decrypt(b4, key); + b5 = AesNi.Decrypt(b5, key); + b6 = AesNi.Decrypt(b6, key); + b7 = AesNi.Decrypt(b7, key); + + key = keys[0]; + out0 = AesNi.DecryptLast(b0, key); + out1 = AesNi.DecryptLast(b1, key); + out2 = AesNi.DecryptLast(b2, key); + out3 = AesNi.DecryptLast(b3, key); + out4 = AesNi.DecryptLast(b4, key); + out5 = AesNi.DecryptLast(b5, key); + out6 = AesNi.DecryptLast(b6, key); + out7 = AesNi.DecryptLast(b7, key); + } + + public static Vector128 EncryptBlock(Vector128 input, Vector128 key) + { + Vector128 curKey = key; + Vector128 b = Sse2.Xor(input, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x01)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x02)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x04)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x08)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x10)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x20)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x40)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x80)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x1b)); + b = AesNi.Encrypt(b, curKey); + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x36)); + return AesNi.EncryptLast(b, curKey); + } + + public static Vector128 DecryptBlock(Vector128 input, Vector128 key) + { + Vector128 key0 = key; + Vector128 key1 = KeyExpansion(key0, AesNi.KeygenAssist(key0, 0x01)); + Vector128 key2 = KeyExpansion(key1, AesNi.KeygenAssist(key1, 0x02)); + Vector128 key3 = KeyExpansion(key2, AesNi.KeygenAssist(key2, 0x04)); + Vector128 key4 = KeyExpansion(key3, AesNi.KeygenAssist(key3, 0x08)); + Vector128 key5 = KeyExpansion(key4, AesNi.KeygenAssist(key4, 0x10)); + Vector128 key6 = KeyExpansion(key5, AesNi.KeygenAssist(key5, 0x20)); + Vector128 key7 = KeyExpansion(key6, AesNi.KeygenAssist(key6, 0x40)); + Vector128 key8 = KeyExpansion(key7, AesNi.KeygenAssist(key7, 0x80)); + Vector128 key9 = KeyExpansion(key8, AesNi.KeygenAssist(key8, 0x1b)); + Vector128 key10 = KeyExpansion(key9, AesNi.KeygenAssist(key9, 0x36)); + + Vector128 b = input; + + b = Sse2.Xor(b, key10); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key9)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key8)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key7)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key6)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key5)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key4)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key3)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key2)); + b = AesNi.Decrypt(b, AesNi.InverseMixColumns(key1)); + return AesNi.DecryptLast(b, key0); + } + + private void KeyExpansion(ReadOnlySpan key, bool isDecrypting) + { + Span> roundKeys = MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount); + + var curKey = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(key)); + roundKeys[0] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x01)); + roundKeys[1] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x02)); + roundKeys[2] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x04)); + roundKeys[3] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x08)); + roundKeys[4] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x10)); + roundKeys[5] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x20)); + roundKeys[6] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x40)); + roundKeys[7] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x80)); + roundKeys[8] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x1b)); + roundKeys[9] = curKey; + + curKey = KeyExpansion(curKey, AesNi.KeygenAssist(curKey, 0x36)); + roundKeys[10] = curKey; + + if (isDecrypting) + { + roundKeys[1] = AesNi.InverseMixColumns(roundKeys[1]); + roundKeys[2] = AesNi.InverseMixColumns(roundKeys[2]); + roundKeys[3] = AesNi.InverseMixColumns(roundKeys[3]); + roundKeys[4] = AesNi.InverseMixColumns(roundKeys[4]); + roundKeys[5] = AesNi.InverseMixColumns(roundKeys[5]); + roundKeys[6] = AesNi.InverseMixColumns(roundKeys[6]); + roundKeys[7] = AesNi.InverseMixColumns(roundKeys[7]); + roundKeys[8] = AesNi.InverseMixColumns(roundKeys[8]); + roundKeys[9] = AesNi.InverseMixColumns(roundKeys[9]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 KeyExpansion(Vector128 s, Vector128 t) + { + t = Sse2.Shuffle(t.AsUInt32(), 0xFF).AsByte(); + + s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 4)); + s = Sse2.Xor(s, Sse2.ShiftLeftLogical128BitLane(s, 8)); + + return Sse2.Xor(s, t); + } } diff --git a/src/LibHac/Crypto/Impl/AesCtrMode.cs b/src/LibHac/Crypto/Impl/AesCtrMode.cs index f6c38f68..b5ac9859 100644 --- a/src/LibHac/Crypto/Impl/AesCtrMode.cs +++ b/src/LibHac/Crypto/Impl/AesCtrMode.cs @@ -7,51 +7,50 @@ using System.Security.Cryptography; using LibHac.Common; using LibHac.Util; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesCtrMode { - public struct AesCtrMode + private AesCore _aesCore; + public Buffer16 Iv; + + public void Initialize(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCore _aesCore; - public Buffer16 Iv; + Debug.Assert(iv.Length == Aes.BlockSize); - public void Initialize(ReadOnlySpan key, ReadOnlySpan iv) + _aesCore = new AesCore(); + _aesCore.Initialize(key, ReadOnlySpan.Empty, CipherMode.ECB, false); + + Iv = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(iv)); + } + + public void Transform(ReadOnlySpan input, Span output) + { + int blockCount = BitUtil.DivideUp(input.Length, Aes.BlockSize); + int length = blockCount * Aes.BlockSize; + + using var counterBuffer = new RentedArray(length); + FillDecryptedCounter(Iv, counterBuffer.Span); + + _aesCore.Encrypt(counterBuffer.Array, counterBuffer.Array, length); + Utilities.XorArrays(output, input, counterBuffer.Span); + } + + private static void FillDecryptedCounter(Span counter, Span buffer) + { + Span bufL = MemoryMarshal.Cast(buffer); + Span counterL = MemoryMarshal.Cast(counter); + + ulong hi = counterL[0]; + ulong lo = BinaryPrimitives.ReverseEndianness(counterL[1]); + + for (int i = 0; i < bufL.Length; i += 2) { - Debug.Assert(iv.Length == Aes.BlockSize); - - _aesCore = new AesCore(); - _aesCore.Initialize(key, ReadOnlySpan.Empty, CipherMode.ECB, false); - - Iv = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(iv)); + bufL[i] = hi; + bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo); + lo++; } - public void Transform(ReadOnlySpan input, Span output) - { - int blockCount = BitUtil.DivideUp(input.Length, Aes.BlockSize); - int length = blockCount * Aes.BlockSize; - - using var counterBuffer = new RentedArray(length); - FillDecryptedCounter(Iv, counterBuffer.Span); - - _aesCore.Encrypt(counterBuffer.Array, counterBuffer.Array, length); - Utilities.XorArrays(output, input, counterBuffer.Span); - } - - private static void FillDecryptedCounter(Span counter, Span buffer) - { - Span bufL = MemoryMarshal.Cast(buffer); - Span counterL = MemoryMarshal.Cast(counter); - - ulong hi = counterL[0]; - ulong lo = BinaryPrimitives.ReverseEndianness(counterL[1]); - - for (int i = 0; i < bufL.Length; i += 2) - { - bufL[i] = hi; - bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo); - lo++; - } - - counterL[1] = BinaryPrimitives.ReverseEndianness(lo); - } + counterL[1] = BinaryPrimitives.ReverseEndianness(lo); } } diff --git a/src/LibHac/Crypto/Impl/AesCtrModeNi.cs b/src/LibHac/Crypto/Impl/AesCtrModeNi.cs index 85fe48f5..85481969 100644 --- a/src/LibHac/Crypto/Impl/AesCtrModeNi.cs +++ b/src/LibHac/Crypto/Impl/AesCtrModeNi.cs @@ -5,121 +5,120 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesCtrModeNi { - public struct AesCtrModeNi + private AesCoreNi _aesCore; + + public Vector128 Iv; + + public void Initialize(ReadOnlySpan key, ReadOnlySpan iv) { - private AesCoreNi _aesCore; + Debug.Assert(iv.Length == Aes.BlockSize); - public Vector128 Iv; + _aesCore.Initialize(key, false); - public void Initialize(ReadOnlySpan key, ReadOnlySpan iv) + Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + } + + public void Transform(ReadOnlySpan input, Span output) + { + int remaining = Math.Min(input.Length, output.Length); + int blockCount = remaining >> 4; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + Vector128 byteSwapMask = Vector128.Create((ulong)0x706050403020100, 0x8090A0B0C0D0E0F).AsByte(); + var inc = Vector128.Create((ulong)0, 1); + + Vector128 iv = Iv; + Vector128 bSwappedIv = Ssse3.Shuffle(iv, byteSwapMask).AsUInt64(); + + while (remaining >= 8 * Aes.BlockSize) { - Debug.Assert(iv.Length == Aes.BlockSize); + Vector128 b0 = iv; - _aesCore.Initialize(key, false); + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b1 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b2 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b3 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b4 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b5 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b6 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + bSwappedIv = Sse2.Add(bSwappedIv, inc); + Vector128 b7 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + _aesCore.EncryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, + out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); + + Unsafe.Add(ref outBlock, 0) = Sse2.Xor(Unsafe.Add(ref inBlock, 0), b0); + Unsafe.Add(ref outBlock, 1) = Sse2.Xor(Unsafe.Add(ref inBlock, 1), b1); + Unsafe.Add(ref outBlock, 2) = Sse2.Xor(Unsafe.Add(ref inBlock, 2), b2); + Unsafe.Add(ref outBlock, 3) = Sse2.Xor(Unsafe.Add(ref inBlock, 3), b3); + Unsafe.Add(ref outBlock, 4) = Sse2.Xor(Unsafe.Add(ref inBlock, 4), b4); + Unsafe.Add(ref outBlock, 5) = Sse2.Xor(Unsafe.Add(ref inBlock, 5), b5); + Unsafe.Add(ref outBlock, 6) = Sse2.Xor(Unsafe.Add(ref inBlock, 6), b6); + Unsafe.Add(ref outBlock, 7) = Sse2.Xor(Unsafe.Add(ref inBlock, 7), b7); + + // Increase the counter + bSwappedIv = Sse2.Add(bSwappedIv, inc); + iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); + + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remaining -= 8 * Aes.BlockSize; } - public void Transform(ReadOnlySpan input, Span output) + while (remaining >= Aes.BlockSize) { - int remaining = Math.Min(input.Length, output.Length); - int blockCount = remaining >> 4; + Vector128 encIv = _aesCore.EncryptBlock(iv); + outBlock = Sse2.Xor(inBlock, encIv); - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + // Increase the counter + bSwappedIv = Sse2.Add(bSwappedIv, inc); + iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - Vector128 byteSwapMask = Vector128.Create((ulong)0x706050403020100, 0x8090A0B0C0D0E0F).AsByte(); - var inc = Vector128.Create((ulong)0, 1); - - Vector128 iv = Iv; - Vector128 bSwappedIv = Ssse3.Shuffle(iv, byteSwapMask).AsUInt64(); - - while (remaining >= 8 * Aes.BlockSize) - { - Vector128 b0 = iv; - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b1 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b2 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b3 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b4 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b5 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b6 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - bSwappedIv = Sse2.Add(bSwappedIv, inc); - Vector128 b7 = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - _aesCore.EncryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, - out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); - - Unsafe.Add(ref outBlock, 0) = Sse2.Xor(Unsafe.Add(ref inBlock, 0), b0); - Unsafe.Add(ref outBlock, 1) = Sse2.Xor(Unsafe.Add(ref inBlock, 1), b1); - Unsafe.Add(ref outBlock, 2) = Sse2.Xor(Unsafe.Add(ref inBlock, 2), b2); - Unsafe.Add(ref outBlock, 3) = Sse2.Xor(Unsafe.Add(ref inBlock, 3), b3); - Unsafe.Add(ref outBlock, 4) = Sse2.Xor(Unsafe.Add(ref inBlock, 4), b4); - Unsafe.Add(ref outBlock, 5) = Sse2.Xor(Unsafe.Add(ref inBlock, 5), b5); - Unsafe.Add(ref outBlock, 6) = Sse2.Xor(Unsafe.Add(ref inBlock, 6), b6); - Unsafe.Add(ref outBlock, 7) = Sse2.Xor(Unsafe.Add(ref inBlock, 7), b7); - - // Increase the counter - bSwappedIv = Sse2.Add(bSwappedIv, inc); - iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remaining -= 8 * Aes.BlockSize; - } - - while (remaining >= Aes.BlockSize) - { - Vector128 encIv = _aesCore.EncryptBlock(iv); - outBlock = Sse2.Xor(inBlock, encIv); - - // Increase the counter - bSwappedIv = Sse2.Add(bSwappedIv, inc); - iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remaining -= Aes.BlockSize; - } - - Iv = iv; - - if (remaining != 0) - { - EncryptCtrPartialBlock(input.Slice(blockCount * 0x10), output.Slice(blockCount * 0x10)); - } + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remaining -= Aes.BlockSize; } - private void EncryptCtrPartialBlock(ReadOnlySpan input, Span output) + Iv = iv; + + if (remaining != 0) { - Span counter = stackalloc byte[0x10]; - Unsafe.WriteUnaligned(ref counter[0], Iv); - - _aesCore.Encrypt(counter, counter); - - input.CopyTo(output); - Utilities.XorArrays(output, counter); - - for (int i = 0; i < counter.Length; i++) - { - if (++counter[i] != 0) break; - } - - Unsafe.ReadUnaligned>(ref counter[0]); + EncryptCtrPartialBlock(input.Slice(blockCount * 0x10), output.Slice(blockCount * 0x10)); } } + + private void EncryptCtrPartialBlock(ReadOnlySpan input, Span output) + { + Span counter = stackalloc byte[0x10]; + Unsafe.WriteUnaligned(ref counter[0], Iv); + + _aesCore.Encrypt(counter, counter); + + input.CopyTo(output); + Utilities.XorArrays(output, counter); + + for (int i = 0; i < counter.Length; i++) + { + if (++counter[i] != 0) break; + } + + Unsafe.ReadUnaligned>(ref counter[0]); + } } diff --git a/src/LibHac/Crypto/Impl/AesEcbMode.cs b/src/LibHac/Crypto/Impl/AesEcbMode.cs index 0b1b7b26..2d76d5fb 100644 --- a/src/LibHac/Crypto/Impl/AesEcbMode.cs +++ b/src/LibHac/Crypto/Impl/AesEcbMode.cs @@ -1,26 +1,25 @@ using System; using System.Security.Cryptography; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesEcbMode { - public struct AesEcbMode + private AesCore _aesCore; + + public void Initialize(ReadOnlySpan key, bool isDecrypting) { - private AesCore _aesCore; + _aesCore = new AesCore(); + _aesCore.Initialize(key, ReadOnlySpan.Empty, CipherMode.ECB, isDecrypting); + } - public void Initialize(ReadOnlySpan key, bool isDecrypting) - { - _aesCore = new AesCore(); - _aesCore.Initialize(key, ReadOnlySpan.Empty, CipherMode.ECB, isDecrypting); - } + public void Encrypt(ReadOnlySpan input, Span output) + { + _aesCore.Encrypt(input, output); + } - public void Encrypt(ReadOnlySpan input, Span output) - { - _aesCore.Encrypt(input, output); - } - - public void Decrypt(ReadOnlySpan input, Span output) - { - _aesCore.Decrypt(input, output); - } + public void Decrypt(ReadOnlySpan input, Span output) + { + _aesCore.Decrypt(input, output); } } diff --git a/src/LibHac/Crypto/Impl/AesEcbModeNi.cs b/src/LibHac/Crypto/Impl/AesEcbModeNi.cs index fa7a03fc..88b5826f 100644 --- a/src/LibHac/Crypto/Impl/AesEcbModeNi.cs +++ b/src/LibHac/Crypto/Impl/AesEcbModeNi.cs @@ -1,24 +1,23 @@ using System; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesEcbModeNi { - public struct AesEcbModeNi + private AesCoreNi _aesCore; + + public void Initialize(ReadOnlySpan key, bool isDecrypting) { - private AesCoreNi _aesCore; + _aesCore.Initialize(key, isDecrypting); + } - public void Initialize(ReadOnlySpan key, bool isDecrypting) - { - _aesCore.Initialize(key, isDecrypting); - } + public void Encrypt(ReadOnlySpan input, Span output) + { + _aesCore.EncryptInterleaved8(input, output); + } - public void Encrypt(ReadOnlySpan input, Span output) - { - _aesCore.EncryptInterleaved8(input, output); - } - - public void Decrypt(ReadOnlySpan input, Span output) - { - _aesCore.DecryptInterleaved8(input, output); - } + public void Decrypt(ReadOnlySpan input, Span output) + { + _aesCore.DecryptInterleaved8(input, output); } } diff --git a/src/LibHac/Crypto/Impl/AesXtsMode.cs b/src/LibHac/Crypto/Impl/AesXtsMode.cs index 4c51f187..af446541 100644 --- a/src/LibHac/Crypto/Impl/AesXtsMode.cs +++ b/src/LibHac/Crypto/Impl/AesXtsMode.cs @@ -5,173 +5,172 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using LibHac.Common; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesXtsMode { - public struct AesXtsMode + private AesCore _dataAesCore; + private AesCore _tweakAesCore; + public Buffer16 Iv; + + public void Initialize(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool isDecrypting) { - private AesCore _dataAesCore; - private AesCore _tweakAesCore; - public Buffer16 Iv; + Debug.Assert(iv.Length == Aes.BlockSize); - public void Initialize(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool isDecrypting) + _dataAesCore = new AesCore(); + _tweakAesCore = new AesCore(); + + _dataAesCore.Initialize(key1, ReadOnlySpan.Empty, CipherMode.ECB, isDecrypting); + _tweakAesCore.Initialize(key2, ReadOnlySpan.Empty, CipherMode.ECB, false); + + Iv = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(iv)); + } + + public void Encrypt(ReadOnlySpan input, Span output) + { + int length = Math.Min(input.Length, output.Length); + int blockCount = length >> 4; + int leftover = length & 0xF; + + // Data units must be at least 1 block long. + if (length < Aes.BlockSize) + throw new ArgumentException(); + + var tweak = new Buffer16(); + + _tweakAesCore.Encrypt(Iv, tweak); + + using var tweakBuffer = new RentedArray(blockCount * Aes.BlockSize); + tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast(tweakBuffer.Span)); + + Utilities.XorArrays(output, input, tweakBuffer.Span); + _dataAesCore.Encrypt(output.Slice(0, blockCount * Aes.BlockSize), output); + Utilities.XorArrays(output, output, tweakBuffer.Array); + + if (leftover != 0) { - Debug.Assert(iv.Length == Aes.BlockSize); + Buffer16 inBlock = + Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount); - _dataAesCore = new AesCore(); - _tweakAesCore = new AesCore(); + ref Buffer16 outBlock = + ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount); - _dataAesCore.Initialize(key1, ReadOnlySpan.Empty, CipherMode.ECB, isDecrypting); - _tweakAesCore.Initialize(key2, ReadOnlySpan.Empty, CipherMode.ECB, false); + ref Buffer16 prevOutBlock = ref Unsafe.Subtract(ref outBlock, 1); - Iv = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(iv)); + var tmp = new Buffer16(); + + for (int i = 0; i < leftover; i++) + { + outBlock[i] = prevOutBlock[i]; + tmp[i] = inBlock[i]; + } + + for (int i = leftover; i < Aes.BlockSize; i++) + { + tmp[i] = prevOutBlock[i]; + } + + XorBuffer(ref tmp, ref tmp, ref tweak); + _dataAesCore.Encrypt(tmp, tmp); + XorBuffer(ref prevOutBlock, ref tmp, ref tweak); } + } - public void Encrypt(ReadOnlySpan input, Span output) + public void Decrypt(ReadOnlySpan input, Span output) + { + int length = Math.Min(input.Length, output.Length); + int blockCount = length >> 4; + int leftover = length & 0xF; + + // Data units must be at least 1 block long. + if (length < Aes.BlockSize) + throw new ArgumentException(); + + if (leftover != 0) blockCount--; + + var tweak = new Buffer16(); + + _tweakAesCore.Encrypt(Iv, tweak); + + if (blockCount > 0) { - int length = Math.Min(input.Length, output.Length); - int blockCount = length >> 4; - int leftover = length & 0xF; - - // Data units must be at least 1 block long. - if (length < Aes.BlockSize) - throw new ArgumentException(); - - var tweak = new Buffer16(); - - _tweakAesCore.Encrypt(Iv, tweak); - using var tweakBuffer = new RentedArray(blockCount * Aes.BlockSize); tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast(tweakBuffer.Span)); Utilities.XorArrays(output, input, tweakBuffer.Span); - _dataAesCore.Encrypt(output.Slice(0, blockCount * Aes.BlockSize), output); - Utilities.XorArrays(output, output, tweakBuffer.Array); - - if (leftover != 0) - { - Buffer16 inBlock = - Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount); - - ref Buffer16 outBlock = - ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount); - - ref Buffer16 prevOutBlock = ref Unsafe.Subtract(ref outBlock, 1); - - var tmp = new Buffer16(); - - for (int i = 0; i < leftover; i++) - { - outBlock[i] = prevOutBlock[i]; - tmp[i] = inBlock[i]; - } - - for (int i = leftover; i < Aes.BlockSize; i++) - { - tmp[i] = prevOutBlock[i]; - } - - XorBuffer(ref tmp, ref tmp, ref tweak); - _dataAesCore.Encrypt(tmp, tmp); - XorBuffer(ref prevOutBlock, ref tmp, ref tweak); - } + _dataAesCore.Decrypt(output.Slice(0, blockCount * Aes.BlockSize), output); + Utilities.XorArrays(output, output, tweakBuffer.Span); } - public void Decrypt(ReadOnlySpan input, Span output) + if (leftover != 0) { - int length = Math.Min(input.Length, output.Length); - int blockCount = length >> 4; - int leftover = length & 0xF; + Buffer16 finalTweak = tweak; + Gf128Mul(ref finalTweak); - // Data units must be at least 1 block long. - if (length < Aes.BlockSize) - throw new ArgumentException(); + Buffer16 inBlock = + Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount); - if (leftover != 0) blockCount--; + ref Buffer16 outBlock = + ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount); - var tweak = new Buffer16(); + var tmp = new Buffer16(); - _tweakAesCore.Encrypt(Iv, tweak); + XorBuffer(ref tmp, ref inBlock, ref finalTweak); + _dataAesCore.Decrypt(tmp, tmp); + XorBuffer(ref outBlock, ref tmp, ref finalTweak); - if (blockCount > 0) + ref Buffer16 finalOutBlock = ref Unsafe.Add(ref outBlock, 1); + + Buffer16 finalInBlock = Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), + blockCount + 1); + + for (int i = 0; i < leftover; i++) { - using var tweakBuffer = new RentedArray(blockCount * Aes.BlockSize); - tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast(tweakBuffer.Span)); - - Utilities.XorArrays(output, input, tweakBuffer.Span); - _dataAesCore.Decrypt(output.Slice(0, blockCount * Aes.BlockSize), output); - Utilities.XorArrays(output, output, tweakBuffer.Span); + finalOutBlock[i] = outBlock[i]; + tmp[i] = finalInBlock[i]; } - if (leftover != 0) + for (int i = leftover; i < Aes.BlockSize; i++) { - Buffer16 finalTweak = tweak; - Gf128Mul(ref finalTweak); - - Buffer16 inBlock = - Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount); - - ref Buffer16 outBlock = - ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount); - - var tmp = new Buffer16(); - - XorBuffer(ref tmp, ref inBlock, ref finalTweak); - _dataAesCore.Decrypt(tmp, tmp); - XorBuffer(ref outBlock, ref tmp, ref finalTweak); - - ref Buffer16 finalOutBlock = ref Unsafe.Add(ref outBlock, 1); - - Buffer16 finalInBlock = Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), - blockCount + 1); - - for (int i = 0; i < leftover; i++) - { - finalOutBlock[i] = outBlock[i]; - tmp[i] = finalInBlock[i]; - } - - for (int i = leftover; i < Aes.BlockSize; i++) - { - tmp[i] = outBlock[i]; - } - - XorBuffer(ref tmp, ref tmp, ref tweak); - _dataAesCore.Decrypt(tmp, tmp); - XorBuffer(ref outBlock, ref tmp, ref tweak); - } - } - - private static Buffer16 FillTweakBuffer(Buffer16 initialTweak, Span tweakBuffer) - { - for (int i = 0; i < tweakBuffer.Length; i++) - { - tweakBuffer[i] = initialTweak; - Gf128Mul(ref initialTweak); + tmp[i] = outBlock[i]; } - return initialTweak; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Gf128Mul(ref Buffer16 buffer) - { - Span b = buffer.AsSpan(); - - ulong tt = (ulong)((long)b[1] >> 63) & 0x87; - - b[1] = (b[1] << 1) | (b[0] >> 63); - b[0] = (b[0] << 1) ^ tt; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void XorBuffer(ref Buffer16 output, ref Buffer16 input1, ref Buffer16 input2) - { - Span outputS = output.AsSpan(); - Span input1S = input1.AsSpan(); - Span input2S = input2.AsSpan(); - - outputS[0] = input1S[0] ^ input2S[0]; - outputS[1] = input1S[1] ^ input2S[1]; + XorBuffer(ref tmp, ref tmp, ref tweak); + _dataAesCore.Decrypt(tmp, tmp); + XorBuffer(ref outBlock, ref tmp, ref tweak); } } + + private static Buffer16 FillTweakBuffer(Buffer16 initialTweak, Span tweakBuffer) + { + for (int i = 0; i < tweakBuffer.Length; i++) + { + tweakBuffer[i] = initialTweak; + Gf128Mul(ref initialTweak); + } + + return initialTweak; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Gf128Mul(ref Buffer16 buffer) + { + Span b = buffer.AsSpan(); + + ulong tt = (ulong)((long)b[1] >> 63) & 0x87; + + b[1] = (b[1] << 1) | (b[0] >> 63); + b[0] = (b[0] << 1) ^ tt; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void XorBuffer(ref Buffer16 output, ref Buffer16 input1, ref Buffer16 input2) + { + Span outputS = output.AsSpan(); + Span input1S = input1.AsSpan(); + Span input2S = input2.AsSpan(); + + outputS[0] = input1S[0] ^ input2S[0]; + outputS[1] = input1S[1] ^ input2S[1]; + } } diff --git a/src/LibHac/Crypto/Impl/AesXtsModeNi.cs b/src/LibHac/Crypto/Impl/AesXtsModeNi.cs index 627bc67f..aa308070 100644 --- a/src/LibHac/Crypto/Impl/AesXtsModeNi.cs +++ b/src/LibHac/Crypto/Impl/AesXtsModeNi.cs @@ -6,250 +6,249 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using LibHac.Common; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct AesXtsModeNi { - public struct AesXtsModeNi + private AesCoreNi _dataAesCore; + private AesCoreNi _tweakAesCore; + + public Vector128 Iv; + + public void Initialize(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool decrypting) { - private AesCoreNi _dataAesCore; - private AesCoreNi _tweakAesCore; + Debug.Assert(iv.Length == Aes.KeySize128); - public Vector128 Iv; + _dataAesCore.Initialize(key1, decrypting); + _tweakAesCore.Initialize(key2, false); - public void Initialize(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool decrypting) + Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + } + + public void Encrypt(ReadOnlySpan input, Span output) + { + int length = Math.Min(input.Length, output.Length); + int remainingBlocks = length >> 4; + int leftover = length & 0xF; + + Debug.Assert(remainingBlocks > 0); + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + Vector128 mask = Vector128.Create(0x87, 1).AsByte(); + + Vector128 tweak = _tweakAesCore.EncryptBlock(Iv); + + while (remainingBlocks > 7) { - Debug.Assert(iv.Length == Aes.KeySize128); + Vector128 b0 = Sse2.Xor(tweak, Unsafe.Add(ref inBlock, 0)); - _dataAesCore.Initialize(key1, decrypting); - _tweakAesCore.Initialize(key2, false); + Vector128 tweak1 = Gf128Mul(tweak, mask); + Vector128 b1 = Sse2.Xor(tweak1, Unsafe.Add(ref inBlock, 1)); - Iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv)); + Vector128 tweak2 = Gf128Mul(tweak1, mask); + Vector128 b2 = Sse2.Xor(tweak2, Unsafe.Add(ref inBlock, 2)); + + Vector128 tweak3 = Gf128Mul(tweak2, mask); + Vector128 b3 = Sse2.Xor(tweak3, Unsafe.Add(ref inBlock, 3)); + + Vector128 tweak4 = Gf128Mul(tweak3, mask); + Vector128 b4 = Sse2.Xor(tweak4, Unsafe.Add(ref inBlock, 4)); + + Vector128 tweak5 = Gf128Mul(tweak4, mask); + Vector128 b5 = Sse2.Xor(tweak5, Unsafe.Add(ref inBlock, 5)); + + Vector128 tweak6 = Gf128Mul(tweak5, mask); + Vector128 b6 = Sse2.Xor(tweak6, Unsafe.Add(ref inBlock, 6)); + + Vector128 tweak7 = Gf128Mul(tweak6, mask); + Vector128 b7 = Sse2.Xor(tweak7, Unsafe.Add(ref inBlock, 7)); + + _dataAesCore.EncryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, + out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); + + Unsafe.Add(ref outBlock, 0) = Sse2.Xor(tweak, b0); + Unsafe.Add(ref outBlock, 1) = Sse2.Xor(tweak1, b1); + Unsafe.Add(ref outBlock, 2) = Sse2.Xor(tweak2, b2); + Unsafe.Add(ref outBlock, 3) = Sse2.Xor(tweak3, b3); + Unsafe.Add(ref outBlock, 4) = Sse2.Xor(tweak4, b4); + Unsafe.Add(ref outBlock, 5) = Sse2.Xor(tweak5, b5); + Unsafe.Add(ref outBlock, 6) = Sse2.Xor(tweak6, b6); + Unsafe.Add(ref outBlock, 7) = Sse2.Xor(tweak7, b7); + + tweak = Gf128Mul(tweak7, mask); + + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remainingBlocks -= 8; } - public void Encrypt(ReadOnlySpan input, Span output) + while (remainingBlocks > 0) { - int length = Math.Min(input.Length, output.Length); - int remainingBlocks = length >> 4; - int leftover = length & 0xF; - - Debug.Assert(remainingBlocks > 0); - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - Vector128 mask = Vector128.Create(0x87, 1).AsByte(); - - Vector128 tweak = _tweakAesCore.EncryptBlock(Iv); - - while (remainingBlocks > 7) - { - Vector128 b0 = Sse2.Xor(tweak, Unsafe.Add(ref inBlock, 0)); - - Vector128 tweak1 = Gf128Mul(tweak, mask); - Vector128 b1 = Sse2.Xor(tweak1, Unsafe.Add(ref inBlock, 1)); - - Vector128 tweak2 = Gf128Mul(tweak1, mask); - Vector128 b2 = Sse2.Xor(tweak2, Unsafe.Add(ref inBlock, 2)); - - Vector128 tweak3 = Gf128Mul(tweak2, mask); - Vector128 b3 = Sse2.Xor(tweak3, Unsafe.Add(ref inBlock, 3)); - - Vector128 tweak4 = Gf128Mul(tweak3, mask); - Vector128 b4 = Sse2.Xor(tweak4, Unsafe.Add(ref inBlock, 4)); - - Vector128 tweak5 = Gf128Mul(tweak4, mask); - Vector128 b5 = Sse2.Xor(tweak5, Unsafe.Add(ref inBlock, 5)); - - Vector128 tweak6 = Gf128Mul(tweak5, mask); - Vector128 b6 = Sse2.Xor(tweak6, Unsafe.Add(ref inBlock, 6)); - - Vector128 tweak7 = Gf128Mul(tweak6, mask); - Vector128 b7 = Sse2.Xor(tweak7, Unsafe.Add(ref inBlock, 7)); - - _dataAesCore.EncryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, - out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); - - Unsafe.Add(ref outBlock, 0) = Sse2.Xor(tweak, b0); - Unsafe.Add(ref outBlock, 1) = Sse2.Xor(tweak1, b1); - Unsafe.Add(ref outBlock, 2) = Sse2.Xor(tweak2, b2); - Unsafe.Add(ref outBlock, 3) = Sse2.Xor(tweak3, b3); - Unsafe.Add(ref outBlock, 4) = Sse2.Xor(tweak4, b4); - Unsafe.Add(ref outBlock, 5) = Sse2.Xor(tweak5, b5); - Unsafe.Add(ref outBlock, 6) = Sse2.Xor(tweak6, b6); - Unsafe.Add(ref outBlock, 7) = Sse2.Xor(tweak7, b7); - - tweak = Gf128Mul(tweak7, mask); - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remainingBlocks -= 8; - } - - while (remainingBlocks > 0) - { - Vector128 tmp = Sse2.Xor(inBlock, tweak); - tmp = _dataAesCore.EncryptBlock(tmp); - outBlock = Sse2.Xor(tmp, tweak); - - tweak = Gf128Mul(tweak, mask); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remainingBlocks--; - } - - if (leftover != 0) - { - EncryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, leftover); - } - } - - public void Decrypt(ReadOnlySpan input, Span output) - { - int length = Math.Min(input.Length, output.Length); - int remainingBlocks = length >> 4; - int leftover = length & 0xF; - - Debug.Assert(remainingBlocks > 0); - - if (leftover != 0) remainingBlocks--; - - ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); - ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); - - Vector128 mask = Vector128.Create(0x87, 1).AsByte(); - - Vector128 tweak = _tweakAesCore.EncryptBlock(Iv); - - while (remainingBlocks > 7) - { - Vector128 b0 = Sse2.Xor(tweak, Unsafe.Add(ref inBlock, 0)); - - Vector128 tweak1 = Gf128Mul(tweak, mask); - Vector128 b1 = Sse2.Xor(tweak1, Unsafe.Add(ref inBlock, 1)); - - Vector128 tweak2 = Gf128Mul(tweak1, mask); - Vector128 b2 = Sse2.Xor(tweak2, Unsafe.Add(ref inBlock, 2)); - - Vector128 tweak3 = Gf128Mul(tweak2, mask); - Vector128 b3 = Sse2.Xor(tweak3, Unsafe.Add(ref inBlock, 3)); - - Vector128 tweak4 = Gf128Mul(tweak3, mask); - Vector128 b4 = Sse2.Xor(tweak4, Unsafe.Add(ref inBlock, 4)); - - Vector128 tweak5 = Gf128Mul(tweak4, mask); - Vector128 b5 = Sse2.Xor(tweak5, Unsafe.Add(ref inBlock, 5)); - - Vector128 tweak6 = Gf128Mul(tweak5, mask); - Vector128 b6 = Sse2.Xor(tweak6, Unsafe.Add(ref inBlock, 6)); - - Vector128 tweak7 = Gf128Mul(tweak6, mask); - Vector128 b7 = Sse2.Xor(tweak7, Unsafe.Add(ref inBlock, 7)); - - _dataAesCore.DecryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, - out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); - - Unsafe.Add(ref outBlock, 0) = Sse2.Xor(tweak, b0); - Unsafe.Add(ref outBlock, 1) = Sse2.Xor(tweak1, b1); - Unsafe.Add(ref outBlock, 2) = Sse2.Xor(tweak2, b2); - Unsafe.Add(ref outBlock, 3) = Sse2.Xor(tweak3, b3); - Unsafe.Add(ref outBlock, 4) = Sse2.Xor(tweak4, b4); - Unsafe.Add(ref outBlock, 5) = Sse2.Xor(tweak5, b5); - Unsafe.Add(ref outBlock, 6) = Sse2.Xor(tweak6, b6); - Unsafe.Add(ref outBlock, 7) = Sse2.Xor(tweak7, b7); - - tweak = Gf128Mul(tweak7, mask); - - inBlock = ref Unsafe.Add(ref inBlock, 8); - outBlock = ref Unsafe.Add(ref outBlock, 8); - remainingBlocks -= 8; - } - - while (remainingBlocks > 0) - { - Vector128 tmp = Sse2.Xor(inBlock, tweak); - tmp = _dataAesCore.DecryptBlock(tmp); - outBlock = Sse2.Xor(tmp, tweak); - - tweak = Gf128Mul(tweak, mask); - - inBlock = ref Unsafe.Add(ref inBlock, 1); - outBlock = ref Unsafe.Add(ref outBlock, 1); - remainingBlocks--; - } - - if (leftover != 0) - { - DecryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, mask, leftover); - } - } - - // ReSharper disable once RedundantAssignment - private void DecryptPartialFinalBlock(ref Vector128 input, ref Vector128 output, - Vector128 tweak, Vector128 mask, int finalBlockLength) - { - Vector128 finalTweak = Gf128Mul(tweak, mask); - - Vector128 tmp = Sse2.Xor(input, finalTweak); - tmp = _dataAesCore.DecryptBlock(tmp); - output = Sse2.Xor(tmp, finalTweak); - - var x = new Buffer16(); - ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output); - Buffer16 nextInBuf = Unsafe.As, Buffer16>(ref Unsafe.Add(ref input, 1)); - ref Buffer16 nextOutBuf = ref Unsafe.As, Buffer16>(ref Unsafe.Add(ref output, 1)); - - for (int i = 0; i < finalBlockLength; i++) - { - nextOutBuf[i] = outBuf[i]; - x[i] = nextInBuf[i]; - } - - for (int i = finalBlockLength; i < 16; i++) - { - x[i] = outBuf[i]; - } - - tmp = Sse2.Xor(x.As>(), tweak); - tmp = _dataAesCore.DecryptBlock(tmp); - output = Sse2.Xor(tmp, tweak); - } - - private void EncryptPartialFinalBlock(ref Vector128 input, ref Vector128 output, - Vector128 tweak, int finalBlockLength) - { - ref Vector128 prevOutBlock = ref Unsafe.Subtract(ref output, 1); - - var x = new Buffer16(); - ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output); - Buffer16 inBuf = Unsafe.As, Buffer16>(ref input); - ref Buffer16 prevOutBuf = ref Unsafe.As, Buffer16>(ref prevOutBlock); - - for (int i = 0; i < finalBlockLength; i++) - { - outBuf[i] = prevOutBuf[i]; - x[i] = inBuf[i]; - } - - for (int i = finalBlockLength; i < 16; i++) - { - x[i] = prevOutBuf[i]; - } - - Vector128 tmp = Sse2.Xor(x.As>(), tweak); + Vector128 tmp = Sse2.Xor(inBlock, tweak); tmp = _dataAesCore.EncryptBlock(tmp); - prevOutBlock = Sse2.Xor(tmp, tweak); + outBlock = Sse2.Xor(tmp, tweak); + + tweak = Gf128Mul(tweak, mask); + + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remainingBlocks--; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 Gf128Mul(Vector128 iv, Vector128 mask) + if (leftover != 0) { - Vector128 tmp1 = Sse2.Add(iv.AsUInt64(), iv.AsUInt64()).AsByte(); - - Vector128 tmp2 = Sse2.Shuffle(iv.AsInt32(), 0x13).AsByte(); - tmp2 = Sse2.ShiftRightArithmetic(tmp2.AsInt32(), 31).AsByte(); - tmp2 = Sse2.And(mask, tmp2); - - return Sse2.Xor(tmp1, tmp2); + EncryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, leftover); } } + + public void Decrypt(ReadOnlySpan input, Span output) + { + int length = Math.Min(input.Length, output.Length); + int remainingBlocks = length >> 4; + int leftover = length & 0xF; + + Debug.Assert(remainingBlocks > 0); + + if (leftover != 0) remainingBlocks--; + + ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input)); + ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output)); + + Vector128 mask = Vector128.Create(0x87, 1).AsByte(); + + Vector128 tweak = _tweakAesCore.EncryptBlock(Iv); + + while (remainingBlocks > 7) + { + Vector128 b0 = Sse2.Xor(tweak, Unsafe.Add(ref inBlock, 0)); + + Vector128 tweak1 = Gf128Mul(tweak, mask); + Vector128 b1 = Sse2.Xor(tweak1, Unsafe.Add(ref inBlock, 1)); + + Vector128 tweak2 = Gf128Mul(tweak1, mask); + Vector128 b2 = Sse2.Xor(tweak2, Unsafe.Add(ref inBlock, 2)); + + Vector128 tweak3 = Gf128Mul(tweak2, mask); + Vector128 b3 = Sse2.Xor(tweak3, Unsafe.Add(ref inBlock, 3)); + + Vector128 tweak4 = Gf128Mul(tweak3, mask); + Vector128 b4 = Sse2.Xor(tweak4, Unsafe.Add(ref inBlock, 4)); + + Vector128 tweak5 = Gf128Mul(tweak4, mask); + Vector128 b5 = Sse2.Xor(tweak5, Unsafe.Add(ref inBlock, 5)); + + Vector128 tweak6 = Gf128Mul(tweak5, mask); + Vector128 b6 = Sse2.Xor(tweak6, Unsafe.Add(ref inBlock, 6)); + + Vector128 tweak7 = Gf128Mul(tweak6, mask); + Vector128 b7 = Sse2.Xor(tweak7, Unsafe.Add(ref inBlock, 7)); + + _dataAesCore.DecryptBlocks8(b0, b1, b2, b3, b4, b5, b6, b7, + out b0, out b1, out b2, out b3, out b4, out b5, out b6, out b7); + + Unsafe.Add(ref outBlock, 0) = Sse2.Xor(tweak, b0); + Unsafe.Add(ref outBlock, 1) = Sse2.Xor(tweak1, b1); + Unsafe.Add(ref outBlock, 2) = Sse2.Xor(tweak2, b2); + Unsafe.Add(ref outBlock, 3) = Sse2.Xor(tweak3, b3); + Unsafe.Add(ref outBlock, 4) = Sse2.Xor(tweak4, b4); + Unsafe.Add(ref outBlock, 5) = Sse2.Xor(tweak5, b5); + Unsafe.Add(ref outBlock, 6) = Sse2.Xor(tweak6, b6); + Unsafe.Add(ref outBlock, 7) = Sse2.Xor(tweak7, b7); + + tweak = Gf128Mul(tweak7, mask); + + inBlock = ref Unsafe.Add(ref inBlock, 8); + outBlock = ref Unsafe.Add(ref outBlock, 8); + remainingBlocks -= 8; + } + + while (remainingBlocks > 0) + { + Vector128 tmp = Sse2.Xor(inBlock, tweak); + tmp = _dataAesCore.DecryptBlock(tmp); + outBlock = Sse2.Xor(tmp, tweak); + + tweak = Gf128Mul(tweak, mask); + + inBlock = ref Unsafe.Add(ref inBlock, 1); + outBlock = ref Unsafe.Add(ref outBlock, 1); + remainingBlocks--; + } + + if (leftover != 0) + { + DecryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, mask, leftover); + } + } + + // ReSharper disable once RedundantAssignment + private void DecryptPartialFinalBlock(ref Vector128 input, ref Vector128 output, + Vector128 tweak, Vector128 mask, int finalBlockLength) + { + Vector128 finalTweak = Gf128Mul(tweak, mask); + + Vector128 tmp = Sse2.Xor(input, finalTweak); + tmp = _dataAesCore.DecryptBlock(tmp); + output = Sse2.Xor(tmp, finalTweak); + + var x = new Buffer16(); + ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output); + Buffer16 nextInBuf = Unsafe.As, Buffer16>(ref Unsafe.Add(ref input, 1)); + ref Buffer16 nextOutBuf = ref Unsafe.As, Buffer16>(ref Unsafe.Add(ref output, 1)); + + for (int i = 0; i < finalBlockLength; i++) + { + nextOutBuf[i] = outBuf[i]; + x[i] = nextInBuf[i]; + } + + for (int i = finalBlockLength; i < 16; i++) + { + x[i] = outBuf[i]; + } + + tmp = Sse2.Xor(x.As>(), tweak); + tmp = _dataAesCore.DecryptBlock(tmp); + output = Sse2.Xor(tmp, tweak); + } + + private void EncryptPartialFinalBlock(ref Vector128 input, ref Vector128 output, + Vector128 tweak, int finalBlockLength) + { + ref Vector128 prevOutBlock = ref Unsafe.Subtract(ref output, 1); + + var x = new Buffer16(); + ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output); + Buffer16 inBuf = Unsafe.As, Buffer16>(ref input); + ref Buffer16 prevOutBuf = ref Unsafe.As, Buffer16>(ref prevOutBlock); + + for (int i = 0; i < finalBlockLength; i++) + { + outBuf[i] = prevOutBuf[i]; + x[i] = inBuf[i]; + } + + for (int i = finalBlockLength; i < 16; i++) + { + x[i] = prevOutBuf[i]; + } + + Vector128 tmp = Sse2.Xor(x.As>(), tweak); + tmp = _dataAesCore.EncryptBlock(tmp); + prevOutBlock = Sse2.Xor(tmp, tweak); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Gf128Mul(Vector128 iv, Vector128 mask) + { + Vector128 tmp1 = Sse2.Add(iv.AsUInt64(), iv.AsUInt64()).AsByte(); + + Vector128 tmp2 = Sse2.Shuffle(iv.AsInt32(), 0x13).AsByte(); + tmp2 = Sse2.ShiftRightArithmetic(tmp2.AsInt32(), 31).AsByte(); + tmp2 = Sse2.And(mask, tmp2); + + return Sse2.Xor(tmp1, tmp2); + } } diff --git a/src/LibHac/Crypto/Impl/HashState.cs b/src/LibHac/Crypto/Impl/HashState.cs index cc63712d..909c539d 100644 --- a/src/LibHac/Crypto/Impl/HashState.cs +++ b/src/LibHac/Crypto/Impl/HashState.cs @@ -1,9 +1,8 @@ -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public enum HashState { - public enum HashState - { - Initial = 0, - Initialized, - Done - } + Initial = 0, + Initialized, + Done } diff --git a/src/LibHac/Crypto/Impl/Sha256Impl.cs b/src/LibHac/Crypto/Impl/Sha256Impl.cs index 3e444215..a1de9759 100644 --- a/src/LibHac/Crypto/Impl/Sha256Impl.cs +++ b/src/LibHac/Crypto/Impl/Sha256Impl.cs @@ -3,50 +3,49 @@ using System.Diagnostics; using System.Security.Cryptography; using LibHac.Common; -namespace LibHac.Crypto.Impl +namespace LibHac.Crypto.Impl; + +public struct Sha256Impl { - public struct Sha256Impl + private SHA256 _baseHash; + private HashState _state; + + public void Initialize() { - private SHA256 _baseHash; - private HashState _state; - - public void Initialize() + if (_state == HashState.Initial) { - if (_state == HashState.Initial) - { - _baseHash = SHA256.Create(); - } - else - { - _baseHash.Initialize(); - } - - _state = HashState.Initialized; + _baseHash = SHA256.Create(); + } + else + { + _baseHash.Initialize(); } - public void Update(ReadOnlySpan data) + _state = HashState.Initialized; + } + + public void Update(ReadOnlySpan data) + { + Debug.Assert(_state == HashState.Initialized); + + using var rented = new RentedArray(data.Length); + + data.CopyTo(rented.Span); + + _baseHash.TransformBlock(rented.Array, 0, data.Length, null, 0); + } + + public void GetHash(Span hashBuffer) + { + Debug.Assert(_state == HashState.Initialized || _state == HashState.Done); + Debug.Assert(hashBuffer.Length >= Sha256.DigestSize); + + if (_state == HashState.Initialized) { - Debug.Assert(_state == HashState.Initialized); - - using var rented = new RentedArray(data.Length); - - data.CopyTo(rented.Span); - - _baseHash.TransformBlock(rented.Array, 0, data.Length, null, 0); + _baseHash.TransformFinalBlock(new byte[0], 0, 0); + _state = HashState.Done; } - public void GetHash(Span hashBuffer) - { - Debug.Assert(_state == HashState.Initialized || _state == HashState.Done); - Debug.Assert(hashBuffer.Length >= Sha256.DigestSize); - - if (_state == HashState.Initialized) - { - _baseHash.TransformFinalBlock(new byte[0], 0, 0); - _state = HashState.Done; - } - - _baseHash.Hash.CopyTo(hashBuffer); - } + _baseHash.Hash.CopyTo(hashBuffer); } } diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs index a096778e..2d13db38 100644 --- a/src/LibHac/Crypto/KeyTypes.cs +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -5,162 +5,161 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = Size)] +public struct AesKey { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = Size)] - public struct AesKey - { - private const int Size = 0x10; + private const int Size = 0x10; - [FieldOffset(0)] private byte _byte; - [FieldOffset(0)] private ulong _ulong; + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; - public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); - public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); - public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; - public static implicit operator Span(in AesKey value) => Unsafe.AsRef(in value).Data; + public static implicit operator Span(in AesKey value) => Unsafe.AsRef(in value).Data; - public static implicit operator ReadOnlySpan(in AesKey value) => value.DataRo; + public static implicit operator ReadOnlySpan(in AesKey value) => value.DataRo; - public override readonly string ToString() => DataRo.ToHexString(); + public override readonly string ToString() => DataRo.ToHexString(); #if DEBUG - [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; #endif - } - - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = Size)] - public struct AesXtsKey - { - private const int Size = 0x20; - - [FieldOffset(0)] private byte _byte; - [FieldOffset(0)] private ulong _ulong; - - [FieldOffset(0)] public AesKey DataKey; - [FieldOffset(0x10)] public AesKey TweakKey; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); - public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); - public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); - - public Span SubKeys => SpanHelpers.CreateSpan(ref DataKey, Size / Unsafe.SizeOf()); - - public static implicit operator Span(in AesXtsKey value) => Unsafe.AsRef(in value).Data; - public static implicit operator ReadOnlySpan(in AesXtsKey value) => value.DataRo; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0; - - public override readonly string ToString() => DataRo.ToHexString(); - } - - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = Size)] - public struct AesIv - { - private const int Size = 0x10; - - [FieldOffset(0)] private byte _byte; - [FieldOffset(0)] private ulong _ulong; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); - public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); - public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; - - public static implicit operator Span(in AesIv value) => Unsafe.AsRef(in value).Data; - public static implicit operator ReadOnlySpan(in AesIv value) => value.DataRo; - - public override readonly string ToString() => DataRo.ToHexString(); - -#if DEBUG - [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; -#endif - } - - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Explicit, Size = Size)] - public struct AesCmac - { - private const int Size = 0x10; - - [FieldOffset(0)] private byte _byte; - [FieldOffset(0)] private ulong _ulong; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); - public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); - public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; - - public static implicit operator Span(in AesCmac value) => Unsafe.AsRef(in value).Data; - public static implicit operator ReadOnlySpan(in AesCmac value) => value.DataRo; - - public override readonly string ToString() => DataRo.ToHexString(); - -#if DEBUG - [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; -#endif - } - - [StructLayout(LayoutKind.Sequential)] - public struct RsaFullKey - { - public Data100 PrivateExponent; - public Data80 Dp; - public Data80 Dq; - public Data3 PublicExponent; - public Data80 InverseQ; - public Data100 Modulus; - public Data80 P; - public Data80 Q; - } - - [StructLayout(LayoutKind.Sequential)] - public struct RsaKey - { - public Data100 Modulus; - public Data3 PublicExponent; - } - - [StructLayout(LayoutKind.Explicit, Size = 0x100)] - public struct Data100 - { - [FieldOffset(0)] private byte _byte; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x100); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x100); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x80)] - public struct Data80 - { - [FieldOffset(0)] private byte _byte; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x80); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x80); - } - - [StructLayout(LayoutKind.Explicit, Size = 3)] - public struct Data3 - { - [FieldOffset(0)] private byte _byte; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, 3); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 3); - } +} + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = Size)] +public struct AesXtsKey +{ + private const int Size = 0x20; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + [FieldOffset(0)] public AesKey DataKey; + [FieldOffset(0x10)] public AesKey TweakKey; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + public Span SubKeys => SpanHelpers.CreateSpan(ref DataKey, Size / Unsafe.SizeOf()); + + public static implicit operator Span(in AesXtsKey value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesXtsKey value) => value.DataRo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0; + + public override readonly string ToString() => DataRo.ToHexString(); +} + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = Size)] +public struct AesIv +{ + private const int Size = 0x10; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; + + public static implicit operator Span(in AesIv value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesIv value) => value.DataRo; + + public override readonly string ToString() => DataRo.ToHexString(); + +#if DEBUG + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; +#endif +} + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Explicit, Size = Size)] +public struct AesCmac +{ + private const int Size = 0x10; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; + + public static implicit operator Span(in AesCmac value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesCmac value) => value.DataRo; + + public override readonly string ToString() => DataRo.ToHexString(); + +#if DEBUG + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; +#endif +} + +[StructLayout(LayoutKind.Sequential)] +public struct RsaFullKey +{ + public Data100 PrivateExponent; + public Data80 Dp; + public Data80 Dq; + public Data3 PublicExponent; + public Data80 InverseQ; + public Data100 Modulus; + public Data80 P; + public Data80 Q; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RsaKey +{ + public Data100 Modulus; + public Data3 PublicExponent; +} + +[StructLayout(LayoutKind.Explicit, Size = 0x100)] +public struct Data100 +{ + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x100); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x100); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x80)] +public struct Data80 +{ + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x80); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x80); +} + +[StructLayout(LayoutKind.Explicit, Size = 3)] +public struct Data3 +{ + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 3); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 3); } diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs index a0ddad7d..672a5463 100644 --- a/src/LibHac/Crypto/Rsa.cs +++ b/src/LibHac/Crypto/Rsa.cs @@ -2,243 +2,242 @@ using System.Numerics; using System.Security.Cryptography; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public static class Rsa { - public static class Rsa + public static bool VerifyRsa2048PssSha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) => + VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss); + + public static bool VerifyRsa2048Pkcs1Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) => + VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pkcs1); + + private static bool VerifyRsa2048Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message, RSASignaturePadding padding) { - public static bool VerifyRsa2048PssSha256(ReadOnlySpan signature, ReadOnlySpan modulus, - ReadOnlySpan exponent, ReadOnlySpan message) => - VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss); - - public static bool VerifyRsa2048Pkcs1Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, - ReadOnlySpan exponent, ReadOnlySpan message) => - VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pkcs1); - - private static bool VerifyRsa2048Sha256(ReadOnlySpan signature, ReadOnlySpan modulus, - ReadOnlySpan exponent, ReadOnlySpan message, RSASignaturePadding padding) + try { - try - { - var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; + var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; - using (var rsa = RSA.Create(param)) - { - return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, padding); - } - } - catch (CryptographicException) + using (var rsa = RSA.Create(param)) { - return false; + return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, padding); } } - - public static bool VerifyRsa2048PssSha256WithHash(ReadOnlySpan signature, ReadOnlySpan modulus, - ReadOnlySpan exponent, ReadOnlySpan message) + catch (CryptographicException) { - try - { - var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; - - using (var rsa = RSA.Create(param)) - { - return rsa.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } - } - catch (CryptographicException) - { - return false; - } - } - - /// The RSA Modulus (n) - /// The RSA Public Exponent (e) - /// The RSA Private Exponent (d) - public static RSAParameters RecoverParameters(BigInteger n, BigInteger e, BigInteger d) - { - (BigInteger p, BigInteger q) = DeriveRsaPrimeNumberPair(n, e, d); - - BigInteger dp = d % (p - BigInteger.One); - BigInteger dq = d % (q - BigInteger.One); - BigInteger inverseQ = ModInverse(q, p); - - byte[] nBytes = n.ToByteArray(); - int modLen = nBytes.Length; - - if (nBytes[^1] == 0) - { - modLen--; - } - - int halfModLen = (modLen + 1) / 2; - - return new RSAParameters - { - Modulus = n.GetBytes(modLen), - Exponent = e.GetBytes(-1), - D = d.GetBytes(modLen), - P = p.GetBytes(halfModLen), - Q = q.GetBytes(halfModLen), - DP = dp.GetBytes(halfModLen), - DQ = dq.GetBytes(halfModLen), - InverseQ = inverseQ.GetBytes(halfModLen) - }; - } - - /// The RSA Modulus (n) - /// The RSA Public Exponent (e) - /// The RSA Private Exponent (d) - public static RSAParameters RecoverParameters(ReadOnlySpan n, ReadOnlySpan e, ReadOnlySpan d) => - RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger()); - - /// - /// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d) - /// - /// The RSA Modulus (n) - /// The RSA Public Exponent (e) - /// The RSA Private Exponent (d) - /// RSA Prime Number Pair - private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d) - { - BigInteger k = d * e - BigInteger.One; - - if (!k.IsEven) - { - throw new InvalidOperationException("d*e - 1 is odd"); - } - - BigInteger two = BigInteger.One + BigInteger.One; - BigInteger t = BigInteger.One; - - BigInteger r = k / two; - - while (r.IsEven) - { - t++; - r /= two; - } - - byte[] rndBuf = n.ToByteArray(); - - if (rndBuf[^1] == 0) - { - rndBuf = new byte[rndBuf.Length - 1]; - } - - BigInteger nMinusOne = n - BigInteger.One; - - bool cracked = false; - BigInteger y = BigInteger.Zero; - - var rng = new Random(0); - - for (int i = 0; i < 100 && !cracked; i++) - { - BigInteger g; - - do - { - rng.NextBytes(rndBuf); - g = GetBigInteger(rndBuf); - } - while (g >= n); - - y = BigInteger.ModPow(g, r, n); - - if (y.IsOne || y == nMinusOne) - { - i--; - continue; - } - - for (BigInteger j = BigInteger.One; j < t; j++) - { - BigInteger x = BigInteger.ModPow(y, two, n); - - if (x.IsOne) - { - cracked = true; - break; - } - - if (x == nMinusOne) - { - break; - } - - y = x; - } - } - - if (!cracked) - { - throw new InvalidOperationException("Prime factors not found"); - } - - BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n); - BigInteger q = n / p; - - return (p, q); - } - - private static BigInteger GetBigInteger(this ReadOnlySpan bytes) - { - byte[] signPadded = new byte[bytes.Length + 1]; - bytes.CopyTo(signPadded.AsSpan(1)); - Array.Reverse(signPadded); - return new BigInteger(signPadded); - } - - private static byte[] GetBytes(this BigInteger value, int size) - { - byte[] bytes = value.ToByteArray(); - - if (size == -1) - { - size = bytes.Length; - } - - if (bytes.Length > size + 1) - { - throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); - } - - if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0) - { - throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); - } - - Array.Resize(ref bytes, size); - Array.Reverse(bytes); - return bytes; - } - - private static BigInteger ModInverse(BigInteger e, BigInteger n) - { - BigInteger r = n; - BigInteger newR = e; - BigInteger t = 0; - BigInteger newT = 1; - - while (newR != 0) - { - BigInteger quotient = r / newR; - BigInteger temp; - - temp = t; - t = newT; - newT = temp - quotient * newT; - - temp = r; - r = newR; - newR = temp - quotient * newR; - } - - if (t < 0) - { - t = t + n; - } - - return t; + return false; } } + + public static bool VerifyRsa2048PssSha256WithHash(ReadOnlySpan signature, ReadOnlySpan modulus, + ReadOnlySpan exponent, ReadOnlySpan message) + { + try + { + var param = new RSAParameters { Modulus = modulus.ToArray(), Exponent = exponent.ToArray() }; + + using (var rsa = RSA.Create(param)) + { + return rsa.VerifyHash(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } + } + catch (CryptographicException) + { + return false; + } + } + + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) + public static RSAParameters RecoverParameters(BigInteger n, BigInteger e, BigInteger d) + { + (BigInteger p, BigInteger q) = DeriveRsaPrimeNumberPair(n, e, d); + + BigInteger dp = d % (p - BigInteger.One); + BigInteger dq = d % (q - BigInteger.One); + BigInteger inverseQ = ModInverse(q, p); + + byte[] nBytes = n.ToByteArray(); + int modLen = nBytes.Length; + + if (nBytes[^1] == 0) + { + modLen--; + } + + int halfModLen = (modLen + 1) / 2; + + return new RSAParameters + { + Modulus = n.GetBytes(modLen), + Exponent = e.GetBytes(-1), + D = d.GetBytes(modLen), + P = p.GetBytes(halfModLen), + Q = q.GetBytes(halfModLen), + DP = dp.GetBytes(halfModLen), + DQ = dq.GetBytes(halfModLen), + InverseQ = inverseQ.GetBytes(halfModLen) + }; + } + + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) + public static RSAParameters RecoverParameters(ReadOnlySpan n, ReadOnlySpan e, ReadOnlySpan d) => + RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger()); + + /// + /// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d) + /// + /// The RSA Modulus (n) + /// The RSA Public Exponent (e) + /// The RSA Private Exponent (d) + /// RSA Prime Number Pair + private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d) + { + BigInteger k = d * e - BigInteger.One; + + if (!k.IsEven) + { + throw new InvalidOperationException("d*e - 1 is odd"); + } + + BigInteger two = BigInteger.One + BigInteger.One; + BigInteger t = BigInteger.One; + + BigInteger r = k / two; + + while (r.IsEven) + { + t++; + r /= two; + } + + byte[] rndBuf = n.ToByteArray(); + + if (rndBuf[^1] == 0) + { + rndBuf = new byte[rndBuf.Length - 1]; + } + + BigInteger nMinusOne = n - BigInteger.One; + + bool cracked = false; + BigInteger y = BigInteger.Zero; + + var rng = new Random(0); + + for (int i = 0; i < 100 && !cracked; i++) + { + BigInteger g; + + do + { + rng.NextBytes(rndBuf); + g = GetBigInteger(rndBuf); + } + while (g >= n); + + y = BigInteger.ModPow(g, r, n); + + if (y.IsOne || y == nMinusOne) + { + i--; + continue; + } + + for (BigInteger j = BigInteger.One; j < t; j++) + { + BigInteger x = BigInteger.ModPow(y, two, n); + + if (x.IsOne) + { + cracked = true; + break; + } + + if (x == nMinusOne) + { + break; + } + + y = x; + } + } + + if (!cracked) + { + throw new InvalidOperationException("Prime factors not found"); + } + + BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n); + BigInteger q = n / p; + + return (p, q); + } + + private static BigInteger GetBigInteger(this ReadOnlySpan bytes) + { + byte[] signPadded = new byte[bytes.Length + 1]; + bytes.CopyTo(signPadded.AsSpan(1)); + Array.Reverse(signPadded); + return new BigInteger(signPadded); + } + + private static byte[] GetBytes(this BigInteger value, int size) + { + byte[] bytes = value.ToByteArray(); + + if (size == -1) + { + size = bytes.Length; + } + + if (bytes.Length > size + 1) + { + throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); + } + + if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0) + { + throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}."); + } + + Array.Resize(ref bytes, size); + Array.Reverse(bytes); + return bytes; + } + + private static BigInteger ModInverse(BigInteger e, BigInteger n) + { + BigInteger r = n; + BigInteger newR = e; + BigInteger t = 0; + BigInteger newT = 1; + + while (newR != 0) + { + BigInteger quotient = r / newR; + BigInteger temp; + + temp = t; + t = newT; + newT = temp - quotient * newT; + + temp = r; + r = newR; + newR = temp - quotient * newR; + } + + if (t < 0) + { + t = t + n; + } + + return t; + } } diff --git a/src/LibHac/Crypto/Sha256.cs b/src/LibHac/Crypto/Sha256.cs index dcdc1306..35f6f46d 100644 --- a/src/LibHac/Crypto/Sha256.cs +++ b/src/LibHac/Crypto/Sha256.cs @@ -1,28 +1,27 @@ using System; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public static class Sha256 { - public static class Sha256 + public const int DigestSize = 0x20; + + /// + /// Creates an uninitialized SHA-256 object. + /// + /// The new uninitialized SHA-256 object. + public static IHash CreateSha256Generator() { - public const int DigestSize = 0x20; + return new Sha256Generator(); + } - /// - /// Creates an uninitialized SHA-256 object. - /// - /// The new uninitialized SHA-256 object. - public static IHash CreateSha256Generator() - { - return new Sha256Generator(); - } + public static void GenerateSha256Hash(ReadOnlySpan data, Span hashBuffer) + { + var sha256 = new Sha256Impl(); + sha256.Initialize(); - public static void GenerateSha256Hash(ReadOnlySpan data, Span hashBuffer) - { - var sha256 = new Sha256Impl(); - sha256.Initialize(); - - sha256.Update(data); - sha256.GetHash(hashBuffer); - } + sha256.Update(data); + sha256.GetHash(hashBuffer); } } diff --git a/src/LibHac/Crypto/Sha256Generator.cs b/src/LibHac/Crypto/Sha256Generator.cs index ddcc0918..50f6f2d4 100644 --- a/src/LibHac/Crypto/Sha256Generator.cs +++ b/src/LibHac/Crypto/Sha256Generator.cs @@ -1,30 +1,29 @@ using System; using LibHac.Crypto.Impl; -namespace LibHac.Crypto +namespace LibHac.Crypto; + +public class Sha256Generator : IHash { - public class Sha256Generator : IHash + private Sha256Impl _baseHash; + + public Sha256Generator() { - private Sha256Impl _baseHash; + _baseHash = new Sha256Impl(); + } - public Sha256Generator() - { - _baseHash = new Sha256Impl(); - } + public void Initialize() + { + _baseHash.Initialize(); + } - public void Initialize() - { - _baseHash.Initialize(); - } + public void Update(ReadOnlySpan data) + { + _baseHash.Update(data); + } - public void Update(ReadOnlySpan data) - { - _baseHash.Update(data); - } - - public void GetHash(Span hashBuffer) - { - _baseHash.GetHash(hashBuffer); - } + public void GetHash(Span hashBuffer) + { + _baseHash.GetHash(hashBuffer); } } diff --git a/src/LibHac/CryptoOld.cs b/src/LibHac/CryptoOld.cs index cdc9dbed..07e61113 100644 --- a/src/LibHac/CryptoOld.cs +++ b/src/LibHac/CryptoOld.cs @@ -6,98 +6,97 @@ using LibHac.FsSystem; using Aes = LibHac.Crypto.Aes; -namespace LibHac +namespace LibHac; + +public static class CryptoOld { - public static class CryptoOld + public static void GenerateKek(byte[] key, byte[] src, byte[] dest, byte[] kekSeed, byte[] keySeed) { - public static void GenerateKek(byte[] key, byte[] src, byte[] dest, byte[] kekSeed, byte[] keySeed) + byte[] kek = new byte[Aes.KeySize128]; + byte[] srcKek = new byte[Aes.KeySize128]; + + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); + + if (keySeed != null) { - byte[] kek = new byte[Aes.KeySize128]; - byte[] srcKek = new byte[Aes.KeySize128]; + Aes.DecryptEcb128(keySeed, dest, srcKek); + } + else + { + Array.Copy(srcKek, dest, Aes.KeySize128); + } + } - Aes.DecryptEcb128(kekSeed, kek, key); - Aes.DecryptEcb128(src, srcKek, kek); + public static RSAParameters DecryptRsaKey(byte[] encryptedKey, byte[] kek) + { + byte[] counter = new byte[0x10]; + Array.Copy(encryptedKey, counter, 0x10); + byte[] key = new byte[0x230]; + Array.Copy(encryptedKey, 0x10, key, 0, 0x230); - if (keySeed != null) + new Aes128CtrTransform(kek, counter).TransformBlock(key); + + byte[] d = new byte[0x100]; + byte[] n = new byte[0x100]; + byte[] e = new byte[4]; + Array.Copy(key, 0, d, 0, 0x100); + Array.Copy(key, 0x100, n, 0, 0x100); + Array.Copy(key, 0x200, e, 0, 4); + + RSAParameters rsaParams = Rsa.RecoverParameters(n, e, d); + TestRsaKey(rsaParams); + return rsaParams; + } + + private static void TestRsaKey(RSAParameters keyParams) + { + var rsa = new RSACryptoServiceProvider(); + rsa.ImportParameters(keyParams); + + byte[] test = { 12, 34, 56, 78 }; + byte[] testEnc = rsa.Encrypt(test, false); + byte[] testDec = rsa.Decrypt(testEnc, false); + + if (!Utilities.ArraysEqual(test, testDec)) + { + throw new InvalidDataException("Could not verify RSA key pair"); + } + } + + public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus) => + Rsa.VerifyRsa2048Pkcs1Sha256(signature, modulus, new byte[] { 1, 0, 1 }, data) + ? Validity.Valid + : Validity.Invalid; + + public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus) => + Rsa.VerifyRsa2048PssSha256(signature, modulus, new byte[] { 1, 0, 1 }, data) + ? Validity.Valid + : Validity.Invalid; + + public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams) + { + var rsa = RSA.Create(); + + rsa.ImportParameters(rsaParams); + return rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256); + } + + public static bool DecryptRsaOaep(ReadOnlySpan data, Span destination, RSAParameters rsaParams, out int bytesWritten) + { + using (var rsa = RSA.Create()) + { + try { - Aes.DecryptEcb128(keySeed, dest, srcKek); + rsa.ImportParameters(rsaParams); + + return rsa.TryDecrypt(data, destination, RSAEncryptionPadding.OaepSHA256, out bytesWritten); } - else + catch (CryptographicException) { - Array.Copy(srcKek, dest, Aes.KeySize128); - } - } - - public static RSAParameters DecryptRsaKey(byte[] encryptedKey, byte[] kek) - { - byte[] counter = new byte[0x10]; - Array.Copy(encryptedKey, counter, 0x10); - byte[] key = new byte[0x230]; - Array.Copy(encryptedKey, 0x10, key, 0, 0x230); - - new Aes128CtrTransform(kek, counter).TransformBlock(key); - - byte[] d = new byte[0x100]; - byte[] n = new byte[0x100]; - byte[] e = new byte[4]; - Array.Copy(key, 0, d, 0, 0x100); - Array.Copy(key, 0x100, n, 0, 0x100); - Array.Copy(key, 0x200, e, 0, 4); - - RSAParameters rsaParams = Rsa.RecoverParameters(n, e, d); - TestRsaKey(rsaParams); - return rsaParams; - } - - private static void TestRsaKey(RSAParameters keyParams) - { - var rsa = new RSACryptoServiceProvider(); - rsa.ImportParameters(keyParams); - - byte[] test = { 12, 34, 56, 78 }; - byte[] testEnc = rsa.Encrypt(test, false); - byte[] testDec = rsa.Decrypt(testEnc, false); - - if (!Utilities.ArraysEqual(test, testDec)) - { - throw new InvalidDataException("Could not verify RSA key pair"); - } - } - - public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus) => - Rsa.VerifyRsa2048Pkcs1Sha256(signature, modulus, new byte[] { 1, 0, 1 }, data) - ? Validity.Valid - : Validity.Invalid; - - public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus) => - Rsa.VerifyRsa2048PssSha256(signature, modulus, new byte[] { 1, 0, 1 }, data) - ? Validity.Valid - : Validity.Invalid; - - public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams) - { - var rsa = RSA.Create(); - - rsa.ImportParameters(rsaParams); - return rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256); - } - - public static bool DecryptRsaOaep(ReadOnlySpan data, Span destination, RSAParameters rsaParams, out int bytesWritten) - { - using (var rsa = RSA.Create()) - { - try - { - rsa.ImportParameters(rsaParams); - - return rsa.TryDecrypt(data, destination, RSAEncryptionPadding.OaepSHA256, out bytesWritten); - } - catch (CryptographicException) - { - bytesWritten = 0; - return false; - } + bytesWritten = 0; + return false; } } } -} \ No newline at end of file +} diff --git a/src/LibHac/Diag/Abort.cs b/src/LibHac/Diag/Abort.cs index 2def128d..17db88e5 100644 --- a/src/LibHac/Diag/Abort.cs +++ b/src/LibHac/Diag/Abort.cs @@ -1,85 +1,84 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace LibHac.Diag +namespace LibHac.Diag; + +public ref struct AbortInfo { - public ref struct AbortInfo + public AbortReason AbortReason; + public string Message; + public string Condition; + public string FunctionName; + public string FileName; + public int LineNumber; +} + +public enum AbortReason +{ + SdkAssert, + SdkRequires, + UserAssert, + Abort, + UnexpectedDefault +} + +public static class Abort +{ + internal static void InvokeAbortObserver(in AbortInfo abortInfo) { - public AbortReason AbortReason; - public string Message; - public string Condition; - public string FunctionName; - public string FileName; - public int LineNumber; + // Todo } - public enum AbortReason + [DoesNotReturn] + public static void DoAbort(Result result, string message = null) { - SdkAssert, - SdkRequires, - UserAssert, - Abort, - UnexpectedDefault + if (string.IsNullOrWhiteSpace(message)) + { + throw new HorizonResultException(result, "Abort."); + } + + throw new LibHacException($"Abort: {message}"); } - public static class Abort + [DoesNotReturn] + public static void DoAbort(string message = null) { - internal static void InvokeAbortObserver(in AbortInfo abortInfo) + if (string.IsNullOrWhiteSpace(message)) { - // Todo + throw new LibHacException("Abort."); } - [DoesNotReturn] - public static void DoAbort(Result result, string message = null) + throw new LibHacException($"Abort: {message}"); + } + + public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, string message = null) + { + if (condition) + return; + + DoAbort(default, message); + } + + public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, Result result, string message = null) + { + if (condition) + return; + + result.Log(); + DoAbort(result, message); + } + + public static void DoAbortUnlessSuccess(Result result, string message = null) + { + if (!result.IsSuccess()) { - if (string.IsNullOrWhiteSpace(message)) - { - throw new HorizonResultException(result, "Abort."); - } - - throw new LibHacException($"Abort: {message}"); - } - - [DoesNotReturn] - public static void DoAbort(string message = null) - { - if (string.IsNullOrWhiteSpace(message)) - { - throw new LibHacException("Abort."); - } - - throw new LibHacException($"Abort: {message}"); - } - - public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, string message = null) - { - if (condition) - return; - - DoAbort(default, message); - } - - public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, Result result, string message = null) - { - if (condition) - return; - - result.Log(); DoAbort(result, message); } + } - public static void DoAbortUnlessSuccess(Result result, string message = null) - { - if (!result.IsSuccess()) - { - DoAbort(result, message); - } - } - - [DoesNotReturn] - public static void UnexpectedDefault([CallerMemberName] string caller = "") - { - throw new LibHacException($"Unexpected value passed to switch statement in {caller}"); - } + [DoesNotReturn] + public static void UnexpectedDefault([CallerMemberName] string caller = "") + { + throw new LibHacException($"Unexpected value passed to switch statement in {caller}"); } } diff --git a/src/LibHac/Diag/Assert.cs b/src/LibHac/Diag/Assert.cs index f4830ee2..318502da 100644 --- a/src/LibHac/Diag/Assert.cs +++ b/src/LibHac/Diag/Assert.cs @@ -5,1089 +5,1088 @@ using System.Runtime.CompilerServices; using LibHac.Diag.Impl; using LibHac.Os; -namespace LibHac.Diag +namespace LibHac.Diag; + +public ref struct AssertionInfo { - public ref struct AssertionInfo + public AssertionType AssertionType; + public string Message; + public string Condition; + public string FunctionName; + public string FileName; + public int LineNumber; +} + +public enum AssertionType +{ + SdkAssert, + SdkRequires, + UserAssert +} + +public enum AssertionFailureOperation +{ + Abort, + Continue +} + +public delegate AssertionFailureOperation AssertionFailureHandler(in AssertionInfo assertionInfo); + +public static class Assert +{ + private const string AssertCondition = "ENABLE_ASSERTS"; + + private static SdkMutexType _mutex = InitMutex(); + private static AssertionFailureHandler _assertionFailureHandler = DefaultAssertionFailureHandler; + + private static SdkMutexType InitMutex() { - public AssertionType AssertionType; - public string Message; - public string Condition; - public string FunctionName; - public string FileName; - public int LineNumber; + var mutex = new SdkMutexType(); + mutex.Initialize(); + return mutex; } - public enum AssertionType + private static AbortReason ToAbortReason(AssertionType assertionType) { - SdkAssert, - SdkRequires, - UserAssert + switch (assertionType) + { + case AssertionType.SdkAssert: return AbortReason.SdkAssert; + case AssertionType.SdkRequires: return AbortReason.SdkRequires; + case AssertionType.UserAssert: return AbortReason.UserAssert; + default: return AbortReason.Abort; + } } - public enum AssertionFailureOperation + private static AssertionFailureOperation DefaultAssertionFailureHandler(in AssertionInfo assertionInfo) { - Abort, - Continue + return AssertionFailureOperation.Abort; } - public delegate AssertionFailureOperation AssertionFailureHandler(in AssertionInfo assertionInfo); - - public static class Assert + private static void ExecuteAssertionFailureOperation(AssertionFailureOperation operation, + in AssertionInfo assertionInfo) { - private const string AssertCondition = "ENABLE_ASSERTS"; - - private static SdkMutexType _mutex = InitMutex(); - private static AssertionFailureHandler _assertionFailureHandler = DefaultAssertionFailureHandler; - - private static SdkMutexType InitMutex() + switch (operation) { - var mutex = new SdkMutexType(); - mutex.Initialize(); - return mutex; - } + case AssertionFailureOperation.Abort: + var abortInfo = new AbortInfo + { + AbortReason = ToAbortReason(assertionInfo.AssertionType), + Message = assertionInfo.Message, + Condition = assertionInfo.Condition, + FunctionName = assertionInfo.FunctionName, + FileName = assertionInfo.FileName, + LineNumber = assertionInfo.LineNumber + }; - private static AbortReason ToAbortReason(AssertionType assertionType) - { - switch (assertionType) - { - case AssertionType.SdkAssert: return AbortReason.SdkAssert; - case AssertionType.SdkRequires: return AbortReason.SdkRequires; - case AssertionType.UserAssert: return AbortReason.UserAssert; - default: return AbortReason.Abort; - } - } - - private static AssertionFailureOperation DefaultAssertionFailureHandler(in AssertionInfo assertionInfo) - { - return AssertionFailureOperation.Abort; - } - - private static void ExecuteAssertionFailureOperation(AssertionFailureOperation operation, - in AssertionInfo assertionInfo) - { - switch (operation) - { - case AssertionFailureOperation.Abort: - var abortInfo = new AbortInfo - { - AbortReason = ToAbortReason(assertionInfo.AssertionType), - Message = assertionInfo.Message, - Condition = assertionInfo.Condition, - FunctionName = assertionInfo.FunctionName, - FileName = assertionInfo.FileName, - LineNumber = assertionInfo.LineNumber - }; - - Abort.InvokeAbortObserver(in abortInfo); - Abort.DoAbort(abortInfo.Message); - break; - case AssertionFailureOperation.Continue: - return; - default: - Abort.DoAbort("Unknown AssertionFailureOperation"); - break; - } - } - - private static void InvokeAssertionFailureHandler(in AssertionInfo assertionInfo) - { - AssertionFailureOperation operation = _assertionFailureHandler(in assertionInfo); - ExecuteAssertionFailureOperation(operation, in assertionInfo); - } - - internal static void OnAssertionFailure(AssertionType assertionType, string condition, string functionName, - string fileName, int lineNumber) - { - OnAssertionFailure(assertionType, condition, functionName, fileName, lineNumber, string.Empty); - } - - internal static void OnAssertionFailure(AssertionType assertionType, string condition, string functionName, - string fileName, int lineNumber, string message) - { - // Invalidate the IPC message buffer and call SynchronizePreemptionState if necessary - // nn::diag::detail::PrepareAbort(); - - if (_mutex.IsLockedByCurrentThread()) - Abort.DoAbort(); - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - var assertionInfo = new AssertionInfo - { - AssertionType = assertionType, - Message = message, - Condition = condition, - FunctionName = functionName, - FileName = fileName, - LineNumber = lineNumber - }; - - InvokeAssertionFailureHandler(in assertionInfo); - } - - public static void SetAssertionFailureHandler(AssertionFailureHandler assertionHandler) - { - _assertionFailureHandler = assertionHandler; - } - - // --------------------------------------------------------------------- - // True - // --------------------------------------------------------------------- - - private static void TrueImpl(AssertionType assertionType, bool condition, string conditionText, string message, - string functionName, string fileName, int lineNumber) - { - if (condition) + Abort.InvokeAbortObserver(in abortInfo); + Abort.DoAbort(abortInfo.Message); + break; + case AssertionFailureOperation.Continue: return; - - OnAssertionFailure(assertionType, conditionText, functionName, fileName, lineNumber, message); + default: + Abort.DoAbort("Unknown AssertionFailureOperation"); + break; } + } - [Conditional(AssertCondition)] - public static void True([DoesNotReturnIf(false)] bool condition, - string message = "", - [CallerArgumentExpression("condition")] string conditionText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) + private static void InvokeAssertionFailureHandler(in AssertionInfo assertionInfo) + { + AssertionFailureOperation operation = _assertionFailureHandler(in assertionInfo); + ExecuteAssertionFailureOperation(operation, in assertionInfo); + } + + internal static void OnAssertionFailure(AssertionType assertionType, string condition, string functionName, + string fileName, int lineNumber) + { + OnAssertionFailure(assertionType, condition, functionName, fileName, lineNumber, string.Empty); + } + + internal static void OnAssertionFailure(AssertionType assertionType, string condition, string functionName, + string fileName, int lineNumber, string message) + { + // Invalidate the IPC message buffer and call SynchronizePreemptionState if necessary + // nn::diag::detail::PrepareAbort(); + + if (_mutex.IsLockedByCurrentThread()) + Abort.DoAbort(); + + using ScopedLock lk = ScopedLock.Lock(ref _mutex); + + var assertionInfo = new AssertionInfo { - TrueImpl(AssertionType.UserAssert, condition, conditionText, message, functionName, fileName, lineNumber); - } + AssertionType = assertionType, + Message = message, + Condition = condition, + FunctionName = functionName, + FileName = fileName, + LineNumber = lineNumber + }; - [Conditional(AssertCondition)] - public static void SdkAssert([DoesNotReturnIf(false)] bool condition, - string message = "", - [CallerArgumentExpression("condition")] string conditionText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - TrueImpl(AssertionType.SdkAssert, condition, conditionText, message, functionName, fileName, lineNumber); - } + InvokeAssertionFailureHandler(in assertionInfo); + } - [Conditional(AssertCondition)] - public static void SdkRequires([DoesNotReturnIf(false)] bool condition, - string message = "", - [CallerArgumentExpression("condition")] string conditionText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - TrueImpl(AssertionType.SdkRequires, condition, conditionText, message, functionName, fileName, lineNumber); - } + public static void SetAssertionFailureHandler(AssertionFailureHandler assertionHandler) + { + _assertionFailureHandler = assertionHandler; + } - // --------------------------------------------------------------------- - // Not null - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // True + // --------------------------------------------------------------------- - private static void NotNullImpl(AssertionType assertionType, [NotNull] T value, string valueText, - string functionName, string fileName, int lineNumber) where T : class - { - if (AssertImpl.NotNull(value)) - return; + private static void TrueImpl(AssertionType assertionType, bool condition, string conditionText, string message, + string functionName, string fileName, int lineNumber) + { + if (condition) + return; - AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); - } + OnAssertionFailure(assertionType, conditionText, functionName, fileName, lineNumber, message); + } - [Conditional(AssertCondition)] - public static void NotNull([NotNull] T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : class - { - NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void True([DoesNotReturnIf(false)] bool condition, + string message = "", + [CallerArgumentExpression("condition")] string conditionText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + TrueImpl(AssertionType.UserAssert, condition, conditionText, message, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkNotNull([NotNull] T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : class - { - NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void SdkAssert([DoesNotReturnIf(false)] bool condition, + string message = "", + [CallerArgumentExpression("condition")] string conditionText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + TrueImpl(AssertionType.SdkAssert, condition, conditionText, message, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkRequiresNotNull([NotNull] T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : class - { - NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void SdkRequires([DoesNotReturnIf(false)] bool condition, + string message = "", + [CallerArgumentExpression("condition")] string conditionText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + TrueImpl(AssertionType.SdkRequires, condition, conditionText, message, functionName, fileName, lineNumber); + } - // --------------------------------------------------------------------- - // Not null ref - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Not null + // --------------------------------------------------------------------- - private static void NotNullImpl(AssertionType assertionType, [NotNull] ref T value, string valueText, - string functionName, string fileName, int lineNumber) - { - if (AssertImpl.NotNull(ref value)) - return; + private static void NotNullImpl(AssertionType assertionType, [NotNull] T value, string valueText, + string functionName, string fileName, int lineNumber) where T : class + { + if (AssertImpl.NotNull(value)) + return; - AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); - } + AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - public static void NotNull([NotNull] ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.UserAssert, ref value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void NotNull([NotNull] T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : class + { + NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkNotNull([NotNull] ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkAssert, ref value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkNotNull([NotNull] T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : class + { + NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkRequiresNotNull([NotNull] ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkRequires, ref value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkRequiresNotNull([NotNull] T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : class + { + NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); + } - // --------------------------------------------------------------------- - // Not null out - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Not null ref + // --------------------------------------------------------------------- - private static void NotNullOutImpl(AssertionType assertionType, [NotNull] out T value, string valueText, - string functionName, string fileName, int lineNumber) - { - Unsafe.SkipInit(out value); + private static void NotNullImpl(AssertionType assertionType, [NotNull] ref T value, string valueText, + string functionName, string fileName, int lineNumber) + { + if (AssertImpl.NotNull(ref value)) + return; + + AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void NotNull([NotNull] ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.UserAssert, ref value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkNotNull([NotNull] ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkAssert, ref value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresNotNull([NotNull] ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkRequires, ref value, valueText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Not null out + // --------------------------------------------------------------------- + + private static void NotNullOutImpl(AssertionType assertionType, [NotNull] out T value, string valueText, + string functionName, string fileName, int lineNumber) + { + Unsafe.SkipInit(out value); #if ENABLE_ASSERTS - if (!Unsafe.IsNullRef(ref value)) - return; + if (!Unsafe.IsNullRef(ref value)) + return; - AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); + AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); #endif - } + } - public static void NotNullOut([NotNull] out T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullOutImpl(AssertionType.UserAssert, out value, valueText, functionName, fileName, lineNumber); - } + public static void NotNullOut([NotNull] out T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullOutImpl(AssertionType.UserAssert, out value, valueText, functionName, fileName, lineNumber); + } - internal static void SdkNotNullOut([NotNull] out T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullOutImpl(AssertionType.SdkAssert, out value, valueText, functionName, fileName, lineNumber); - } + internal static void SdkNotNullOut([NotNull] out T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullOutImpl(AssertionType.SdkAssert, out value, valueText, functionName, fileName, lineNumber); + } - internal static void SdkRequiresNotNullOut([NotNull] out T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullOutImpl(AssertionType.SdkRequires, out value, valueText, functionName, fileName, lineNumber); - } + internal static void SdkRequiresNotNullOut([NotNull] out T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullOutImpl(AssertionType.SdkRequires, out value, valueText, functionName, fileName, lineNumber); + } - // --------------------------------------------------------------------- - // Not null span - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Not null span + // --------------------------------------------------------------------- - private static void NotNullImpl(AssertionType assertionType, Span value, - string valueText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.NotNull(value)) - return; + private static void NotNullImpl(AssertionType assertionType, Span value, + string valueText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.NotNull(value)) + return; - AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); - } + AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - public static void NotNull(Span value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void NotNull(Span value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkNotNull(Span value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkNotNull(Span value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkRequiresNotNull(Span value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkRequiresNotNull(Span value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); + } - // --------------------------------------------------------------------- - // Not null read-only span - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Not null read-only span + // --------------------------------------------------------------------- - private static void NotNullImpl(AssertionType assertionType, ReadOnlySpan value, - string valueText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.NotNull(value)) - return; + private static void NotNullImpl(AssertionType assertionType, ReadOnlySpan value, + string valueText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.NotNull(value)) + return; - AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); - } + AssertImpl.InvokeAssertionNotNull(assertionType, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - public static void NotNull(ReadOnlySpan value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + public static void NotNull(ReadOnlySpan value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkNotNull(ReadOnlySpan value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkNotNull(ReadOnlySpan value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - internal static void SdkRequiresNotNull(ReadOnlySpan value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); - } + [Conditional(AssertCondition)] + internal static void SdkRequiresNotNull(ReadOnlySpan value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NotNullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); + } - // --------------------------------------------------------------------- - // Null - // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Null + // --------------------------------------------------------------------- - private static void NullImpl(AssertionType assertionType, T value, string valueText, - string functionName, string fileName, int lineNumber) where T : class - { - if (AssertImpl.Null(value)) - return; + private static void NullImpl(AssertionType assertionType, T value, string valueText, + string functionName, string fileName, int lineNumber) where T : class + { + if (AssertImpl.Null(value)) + return; - AssertImpl.InvokeAssertionNull(assertionType, valueText, functionName, fileName, lineNumber); - } + AssertImpl.InvokeAssertionNull(assertionType, valueText, functionName, fileName, lineNumber); + } - [Conditional(AssertCondition)] - public static void Null(T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] + [Conditional(AssertCondition)] + public static void Null(T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) - where T : class - { - NullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkNull(T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : class - { - NullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresNull(T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : class - { - NullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Null ref - // --------------------------------------------------------------------- - - private static void NullImpl(AssertionType assertionType, ref T value, string valueText, - string functionName, string fileName, int lineNumber) - { - if (AssertImpl.Null(ref value)) - return; - - AssertImpl.InvokeAssertionNull(assertionType, valueText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void Null(ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NullImpl(AssertionType.UserAssert, ref value, valueText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkNull(ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NullImpl(AssertionType.SdkAssert, ref value, valueText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresNull(ref T value, - [CallerArgumentExpression("value")] string valueText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - NullImpl(AssertionType.SdkRequires, ref value, valueText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // In range - // --------------------------------------------------------------------- - - private static void InRangeImpl(AssertionType assertionType, int value, int lower, int upper, string valueText, - string lowerText, string upperText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.WithinRange(value, lower, upper)) - return; - - AssertImpl.InvokeAssertionInRange(assertionType, value, lower, upper, valueText, lowerText, upperText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void InRange(int value, int lowerInclusive, int upperExclusive, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", - [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - InRangeImpl(AssertionType.UserAssert, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, - upperExclusiveText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkInRange(int value, int lowerInclusive, int upperExclusive, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", - [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - InRangeImpl(AssertionType.SdkAssert, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, - upperExclusiveText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresInRange(int value, int lowerInclusive, int upperExclusive, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", - [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - InRangeImpl(AssertionType.SdkRequires, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, - upperExclusiveText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Within min-max int - // --------------------------------------------------------------------- - - private static void WithinMinMaxImpl(AssertionType assertionType, int value, int min, int max, string valueText, - string minText, string maxText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.WithinMinMax(value, min, max)) - return; - - AssertImpl.InvokeAssertionWithinMinMax(assertionType, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void WithinMinMax(int value, int min, int max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.UserAssert, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkWithinMinMax(int value, int min, int max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.SdkAssert, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresWithinMinMax(int value, int min, int max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.SdkRequires, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Within min-max long - // --------------------------------------------------------------------- - - private static void WithinMinMaxImpl(AssertionType assertionType, long value, long min, long max, - string valueText, string minText, string maxText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.WithinMinMax(value, min, max)) - return; - - AssertImpl.InvokeAssertionWithinMinMax(assertionType, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void WithinMinMax(long value, long min, long max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.UserAssert, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkWithinMinMax(long value, long min, long max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.SdkAssert, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresWithinMinMax(long value, long min, long max, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("min")] string minText = "", - [CallerArgumentExpression("max")] string maxText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - WithinMinMaxImpl(AssertionType.SdkRequires, value, min, max, valueText, minText, maxText, functionName, - fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Equal - // --------------------------------------------------------------------- - - private static void EqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, - string functionName, string fileName, int lineNumber) where T : IEquatable - { - if (AssertImpl.Equal(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void Equal(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Equal ref - // --------------------------------------------------------------------- - - private static void EqualImpl(AssertionType assertionType, ref T lhs, ref T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable - { - if (AssertImpl.Equal(ref lhs, ref rhs)) - return; - - AssertImpl.InvokeAssertionEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void Equal(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.UserAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkEqual(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.SdkAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresEqual(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - EqualImpl(AssertionType.SdkRequires, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, - lineNumber); - } - - // --------------------------------------------------------------------- - // Not equal - // --------------------------------------------------------------------- - - private static void NotEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, - string functionName, string fileName, int lineNumber) where T : IEquatable - { - if (AssertImpl.NotEqual(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionNotEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void NotEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkNotEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresNotEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Not equal ref - // --------------------------------------------------------------------- - - private static void NotEqualImpl(AssertionType assertionType, ref T lhs, ref T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable - { - if (AssertImpl.NotEqual(ref lhs, ref rhs)) - return; - - AssertImpl.InvokeAssertionNotEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void NotEqual(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.UserAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, - lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkNotEqual(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.SdkAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, - lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresNotEqual(ref T lhs, ref T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IEquatable - { - NotEqualImpl(AssertionType.SdkRequires, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, - lineNumber); - } - - // --------------------------------------------------------------------- - // Less - // --------------------------------------------------------------------- - - private static void LessImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, - string functionName, string fileName, int lineNumber) where T : IComparable - { - if (AssertImpl.Less(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionLess(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void Less(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkLess(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresLess(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Less equal - // --------------------------------------------------------------------- - - private static void LessEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, - string functionName, string fileName, int lineNumber) where T : IComparable - { - if (AssertImpl.LessEqual(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionLessEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void LessEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkLessEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresLessEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - LessEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Greater - // --------------------------------------------------------------------- - - private static void GreaterImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, - string functionName, string fileName, int lineNumber) where T : IComparable - { - if (AssertImpl.Greater(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionGreater(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void Greater(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkGreater(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresGreater(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Greater equal - // --------------------------------------------------------------------- - - private static void GreaterEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable - { - if (AssertImpl.GreaterEqual(lhs, rhs)) - return; - - AssertImpl.InvokeAssertionGreaterEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - public static void GreaterEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkGreaterEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresGreaterEqual(T lhs, T rhs, - [CallerArgumentExpression("lhs")] string lhsText = "", - [CallerArgumentExpression("rhs")] string rhsText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - where T : IComparable - { - GreaterEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); - } - - // --------------------------------------------------------------------- - // Aligned - // --------------------------------------------------------------------- - - private static void AlignedImpl(AssertionType assertionType, ulong value, int alignment, string valueText, - string alignmentText, string functionName, string fileName, int lineNumber) - { - if (AssertImpl.IsAligned(value, alignment)) - return; - - AssertImpl.InvokeAssertionAligned(assertionType, value, alignment, valueText, alignmentText, functionName, fileName, - lineNumber); - } - - [Conditional(AssertCondition)] - public static void Aligned(ulong value, int alignment, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("alignment")] string alignmentText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - AlignedImpl(AssertionType.UserAssert, value, alignment, valueText, alignmentText, functionName, fileName, - lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkAligned(ulong value, int alignment, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("alignment")] string alignmentText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - AlignedImpl(AssertionType.SdkAssert, value, alignment, valueText, alignmentText, functionName, fileName, - lineNumber); - } - - [Conditional(AssertCondition)] - internal static void SdkRequiresAligned(ulong value, int alignment, - [CallerArgumentExpression("value")] string valueText = "", - [CallerArgumentExpression("alignment")] string alignmentText = "", - [CallerMemberName] string functionName = "", - [CallerFilePath] string fileName = "", - [CallerLineNumber] int lineNumber = 0) - { - AlignedImpl(AssertionType.SdkRequires, value, alignment, valueText, alignmentText, functionName, fileName, - lineNumber); - } + where T : class + { + NullImpl(AssertionType.UserAssert, value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkNull(T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : class + { + NullImpl(AssertionType.SdkAssert, value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresNull(T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : class + { + NullImpl(AssertionType.SdkRequires, value, valueText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Null ref + // --------------------------------------------------------------------- + + private static void NullImpl(AssertionType assertionType, ref T value, string valueText, + string functionName, string fileName, int lineNumber) + { + if (AssertImpl.Null(ref value)) + return; + + AssertImpl.InvokeAssertionNull(assertionType, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void Null(ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NullImpl(AssertionType.UserAssert, ref value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkNull(ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NullImpl(AssertionType.SdkAssert, ref value, valueText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresNull(ref T value, + [CallerArgumentExpression("value")] string valueText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + NullImpl(AssertionType.SdkRequires, ref value, valueText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // In range + // --------------------------------------------------------------------- + + private static void InRangeImpl(AssertionType assertionType, int value, int lower, int upper, string valueText, + string lowerText, string upperText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.WithinRange(value, lower, upper)) + return; + + AssertImpl.InvokeAssertionInRange(assertionType, value, lower, upper, valueText, lowerText, upperText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void InRange(int value, int lowerInclusive, int upperExclusive, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", + [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + InRangeImpl(AssertionType.UserAssert, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, + upperExclusiveText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkInRange(int value, int lowerInclusive, int upperExclusive, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", + [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + InRangeImpl(AssertionType.SdkAssert, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, + upperExclusiveText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresInRange(int value, int lowerInclusive, int upperExclusive, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("lowerInclusive")] string lowerInclusiveText = "", + [CallerArgumentExpression("upperExclusive")] string upperExclusiveText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + InRangeImpl(AssertionType.SdkRequires, value, lowerInclusive, upperExclusive, valueText, lowerInclusiveText, + upperExclusiveText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Within min-max int + // --------------------------------------------------------------------- + + private static void WithinMinMaxImpl(AssertionType assertionType, int value, int min, int max, string valueText, + string minText, string maxText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.WithinMinMax(value, min, max)) + return; + + AssertImpl.InvokeAssertionWithinMinMax(assertionType, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void WithinMinMax(int value, int min, int max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.UserAssert, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkWithinMinMax(int value, int min, int max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.SdkAssert, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresWithinMinMax(int value, int min, int max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.SdkRequires, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Within min-max long + // --------------------------------------------------------------------- + + private static void WithinMinMaxImpl(AssertionType assertionType, long value, long min, long max, + string valueText, string minText, string maxText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.WithinMinMax(value, min, max)) + return; + + AssertImpl.InvokeAssertionWithinMinMax(assertionType, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void WithinMinMax(long value, long min, long max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.UserAssert, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkWithinMinMax(long value, long min, long max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.SdkAssert, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresWithinMinMax(long value, long min, long max, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("min")] string minText = "", + [CallerArgumentExpression("max")] string maxText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + WithinMinMaxImpl(AssertionType.SdkRequires, value, min, max, valueText, minText, maxText, functionName, + fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Equal + // --------------------------------------------------------------------- + + private static void EqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, + string functionName, string fileName, int lineNumber) where T : IEquatable + { + if (AssertImpl.Equal(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void Equal(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Equal ref + // --------------------------------------------------------------------- + + private static void EqualImpl(AssertionType assertionType, ref T lhs, ref T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable + { + if (AssertImpl.Equal(ref lhs, ref rhs)) + return; + + AssertImpl.InvokeAssertionEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void Equal(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.UserAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkEqual(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.SdkAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresEqual(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + EqualImpl(AssertionType.SdkRequires, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, + lineNumber); + } + + // --------------------------------------------------------------------- + // Not equal + // --------------------------------------------------------------------- + + private static void NotEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, + string functionName, string fileName, int lineNumber) where T : IEquatable + { + if (AssertImpl.NotEqual(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionNotEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void NotEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkNotEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresNotEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Not equal ref + // --------------------------------------------------------------------- + + private static void NotEqualImpl(AssertionType assertionType, ref T lhs, ref T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable + { + if (AssertImpl.NotEqual(ref lhs, ref rhs)) + return; + + AssertImpl.InvokeAssertionNotEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void NotEqual(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.UserAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, + lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkNotEqual(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.SdkAssert, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, + lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresNotEqual(ref T lhs, ref T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IEquatable + { + NotEqualImpl(AssertionType.SdkRequires, ref lhs, ref rhs, lhsText, rhsText, functionName, fileName, + lineNumber); + } + + // --------------------------------------------------------------------- + // Less + // --------------------------------------------------------------------- + + private static void LessImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, + string functionName, string fileName, int lineNumber) where T : IComparable + { + if (AssertImpl.Less(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionLess(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void Less(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkLess(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresLess(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Less equal + // --------------------------------------------------------------------- + + private static void LessEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, + string functionName, string fileName, int lineNumber) where T : IComparable + { + if (AssertImpl.LessEqual(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionLessEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void LessEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkLessEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresLessEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + LessEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Greater + // --------------------------------------------------------------------- + + private static void GreaterImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, string rhsText, + string functionName, string fileName, int lineNumber) where T : IComparable + { + if (AssertImpl.Greater(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionGreater(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void Greater(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkGreater(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresGreater(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Greater equal + // --------------------------------------------------------------------- + + private static void GreaterEqualImpl(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable + { + if (AssertImpl.GreaterEqual(lhs, rhs)) + return; + + AssertImpl.InvokeAssertionGreaterEqual(assertionType, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + public static void GreaterEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterEqualImpl(AssertionType.UserAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkGreaterEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterEqualImpl(AssertionType.SdkAssert, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresGreaterEqual(T lhs, T rhs, + [CallerArgumentExpression("lhs")] string lhsText = "", + [CallerArgumentExpression("rhs")] string rhsText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + where T : IComparable + { + GreaterEqualImpl(AssertionType.SdkRequires, lhs, rhs, lhsText, rhsText, functionName, fileName, lineNumber); + } + + // --------------------------------------------------------------------- + // Aligned + // --------------------------------------------------------------------- + + private static void AlignedImpl(AssertionType assertionType, ulong value, int alignment, string valueText, + string alignmentText, string functionName, string fileName, int lineNumber) + { + if (AssertImpl.IsAligned(value, alignment)) + return; + + AssertImpl.InvokeAssertionAligned(assertionType, value, alignment, valueText, alignmentText, functionName, fileName, + lineNumber); + } + + [Conditional(AssertCondition)] + public static void Aligned(ulong value, int alignment, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("alignment")] string alignmentText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + AlignedImpl(AssertionType.UserAssert, value, alignment, valueText, alignmentText, functionName, fileName, + lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkAligned(ulong value, int alignment, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("alignment")] string alignmentText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + AlignedImpl(AssertionType.SdkAssert, value, alignment, valueText, alignmentText, functionName, fileName, + lineNumber); + } + + [Conditional(AssertCondition)] + internal static void SdkRequiresAligned(ulong value, int alignment, + [CallerArgumentExpression("value")] string valueText = "", + [CallerArgumentExpression("alignment")] string alignmentText = "", + [CallerMemberName] string functionName = "", + [CallerFilePath] string fileName = "", + [CallerLineNumber] int lineNumber = 0) + { + AlignedImpl(AssertionType.SdkRequires, value, alignment, valueText, alignmentText, functionName, fileName, + lineNumber); } } diff --git a/src/LibHac/Diag/DiagClient.cs b/src/LibHac/Diag/DiagClient.cs index b4990101..bce160e8 100644 --- a/src/LibHac/Diag/DiagClient.cs +++ b/src/LibHac/Diag/DiagClient.cs @@ -1,38 +1,37 @@ -namespace LibHac.Diag +namespace LibHac.Diag; + +public class DiagClient { - public class DiagClient + internal DiagClientGlobals Globals; + + public DiagClientImpl Impl => new DiagClientImpl(this); + internal HorizonClient Hos => Globals.Hos; + + public DiagClient(HorizonClient horizonClient) { - internal DiagClientGlobals Globals; - - public DiagClientImpl Impl => new DiagClientImpl(this); - internal HorizonClient Hos => Globals.Hos; - - public DiagClient(HorizonClient horizonClient) - { - Globals.Initialize(this, horizonClient); - } - } - - internal struct DiagClientGlobals - { - public HorizonClient Hos; - public object InitMutex; - public LogObserverGlobals LogObserver; - - public void Initialize(DiagClient diagClient, HorizonClient horizonClient) - { - Hos = horizonClient; - InitMutex = new object(); - } - } - - // Functions in the nn::diag::detail namespace use this struct. - public readonly struct DiagClientImpl - { - internal readonly DiagClient Diag; - internal HorizonClient Hos => Diag.Hos; - internal ref DiagClientGlobals Globals => ref Diag.Globals; - - internal DiagClientImpl(DiagClient parentClient) => Diag = parentClient; + Globals.Initialize(this, horizonClient); } } + +internal struct DiagClientGlobals +{ + public HorizonClient Hos; + public object InitMutex; + public LogObserverGlobals LogObserver; + + public void Initialize(DiagClient diagClient, HorizonClient horizonClient) + { + Hos = horizonClient; + InitMutex = new object(); + } +} + +// Functions in the nn::diag::detail namespace use this struct. +public readonly struct DiagClientImpl +{ + internal readonly DiagClient Diag; + internal HorizonClient Hos => Diag.Hos; + internal ref DiagClientGlobals Globals => ref Diag.Globals; + + internal DiagClientImpl(DiagClient parentClient) => Diag = parentClient; +} diff --git a/src/LibHac/Diag/Impl/AssertImpl.cs b/src/LibHac/Diag/Impl/AssertImpl.cs index 69c06d37..63ce1c12 100644 --- a/src/LibHac/Diag/Impl/AssertImpl.cs +++ b/src/LibHac/Diag/Impl/AssertImpl.cs @@ -3,231 +3,230 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Util; -namespace LibHac.Diag.Impl +namespace LibHac.Diag.Impl; + +internal static class AssertImpl { - internal static class AssertImpl + internal static void InvokeAssertionNotNull(AssertionType assertionType, string valueText, string functionName, + string fileName, int lineNumber) { - internal static void InvokeAssertionNotNull(AssertionType assertionType, string valueText, string functionName, - string fileName, int lineNumber) - { - Assert.OnAssertionFailure(assertionType, "NotNull", functionName, fileName, lineNumber, - $"{valueText} must not be nullptr."); - } + Assert.OnAssertionFailure(assertionType, "NotNull", functionName, fileName, lineNumber, + $"{valueText} must not be nullptr."); + } - internal static void InvokeAssertionNull(AssertionType assertionType, string valueText, string functionName, - string fileName, int lineNumber) - { - Assert.OnAssertionFailure(assertionType, "Null", functionName, fileName, lineNumber, - $"{valueText} must be nullptr."); - } + internal static void InvokeAssertionNull(AssertionType assertionType, string valueText, string functionName, + string fileName, int lineNumber) + { + Assert.OnAssertionFailure(assertionType, "Null", functionName, fileName, lineNumber, + $"{valueText} must be nullptr."); + } - internal static void InvokeAssertionInRange(AssertionType assertionType, int value, int lower, int upper, - string valueText, string lowerText, string upperText, string functionName, string fileName, int lineNumber) - { - string message = - string.Format( - "{0} must be within the range [{1}, {2})\nThe actual values are as follows.\n{0}: {3}\n{1}: {4}\n{2}: {5}", - valueText, lowerText, upperText, value, lower, upper); + internal static void InvokeAssertionInRange(AssertionType assertionType, int value, int lower, int upper, + string valueText, string lowerText, string upperText, string functionName, string fileName, int lineNumber) + { + string message = + string.Format( + "{0} must be within the range [{1}, {2})\nThe actual values are as follows.\n{0}: {3}\n{1}: {4}\n{2}: {5}", + valueText, lowerText, upperText, value, lower, upper); - Assert.OnAssertionFailure(assertionType, "RangeCheck", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "RangeCheck", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionWithinMinMax(AssertionType assertionType, long value, long min, long max, - string valueText, string minText, string maxText, string functionName, string fileName, int lineNumber) - { - string message = - string.Format( - "{0} must satisfy the condition min:{1} and max:{2}\nThe actual values are as follows.\n{0}: {3}\n{1}: {4}\n{2}: {5}", - valueText, minText, maxText, value, min, max); + internal static void InvokeAssertionWithinMinMax(AssertionType assertionType, long value, long min, long max, + string valueText, string minText, string maxText, string functionName, string fileName, int lineNumber) + { + string message = + string.Format( + "{0} must satisfy the condition min:{1} and max:{2}\nThe actual values are as follows.\n{0}: {3}\n{1}: {4}\n{2}: {5}", + valueText, minText, maxText, value, min, max); - Assert.OnAssertionFailure(assertionType, "MinMaxCheck", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "MinMaxCheck", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable - { - string message = - string.Format("{0} must be equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable + { + string message = + string.Format("{0} must be equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "Equal", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "Equal", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionNotEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable - { - string message = - string.Format("{0} must be equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionNotEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IEquatable + { + string message = + string.Format("{0} must be equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "Equal", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "Equal", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionLess(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable - { - string message = - string.Format("{0} must be less than {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionLess(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable + { + string message = + string.Format("{0} must be less than {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "Less", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "Less", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionLessEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable - { - string message = - string.Format( - "{0} must be less than or equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionLessEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable + { + string message = + string.Format( + "{0} must be less than or equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "LessEqual", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "LessEqual", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionGreater(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable - { - string message = - string.Format("{0} must be greater than {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionGreater(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable + { + string message = + string.Format("{0} must be greater than {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "Greater", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "Greater", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionGreaterEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, - string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable - { - string message = - string.Format( - "{0} must be greater than or equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - lhsText, rhsText, lhs, rhs); + internal static void InvokeAssertionGreaterEqual(AssertionType assertionType, T lhs, T rhs, string lhsText, + string rhsText, string functionName, string fileName, int lineNumber) where T : IComparable + { + string message = + string.Format( + "{0} must be greater than or equal to {1}.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + lhsText, rhsText, lhs, rhs); - Assert.OnAssertionFailure(assertionType, "GreaterEqual", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "GreaterEqual", functionName, fileName, lineNumber, message); + } - internal static void InvokeAssertionAligned(AssertionType assertionType, ulong value, int alignment, - string valueText, string alignmentText, string functionName, string fileName, int lineNumber) - { - string message = - string.Format("{0} must be {1}-bytes aligned.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", - valueText, alignmentText, value, alignment); + internal static void InvokeAssertionAligned(AssertionType assertionType, ulong value, int alignment, + string valueText, string alignmentText, string functionName, string fileName, int lineNumber) + { + string message = + string.Format("{0} must be {1}-bytes aligned.\nThe actual values are as follows.\n{0}: {2}\n{1}: {3}", + valueText, alignmentText, value, alignment); - Assert.OnAssertionFailure(assertionType, "Aligned", functionName, fileName, lineNumber, message); - } + Assert.OnAssertionFailure(assertionType, "Aligned", functionName, fileName, lineNumber, message); + } - public static bool Null(T item) where T : class - { - return item is null; - } + public static bool Null(T item) where T : class + { + return item is null; + } - public static bool Null(ref T item) - { - return Unsafe.IsNullRef(ref item); - } + public static bool Null(ref T item) + { + return Unsafe.IsNullRef(ref item); + } - public static bool NotNull(T item) where T : class - { - return item is not null; - } + public static bool NotNull(T item) where T : class + { + return item is not null; + } - public static bool NotNull(ref T item) - { - return !Unsafe.IsNullRef(ref item); - } + public static bool NotNull(ref T item) + { + return !Unsafe.IsNullRef(ref item); + } - public static bool NotNull(Span span) - { - return !Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)) || span.Length == 0; - } + public static bool NotNull(Span span) + { + return !Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)) || span.Length == 0; + } - public static bool NotNull(ReadOnlySpan span) - { - return !Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)) || span.Length == 0; - } + public static bool NotNull(ReadOnlySpan span) + { + return !Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span)) || span.Length == 0; + } - public static bool WithinRange(int value, int lowerInclusive, int upperExclusive) - { - return lowerInclusive <= value && value < upperExclusive; - } + public static bool WithinRange(int value, int lowerInclusive, int upperExclusive) + { + return lowerInclusive <= value && value < upperExclusive; + } - public static bool WithinRange(long value, long lowerInclusive, long upperExclusive) - { - return lowerInclusive <= value && value < upperExclusive; - } + public static bool WithinRange(long value, long lowerInclusive, long upperExclusive) + { + return lowerInclusive <= value && value < upperExclusive; + } - public static bool WithinMinMax(int value, int min, int max) - { - return min <= value && value <= max; - } + public static bool WithinMinMax(int value, int min, int max) + { + return min <= value && value <= max; + } - public static bool WithinMinMax(long value, long min, long max) - { - return min <= value && value <= max; - } + public static bool WithinMinMax(long value, long min, long max) + { + return min <= value && value <= max; + } - public static bool Equal(T lhs, T rhs) where T : IEquatable - { - return lhs.Equals(rhs); - } + public static bool Equal(T lhs, T rhs) where T : IEquatable + { + return lhs.Equals(rhs); + } - public static bool Equal(ref T lhs, ref T rhs) where T : IEquatable - { - return lhs.Equals(rhs); - } + public static bool Equal(ref T lhs, ref T rhs) where T : IEquatable + { + return lhs.Equals(rhs); + } - public static bool NotEqual(T lhs, T rhs) where T : IEquatable - { - return !lhs.Equals(rhs); - } + public static bool NotEqual(T lhs, T rhs) where T : IEquatable + { + return !lhs.Equals(rhs); + } - public static bool NotEqual(ref T lhs, ref T rhs) where T : IEquatable - { - return !lhs.Equals(rhs); - } + public static bool NotEqual(ref T lhs, ref T rhs) where T : IEquatable + { + return !lhs.Equals(rhs); + } - public static bool Less(T lhs, T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) < 0; - } + public static bool Less(T lhs, T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) < 0; + } - public static bool Less(ref T lhs, ref T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) < 0; - } + public static bool Less(ref T lhs, ref T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) < 0; + } - public static bool LessEqual(T lhs, T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) <= 0; - } + public static bool LessEqual(T lhs, T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) <= 0; + } - public static bool LessEqual(ref T lhs, ref T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) <= 0; - } + public static bool LessEqual(ref T lhs, ref T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) <= 0; + } - public static bool Greater(T lhs, T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) > 0; - } + public static bool Greater(T lhs, T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) > 0; + } - public static bool Greater(ref T lhs, ref T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) > 0; - } + public static bool Greater(ref T lhs, ref T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) > 0; + } - public static bool GreaterEqual(T lhs, T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) >= 0; - } + public static bool GreaterEqual(T lhs, T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) >= 0; + } - public static bool GreaterEqual(ref T lhs, ref T rhs) where T : IComparable - { - return lhs.CompareTo(rhs) >= 0; - } + public static bool GreaterEqual(ref T lhs, ref T rhs) where T : IComparable + { + return lhs.CompareTo(rhs) >= 0; + } - public static bool IsAligned(ulong value, int alignment) - { - return Alignment.IsAlignedPow2(value, (uint)alignment); - } + public static bool IsAligned(ulong value, int alignment) + { + return Alignment.IsAlignedPow2(value, (uint)alignment); } } diff --git a/src/LibHac/Diag/Impl/ObserverManager.cs b/src/LibHac/Diag/Impl/ObserverManager.cs index 27b1a00f..a65e577e 100644 --- a/src/LibHac/Diag/Impl/ObserverManager.cs +++ b/src/LibHac/Diag/Impl/ObserverManager.cs @@ -1,145 +1,144 @@ using System.Collections.Generic; using LibHac.Os; -namespace LibHac.Diag.Impl +namespace LibHac.Diag.Impl; + +internal interface IObserverHolder { - internal interface IObserverHolder + bool IsRegistered { get; set; } +} + +internal class ObserverManager where TObserver : IObserverHolder +{ + private LinkedList _observers; + private ReaderWriterLock _rwLock; + + public delegate void Function(ref TObserver observer, in TItem item); + + public ObserverManager(HorizonClient hos) { - bool IsRegistered { get; set; } + _observers = new LinkedList(); + _rwLock = new ReaderWriterLock(hos.Os); } - internal class ObserverManager where TObserver : IObserverHolder + public void RegisterObserver(TObserver observerHolder) { - private LinkedList _observers; - private ReaderWriterLock _rwLock; + Assert.SdkRequires(!observerHolder.IsRegistered); - public delegate void Function(ref TObserver observer, in TItem item); + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - public ObserverManager(HorizonClient hos) - { - _observers = new LinkedList(); - _rwLock = new ReaderWriterLock(hos.Os); - } - - public void RegisterObserver(TObserver observerHolder) - { - Assert.SdkRequires(!observerHolder.IsRegistered); - - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - _observers.AddFirst(observerHolder); - observerHolder.IsRegistered = true; - } - - public void UnregisterObserver(TObserver observerHolder) - { - Assert.SdkRequires(observerHolder.IsRegistered); - - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode foundObserver = _observers.Find(observerHolder); - if (foundObserver is not null) - { - _observers.Remove(foundObserver); - } - - observerHolder.IsRegistered = false; - } - - public void UnregisterAllObservers() - { - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode curNode = _observers.First; - - while (curNode is not null) - { - curNode.ValueRef.IsRegistered = false; - curNode = curNode.Next; - } - - _observers.Clear(); - } - - public void InvokeAllObserver(in TItem item, Function function) - { - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode curNode = _observers.First; - - while (curNode is not null) - { - function(ref curNode.ValueRef, in item); - curNode = curNode.Next; - } - } + _observers.AddFirst(observerHolder); + observerHolder.IsRegistered = true; } - // Todo: Use generic class when ref structs can be used as generics - internal class LogObserverManager + public void UnregisterObserver(TObserver observerHolder) { - private readonly LinkedList _observers; - private ReaderWriterLock _rwLock; + Assert.SdkRequires(observerHolder.IsRegistered); - public delegate void Function(ref LogObserverHolder observer, in LogObserverContext item); + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - public LogObserverManager(HorizonClient hos) + LinkedListNode foundObserver = _observers.Find(observerHolder); + if (foundObserver is not null) { - _observers = new LinkedList(); - _rwLock = new ReaderWriterLock(hos.Os); + _observers.Remove(foundObserver); } - public void RegisterObserver(LogObserverHolder observerHolder) + observerHolder.IsRegistered = false; + } + + public void UnregisterAllObservers() + { + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + LinkedListNode curNode = _observers.First; + + while (curNode is not null) { - Assert.SdkRequires(!observerHolder.IsRegistered); - - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - _observers.AddFirst(observerHolder); - observerHolder.IsRegistered = true; + curNode.ValueRef.IsRegistered = false; + curNode = curNode.Next; } - public void UnregisterObserver(LogObserverHolder observerHolder) + _observers.Clear(); + } + + public void InvokeAllObserver(in TItem item, Function function) + { + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + LinkedListNode curNode = _observers.First; + + while (curNode is not null) { - Assert.SdkRequires(observerHolder.IsRegistered); - - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode foundObserver = _observers.Find(observerHolder); - if (foundObserver is not null) - { - _observers.Remove(foundObserver); - } - - observerHolder.IsRegistered = false; - } - - public void UnregisterAllObservers() - { - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode curNode = _observers.First; - - while (curNode is not null) - { - curNode.ValueRef.IsRegistered = false; - curNode = curNode.Next; - } - - _observers.Clear(); - } - - public void InvokeAllObserver(in LogObserverContext item, Function function) - { - using ScopedLock lk = ScopedLock.Lock(ref _rwLock); - - LinkedListNode curNode = _observers.First; - - while (curNode is not null) - { - function(ref curNode.ValueRef, in item); - curNode = curNode.Next; - } + function(ref curNode.ValueRef, in item); + curNode = curNode.Next; + } + } +} + +// Todo: Use generic class when ref structs can be used as generics +internal class LogObserverManager +{ + private readonly LinkedList _observers; + private ReaderWriterLock _rwLock; + + public delegate void Function(ref LogObserverHolder observer, in LogObserverContext item); + + public LogObserverManager(HorizonClient hos) + { + _observers = new LinkedList(); + _rwLock = new ReaderWriterLock(hos.Os); + } + + public void RegisterObserver(LogObserverHolder observerHolder) + { + Assert.SdkRequires(!observerHolder.IsRegistered); + + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + _observers.AddFirst(observerHolder); + observerHolder.IsRegistered = true; + } + + public void UnregisterObserver(LogObserverHolder observerHolder) + { + Assert.SdkRequires(observerHolder.IsRegistered); + + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + LinkedListNode foundObserver = _observers.Find(observerHolder); + if (foundObserver is not null) + { + _observers.Remove(foundObserver); + } + + observerHolder.IsRegistered = false; + } + + public void UnregisterAllObservers() + { + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + LinkedListNode curNode = _observers.First; + + while (curNode is not null) + { + curNode.ValueRef.IsRegistered = false; + curNode = curNode.Next; + } + + _observers.Clear(); + } + + public void InvokeAllObserver(in LogObserverContext item, Function function) + { + using ScopedLock lk = ScopedLock.Lock(ref _rwLock); + + LinkedListNode curNode = _observers.First; + + while (curNode is not null) + { + function(ref curNode.ValueRef, in item); + curNode = curNode.Next; } } } diff --git a/src/LibHac/Diag/Log.cs b/src/LibHac/Diag/Log.cs index eb2e4504..e452a426 100644 --- a/src/LibHac/Diag/Log.cs +++ b/src/LibHac/Diag/Log.cs @@ -2,46 +2,45 @@ using System.Runtime.CompilerServices; using LibHac.Common; -namespace LibHac.Diag +namespace LibHac.Diag; + +public static class Log { - public static class Log + // Todo: Should we split large logs into smaller chunks like Horizon does? + public static void LogImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan message) { - // Todo: Should we split large logs into smaller chunks like Horizon does? - public static void LogImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan message) - { - diag.PutImpl(in metaData, message); - } + diag.PutImpl(in metaData, message); + } - public static void PutImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan message) + public static void PutImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan message) + { + var logBody = new LogBody { - var logBody = new LogBody + Message = new U8Span(message), + IsHead = true, + IsTail = true + }; + + diag.CallAllLogObserver(in metaData, in logBody); + } + + public static void LogImpl(this DiagClientImpl diag, ReadOnlySpan moduleName, LogSeverity severity, + ReadOnlySpan message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", + [CallerMemberName] string functionName = "") + { + var metaData = new LogMetaData + { + SourceInfo = new SourceInfo { - Message = new U8Span(message), - IsHead = true, - IsTail = true - }; + LineNumber = lineNumber, + FileName = fileName, + FunctionName = functionName + }, + ModuleName = new U8Span(moduleName), + Severity = severity, + Verbosity = 0 + }; - diag.CallAllLogObserver(in metaData, in logBody); - } - - public static void LogImpl(this DiagClientImpl diag, ReadOnlySpan moduleName, LogSeverity severity, - ReadOnlySpan message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", - [CallerMemberName] string functionName = "") - { - var metaData = new LogMetaData - { - SourceInfo = new SourceInfo - { - LineNumber = lineNumber, - FileName = fileName, - FunctionName = functionName - }, - ModuleName = new U8Span(moduleName), - Severity = severity, - Verbosity = 0 - }; - - diag.LogImpl(in metaData, message); - } + diag.LogImpl(in metaData, message); } } diff --git a/src/LibHac/Diag/LogTypes.cs b/src/LibHac/Diag/LogTypes.cs index eb673f15..deeb65e2 100644 --- a/src/LibHac/Diag/LogTypes.cs +++ b/src/LibHac/Diag/LogTypes.cs @@ -1,38 +1,37 @@ using System; using LibHac.Common; -namespace LibHac.Diag +namespace LibHac.Diag; + +public enum LogSeverity { - public enum LogSeverity - { - Trace, - Info, - Warn, - Error, - Fatal - } - - public ref struct LogMetaData - { - public SourceInfo SourceInfo; - public U8Span ModuleName; - public LogSeverity Severity; - public int Verbosity; - public bool UseDefaultLocaleCharset; - public Span AdditionalData; - } - - public struct SourceInfo - { - public int LineNumber; - public string FileName; - public string FunctionName; - } - - public ref struct LogBody - { - public U8Span Message; - public bool IsHead; - public bool IsTail; - } + Trace, + Info, + Warn, + Error, + Fatal +} + +public ref struct LogMetaData +{ + public SourceInfo SourceInfo; + public U8Span ModuleName; + public LogSeverity Severity; + public int Verbosity; + public bool UseDefaultLocaleCharset; + public Span AdditionalData; +} + +public struct SourceInfo +{ + public int LineNumber; + public string FileName; + public string FunctionName; +} + +public ref struct LogBody +{ + public U8Span Message; + public bool IsHead; + public bool IsTail; } diff --git a/src/LibHac/Fat/FatAttribute.cs b/src/LibHac/Fat/FatAttribute.cs index 4019b8f2..1717f848 100644 --- a/src/LibHac/Fat/FatAttribute.cs +++ b/src/LibHac/Fat/FatAttribute.cs @@ -1,9 +1,8 @@ -namespace LibHac.Fat +namespace LibHac.Fat; + +public struct FatAttribute { - public struct FatAttribute - { - public bool IsFatSafeEnabled; - public bool IsFatFormatNormalized; - public bool IsTimeStampUpdated; - } + public bool IsFatSafeEnabled; + public bool IsFatFormatNormalized; + public bool IsTimeStampUpdated; } diff --git a/src/LibHac/Fat/FatError.cs b/src/LibHac/Fat/FatError.cs index a6ff8b3d..bef2cf1a 100644 --- a/src/LibHac/Fat/FatError.cs +++ b/src/LibHac/Fat/FatError.cs @@ -1,19 +1,18 @@ using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fat +namespace LibHac.Fat; + +[StructLayout(LayoutKind.Explicit, Size = 0x20)] +public struct FatError { - [StructLayout(LayoutKind.Explicit, Size = 0x20)] - public struct FatError - { - private const int FunctionNameLength = 0x10; + private const int FunctionNameLength = 0x10; - [FieldOffset(0x00)] public int Error; - [FieldOffset(0x04)] public int ExtraError; - [FieldOffset(0x08)] public int DriveId; - [FieldOffset(0x0C)] private byte _functionName; + [FieldOffset(0x00)] public int Error; + [FieldOffset(0x04)] public int ExtraError; + [FieldOffset(0x08)] public int DriveId; + [FieldOffset(0x0C)] private byte _functionName; - public U8SpanMutable ErrorName => - new U8SpanMutable(SpanHelpers.CreateSpan(ref _functionName, FunctionNameLength)); - } + public U8SpanMutable ErrorName => + new U8SpanMutable(SpanHelpers.CreateSpan(ref _functionName, FunctionNameLength)); } diff --git a/src/LibHac/Fat/FatFormatParam.cs b/src/LibHac/Fat/FatFormatParam.cs index e6106fde..2c8964ea 100644 --- a/src/LibHac/Fat/FatFormatParam.cs +++ b/src/LibHac/Fat/FatFormatParam.cs @@ -1,12 +1,11 @@ using System; -namespace LibHac.Fat +namespace LibHac.Fat; + +public struct FatFormatParam { - public struct FatFormatParam - { - public bool IsSdCard; - public uint ProtectedAreaSectors; - public Result WriteVerifyErrorResult; - public Memory WorkBuffer; - } + public bool IsSdCard; + public uint ProtectedAreaSectors; + public Result WriteVerifyErrorResult; + public Memory WorkBuffer; } diff --git a/src/LibHac/Fs/AccessLogHelpers.cs b/src/LibHac/Fs/AccessLogHelpers.cs index 50e840d9..6a7b18a3 100644 --- a/src/LibHac/Fs/AccessLogHelpers.cs +++ b/src/LibHac/Fs/AccessLogHelpers.cs @@ -1,12 +1,11 @@ -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class AccessLogHelpers { - public static class AccessLogHelpers + public static string BuildDefaultLogLine(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, + string message, string caller) { - public static string BuildDefaultLogLine(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, - string message, string caller) - { - return - $"FS_ACCESS: {{ start: {(long)startTime.TotalMilliseconds,9}, end: {(long)endTime.TotalMilliseconds,9}, result: 0x{result.Value:x8}, handle: 0x{handleId:x8}, function: \"{caller}\"{message} }}"; - } + return + $"FS_ACCESS: {{ start: {(long)startTime.TotalMilliseconds,9}, end: {(long)endTime.TotalMilliseconds,9}, result: 0x{result.Value:x8}, handle: 0x{handleId:x8}, function: \"{caller}\"{message} }}"; } } diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 4c62cfae..549e3e08 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -7,97 +7,114 @@ using LibHac.Ncm; using LibHac.Ns; using LibHac.Util; -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class ApplicationSaveDataManagement { - public static class ApplicationSaveDataManagement + public static Result EnsureApplicationSaveData(FileSystemClient fs, out long requiredSize, Ncm.ApplicationId applicationId, + ref ApplicationControlProperty nacp, ref Uid uid) { - public static Result EnsureApplicationSaveData(FileSystemClient fs, out long requiredSize, Ncm.ApplicationId applicationId, - ref ApplicationControlProperty nacp, ref Uid uid) + UnsafeHelpers.SkipParamInit(out requiredSize); + long requiredSizeSum = 0; + + // Create local variable for use in closures + ProgramId saveDataOwnerId = nacp.SaveDataOwnerId; + + // Ensure the user account save exists + if (uid != Uid.Zero && nacp.UserAccountSaveDataSize > 0) { - UnsafeHelpers.SkipParamInit(out requiredSize); - long requiredSizeSum = 0; + // More local variables for use in closures + Uid uidLocal = uid; + long accountSaveDataSize = nacp.UserAccountSaveDataSize; + long accountSaveJournalSize = nacp.UserAccountSaveDataJournalSize; - // Create local variable for use in closures - ProgramId saveDataOwnerId = nacp.SaveDataOwnerId; - - // Ensure the user account save exists - if (uid != Uid.Zero && nacp.UserAccountSaveDataSize > 0) + Result CreateAccountSaveFunc() { - // More local variables for use in closures - Uid uidLocal = uid; - long accountSaveDataSize = nacp.UserAccountSaveDataSize; - long accountSaveJournalSize = nacp.UserAccountSaveDataJournalSize; + UserId userId = ConvertAccountUidToFsUserId(uidLocal); + return fs.CreateSaveData(applicationId, userId, saveDataOwnerId.Value, accountSaveDataSize, + accountSaveJournalSize, SaveDataFlags.None); + } - Result CreateAccountSaveFunc() - { - UserId userId = ConvertAccountUidToFsUserId(uidLocal); - return fs.CreateSaveData(applicationId, userId, saveDataOwnerId.Value, accountSaveDataSize, - accountSaveJournalSize, SaveDataFlags.None); - } + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetSaveDataType(SaveDataType.Account); + filter.SetUserId(new UserId(uid.Id.High, uid.Id.Low)); + // The 0x4c000 includes the save meta and other stuff + Result rc = EnsureAndExtendSaveData(fs, CreateAccountSaveFunc, ref requiredSizeSum, ref filter, 0x4c000, + accountSaveDataSize, accountSaveJournalSize); + + if (rc.IsFailure()) return rc; + } + + // Ensure the device save exists + if (nacp.DeviceSaveDataSize > 0) + { + long deviceSaveDataSize = nacp.DeviceSaveDataSize; + long deviceSaveJournalSize = nacp.DeviceSaveDataJournalSize; + + Result CreateDeviceSaveFunc() => fs.CreateDeviceSaveData(applicationId, saveDataOwnerId.Value, + deviceSaveDataSize, deviceSaveJournalSize, 0); + + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetSaveDataType(SaveDataType.Device); + + Result rc = EnsureAndExtendSaveData(fs, CreateDeviceSaveFunc, ref requiredSizeSum, ref filter, 0x4000, + deviceSaveDataSize, deviceSaveJournalSize); + + if (rc.IsFailure()) return rc; + } + + Result bcatRc = EnsureApplicationBcatDeliveryCacheStorageImpl(fs, + out long requiredSizeBcat, applicationId, ref nacp); + + if (bcatRc.IsFailure()) + { + if (!ResultFs.UsableSpaceNotEnough.Includes(bcatRc)) + { + return bcatRc; + } + + requiredSizeSum += requiredSizeBcat; + } + + if (nacp.TemporaryStorageSize > 0) + { + if (requiredSizeSum > 0) + { + // If there was already insufficient space to create the previous saves, check if the temp + // save already exists instead of trying to create a new one. var filter = new SaveDataFilter(); filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Account); - filter.SetUserId(new UserId(uid.Id.High, uid.Id.Low)); + filter.SetSaveDataType(SaveDataType.Temporary); - // The 0x4c000 includes the save meta and other stuff - Result rc = EnsureAndExtendSaveData(fs, CreateAccountSaveFunc, ref requiredSizeSum, ref filter, 0x4c000, - accountSaveDataSize, accountSaveJournalSize); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, in filter); - if (rc.IsFailure()) return rc; - } - - // Ensure the device save exists - if (nacp.DeviceSaveDataSize > 0) - { - long deviceSaveDataSize = nacp.DeviceSaveDataSize; - long deviceSaveJournalSize = nacp.DeviceSaveDataJournalSize; - - Result CreateDeviceSaveFunc() => fs.CreateDeviceSaveData(applicationId, saveDataOwnerId.Value, - deviceSaveDataSize, deviceSaveJournalSize, 0); - - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Device); - - Result rc = EnsureAndExtendSaveData(fs, CreateDeviceSaveFunc, ref requiredSizeSum, ref filter, 0x4000, - deviceSaveDataSize, deviceSaveJournalSize); - - if (rc.IsFailure()) return rc; - } - - Result bcatRc = EnsureApplicationBcatDeliveryCacheStorageImpl(fs, - out long requiredSizeBcat, applicationId, ref nacp); - - if (bcatRc.IsFailure()) - { - if (!ResultFs.UsableSpaceNotEnough.Includes(bcatRc)) + if (rc.IsFailure()) { - return bcatRc; - } - - requiredSizeSum += requiredSizeBcat; - } - - if (nacp.TemporaryStorageSize > 0) - { - if (requiredSizeSum > 0) - { - // If there was already insufficient space to create the previous saves, check if the temp - // save already exists instead of trying to create a new one. - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Temporary); - - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.Temporary, in filter); - - if (rc.IsFailure()) + if (!ResultFs.TargetNotFound.Includes(rc)) { - if (!ResultFs.TargetNotFound.Includes(rc)) - { - return rc; - } + return rc; + } + Result queryRc = fs.QuerySaveDataTotalSize(out long tempSaveTotalSize, + nacp.TemporaryStorageSize, 0); + + if (queryRc.IsFailure()) return queryRc; + + requiredSizeSum += Alignment.AlignUp(tempSaveTotalSize, 0x4000) + 0x4000; + } + } + else + { + Result createRc = fs.CreateTemporaryStorage(applicationId, nacp.SaveDataOwnerId.Value, + nacp.TemporaryStorageSize, 0); + + if (createRc.IsFailure()) + { + if (ResultFs.UsableSpaceNotEnough.Includes(createRc)) + { Result queryRc = fs.QuerySaveDataTotalSize(out long tempSaveTotalSize, nacp.TemporaryStorageSize, 0); @@ -105,371 +122,353 @@ namespace LibHac.Fs requiredSizeSum += Alignment.AlignUp(tempSaveTotalSize, 0x4000) + 0x4000; } - } - else - { - Result createRc = fs.CreateTemporaryStorage(applicationId, nacp.SaveDataOwnerId.Value, - nacp.TemporaryStorageSize, 0); - - if (createRc.IsFailure()) + else if (ResultFs.PathAlreadyExists.Includes(createRc)) { - if (ResultFs.UsableSpaceNotEnough.Includes(createRc)) - { - Result queryRc = fs.QuerySaveDataTotalSize(out long tempSaveTotalSize, - nacp.TemporaryStorageSize, 0); - - if (queryRc.IsFailure()) return queryRc; - - requiredSizeSum += Alignment.AlignUp(tempSaveTotalSize, 0x4000) + 0x4000; - } - else if (ResultFs.PathAlreadyExists.Includes(createRc)) - { - requiredSizeSum += 0; - } - else - { - return createRc; - } + requiredSizeSum += 0; + } + else + { + return createRc; } } } - - requiredSize = requiredSizeSum; - - return requiredSize == 0 ? Result.Success : ResultFs.UsableSpaceNotEnough.Log(); } - [SuppressMessage("ReSharper", "UnusedParameter.Local")] - private static Result ExtendSaveDataIfNeeded(FileSystemClient fs, out long requiredSize, - SaveDataSpaceId spaceId, ulong saveDataId, long requiredDataSize, long requiredJournalSize) - { - // Checks the current save data size and extends it if needed. - // If there is not enough space to do the extension, the amount of space - // the extension requires will be written to requiredSize. + requiredSize = requiredSizeSum; - requiredSize = 0; + return requiredSize == 0 ? Result.Success : ResultFs.UsableSpaceNotEnough.Log(); + } + + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + private static Result ExtendSaveDataIfNeeded(FileSystemClient fs, out long requiredSize, + SaveDataSpaceId spaceId, ulong saveDataId, long requiredDataSize, long requiredJournalSize) + { + // Checks the current save data size and extends it if needed. + // If there is not enough space to do the extension, the amount of space + // the extension requires will be written to requiredSize. + + requiredSize = 0; + return Result.Success; + } + + private static Result CreateSaveData(FileSystemClient fs, Func createFunc, ref long requiredSize, long baseSize, + long dataSize, long journalSize) + { + Result rc = createFunc(); + + if (rc.IsSuccess()) return Result.Success; - } - private static Result CreateSaveData(FileSystemClient fs, Func createFunc, ref long requiredSize, long baseSize, - long dataSize, long journalSize) + if (ResultFs.UsableSpaceNotEnough.Includes(rc)) { - Result rc = createFunc(); + Result queryRc = fs.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize); + if (queryRc.IsFailure()) return queryRc; - if (rc.IsSuccess()) - return Result.Success; - - if (ResultFs.UsableSpaceNotEnough.Includes(rc)) - { - Result queryRc = fs.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize); - if (queryRc.IsFailure()) return queryRc; - - requiredSize += Alignment.AlignUp(totalSize, 0x4000) + baseSize; - } - else if (!ResultFs.PathAlreadyExists.Includes(rc)) - { - return rc; - } - - return Result.Success; + requiredSize += Alignment.AlignUp(totalSize, 0x4000) + baseSize; } - - private static Result EnsureAndExtendSaveData(FileSystemClient fs, Func createFunc, - ref long requiredSize, ref SaveDataFilter filter, long baseSize, long dataSize, long journalSize) + else if (!ResultFs.PathAlreadyExists.Includes(rc)) { - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, in filter); - - if (rc.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(rc)) - { - rc = CreateSaveData(fs, createFunc, ref requiredSize, baseSize, dataSize, journalSize); - } - - return rc; - } - - rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeExtend, SaveDataSpaceId.User, info.SaveDataId, - dataSize, journalSize); - - if (rc.IsFailure()) - { - if (!ResultFs.UsableSpaceNotEnough.Includes(rc)) - return rc; - - requiredSize += requiredSizeExtend; - } - - return Result.Success; - } - - private static Result EnsureApplicationBcatDeliveryCacheStorageImpl(FileSystemClient fs, out long requiredSize, - Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) - { - const long bcatDeliveryCacheJournalSize = 0x200000; - - long bcatStorageSize = nacp.BcatDeliveryCacheStorageSize; - if (bcatStorageSize <= 0) - { - requiredSize = 0; - return Result.Success; - } - - UnsafeHelpers.SkipParamInit(out requiredSize); - long requiredSizeBcat = 0; - - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Bcat); - - Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatStorageSize); - - Result rc = EnsureAndExtendSaveData(fs, CreateBcatStorageFunc, - ref requiredSizeBcat, ref filter, 0x4000, bcatStorageSize, bcatDeliveryCacheJournalSize); - - if (rc.IsFailure()) return rc; - - requiredSize = requiredSizeBcat; - return requiredSizeBcat > 0 ? ResultFs.UsableSpaceNotEnough.Log() : Result.Success; - } - - private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, - long dataSize, long journalSize, bool allowExisting) - { - UnsafeHelpers.SkipParamInit(out requiredSize); - target = CacheStorageTargetMedia.SdCard; - - Result rc = fs.GetCacheStorageTargetMediaImpl(out CacheStorageTargetMedia targetMedia, applicationId); - if (rc.IsFailure()) return rc; - - long requiredSizeLocal = 0; - - if (targetMedia == CacheStorageTargetMedia.Nand) - { - rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.User, applicationId, - saveDataOwnerId, index, dataSize, journalSize, allowExisting); - if (rc.IsFailure()) return rc; - } - else if (targetMedia == CacheStorageTargetMedia.SdCard) - { - rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.SdCache, applicationId, - saveDataOwnerId, index, dataSize, journalSize, allowExisting); - if (rc.IsFailure()) return rc; - } - // Savedata doesn't exist. Try to create a new one. - else - { - // Try to create the savedata on the SD card first - if (fs.IsSdCardAccessible()) - { - target = CacheStorageTargetMedia.SdCard; - - Result CreateFuncSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, - saveDataOwnerId, index, dataSize, journalSize, SaveDataFlags.None); - - rc = CreateSaveData(fs, CreateFuncSdCard, ref requiredSizeLocal, 0x4000, dataSize, journalSize); - if (rc.IsFailure()) return rc; - - if (requiredSizeLocal == 0) - { - requiredSize = 0; - return Result.Success; - } - } - - // If the save can't be created on the SD card, try creating it on the User BIS partition - requiredSizeLocal = 0; - target = CacheStorageTargetMedia.Nand; - - Result CreateFuncNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId, - index, dataSize, journalSize, SaveDataFlags.None); - - rc = CreateSaveData(fs, CreateFuncNand, ref requiredSizeLocal, 0x4000, dataSize, journalSize); - if (rc.IsFailure()) return rc; - - if (requiredSizeLocal != 0) - { - target = CacheStorageTargetMedia.None; - requiredSize = requiredSizeLocal; - return ResultFs.UsableSpaceNotEnough.Log(); - } - } - - requiredSize = 0; - return Result.Success; - } - - public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, - long dataSize, long journalSize, bool allowExisting) - { - return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, saveDataOwnerId, - index, dataSize, journalSize, allowExisting); - } - - public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, - Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) - { - return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out _, applicationId, nacp.SaveDataOwnerId.Value, - 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true); - } - - public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) - { - UnsafeHelpers.SkipParamInit(out requiredSize, out target); - - if (nacp.CacheStorageSize <= 0) - return Result.Success; - - return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, - nacp.SaveDataOwnerId.Value, 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true); - - } - - public static Result CreateApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp, - ushort index, long dataSize, long journalSize) - { - UnsafeHelpers.SkipParamInit(out requiredSize, out target); - - if (index > nacp.CacheStorageMaxIndex) - return ResultFs.CacheStorageIndexTooLarge.Log(); - - if (dataSize + journalSize > nacp.CacheStorageMaxSizeAndMaxJournalSize) - return ResultFs.CacheStorageSizeTooLarge.Log(); - - Result rc = fs.EnsureApplicationCacheStorage(out requiredSize, out target, applicationId, - nacp.SaveDataOwnerId.Value, index, dataSize, journalSize, false); - - fs.Impl.AbortIfNeeded(rc); return rc; } - public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long requiredSize, - Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) + return Result.Success; + } + + private static Result EnsureAndExtendSaveData(FileSystemClient fs, Func createFunc, + ref long requiredSize, ref SaveDataFilter filter, long baseSize, long dataSize, long journalSize) + { + Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, SaveDataSpaceId.User, in filter); + + if (rc.IsFailure()) { - return EnsureApplicationBcatDeliveryCacheStorageImpl(fs, out requiredSize, applicationId, ref nacp); - } - - public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize, - SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, - long dataSize, long journalSize, bool allowExisting) - { - UnsafeHelpers.SkipParamInit(out requiredSize); - long requiredSizeLocal = 0; - - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetIndex(index); - filter.SetSaveDataType(SaveDataType.Cache); - - Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, spaceId, in filter); - - if (rc.IsFailure()) + if (ResultFs.TargetNotFound.Includes(rc)) { - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; - - Result CreateCacheFunc() => fs.CreateCacheStorage(applicationId, spaceId, saveDataOwnerId, index, - dataSize, journalSize, SaveDataFlags.None); - - rc = CreateSaveData(fs, CreateCacheFunc, ref requiredSizeLocal, 0x4000, dataSize, journalSize); - if (rc.IsFailure()) return rc; - - requiredSize = requiredSizeLocal; - return Result.Success; - } - - if (!allowExisting) - { - return ResultFs.AlreadyExists.Log(); - } - - rc = ExtendSaveDataIfNeeded(fs, out requiredSizeLocal, spaceId, info.SaveDataId, dataSize, journalSize); - - if (rc.IsSuccess() || ResultFs.UsableSpaceNotEnough.Includes(rc)) - { - requiredSize = requiredSizeLocal; - return Result.Success; - } - - if (ResultFs.SaveDataExtending.Includes(rc)) - { - return ResultFs.SaveDataCorrupted.LogConverted(rc); + rc = CreateSaveData(fs, createFunc, ref requiredSize, baseSize, dataSize, journalSize); } return rc; } - public static Result GetCacheStorageTargetMedia(this FileSystemClient fs, out CacheStorageTargetMedia target, - Ncm.ApplicationId applicationId) + rc = ExtendSaveDataIfNeeded(fs, out long requiredSizeExtend, SaveDataSpaceId.User, info.SaveDataId, + dataSize, journalSize); + + if (rc.IsFailure()) { - return GetCacheStorageTargetMediaImpl(fs, out target, applicationId); + if (!ResultFs.UsableSpaceNotEnough.Includes(rc)) + return rc; + + requiredSize += requiredSizeExtend; } - private static Result GetCacheStorageTargetMediaImpl(this FileSystemClient fs, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId) + return Result.Success; + } + + private static Result EnsureApplicationBcatDeliveryCacheStorageImpl(FileSystemClient fs, out long requiredSize, + Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) + { + const long bcatDeliveryCacheJournalSize = 0x200000; + + long bcatStorageSize = nacp.BcatDeliveryCacheStorageSize; + if (bcatStorageSize <= 0) { - target = CacheStorageTargetMedia.None; + requiredSize = 0; + return Result.Success; + } - var filter = new SaveDataFilter(); - filter.SetProgramId(applicationId); - filter.SetSaveDataType(SaveDataType.Cache); + UnsafeHelpers.SkipParamInit(out requiredSize); + long requiredSizeBcat = 0; + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetSaveDataType(SaveDataType.Bcat); + + Result CreateBcatStorageFunc() => fs.CreateBcatSaveData(applicationId, bcatStorageSize); + + Result rc = EnsureAndExtendSaveData(fs, CreateBcatStorageFunc, + ref requiredSizeBcat, ref filter, 0x4000, bcatStorageSize, bcatDeliveryCacheJournalSize); + + if (rc.IsFailure()) return rc; + + requiredSize = requiredSizeBcat; + return requiredSizeBcat > 0 ? ResultFs.UsableSpaceNotEnough.Log() : Result.Success; + } + + private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, + long dataSize, long journalSize, bool allowExisting) + { + UnsafeHelpers.SkipParamInit(out requiredSize); + target = CacheStorageTargetMedia.SdCard; + + Result rc = fs.GetCacheStorageTargetMediaImpl(out CacheStorageTargetMedia targetMedia, applicationId); + if (rc.IsFailure()) return rc; + + long requiredSizeLocal = 0; + + if (targetMedia == CacheStorageTargetMedia.Nand) + { + rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.User, applicationId, + saveDataOwnerId, index, dataSize, journalSize, allowExisting); + if (rc.IsFailure()) return rc; + } + else if (targetMedia == CacheStorageTargetMedia.SdCard) + { + rc = TryCreateCacheStorage(fs, out requiredSizeLocal, SaveDataSpaceId.SdCache, applicationId, + saveDataOwnerId, index, dataSize, journalSize, allowExisting); + if (rc.IsFailure()) return rc; + } + // Savedata doesn't exist. Try to create a new one. + else + { + // Try to create the savedata on the SD card first if (fs.IsSdCardAccessible()) { - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, in filter); - if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; + target = CacheStorageTargetMedia.SdCard; - if (rc.IsSuccess()) + Result CreateFuncSdCard() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, + saveDataOwnerId, index, dataSize, journalSize, SaveDataFlags.None); + + rc = CreateSaveData(fs, CreateFuncSdCard, ref requiredSizeLocal, 0x4000, dataSize, journalSize); + if (rc.IsFailure()) return rc; + + if (requiredSizeLocal == 0) { - target = CacheStorageTargetMedia.SdCard; + requiredSize = 0; + return Result.Success; } } - // Not on the SD card. Check it it's in NAND - if (target == CacheStorageTargetMedia.None) + // If the save can't be created on the SD card, try creating it on the User BIS partition + requiredSizeLocal = 0; + target = CacheStorageTargetMedia.Nand; + + Result CreateFuncNand() => fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, saveDataOwnerId, + index, dataSize, journalSize, SaveDataFlags.None); + + rc = CreateSaveData(fs, CreateFuncNand, ref requiredSizeLocal, 0x4000, dataSize, journalSize); + if (rc.IsFailure()) return rc; + + if (requiredSizeLocal != 0) { - Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, in filter); - if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; - - if (rc.IsSuccess()) - { - target = CacheStorageTargetMedia.Nand; - } + target = CacheStorageTargetMedia.None; + requiredSize = requiredSizeLocal; + return ResultFs.UsableSpaceNotEnough.Log(); } + } + requiredSize = 0; + return Result.Success; + } + + public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, + long dataSize, long journalSize, bool allowExisting) + { + return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, saveDataOwnerId, + index, dataSize, journalSize, allowExisting); + } + + public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, + Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) + { + return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out _, applicationId, nacp.SaveDataOwnerId.Value, + 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true); + } + + public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) + { + UnsafeHelpers.SkipParamInit(out requiredSize, out target); + + if (nacp.CacheStorageSize <= 0) + return Result.Success; + + return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, + nacp.SaveDataOwnerId.Value, 0, nacp.CacheStorageSize, nacp.CacheStorageJournalSize, true); + + } + + public static Result CreateApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp, + ushort index, long dataSize, long journalSize) + { + UnsafeHelpers.SkipParamInit(out requiredSize, out target); + + if (index > nacp.CacheStorageMaxIndex) + return ResultFs.CacheStorageIndexTooLarge.Log(); + + if (dataSize + journalSize > nacp.CacheStorageMaxSizeAndMaxJournalSize) + return ResultFs.CacheStorageSizeTooLarge.Log(); + + Result rc = fs.EnsureApplicationCacheStorage(out requiredSize, out target, applicationId, + nacp.SaveDataOwnerId.Value, index, dataSize, journalSize, false); + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result EnsureApplicationBcatDeliveryCacheStorage(this FileSystemClient fs, out long requiredSize, + Ncm.ApplicationId applicationId, ref ApplicationControlProperty nacp) + { + return EnsureApplicationBcatDeliveryCacheStorageImpl(fs, out requiredSize, applicationId, ref nacp); + } + + public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize, + SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, + long dataSize, long journalSize, bool allowExisting) + { + UnsafeHelpers.SkipParamInit(out requiredSize); + long requiredSizeLocal = 0; + + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetIndex(index); + filter.SetSaveDataType(SaveDataType.Cache); + + Result rc = fs.FindSaveDataWithFilter(out SaveDataInfo info, spaceId, in filter); + + if (rc.IsFailure()) + { + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; + + Result CreateCacheFunc() => fs.CreateCacheStorage(applicationId, spaceId, saveDataOwnerId, index, + dataSize, journalSize, SaveDataFlags.None); + + rc = CreateSaveData(fs, CreateCacheFunc, ref requiredSizeLocal, 0x4000, dataSize, journalSize); + if (rc.IsFailure()) return rc; + + requiredSize = requiredSizeLocal; return Result.Success; } - public static Result CleanUpTemporaryStorage(FileSystemClient fs) + if (!allowExisting) { - var filter = new SaveDataFilter(); - filter.SetSaveDataType(SaveDataType.Temporary); + return ResultFs.AlreadyExists.Log(); + } - Result rc; + rc = ExtendSaveDataIfNeeded(fs, out requiredSizeLocal, spaceId, info.SaveDataId, dataSize, journalSize); - while (true) + if (rc.IsSuccess() || ResultFs.UsableSpaceNotEnough.Includes(rc)) + { + requiredSize = requiredSizeLocal; + return Result.Success; + } + + if (ResultFs.SaveDataExtending.Includes(rc)) + { + return ResultFs.SaveDataCorrupted.LogConverted(rc); + } + + return rc; + } + + public static Result GetCacheStorageTargetMedia(this FileSystemClient fs, out CacheStorageTargetMedia target, + Ncm.ApplicationId applicationId) + { + return GetCacheStorageTargetMediaImpl(fs, out target, applicationId); + } + + private static Result GetCacheStorageTargetMediaImpl(this FileSystemClient fs, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId) + { + target = CacheStorageTargetMedia.None; + + var filter = new SaveDataFilter(); + filter.SetProgramId(applicationId); + filter.SetSaveDataType(SaveDataType.Cache); + + if (fs.IsSdCardAccessible()) + { + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.SdCache, in filter); + if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; + + if (rc.IsSuccess()) { - rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveInfo, SaveDataSpaceId.Temporary, in filter); - - if (rc.IsFailure()) break; - - rc = fs.DeleteSaveData(SaveDataSpaceId.Temporary, saveInfo.SaveDataId); - if (rc.IsFailure()) return rc; + target = CacheStorageTargetMedia.SdCard; } - - if (ResultFs.TargetNotFound.Includes(rc)) - return Result.Success; - - return rc; } - public static UserId ConvertAccountUidToFsUserId(Uid uid) + // Not on the SD card. Check it it's in NAND + if (target == CacheStorageTargetMedia.None) { - return new UserId(uid.Id.High, uid.Id.Low); + Result rc = fs.FindSaveDataWithFilter(out _, SaveDataSpaceId.User, in filter); + if (rc.IsFailure() && !ResultFs.TargetNotFound.Includes(rc)) return rc; + + if (rc.IsSuccess()) + { + target = CacheStorageTargetMedia.Nand; + } } + + return Result.Success; + } + + public static Result CleanUpTemporaryStorage(FileSystemClient fs) + { + var filter = new SaveDataFilter(); + filter.SetSaveDataType(SaveDataType.Temporary); + + Result rc; + + while (true) + { + rc = fs.FindSaveDataWithFilter(out SaveDataInfo saveInfo, SaveDataSpaceId.Temporary, in filter); + + if (rc.IsFailure()) break; + + rc = fs.DeleteSaveData(SaveDataSpaceId.Temporary, saveInfo.SaveDataId); + if (rc.IsFailure()) return rc; + } + + if (ResultFs.TargetNotFound.Includes(rc)) + return Result.Success; + + return rc; + } + + public static UserId ConvertAccountUidToFsUserId(Uid uid) + { + return new UserId(uid.Id.High, uid.Id.Low); } } diff --git a/src/LibHac/Fs/Buffers/Buffer.cs b/src/LibHac/Fs/Buffers/Buffer.cs index ae1400c2..de6aab11 100644 --- a/src/LibHac/Fs/Buffers/Buffer.cs +++ b/src/LibHac/Fs/Buffers/Buffer.cs @@ -2,27 +2,26 @@ using System.ComponentModel; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +public readonly struct Buffer : IEquatable { - public readonly struct Buffer : IEquatable + public static Buffer Empty => default; + public Memory Memory { get; } + public Span Span => Memory.Span; + public int Length => Memory.Length; + public bool IsNull => Memory.IsEmpty; + + public Buffer(Memory buffer) { - public static Buffer Empty => default; - public Memory Memory { get; } - public Span Span => Memory.Span; - public int Length => Memory.Length; - public bool IsNull => Memory.IsEmpty; - - public Buffer(Memory buffer) - { - Memory = buffer; - } - - public static bool operator ==(Buffer left, Buffer right) => left.Memory.Equals(right.Memory); - public static bool operator !=(Buffer left, Buffer right) => !(left == right); - - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => obj is Buffer other && Equals(other); - public bool Equals(Buffer other) => Memory.Equals(other.Memory); - public override int GetHashCode() => Memory.GetHashCode(); + Memory = buffer; } + + public static bool operator ==(Buffer left, Buffer right) => left.Memory.Equals(right.Memory); + public static bool operator !=(Buffer left, Buffer right) => !(left == right); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => obj is Buffer other && Equals(other); + public bool Equals(Buffer other) => Memory.Equals(other.Memory); + public override int GetHashCode() => Memory.GetHashCode(); } diff --git a/src/LibHac/Fs/Buffers/IBufferManager.cs b/src/LibHac/Fs/Buffers/IBufferManager.cs index 4775060d..dcc3e0f9 100644 --- a/src/LibHac/Fs/Buffers/IBufferManager.cs +++ b/src/LibHac/Fs/Buffers/IBufferManager.cs @@ -3,128 +3,127 @@ using CacheHandle = System.Int64; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +// ReSharper disable once InconsistentNaming +/// +/// Handles buffer allocation, deallocation, and caching.
+/// An allocated buffer may be placed in the cache using . +/// Caching a buffer saves the buffer for later retrieval, but tells the buffer manager that it can deallocate the +/// buffer if the memory is needed elsewhere. Any cached buffer may be evicted from the cache if there is no free +/// space for a requested allocation or if the cache is full when caching a new buffer. +/// A cached buffer can be retrieved using . +///
+public abstract class IBufferManager : IDisposable { - // ReSharper disable once InconsistentNaming - /// - /// Handles buffer allocation, deallocation, and caching.
- /// An allocated buffer may be placed in the cache using . - /// Caching a buffer saves the buffer for later retrieval, but tells the buffer manager that it can deallocate the - /// buffer if the memory is needed elsewhere. Any cached buffer may be evicted from the cache if there is no free - /// space for a requested allocation or if the cache is full when caching a new buffer. - /// A cached buffer can be retrieved using . - ///
- public abstract class IBufferManager : IDisposable + public readonly struct BufferAttribute { - public readonly struct BufferAttribute + public int Level { get; } + + public BufferAttribute(int level) { - public int Level { get; } - - public BufferAttribute(int level) - { - Level = level; - } - } - - public const int BufferLevelMin = 0; - - public Buffer AllocateBuffer(int size, BufferAttribute attribute) => - DoAllocateBuffer(size, attribute); - - /// - /// Allocates a new buffer with an attribute of level 0. - /// - /// The minimum size of the buffer to allocate - /// The allocated if successful. Otherwise a null . - public Buffer AllocateBuffer(int size) => DoAllocateBuffer(size, new BufferAttribute()); - - /// - /// Deallocates the provided . - /// - /// The Buffer to deallocate. - public void DeallocateBuffer(Buffer buffer) => DoDeallocateBuffer(buffer); - - /// - /// Adds a to the cache. - /// The buffer must have been allocated from this .
- /// The buffer must not be used after adding it to the cache. - ///
- /// The buffer to cache. - /// The buffer attribute. - /// A handle that can be used to retrieve the buffer at a later time. - public CacheHandle RegisterCache(Buffer buffer, BufferAttribute attribute) => - DoRegisterCache(buffer, attribute); - - /// - /// Attempts to acquire a cached . - /// If the buffer was evicted from the cache, a null buffer is returned. - /// - /// The handle received when registering the buffer. - /// The requested if it's still in the cache; - /// otherwise a null - public Buffer AcquireCache(CacheHandle handle) => DoAcquireCache(handle); - - /// - /// Gets the total size of the 's heap. - /// - /// The total size of the heap. - public int GetTotalSize() => DoGetTotalSize(); - - /// - /// Gets the amount of free space in the heap that is not currently allocated or cached. - /// - /// The amount of free space. - public int GetFreeSize() => DoGetFreeSize(); - - /// - /// Gets the amount of space that can be used for new allocations. - /// This includes free space and space used by cached buffers. - /// - /// The amount of allocatable space. - public int GetTotalAllocatableSize() => DoGetTotalAllocatableSize(); - - /// - /// Gets the largest amount of free space there's been at one time since the peak was last cleared. - /// - /// The peak amount of free space. - public int GetFreeSizePeak() => DoGetFreeSizePeak(); - - /// - /// Gets the largest amount of allocatable space there's been at one time since the peak was last cleared. - /// - /// The peak amount of allocatable space. - public int GetTotalAllocatableSizePeak() => DoGetTotalAllocatableSizePeak(); - - /// - /// Gets the number of times an allocation or cache registration needed to be retried after deallocating - /// a cache entry because of insufficient heap space or cache space. - /// - /// The number of retries. - public int GetRetriedCount() => DoGetRetriedCount(); - - /// - /// Resets the free and allocatable peak sizes, setting the peak sizes to the actual current sizes. - /// - public void ClearPeak() => DoClearPeak(); - - protected abstract Buffer DoAllocateBuffer(int size, BufferAttribute attribute); - protected abstract void DoDeallocateBuffer(Buffer buffer); - protected abstract CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute); - protected abstract Buffer DoAcquireCache(CacheHandle handle); - protected abstract int DoGetTotalSize(); - protected abstract int DoGetFreeSize(); - protected abstract int DoGetTotalAllocatableSize(); - protected abstract int DoGetFreeSizePeak(); - protected abstract int DoGetTotalAllocatableSizePeak(); - protected abstract int DoGetRetriedCount(); - protected abstract void DoClearPeak(); - - protected virtual void Dispose(bool disposing) { } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + Level = level; } } + + public const int BufferLevelMin = 0; + + public Buffer AllocateBuffer(int size, BufferAttribute attribute) => + DoAllocateBuffer(size, attribute); + + /// + /// Allocates a new buffer with an attribute of level 0. + /// + /// The minimum size of the buffer to allocate + /// The allocated if successful. Otherwise a null . + public Buffer AllocateBuffer(int size) => DoAllocateBuffer(size, new BufferAttribute()); + + /// + /// Deallocates the provided . + /// + /// The Buffer to deallocate. + public void DeallocateBuffer(Buffer buffer) => DoDeallocateBuffer(buffer); + + /// + /// Adds a to the cache. + /// The buffer must have been allocated from this .
+ /// The buffer must not be used after adding it to the cache. + ///
+ /// The buffer to cache. + /// The buffer attribute. + /// A handle that can be used to retrieve the buffer at a later time. + public CacheHandle RegisterCache(Buffer buffer, BufferAttribute attribute) => + DoRegisterCache(buffer, attribute); + + /// + /// Attempts to acquire a cached . + /// If the buffer was evicted from the cache, a null buffer is returned. + /// + /// The handle received when registering the buffer. + /// The requested if it's still in the cache; + /// otherwise a null + public Buffer AcquireCache(CacheHandle handle) => DoAcquireCache(handle); + + /// + /// Gets the total size of the 's heap. + /// + /// The total size of the heap. + public int GetTotalSize() => DoGetTotalSize(); + + /// + /// Gets the amount of free space in the heap that is not currently allocated or cached. + /// + /// The amount of free space. + public int GetFreeSize() => DoGetFreeSize(); + + /// + /// Gets the amount of space that can be used for new allocations. + /// This includes free space and space used by cached buffers. + /// + /// The amount of allocatable space. + public int GetTotalAllocatableSize() => DoGetTotalAllocatableSize(); + + /// + /// Gets the largest amount of free space there's been at one time since the peak was last cleared. + /// + /// The peak amount of free space. + public int GetFreeSizePeak() => DoGetFreeSizePeak(); + + /// + /// Gets the largest amount of allocatable space there's been at one time since the peak was last cleared. + /// + /// The peak amount of allocatable space. + public int GetTotalAllocatableSizePeak() => DoGetTotalAllocatableSizePeak(); + + /// + /// Gets the number of times an allocation or cache registration needed to be retried after deallocating + /// a cache entry because of insufficient heap space or cache space. + /// + /// The number of retries. + public int GetRetriedCount() => DoGetRetriedCount(); + + /// + /// Resets the free and allocatable peak sizes, setting the peak sizes to the actual current sizes. + /// + public void ClearPeak() => DoClearPeak(); + + protected abstract Buffer DoAllocateBuffer(int size, BufferAttribute attribute); + protected abstract void DoDeallocateBuffer(Buffer buffer); + protected abstract CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute); + protected abstract Buffer DoAcquireCache(CacheHandle handle); + protected abstract int DoGetTotalSize(); + protected abstract int DoGetFreeSize(); + protected abstract int DoGetTotalAllocatableSize(); + protected abstract int DoGetFreeSizePeak(); + protected abstract int DoGetTotalAllocatableSizePeak(); + protected abstract int DoGetRetriedCount(); + protected abstract void DoClearPeak(); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } diff --git a/src/LibHac/Fs/CodeVerificationData.cs b/src/LibHac/Fs/CodeVerificationData.cs index c2265efc..829a3114 100644 --- a/src/LibHac/Fs/CodeVerificationData.cs +++ b/src/LibHac/Fs/CodeVerificationData.cs @@ -2,17 +2,16 @@ using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Explicit, Size = 0x124)] +public struct CodeVerificationData { - [StructLayout(LayoutKind.Explicit, Size = 0x124)] - public struct CodeVerificationData - { - private const int Signature2Size = 0x100; + private const int Signature2Size = 0x100; - [FieldOffset(0x000)] private byte _signature2; - [FieldOffset(0x100)] public Buffer32 NcaHeaderHash; - [FieldOffset(0x120)] public bool IsValid; + [FieldOffset(0x000)] private byte _signature2; + [FieldOffset(0x100)] public Buffer32 NcaHeaderHash; + [FieldOffset(0x120)] public bool IsValid; - public Span NcaSignature2 => SpanHelpers.CreateSpan(ref _signature2, Signature2Size); - } + public Span NcaSignature2 => SpanHelpers.CreateSpan(ref _signature2, Signature2Size); } diff --git a/src/LibHac/Fs/Common/DirectoryPathParser.cs b/src/LibHac/Fs/Common/DirectoryPathParser.cs index 4eb7887e..d59673fe 100644 --- a/src/LibHac/Fs/Common/DirectoryPathParser.cs +++ b/src/LibHac/Fs/Common/DirectoryPathParser.cs @@ -3,118 +3,117 @@ using LibHac.Common; using LibHac.Diag; using static LibHac.Fs.StringTraits; -namespace LibHac.Fs.Common +namespace LibHac.Fs.Common; + +[NonCopyableDisposable] +public ref struct DirectoryPathParser { - [NonCopyableDisposable] - public ref struct DirectoryPathParser + private Span _buffer; + private byte _replacedChar; + private int _position; + + // Todo: Make private so we can use the GetCurrentPath method once lifetime tracking is better + public Path CurrentPath; + + public void Dispose() { - private Span _buffer; - private byte _replacedChar; - private int _position; + CurrentPath.Dispose(); + } - // Todo: Make private so we can use the GetCurrentPath method once lifetime tracking is better - public Path CurrentPath; + public Result Initialize(ref Path path) + { + Span pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span.Empty; - public void Dispose() + int windowsSkipLength = WindowsPath.GetWindowsSkipLength(pathBuffer); + _buffer = pathBuffer.Slice(windowsSkipLength); + + if (windowsSkipLength != 0) { - CurrentPath.Dispose(); + Result rc = CurrentPath.InitializeWithNormalization(pathBuffer, windowsSkipLength + 1); + if (rc.IsFailure()) return rc; + + _buffer = _buffer.Slice(1); + } + else + { + Span initialPath = ReadNextImpl(); + + if (!initialPath.IsEmpty) + { + Result rc = CurrentPath.InitializeWithNormalization(initialPath); + if (rc.IsFailure()) return rc; + } } - public Result Initialize(ref Path path) + return Result.Success; + } + + // Todo: Return reference when escape semantics are better + //public ref readonly Path GetCurrentPath() + //{ + // return ref CurrentPath; + //} + + public Result ReadNext(out bool isFinished) + { + isFinished = false; + + Span nextEntry = ReadNextImpl(); + + if (nextEntry.IsEmpty) { - Span pathBuffer = path.GetWriteBufferLength() != 0 ? path.GetWriteBuffer() : Span.Empty; - - int windowsSkipLength = WindowsPath.GetWindowsSkipLength(pathBuffer); - _buffer = pathBuffer.Slice(windowsSkipLength); - - if (windowsSkipLength != 0) - { - Result rc = CurrentPath.InitializeWithNormalization(pathBuffer, windowsSkipLength + 1); - if (rc.IsFailure()) return rc; - - _buffer = _buffer.Slice(1); - } - else - { - Span initialPath = ReadNextImpl(); - - if (!initialPath.IsEmpty) - { - Result rc = CurrentPath.InitializeWithNormalization(initialPath); - if (rc.IsFailure()) return rc; - } - } - + isFinished = true; return Result.Success; } - // Todo: Return reference when escape semantics are better - //public ref readonly Path GetCurrentPath() - //{ - // return ref CurrentPath; - //} + return CurrentPath.AppendChild(nextEntry); + } - public Result ReadNext(out bool isFinished) + private Span ReadNextImpl() + { + // Check if we've already hit the end of the path. + if (_position < 0 || _buffer.At(0) == 0) + return Span.Empty; + + // Restore the character we previously replaced with a null terminator. + if (_replacedChar != 0) { - isFinished = false; + _buffer[_position] = _replacedChar; - Span nextEntry = ReadNextImpl(); - - if (nextEntry.IsEmpty) - { - isFinished = true; - return Result.Success; - } - - return CurrentPath.AppendChild(nextEntry); + if (_replacedChar == DirectorySeparator) + _position++; } - private Span ReadNextImpl() + // If the path is rooted, the first entry should be the root directory. + if (_position == 0 && _buffer.At(0) == DirectorySeparator) { - // Check if we've already hit the end of the path. - if (_position < 0 || _buffer.At(0) == 0) - return Span.Empty; - - // Restore the character we previously replaced with a null terminator. - if (_replacedChar != 0) - { - _buffer[_position] = _replacedChar; - - if (_replacedChar == DirectorySeparator) - _position++; - } - - // If the path is rooted, the first entry should be the root directory. - if (_position == 0 && _buffer.At(0) == DirectorySeparator) - { - _replacedChar = _buffer[1]; - _buffer[1] = 0; - _position = 1; - return _buffer; - } - - // Find the end of the next entry, replacing the directory separator with a null terminator. - Span entry = _buffer.Slice(_position); - - int i; - for (i = _position; _buffer.At(i) != DirectorySeparator; i++) - { - if (_buffer.At(i) == 0) - { - if (i == _position) - entry = Span.Empty; - - _position = -1; - return entry; - } - } - - Assert.SdkAssert(_buffer.At(i + 1) != NullTerminator); - - _replacedChar = DirectorySeparator; - _buffer[i] = 0; - _position = i; - return entry; + _replacedChar = _buffer[1]; + _buffer[1] = 0; + _position = 1; + return _buffer; } + + // Find the end of the next entry, replacing the directory separator with a null terminator. + Span entry = _buffer.Slice(_position); + + int i; + for (i = _position; _buffer.At(i) != DirectorySeparator; i++) + { + if (_buffer.At(i) == 0) + { + if (i == _position) + entry = Span.Empty; + + _position = -1; + return entry; + } + } + + Assert.SdkAssert(_buffer.At(i + 1) != NullTerminator); + + _replacedChar = DirectorySeparator; + _buffer[i] = 0; + _position = i; + return entry; } } diff --git a/src/LibHac/Fs/Common/FileStorage.cs b/src/LibHac/Fs/Common/FileStorage.cs index 30785d13..8317065a 100644 --- a/src/LibHac/Fs/Common/FileStorage.cs +++ b/src/LibHac/Fs/Common/FileStorage.cs @@ -6,281 +6,280 @@ using LibHac.Fs.Fsa; using LibHac.Os; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +public class FileStorage : IStorage { - public class FileStorage : IStorage + private const long InvalidSize = -1; + + private SharedRef _baseFileShared; + private IFile _baseFile; + private long _fileSize; + + protected FileStorage() { - private const long InvalidSize = -1; + _fileSize = InvalidSize; + } - private SharedRef _baseFileShared; - private IFile _baseFile; - private long _fileSize; + public FileStorage(IFile baseFile) + { + _baseFile = baseFile; + _fileSize = InvalidSize; + } - protected FileStorage() - { - _fileSize = InvalidSize; - } + public FileStorage(ref SharedRef baseFile) + { + _baseFile = baseFile.Get; + _baseFileShared = SharedRef.CreateMove(ref baseFile); + _fileSize = InvalidSize; + } - public FileStorage(IFile baseFile) - { - _baseFile = baseFile; - _fileSize = InvalidSize; - } + public override void Dispose() + { + _baseFileShared.Destroy(); + base.Dispose(); + } - public FileStorage(ref SharedRef baseFile) - { - _baseFile = baseFile.Get; - _baseFileShared = SharedRef.CreateMove(ref baseFile); - _fileSize = InvalidSize; - } + protected void SetFile(IFile file) + { + Assert.SdkRequiresNotNull(file); + Assert.SdkRequiresNull(_baseFile); - public override void Dispose() - { - _baseFileShared.Destroy(); - base.Dispose(); - } + _baseFile = file; + } - protected void SetFile(IFile file) - { - Assert.SdkRequiresNotNull(file); - Assert.SdkRequiresNull(_baseFile); - - _baseFile = file; - } - - protected override Result DoRead(long offset, Span destination) - { - if (destination.Length == 0) - return Result.Success; - - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - if (!CheckAccessRange(offset, destination.Length, _fileSize)) - return ResultFs.OutOfRange.Log(); - - return _baseFile.Read(out _, offset, destination, ReadOption.None); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - if (source.Length == 0) - return Result.Success; - - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - if (!CheckAccessRange(offset, source.Length, _fileSize)) - return ResultFs.OutOfRange.Log(); - - return _baseFile.Write(offset, source, WriteOption.None); - } - - protected override Result DoFlush() - { - return _baseFile.Flush(); - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - size = _fileSize; + protected override Result DoRead(long offset, Span destination) + { + if (destination.Length == 0) return Result.Success; - } - protected override Result DoSetSize(long size) - { - _fileSize = InvalidSize; - return _baseFile.SetSize(size); - } + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + if (!CheckAccessRange(offset, destination.Length, _fileSize)) + return ResultFs.OutOfRange.Log(); + + return _baseFile.Read(out _, offset, destination, ReadOption.None); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + if (source.Length == 0) + return Result.Success; + + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); + + if (!CheckAccessRange(offset, source.Length, _fileSize)) + return ResultFs.OutOfRange.Log(); + + return _baseFile.Write(offset, source, WriteOption.None); + } + + protected override Result DoFlush() + { + return _baseFile.Flush(); + } + + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); + + size = _fileSize; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + _fileSize = InvalidSize; + return _baseFile.SetSize(size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + if (operationId == OperationId.InvalidateCache || operationId == OperationId.QueryRange) { - if (operationId == OperationId.InvalidateCache || operationId == OperationId.QueryRange) + if (size == 0) { - if (size == 0) + if (operationId == OperationId.QueryRange) { - if (operationId == OperationId.QueryRange) - { - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); - SpanHelpers.AsStruct(outBuffer).Clear(); - } - - return Result.Success; + SpanHelpers.AsStruct(outBuffer).Clear(); } - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - if (!CheckOffsetAndSize(offset, size)) - return ResultFs.OutOfRange.Log(); - - return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); + return Result.Success; } - return ResultFs.UnsupportedOperateRangeForFileStorage.Log(); - } - - private Result UpdateSize() - { - if (_fileSize != InvalidSize) - return Result.Success; - - Result rc = _baseFile.GetSize(out long size); - if (rc.IsFailure()) return rc.Miss(); - - _fileSize = size; - return Result.Success; - } - } - - public class FileStorageBasedFileSystem : FileStorage - { - private SharedRef _baseFileSystem; - private UniqueRef _baseFile; - - public override void Dispose() - { - _baseFile.Destroy(); - _baseFileSystem.Destroy(); - - base.Dispose(); - } - - public Result Initialize(ref SharedRef baseFileSystem, in Path path, OpenMode mode) - { - using var baseFile = new UniqueRef(); - - Result rc = baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); - if (rc.IsFailure()) return rc.Miss(); - - SetFile(baseFile.Get); - _baseFileSystem.SetByMove(ref baseFileSystem); - _baseFile.Set(ref baseFile.Ref()); - - return Result.Success; - } - } - - public class FileHandleStorage : IStorage - { - private const long InvalidSize = -1; - - private FileHandle _handle; - private bool _closeFile; - private long _size; - private SdkMutexType _mutex; - - // LibHac addition - private FileSystemClient _fsClient; - - public FileHandleStorage(FileSystemClient fsClient, FileHandle handle) : this(fsClient, handle, false) { } - - public FileHandleStorage(FileSystemClient fsClient, FileHandle handle, bool closeFile) - { - _fsClient = fsClient; - _handle = handle; - _closeFile = closeFile; - _size = InvalidSize; - _mutex.Initialize(); - } - - public override void Dispose() - { - if (_closeFile) - { - _fsClient.CloseFile(_handle); - _closeFile = false; - _handle = default; - } - - base.Dispose(); - } - - protected override Result DoRead(long offset, Span destination) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - if (destination.Length == 0) - return Result.Success; - Result rc = UpdateSize(); if (rc.IsFailure()) return rc.Miss(); - if (!CheckAccessRange(offset, destination.Length, _size)) + if (!CheckOffsetAndSize(offset, size)) return ResultFs.OutOfRange.Log(); - return _fsClient.ReadFile(_handle, offset, destination, ReadOption.None); + return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + return ResultFs.UnsupportedOperateRangeForFileStorage.Log(); + } - if (source.Length == 0) - return Result.Success; - - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - if (!CheckAccessRange(offset, source.Length, _size)) - return ResultFs.OutOfRange.Log(); - - return _fsClient.WriteFile(_handle, offset, source, WriteOption.None); - } - - protected override Result DoFlush() - { - return _fsClient.FlushFile(_handle); - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - Result rc = UpdateSize(); - if (rc.IsFailure()) return rc.Miss(); - - size = _size; + private Result UpdateSize() + { + if (_fileSize != InvalidSize) return Result.Success; - } - protected override Result DoSetSize(long size) - { - _size = InvalidSize; - return _fsClient.SetFileSize(_handle, size); - } + Result rc = _baseFile.GetSize(out long size); + if (rc.IsFailure()) return rc.Miss(); - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - if (operationId != OperationId.QueryRange) - return ResultFs.UnsupportedOperateRangeForFileHandleStorage.Log(); - - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); - - Result rc = _fsClient.QueryRange(out SpanHelpers.AsStruct(outBuffer), _handle, offset, size); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - private Result UpdateSize() - { - if (_size != InvalidSize) - return Result.Success; - - Result rc = _fsClient.GetFileSize(out long size, _handle); - if (rc.IsFailure()) return rc.Miss(); - - _size = size; - return Result.Success; - } + _fileSize = size; + return Result.Success; + } +} + +public class FileStorageBasedFileSystem : FileStorage +{ + private SharedRef _baseFileSystem; + private UniqueRef _baseFile; + + public override void Dispose() + { + _baseFile.Destroy(); + _baseFileSystem.Destroy(); + + base.Dispose(); + } + + public Result Initialize(ref SharedRef baseFileSystem, in Path path, OpenMode mode) + { + using var baseFile = new UniqueRef(); + + Result rc = baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); + if (rc.IsFailure()) return rc.Miss(); + + SetFile(baseFile.Get); + _baseFileSystem.SetByMove(ref baseFileSystem); + _baseFile.Set(ref baseFile.Ref()); + + return Result.Success; + } +} + +public class FileHandleStorage : IStorage +{ + private const long InvalidSize = -1; + + private FileHandle _handle; + private bool _closeFile; + private long _size; + private SdkMutexType _mutex; + + // LibHac addition + private FileSystemClient _fsClient; + + public FileHandleStorage(FileSystemClient fsClient, FileHandle handle) : this(fsClient, handle, false) { } + + public FileHandleStorage(FileSystemClient fsClient, FileHandle handle, bool closeFile) + { + _fsClient = fsClient; + _handle = handle; + _closeFile = closeFile; + _size = InvalidSize; + _mutex.Initialize(); + } + + public override void Dispose() + { + if (_closeFile) + { + _fsClient.CloseFile(_handle); + _closeFile = false; + _handle = default; + } + + base.Dispose(); + } + + protected override Result DoRead(long offset, Span destination) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (destination.Length == 0) + return Result.Success; + + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); + + if (!CheckAccessRange(offset, destination.Length, _size)) + return ResultFs.OutOfRange.Log(); + + return _fsClient.ReadFile(_handle, offset, destination, ReadOption.None); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (source.Length == 0) + return Result.Success; + + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); + + if (!CheckAccessRange(offset, source.Length, _size)) + return ResultFs.OutOfRange.Log(); + + return _fsClient.WriteFile(_handle, offset, source, WriteOption.None); + } + + protected override Result DoFlush() + { + return _fsClient.FlushFile(_handle); + } + + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + Result rc = UpdateSize(); + if (rc.IsFailure()) return rc.Miss(); + + size = _size; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + _size = InvalidSize; + return _fsClient.SetFileSize(_handle, size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + if (operationId != OperationId.QueryRange) + return ResultFs.UnsupportedOperateRangeForFileHandleStorage.Log(); + + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + Result rc = _fsClient.QueryRange(out SpanHelpers.AsStruct(outBuffer), _handle, offset, size); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + private Result UpdateSize() + { + if (_size != InvalidSize) + return Result.Success; + + Result rc = _fsClient.GetFileSize(out long size, _handle); + if (rc.IsFailure()) return rc.Miss(); + + _size = size; + return Result.Success; } } diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index e580423a..502e8033 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -8,1219 +8,1218 @@ using static LibHac.Fs.StringTraits; using static InlineIL.IL.Emit; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +public struct PathFlags { - public struct PathFlags + private uint _value; + + public void AllowWindowsPath() => _value |= 1 << 0; + public void AllowRelativePath() => _value |= 1 << 1; + public void AllowEmptyPath() => _value |= 1 << 2; + public void AllowMountName() => _value |= 1 << 3; + public void AllowBackslash() => _value |= 1 << 4; + + public bool IsWindowsPathAllowed() => (_value & (1 << 0)) != 0; + public bool IsRelativePathAllowed() => (_value & (1 << 1)) != 0; + public bool IsEmptyPathAllowed() => (_value & (1 << 2)) != 0; + public bool IsMountNameAllowed() => (_value & (1 << 3)) != 0; + public bool IsBackslashAllowed() => (_value & (1 << 4)) != 0; +} + +/// +/// Contains functions like those in because ref struct +/// types can't be used as generics yet. +/// +public static class PathExtensions +{ + /// + /// Reinterprets the given read-only reference as a reference. + /// + /// This function allows using a expression with s + /// while still being able to pass it by reference. + /// This function is a static method instead of an instance method because + /// as a static method we get escape analysis so the lifetime of the returned reference is restricted to that + /// of the input reference. + /// The read-only reference to reinterpret. + /// A reference to the given . + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref Path Ref(this in Path path) { - private uint _value; - - public void AllowWindowsPath() => _value |= 1 << 0; - public void AllowRelativePath() => _value |= 1 << 1; - public void AllowEmptyPath() => _value |= 1 << 2; - public void AllowMountName() => _value |= 1 << 3; - public void AllowBackslash() => _value |= 1 << 4; - - public bool IsWindowsPathAllowed() => (_value & (1 << 0)) != 0; - public bool IsRelativePathAllowed() => (_value & (1 << 1)) != 0; - public bool IsEmptyPathAllowed() => (_value & (1 << 2)) != 0; - public bool IsMountNameAllowed() => (_value & (1 << 3)) != 0; - public bool IsBackslashAllowed() => (_value & (1 << 4)) != 0; + Ldarg(nameof(path)); + Ret(); + throw InlineIL.IL.Unreachable(); } - /// - /// Contains functions like those in because ref struct - /// types can't be used as generics yet. - /// - public static class PathExtensions + public static ref Path GetNullRef() { - /// - /// Reinterprets the given read-only reference as a reference. - /// - /// This function allows using a expression with s - /// while still being able to pass it by reference. - /// This function is a static method instead of an instance method because - /// as a static method we get escape analysis so the lifetime of the returned reference is restricted to that - /// of the input reference. - /// The read-only reference to reinterpret. - /// A reference to the given . - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref Path Ref(this in Path path) - { - Ldarg(nameof(path)); - Ret(); - throw InlineIL.IL.Unreachable(); - } - - public static ref Path GetNullRef() - { - Ldc_I4_0(); - Conv_U(); - Ret(); - throw InlineIL.IL.Unreachable(); - } - - public static bool IsNullRef(in Path path) - { - Ldarg_0(); - Ldc_I4_0(); - Conv_U(); - Ceq(); - return InlineIL.IL.Return(); - } + Ldc_I4_0(); + Conv_U(); + Ret(); + throw InlineIL.IL.Unreachable(); } + public static bool IsNullRef(in Path path) + { + Ldarg_0(); + Ldc_I4_0(); + Conv_U(); + Ceq(); + return InlineIL.IL.Return(); + } +} + +/// +/// Represents a file path stored as a UTF-8 string. +/// +/// +/// A has three parts to it:
+/// 1. A that points to the current path string.
+/// 2. A write buffer that can be allocated if operations need to be done on the path.
+/// 3. An IsNormalized flag that tracks the path normalization status of the current path.
+/// There are two different ways to initialize a . The "Initialize*" methods will +/// ensure a write buffer is allocated and copy the input path to it. will +/// directly use the input buffer without copying. If this method is used, the caller must ensure the path +/// is normalized before passing it to . +/// Based on FS 12.1.0 (nnSdk 12.3.1)
+[DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] +[NonCopyableDisposable] +public ref struct Path +{ /// - /// Represents a file path stored as a UTF-8 string. + /// Used to store a path in a non-ref struct. /// - /// - /// A has three parts to it:
- /// 1. A that points to the current path string.
- /// 2. A write buffer that can be allocated if operations need to be done on the path.
- /// 3. An IsNormalized flag that tracks the path normalization status of the current path.
- /// There are two different ways to initialize a . The "Initialize*" methods will - /// ensure a write buffer is allocated and copy the input path to it. will - /// directly use the input buffer without copying. If this method is used, the caller must ensure the path - /// is normalized before passing it to . - /// Based on FS 12.1.0 (nnSdk 12.3.1)
[DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] - [NonCopyableDisposable] - public ref struct Path + public struct Stored : IDisposable { - /// - /// Used to store a path in a non-ref struct. - /// - [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")] - public struct Stored : IDisposable - { - private byte[] _buffer; - private int _length; - - public void Dispose() - { - byte[] buffer = Shared.Move(ref _buffer); - if (buffer is not null) - { - ArrayPool.Shared.Return(buffer); - } - } - - /// - /// Initializes this path with the data from a standard . - /// must be normalized. - /// - /// The used to initialize this one. - /// : The operation was successful.
- /// : The IsNormalized flag of - /// is not .
- public Result Initialize(in Path path) - { - if (!path._isNormalized) - return ResultFs.NotNormalized.Log(); - - _length = path.GetLength(); - - Result rc = Preallocate(_length + NullTerminatorLength); - if (rc.IsFailure()) return rc; - - int bytesCopied = StringUtils.Copy(_buffer, path._string, _length + NullTerminatorLength); - - if (bytesCopied != _length) - return ResultFs.UnexpectedInPathA.Log(); - - return Result.Success; - } - - public readonly int GetLength() => _length; - public readonly ReadOnlySpan GetString() => _buffer; - - /// - /// Creates a from this . This - /// must not be reinitialized or disposed for the lifetime of the created . - /// - /// The created . - public readonly Path DangerousGetPath() - { - return new Path - { - _string = _buffer, - _isNormalized = true - }; - } - - private Result Preallocate(int length) - { - if (_buffer is not null && _buffer.Length > length) - return Result.Success; - - int alignedLength = Alignment.AlignUpPow2(length, WriteBufferAlignmentLength); - byte[] buffer = ArrayPool.Shared.Rent(alignedLength); - - byte[] oldBuffer = _buffer; - _buffer = buffer; - - if (oldBuffer is not null) - ArrayPool.Shared.Return(oldBuffer); - - return Result.Success; - } - - public override string ToString() => StringUtils.Utf8ZToString(_buffer); - } - - private const int SeparatorLength = 1; - private const int NullTerminatorLength = 1; - private const int WriteBufferAlignmentLength = 8; - private static ReadOnlySpan EmptyPath => new byte[] { 0 }; - - private ReadOnlySpan _string; - private byte[] _writeBuffer; - private int _writeBufferLength; - private bool _isNormalized; + private byte[] _buffer; + private int _length; public void Dispose() { - byte[] writeBuffer = Shared.Move(ref _writeBuffer); - if (writeBuffer is not null) + byte[] buffer = Shared.Move(ref _buffer); + if (buffer is not null) { - ArrayPool.Shared.Return(writeBuffer); + ArrayPool.Shared.Return(buffer); } } /// - /// Gets the current write buffer. + /// Initializes this path with the data from a standard . + /// must be normalized. /// - /// The write buffer. - internal Span GetWriteBuffer() + /// The used to initialize this one. + /// : The operation was successful.
+ /// : The IsNormalized flag of + /// is not .
+ public Result Initialize(in Path path) { - Assert.SdkRequires(_writeBuffer is not null); - return _writeBuffer.AsSpan(); + if (!path._isNormalized) + return ResultFs.NotNormalized.Log(); + + _length = path.GetLength(); + + Result rc = Preallocate(_length + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + int bytesCopied = StringUtils.Copy(_buffer, path._string, _length + NullTerminatorLength); + + if (bytesCopied != _length) + return ResultFs.UnexpectedInPathA.Log(); + + return Result.Success; } + public readonly int GetLength() => _length; + public readonly ReadOnlySpan GetString() => _buffer; + /// - /// Gets the current length of the write buffer. + /// Creates a from this . This + /// must not be reinitialized or disposed for the lifetime of the created . /// - /// The write buffer length. - internal readonly long GetWriteBufferLength() + /// The created . + public readonly Path DangerousGetPath() { - return _writeBufferLength; + return new Path + { + _string = _buffer, + _isNormalized = true + }; } - /// - /// Calculates the length of the current string. - /// - /// The length of the current string> - public readonly int GetLength() - { - return StringUtils.GetLength(GetString()); - } - - /// - /// Returns if - /// - /// - public readonly bool IsEmpty() - { - return _string.At(0) == 0; - } - - /// - /// Calculates if the first "" characters of the - /// current path and are the same. - /// - /// The string to compare to this . - /// The maximum number of characters to compare. - /// if the strings are the same; otherwise . - public readonly bool IsMatchHead(ReadOnlySpan value, int length) - { - return StringUtils.Compare(GetString(), value, length) == 0; - } - - public static bool operator !=(in Path left, in Path right) - { - return !(left == right); - } - - public static bool operator !=(in Path left, ReadOnlySpan right) - { - return !(left == right); - } - - public static bool operator ==(in Path left, in Path right) - { - return StringUtils.Compare(left.GetString(), right.GetString()) == 0; - } - - public static bool operator ==(in Path left, ReadOnlySpan right) - { - return StringUtils.Compare(left.GetString(), right) == 0; - } - - /// - /// Releases this 's write buffer and returns it to the caller. - /// - /// The write buffer if the had one; otherwise . - public byte[] ReleaseBuffer() - { - Assert.SdkRequires(_writeBuffer is not null); - - _string = EmptyPath; - _writeBufferLength = 0; - - return Shared.Move(ref _writeBuffer); - } - - /// - /// Releases any current write buffer and sets this to an empty string. - /// - private void ClearBuffer() - { - byte[] oldBuffer = Shared.Move(ref _writeBuffer); - - if (oldBuffer is not null) - ArrayPool.Shared.Return(oldBuffer); - - _writeBufferLength = 0; - _string = EmptyPath; - } - - /// - /// Releases any current write buffer and sets the provided buffer as the new write buffer. - /// - /// The new write buffer. - /// The length of the write buffer. - /// Must be a multiple of . - private void SetModifiableBuffer(byte[] buffer, int length) - { - Assert.SdkRequiresNotNull(buffer); - Assert.SdkRequires(length > 0); - Assert.SdkRequires(Alignment.IsAlignedPow2(length, WriteBufferAlignmentLength)); - - byte[] oldBuffer = _writeBuffer; - _writeBuffer = buffer; - - if (oldBuffer is not null) - ArrayPool.Shared.Return(oldBuffer); - - _writeBufferLength = length; - _string = buffer; - } - - /// - /// Releases any current write buffer and sets as this 's string. - /// - /// The buffer containing the new path. - private void SetReadOnlyBuffer(ReadOnlySpan buffer) - { - _string = buffer; - - byte[] oldBuffer = Shared.Move(ref _writeBuffer); - - if (oldBuffer is not null) - ArrayPool.Shared.Return(oldBuffer); - - _writeBufferLength = 0; - } - - /// - /// Ensures the write buffer is the specified or larger. - /// - /// The minimum desired length. - /// : The operation was successful. private Result Preallocate(int length) { - if (_writeBufferLength > length) + if (_buffer is not null && _buffer.Length > length) return Result.Success; int alignedLength = Alignment.AlignUpPow2(length, WriteBufferAlignmentLength); byte[] buffer = ArrayPool.Shared.Rent(alignedLength); - SetModifiableBuffer(buffer, alignedLength); + + byte[] oldBuffer = _buffer; + _buffer = buffer; + + if (oldBuffer is not null) + ArrayPool.Shared.Return(oldBuffer); return Result.Success; } - /// - /// Releases any current write buffer and sets as this 's string.
- /// The path contained by must be normalized. - ///
- /// It is up to the caller to ensure the path contained by is normalized. - /// This function will always set the IsNormalized flag to . - /// The buffer containing the new path. - /// : The operation was successful. - public Result SetShallowBuffer(ReadOnlySpan buffer) - { - Assert.SdkRequires(_writeBufferLength == 0); + public override string ToString() => StringUtils.Utf8ZToString(_buffer); + } - SetReadOnlyBuffer(buffer); - _isNormalized = true; + private const int SeparatorLength = 1; + private const int NullTerminatorLength = 1; + private const int WriteBufferAlignmentLength = 8; + private static ReadOnlySpan EmptyPath => new byte[] { 0 }; + + private ReadOnlySpan _string; + private byte[] _writeBuffer; + private int _writeBufferLength; + private bool _isNormalized; + + public void Dispose() + { + byte[] writeBuffer = Shared.Move(ref _writeBuffer); + if (writeBuffer is not null) + { + ArrayPool.Shared.Return(writeBuffer); + } + } + + /// + /// Gets the current write buffer. + /// + /// The write buffer. + internal Span GetWriteBuffer() + { + Assert.SdkRequires(_writeBuffer is not null); + return _writeBuffer.AsSpan(); + } + + /// + /// Gets the current length of the write buffer. + /// + /// The write buffer length. + internal readonly long GetWriteBufferLength() + { + return _writeBufferLength; + } + + /// + /// Calculates the length of the current string. + /// + /// The length of the current string> + public readonly int GetLength() + { + return StringUtils.GetLength(GetString()); + } + + /// + /// Returns if + /// + /// + public readonly bool IsEmpty() + { + return _string.At(0) == 0; + } + + /// + /// Calculates if the first "" characters of the + /// current path and are the same. + /// + /// The string to compare to this . + /// The maximum number of characters to compare. + /// if the strings are the same; otherwise . + public readonly bool IsMatchHead(ReadOnlySpan value, int length) + { + return StringUtils.Compare(GetString(), value, length) == 0; + } + + public static bool operator !=(in Path left, in Path right) + { + return !(left == right); + } + + public static bool operator !=(in Path left, ReadOnlySpan right) + { + return !(left == right); + } + + public static bool operator ==(in Path left, in Path right) + { + return StringUtils.Compare(left.GetString(), right.GetString()) == 0; + } + + public static bool operator ==(in Path left, ReadOnlySpan right) + { + return StringUtils.Compare(left.GetString(), right) == 0; + } + + /// + /// Releases this 's write buffer and returns it to the caller. + /// + /// The write buffer if the had one; otherwise . + public byte[] ReleaseBuffer() + { + Assert.SdkRequires(_writeBuffer is not null); + + _string = EmptyPath; + _writeBufferLength = 0; + + return Shared.Move(ref _writeBuffer); + } + + /// + /// Releases any current write buffer and sets this to an empty string. + /// + private void ClearBuffer() + { + byte[] oldBuffer = Shared.Move(ref _writeBuffer); + + if (oldBuffer is not null) + ArrayPool.Shared.Return(oldBuffer); + + _writeBufferLength = 0; + _string = EmptyPath; + } + + /// + /// Releases any current write buffer and sets the provided buffer as the new write buffer. + /// + /// The new write buffer. + /// The length of the write buffer. + /// Must be a multiple of . + private void SetModifiableBuffer(byte[] buffer, int length) + { + Assert.SdkRequiresNotNull(buffer); + Assert.SdkRequires(length > 0); + Assert.SdkRequires(Alignment.IsAlignedPow2(length, WriteBufferAlignmentLength)); + + byte[] oldBuffer = _writeBuffer; + _writeBuffer = buffer; + + if (oldBuffer is not null) + ArrayPool.Shared.Return(oldBuffer); + + _writeBufferLength = length; + _string = buffer; + } + + /// + /// Releases any current write buffer and sets as this 's string. + /// + /// The buffer containing the new path. + private void SetReadOnlyBuffer(ReadOnlySpan buffer) + { + _string = buffer; + + byte[] oldBuffer = Shared.Move(ref _writeBuffer); + + if (oldBuffer is not null) + ArrayPool.Shared.Return(oldBuffer); + + _writeBufferLength = 0; + } + + /// + /// Ensures the write buffer is the specified or larger. + /// + /// The minimum desired length. + /// : The operation was successful. + private Result Preallocate(int length) + { + if (_writeBufferLength > length) + return Result.Success; + + int alignedLength = Alignment.AlignUpPow2(length, WriteBufferAlignmentLength); + byte[] buffer = ArrayPool.Shared.Rent(alignedLength); + SetModifiableBuffer(buffer, alignedLength); + + return Result.Success; + } + + /// + /// Releases any current write buffer and sets as this 's string.
+ /// The path contained by must be normalized. + ///
+ /// It is up to the caller to ensure the path contained by is normalized. + /// This function will always set the IsNormalized flag to . + /// The buffer containing the new path. + /// : The operation was successful. + public Result SetShallowBuffer(ReadOnlySpan buffer) + { + Assert.SdkRequires(_writeBufferLength == 0); + + SetReadOnlyBuffer(buffer); + _isNormalized = true; + return Result.Success; + } + + /// + /// Gets the buffer containing the current path. + /// + /// This 's IsNormalized flag should be + /// before calling this function. + /// The buffer containing the current path. + public readonly ReadOnlySpan GetString() + { + Assert.SdkAssert(_isNormalized); + + return _string; + } + + /// + /// Initializes this with the data from another Path.
+ /// must be normalized. + ///
+ /// This 's IsNormalized flag will be set to + /// the value of 's flag. + /// The used to initialize this one. + /// : The operation was successful.
+ /// : The IsNormalized flag of + /// is not .
+ public Result Initialize(in Path other) + { + if (!other._isNormalized) + return ResultFs.NotNormalized.Log(); + + int otherLength = other.GetLength(); + + Result rc = Preallocate(otherLength + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + int bytesCopied = StringUtils.Copy(_writeBuffer, other.GetString(), otherLength + NullTerminatorLength); + + if (bytesCopied != otherLength) + return ResultFs.UnexpectedInPathA.Log(); + + _isNormalized = other._isNormalized; + return Result.Success; + } + + /// + /// Initializes this with the data from a path. + /// + /// Ensures we have a large enough write buffer and copies the path to it. + /// This function always sets the IsNormalized flag to + /// because paths are always normalized upon initialization. + /// The path used to initialize this . + /// : The operation was successful. + public Result Initialize(in Stored other) + { + int otherLength = other.GetLength(); + + Result rc = Preallocate(otherLength + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + int bytesCopied = StringUtils.Copy(_writeBuffer, other.GetString(), otherLength + NullTerminatorLength); + + if (bytesCopied != otherLength) + return ResultFs.UnexpectedInPathA.Log(); + + _isNormalized = true; + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function does not modify the IsNormalized flag.
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful. + private Result InitializeImpl(ReadOnlySpan path, int length) + { + if (length == 0 || path.At(0) == NullTerminator) + { + ClearBuffer(); return Result.Success; } - /// - /// Gets the buffer containing the current path. - /// - /// This 's IsNormalized flag should be - /// before calling this function. - /// The buffer containing the current path. - public readonly ReadOnlySpan GetString() + Result rc = Preallocate(length + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + int bytesCopied = StringUtils.Copy(GetWriteBuffer(), path, length + NullTerminatorLength); + + if (bytesCopied < length) + return ResultFs.UnexpectedInPathA.Log(); + + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function will always set the IsNormalized flag to .
+ /// The buffer containing the path to use. + /// : The operation was successful. + public Result Initialize(ReadOnlySpan path) + { + Result rc = InitializeImpl(path, StringUtils.GetLength(path)); + if (rc.IsFailure()) return rc; + + _isNormalized = false; + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer and + /// normalizes it if the path is a relative path or a Windows path. + /// + /// This function normalizes relative paths and Windows paths but does not normalize any other paths, + /// although all paths are checked for invalid characters and if the path is in a valid format.
+ /// The IsNormalized flag will always be set to even if the incoming path + /// is not normalized. This can lead to a situation where the path is not normalized yet the + /// IsNormalized flag is still .
+ /// The buffer containing the path to use. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
+ public Result InitializeWithNormalization(ReadOnlySpan path) + { + Result rc = Initialize(path); + if (rc.IsFailure()) return rc; + + if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) && + _string.At(0) != DirectorySeparator) { - Assert.SdkAssert(_isNormalized); + var flags = new PathFlags(); + flags.AllowRelativePath(); - return _string; - } - - /// - /// Initializes this with the data from another Path.
- /// must be normalized. - ///
- /// This 's IsNormalized flag will be set to - /// the value of 's flag. - /// The used to initialize this one. - /// : The operation was successful.
- /// : The IsNormalized flag of - /// is not .
- public Result Initialize(in Path other) - { - if (!other._isNormalized) - return ResultFs.NotNormalized.Log(); - - int otherLength = other.GetLength(); - - Result rc = Preallocate(otherLength + NullTerminatorLength); + rc = Normalize(flags); if (rc.IsFailure()) return rc; + } + else if (WindowsPath.IsWindowsPath(_string, true)) + { + var flags = new PathFlags(); + flags.AllowWindowsPath(); - int bytesCopied = StringUtils.Copy(_writeBuffer, other.GetString(), otherLength + NullTerminatorLength); - - if (bytesCopied != otherLength) - return ResultFs.UnexpectedInPathA.Log(); - - _isNormalized = other._isNormalized; - return Result.Success; + rc = Normalize(flags); + if (rc.IsFailure()) return rc; + } + else + { + rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string); + if (rc.IsFailure()) return rc; } - /// - /// Initializes this with the data from a path. - /// - /// Ensures we have a large enough write buffer and copies the path to it. - /// This function always sets the IsNormalized flag to - /// because paths are always normalized upon initialization. - /// The path used to initialize this . - /// : The operation was successful. - public Result Initialize(in Stored other) + // Note: I have no idea why Nintendo checks if the path is normalized + // and then unconditionally sets _isNormalized to true right after. + // Maybe it's a mistake and somehow nobody noticed? + _isNormalized = true; + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer. + /// + /// Ensures the write buffer is large enough to hold + /// and copies to the write buffer.
+ /// This function will always set the IsNormalized flag to .
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful. + public Result Initialize(ReadOnlySpan path, int length) + { + Result rc = InitializeImpl(path, length); + if (rc.IsFailure()) return rc; + + _isNormalized = false; + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer and + /// normalizes it if the path is a relative path or a Windows path. + /// + /// This function normalizes relative paths and Windows paths but does not normalize any other paths, + /// although all paths are checked for invalid characters and if the path is in a valid format.
+ /// The IsNormalized flag will always be set to even if the incoming path + /// is not normalized. This can lead to a situation where the path is not normalized yet the + /// IsNormalized flag is still .
+ /// The buffer containing the path to use. + /// The length of the provided path. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
+ public Result InitializeWithNormalization(ReadOnlySpan path, int length) + { + Result rc = Initialize(path, length); + if (rc.IsFailure()) return rc; + + if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) && + _string.At(0) != DirectorySeparator) { - int otherLength = other.GetLength(); + var flags = new PathFlags(); + flags.AllowRelativePath(); - Result rc = Preallocate(otherLength + NullTerminatorLength); + rc = Normalize(flags); if (rc.IsFailure()) return rc; + } + else if (WindowsPath.IsWindowsPath(_string, true)) + { + var flags = new PathFlags(); + flags.AllowWindowsPath(); - int bytesCopied = StringUtils.Copy(_writeBuffer, other.GetString(), otherLength + NullTerminatorLength); - - if (bytesCopied != otherLength) - return ResultFs.UnexpectedInPathA.Log(); - - _isNormalized = true; - return Result.Success; + rc = Normalize(flags); + if (rc.IsFailure()) return rc; + } + else + { + rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string); + if (rc.IsFailure()) return rc; } - /// - /// Initializes this using the path in the provided buffer. - /// - /// Ensures the write buffer is large enough to hold - /// and copies to the write buffer.
- /// This function does not modify the IsNormalized flag.
- /// The buffer containing the path to use. - /// The length of the provided path. - /// : The operation was successful. - private Result InitializeImpl(ReadOnlySpan path, int length) + // Note: I have no idea why Nintendo checks if the path is normalized + // and then unconditionally sets _isNormalized to true right after. + // Maybe it's a mistake and somehow nobody noticed? + _isNormalized = true; + return Result.Success; + } + + + /// + /// Initializes this using the path in the provided buffer and + /// replaces any backslashes in the path with forward slashes. + /// + /// This function will always set the IsNormalized flag to . + /// The buffer containing the path to use. + /// : The operation was successful. + public Result InitializeWithReplaceBackslash(ReadOnlySpan path) + { + Result rc = InitializeImpl(path, StringUtils.GetLength(path)); + if (rc.IsFailure()) return rc; + + if (_writeBufferLength > 1) { - if (length == 0 || path.At(0) == NullTerminator) - { - ClearBuffer(); - return Result.Success; - } - - Result rc = Preallocate(length + NullTerminatorLength); - if (rc.IsFailure()) return rc; - - int bytesCopied = StringUtils.Copy(GetWriteBuffer(), path, length + NullTerminatorLength); - - if (bytesCopied < length) - return ResultFs.UnexpectedInPathA.Log(); - - return Result.Success; + PathUtility.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator, + DirectorySeparator); } - /// - /// Initializes this using the path in the provided buffer. - /// - /// Ensures the write buffer is large enough to hold - /// and copies to the write buffer.
- /// This function will always set the IsNormalized flag to .
- /// The buffer containing the path to use. - /// : The operation was successful. - public Result Initialize(ReadOnlySpan path) + _isNormalized = false; + return Result.Success; + } + + /// + /// Initializes this using the path in the provided buffer. If the path begins with two + /// forward slashes (//), those two forward slashes will be replaced with two backslashes (\\). + /// + /// This function will always set the IsNormalized flag to . + /// The buffer containing the path to use. + /// : The operation was successful. + public Result InitializeWithReplaceForwardSlashes(ReadOnlySpan path) + { + Result rc = InitializeImpl(path, StringUtils.GetLength(path)); + if (rc.IsFailure()) return rc; + + if (_writeBufferLength > 1) { - Result rc = InitializeImpl(path, StringUtils.GetLength(path)); - if (rc.IsFailure()) return rc; - - _isNormalized = false; - return Result.Success; - } - - /// - /// Initializes this using the path in the provided buffer and - /// normalizes it if the path is a relative path or a Windows path. - /// - /// This function normalizes relative paths and Windows paths but does not normalize any other paths, - /// although all paths are checked for invalid characters and if the path is in a valid format.
- /// The IsNormalized flag will always be set to even if the incoming path - /// is not normalized. This can lead to a situation where the path is not normalized yet the - /// IsNormalized flag is still .
- /// The buffer containing the path to use. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is not in a valid format.
- public Result InitializeWithNormalization(ReadOnlySpan path) - { - Result rc = Initialize(path); - if (rc.IsFailure()) return rc; - - if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) && - _string.At(0) != DirectorySeparator) - { - var flags = new PathFlags(); - flags.AllowRelativePath(); - - rc = Normalize(flags); - if (rc.IsFailure()) return rc; - } - else if (WindowsPath.IsWindowsPath(_string, true)) - { - var flags = new PathFlags(); - flags.AllowWindowsPath(); - - rc = Normalize(flags); - if (rc.IsFailure()) return rc; - } - else - { - rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string); - if (rc.IsFailure()) return rc; - } - - // Note: I have no idea why Nintendo checks if the path is normalized - // and then unconditionally sets _isNormalized to true right after. - // Maybe it's a mistake and somehow nobody noticed? - _isNormalized = true; - return Result.Success; - } - - /// - /// Initializes this using the path in the provided buffer. - /// - /// Ensures the write buffer is large enough to hold - /// and copies to the write buffer.
- /// This function will always set the IsNormalized flag to .
- /// The buffer containing the path to use. - /// The length of the provided path. - /// : The operation was successful. - public Result Initialize(ReadOnlySpan path, int length) - { - Result rc = InitializeImpl(path, length); - if (rc.IsFailure()) return rc; - - _isNormalized = false; - return Result.Success; - } - - /// - /// Initializes this using the path in the provided buffer and - /// normalizes it if the path is a relative path or a Windows path. - /// - /// This function normalizes relative paths and Windows paths but does not normalize any other paths, - /// although all paths are checked for invalid characters and if the path is in a valid format.
- /// The IsNormalized flag will always be set to even if the incoming path - /// is not normalized. This can lead to a situation where the path is not normalized yet the - /// IsNormalized flag is still .
- /// The buffer containing the path to use. - /// The length of the provided path. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is not in a valid format.
- public Result InitializeWithNormalization(ReadOnlySpan path, int length) - { - Result rc = Initialize(path, length); - if (rc.IsFailure()) return rc; - - if (_string.At(0) != NullTerminator && !WindowsPath.IsWindowsPath(_string, false) && - _string.At(0) != DirectorySeparator) - { - var flags = new PathFlags(); - flags.AllowRelativePath(); - - rc = Normalize(flags); - if (rc.IsFailure()) return rc; - } - else if (WindowsPath.IsWindowsPath(_string, true)) - { - var flags = new PathFlags(); - flags.AllowWindowsPath(); - - rc = Normalize(flags); - if (rc.IsFailure()) return rc; - } - else - { - rc = PathNormalizer.IsNormalized(out _isNormalized, out _, _string); - if (rc.IsFailure()) return rc; - } - - // Note: I have no idea why Nintendo checks if the path is normalized - // and then unconditionally sets _isNormalized to true right after. - // Maybe it's a mistake and somehow nobody noticed? - _isNormalized = true; - return Result.Success; - } - - - /// - /// Initializes this using the path in the provided buffer and - /// replaces any backslashes in the path with forward slashes. - /// - /// This function will always set the IsNormalized flag to . - /// The buffer containing the path to use. - /// : The operation was successful. - public Result InitializeWithReplaceBackslash(ReadOnlySpan path) - { - Result rc = InitializeImpl(path, StringUtils.GetLength(path)); - if (rc.IsFailure()) return rc; - - if (_writeBufferLength > 1) - { - PathUtility.Replace(GetWriteBuffer().Slice(0, _writeBufferLength - 1), AltDirectorySeparator, - DirectorySeparator); - } - - _isNormalized = false; - return Result.Success; - } - - /// - /// Initializes this using the path in the provided buffer. If the path begins with two - /// forward slashes (//), those two forward slashes will be replaced with two backslashes (\\). - /// - /// This function will always set the IsNormalized flag to . - /// The buffer containing the path to use. - /// : The operation was successful. - public Result InitializeWithReplaceForwardSlashes(ReadOnlySpan path) - { - Result rc = InitializeImpl(path, StringUtils.GetLength(path)); - if (rc.IsFailure()) return rc; - - if (_writeBufferLength > 1) - { - Span writeBuffer = GetWriteBuffer(); - if (writeBuffer[0] == DirectorySeparator && writeBuffer[1] == DirectorySeparator) - { - writeBuffer[0] = AltDirectorySeparator; - writeBuffer[1] = AltDirectorySeparator; - } - } - - _isNormalized = false; - return Result.Success; - } - - /// - /// Initializes this using the path in the provided buffer - /// and makes various UNC path-related replacements. - /// - /// The following replacements are made:
- /// :/// located anywhere in the path is replaced with :/\\
- /// @Host:// located at the beginning of the path is replaced with @Host:\\
- /// // located at the beginning of the path is replaced with \\ - /// This function does not modify the IsNormalized flag. - ///
- /// The buffer containing the path to use. - /// : The operation was successful. - public Result InitializeWithReplaceUnc(ReadOnlySpan path) - { - Result rc = InitializeImpl(path, StringUtils.GetLength(path)); - if (rc.IsFailure()) return rc; - - _isNormalized = false; - - if (path.At(0) == NullTerminator) - return Result.Success; - Span writeBuffer = GetWriteBuffer(); - - ReadOnlySpan search = new[] { (byte)':', (byte)'/', (byte)'/', (byte)'/' }; // ":///" - int index = StringUtils.Find(writeBuffer, search); - if (index >= 0) - { - writeBuffer[index + 2] = AltDirectorySeparator; - writeBuffer[index + 3] = AltDirectorySeparator; - } - - ReadOnlySpan hostMountUnc = new[] // "@Host://" - { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)':', (byte)'/', (byte)'/' }; - if (StringUtils.Compare(writeBuffer, hostMountUnc, 8) == 0) - { - writeBuffer[6] = AltDirectorySeparator; - writeBuffer[7] = AltDirectorySeparator; - } - - if (writeBuffer.At(0) == DirectorySeparator && writeBuffer.At(1) == DirectorySeparator) + if (writeBuffer[0] == DirectorySeparator && writeBuffer[1] == DirectorySeparator) { writeBuffer[0] = AltDirectorySeparator; writeBuffer[1] = AltDirectorySeparator; } - - return Result.Success; } - /// - /// Initializes the as an empty string. - /// - /// This function will always set the IsNormalized flag to . - /// : The operation was successful. - public Result InitializeAsEmpty() - { - ClearBuffer(); - _isNormalized = true; + _isNormalized = false; + return Result.Success; + } + /// + /// Initializes this using the path in the provided buffer + /// and makes various UNC path-related replacements. + /// + /// The following replacements are made:
+ /// :/// located anywhere in the path is replaced with :/\\
+ /// @Host:// located at the beginning of the path is replaced with @Host:\\
+ /// // located at the beginning of the path is replaced with \\ + /// This function does not modify the IsNormalized flag. + ///
+ /// The buffer containing the path to use. + /// : The operation was successful. + public Result InitializeWithReplaceUnc(ReadOnlySpan path) + { + Result rc = InitializeImpl(path, StringUtils.GetLength(path)); + if (rc.IsFailure()) return rc; + + _isNormalized = false; + + if (path.At(0) == NullTerminator) return Result.Success; + + Span writeBuffer = GetWriteBuffer(); + + ReadOnlySpan search = new[] { (byte)':', (byte)'/', (byte)'/', (byte)'/' }; // ":///" + int index = StringUtils.Find(writeBuffer, search); + if (index >= 0) + { + writeBuffer[index + 2] = AltDirectorySeparator; + writeBuffer[index + 3] = AltDirectorySeparator; } - /// - /// Updates this by prepending to the current path. - /// - /// This function does not modify the IsNormalized flag. - /// If is not normalized, this can lead to a situation where the resulting - /// path is not normalized yet the IsNormalized flag is still . - /// The buffer containing the path to insert. - /// : The operation was successful.
- /// : The path provided in is a Windows path.
- public Result InsertParent(ReadOnlySpan parent) + ReadOnlySpan hostMountUnc = new[] // "@Host://" + { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)':', (byte)'/', (byte)'/' }; + if (StringUtils.Compare(writeBuffer, hostMountUnc, 8) == 0) { - if (parent.Length == 0 || parent[0] == NullTerminator) - return Result.Success; + writeBuffer[6] = AltDirectorySeparator; + writeBuffer[7] = AltDirectorySeparator; + } - if (WindowsPath.IsWindowsPath(_string, false)) - return ResultFs.NotImplemented.Log(); + if (writeBuffer.At(0) == DirectorySeparator && writeBuffer.At(1) == DirectorySeparator) + { + writeBuffer[0] = AltDirectorySeparator; + writeBuffer[1] = AltDirectorySeparator; + } - // Remove a trailing separator from the parent and a leading one from the child so we can - // make sure there's only one separator between them when we slap them together. - // Trim a trailing directory separator from the parent path. - bool parentHasTrailingSlash = false; - int parentLength = StringUtils.GetLength(parent); + return Result.Success; + } - if (parent[parentLength - 1] == DirectorySeparator || parent[parentLength - 1] == AltDirectorySeparator) + /// + /// Initializes the as an empty string. + /// + /// This function will always set the IsNormalized flag to . + /// : The operation was successful. + public Result InitializeAsEmpty() + { + ClearBuffer(); + _isNormalized = true; + + return Result.Success; + } + + /// + /// Updates this by prepending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The buffer containing the path to insert. + /// : The operation was successful.
+ /// : The path provided in is a Windows path.
+ public Result InsertParent(ReadOnlySpan parent) + { + if (parent.Length == 0 || parent[0] == NullTerminator) + return Result.Success; + + if (WindowsPath.IsWindowsPath(_string, false)) + return ResultFs.NotImplemented.Log(); + + // Remove a trailing separator from the parent and a leading one from the child so we can + // make sure there's only one separator between them when we slap them together. + // Trim a trailing directory separator from the parent path. + bool parentHasTrailingSlash = false; + int parentLength = StringUtils.GetLength(parent); + + if (parent[parentLength - 1] == DirectorySeparator || parent[parentLength - 1] == AltDirectorySeparator) + { + parentLength--; + parentHasTrailingSlash = true; + } + + // Trim a leading directory separator from the current path. + bool childHasLeadingSlash = false; + ReadOnlySpan childPath = _string; + + if (childPath.Length != 0 && childPath[0] == DirectorySeparator) + { + childPath = childPath.Slice(1); + childHasLeadingSlash = true; + } + + int childLength = StringUtils.GetLength(childPath); + + byte[] childBuffer = null; + try + { + // Get and clear our Path's current buffer. + if (_writeBuffer is not null) { - parentLength--; - parentHasTrailingSlash = true; + childBuffer = Shared.Move(ref _writeBuffer); + ClearBuffer(); } - // Trim a leading directory separator from the current path. - bool childHasLeadingSlash = false; - ReadOnlySpan childPath = _string; + // Give our Path a buffer that can hold the combined string. + Result rc = Preallocate(parentLength + DirectorySeparator + childLength + NullTerminatorLength); + if (rc.IsFailure()) return rc; - if (childPath.Length != 0 && childPath[0] == DirectorySeparator) + Span destBuffer = GetWriteBuffer(); + + int childStartOffset = childHasLeadingSlash ? 1 : 0; + + if (childLength > 0) { - childPath = childPath.Slice(1); - childHasLeadingSlash = true; - } - - int childLength = StringUtils.GetLength(childPath); - - byte[] childBuffer = null; - try - { - // Get and clear our Path's current buffer. - if (_writeBuffer is not null) + // Copy the child part of the path to the destination buffer. + if (childBuffer is not null) { - childBuffer = Shared.Move(ref _writeBuffer); - ClearBuffer(); + StringUtils.Copy(destBuffer.Slice(parentLength + SeparatorLength), + childBuffer.AsSpan(childStartOffset), childLength + NullTerminatorLength); } + else + { + Span destBuffer2 = destBuffer.Slice(childStartOffset); - // Give our Path a buffer that can hold the combined string. - Result rc = Preallocate(parentLength + DirectorySeparator + childLength + NullTerminatorLength); + for (int i = childLength; i > 0; i--) + { + destBuffer2[i - 1 + parentLength] = destBuffer2[i - 1]; + } + + destBuffer2[childLength + parentLength] = 0; + } + } + + // Copy the parent part of the path to the destination buffer. + int parentBytesCopied = StringUtils.Copy(destBuffer, parent, parentLength + SeparatorLength); + + // Make sure we copied the expected number of parent bytes. + if (!parentHasTrailingSlash) + { + if (parentBytesCopied != parentLength) + return ResultFs.UnexpectedInPathA.Log(); + } + else if (parentBytesCopied != parentLength + SeparatorLength) + { + return ResultFs.UnexpectedInPathA.Log(); + } + + // Add a directory separator between the parent and child parts of the path. + if (childLength > 0) + { + destBuffer[parentLength] = DirectorySeparator; + } + + // Note: Nintendo does not reset the "_isNormalized" field on the Path. + // This can result in the field and the actual normalization state being out of sync. + + return Result.Success; + } + finally + { + if (childBuffer is not null) + ArrayPool.Shared.Return(childBuffer); + } + } + + /// + /// Updates this by prepending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The to insert. + /// : The operation was successful.
+ /// : The path provided in is a Windows path.
+ public Result InsertParent(in Path parent) + { + return InsertParent(parent.GetString()); + } + + /// + /// Updates this by appending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The buffer containing the child path to append to the current path. + /// : The operation was successful. + public Result AppendChild(ReadOnlySpan child) + { + ReadOnlySpan trimmedChild = child; + + // Trim a leading directory separator from the child path. + if (_string.At(0) != NullTerminator) + { + if (trimmedChild.Length != 0 && trimmedChild[0] == DirectorySeparator) + { + trimmedChild = trimmedChild.Slice(1); + } + + // Nothing to do if the child path is empty or the root directory. + if (trimmedChild.At(0) == NullTerminator) + { + return Result.Success; + } + } + + // If our current path is empty there's nothing to append the child path to, + // so we'll simply replace the current path with the child path. + int parentLength = StringUtils.GetLength(_string); + if (parentLength == 0) + { + return Initialize(child); + } + + // Trim a trailing directory separator from our current path. + if (_string[parentLength - 1] == DirectorySeparator || _string[parentLength - 1] == AltDirectorySeparator) + parentLength--; + + int childLength = StringUtils.GetLength(trimmedChild); + + byte[] parentBuffer = null; + try + { + if (_writeBuffer is not null) + { + parentBuffer = Shared.Move(ref _writeBuffer); + ClearBuffer(); + } + + Result rc = Preallocate(parentLength + SeparatorLength + childLength + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + Span destBuffer = GetWriteBuffer(); + + if (parentBuffer is not null && parentLength != 0) + { + StringUtils.Copy(destBuffer, parentBuffer, parentLength + SeparatorLength); + } + + destBuffer[parentLength] = DirectorySeparator; + + int childBytesCopied = StringUtils.Copy(destBuffer.Slice(parentLength + 1), trimmedChild, + childLength + NullTerminatorLength); + + if (childBytesCopied != childLength) + return ResultFs.UnexpectedInPathA.Log(); + + // Note: Nintendo does not reset the "_isNormalized" field on the Path. + // This can result in the field and the actual normalization state being out of sync. + + return Result.Success; + } + finally + { + if (parentBuffer is not null) + ArrayPool.Shared.Return(parentBuffer); + } + } + + /// + /// Updates this by appending to the current path. + /// + /// This function does not modify the IsNormalized flag. + /// If is not normalized, this can lead to a situation where the resulting + /// path is not normalized yet the IsNormalized flag is still . + /// The child to append to the current path. + /// : The operation was successful. + public Result AppendChild(in Path child) + { + return AppendChild(child.GetString()); + } + + /// + /// Combines 2 s into a single path. + /// + /// If is empty, this 's IsNormalized flag will + /// be set to the value of 's flag. + /// Otherwise the flag will be set to the value of 's flag. + /// The first path to combine. + /// The second path to combine. + /// : The operation was successful.
+ /// : The IsNormalized flag of either + /// or is not .
+ public Result Combine(in Path path1, in Path path2) + { + int path1Length = path1.GetLength(); + int path2Length = path2.GetLength(); + + Result rc = Preallocate(path1Length + SeparatorLength + path2Length + NullTerminatorLength); + if (rc.IsFailure()) return rc; + + rc = Initialize(in path1); + if (rc.IsFailure()) return rc; + + if (IsEmpty()) + { + rc = Initialize(in path2); + if (rc.IsFailure()) return rc; + } + else + { + rc = AppendChild(in path2); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + /// + /// Removes the last entry from this . + /// + /// This function does not modify the IsNormalized flag. + /// : The operation was successful.
+ /// : The path before calling this function was + /// one of ".", "..", "/" or "\".
+ public Result RemoveChild() + { + // Make sure the Path has a buffer that we can write to. + if (_writeBuffer is null) + { + int oldLength = StringUtils.GetLength(_string); + + if (oldLength > 0) + { + ReadOnlySpan oldString = _string; + Result rc = Preallocate(oldLength); if (rc.IsFailure()) return rc; - Span destBuffer = GetWriteBuffer(); + StringUtils.Copy(_writeBuffer, oldString, oldLength + NullTerminatorLength); + } + } - int childStartOffset = childHasLeadingSlash ? 1 : 0; + Span path = GetWriteBuffer(); + int originalLength = StringUtils.GetLength(path); - if (childLength > 0) + // We don't handle the current directory or root directory. + if (originalLength == 1 && path[0] == DirectorySeparator) + return ResultFs.NotImplemented.Log(); + + if (originalLength == 1 && path[0] == Dot) + return ResultFs.NotImplemented.Log(); + + // Now look backward through the path for the first separator and terminate the string there. + int currentPos = originalLength; + + // Ignore a trailing slash. + if (originalLength > 0 && + (path[currentPos - 1] == DirectorySeparator || path[currentPos - 1] == AltDirectorySeparator)) + { + currentPos--; + } + + if (currentPos > 0) + { + currentPos--; + + while (currentPos >= 0 && path[currentPos] != NullTerminator) + { + if (path[currentPos] == DirectorySeparator || path[currentPos] == AltDirectorySeparator) { - // Copy the child part of the path to the destination buffer. - if (childBuffer is not null) + // Don't leave a trailing slash unless the resulting path is the root directory. + if (currentPos == 0) { - StringUtils.Copy(destBuffer.Slice(parentLength + SeparatorLength), - childBuffer.AsSpan(childStartOffset), childLength + NullTerminatorLength); + path[1] = NullTerminator; + currentPos = 1; } else { - Span destBuffer2 = destBuffer.Slice(childStartOffset); - - for (int i = childLength; i > 0; i--) - { - destBuffer2[i - 1 + parentLength] = destBuffer2[i - 1]; - } - - destBuffer2[childLength + parentLength] = 0; - } - } - - // Copy the parent part of the path to the destination buffer. - int parentBytesCopied = StringUtils.Copy(destBuffer, parent, parentLength + SeparatorLength); - - // Make sure we copied the expected number of parent bytes. - if (!parentHasTrailingSlash) - { - if (parentBytesCopied != parentLength) - return ResultFs.UnexpectedInPathA.Log(); - } - else if (parentBytesCopied != parentLength + SeparatorLength) - { - return ResultFs.UnexpectedInPathA.Log(); - } - - // Add a directory separator between the parent and child parts of the path. - if (childLength > 0) - { - destBuffer[parentLength] = DirectorySeparator; - } - - // Note: Nintendo does not reset the "_isNormalized" field on the Path. - // This can result in the field and the actual normalization state being out of sync. - - return Result.Success; - } - finally - { - if (childBuffer is not null) - ArrayPool.Shared.Return(childBuffer); - } - } - - /// - /// Updates this by prepending to the current path. - /// - /// This function does not modify the IsNormalized flag. - /// If is not normalized, this can lead to a situation where the resulting - /// path is not normalized yet the IsNormalized flag is still . - /// The to insert. - /// : The operation was successful.
- /// : The path provided in is a Windows path.
- public Result InsertParent(in Path parent) - { - return InsertParent(parent.GetString()); - } - - /// - /// Updates this by appending to the current path. - /// - /// This function does not modify the IsNormalized flag. - /// If is not normalized, this can lead to a situation where the resulting - /// path is not normalized yet the IsNormalized flag is still . - /// The buffer containing the child path to append to the current path. - /// : The operation was successful. - public Result AppendChild(ReadOnlySpan child) - { - ReadOnlySpan trimmedChild = child; - - // Trim a leading directory separator from the child path. - if (_string.At(0) != NullTerminator) - { - if (trimmedChild.Length != 0 && trimmedChild[0] == DirectorySeparator) - { - trimmedChild = trimmedChild.Slice(1); - } - - // Nothing to do if the child path is empty or the root directory. - if (trimmedChild.At(0) == NullTerminator) - { - return Result.Success; - } - } - - // If our current path is empty there's nothing to append the child path to, - // so we'll simply replace the current path with the child path. - int parentLength = StringUtils.GetLength(_string); - if (parentLength == 0) - { - return Initialize(child); - } - - // Trim a trailing directory separator from our current path. - if (_string[parentLength - 1] == DirectorySeparator || _string[parentLength - 1] == AltDirectorySeparator) - parentLength--; - - int childLength = StringUtils.GetLength(trimmedChild); - - byte[] parentBuffer = null; - try - { - if (_writeBuffer is not null) - { - parentBuffer = Shared.Move(ref _writeBuffer); - ClearBuffer(); - } - - Result rc = Preallocate(parentLength + SeparatorLength + childLength + NullTerminatorLength); - if (rc.IsFailure()) return rc; - - Span destBuffer = GetWriteBuffer(); - - if (parentBuffer is not null && parentLength != 0) - { - StringUtils.Copy(destBuffer, parentBuffer, parentLength + SeparatorLength); - } - - destBuffer[parentLength] = DirectorySeparator; - - int childBytesCopied = StringUtils.Copy(destBuffer.Slice(parentLength + 1), trimmedChild, - childLength + NullTerminatorLength); - - if (childBytesCopied != childLength) - return ResultFs.UnexpectedInPathA.Log(); - - // Note: Nintendo does not reset the "_isNormalized" field on the Path. - // This can result in the field and the actual normalization state being out of sync. - - return Result.Success; - } - finally - { - if (parentBuffer is not null) - ArrayPool.Shared.Return(parentBuffer); - } - } - - /// - /// Updates this by appending to the current path. - /// - /// This function does not modify the IsNormalized flag. - /// If is not normalized, this can lead to a situation where the resulting - /// path is not normalized yet the IsNormalized flag is still . - /// The child to append to the current path. - /// : The operation was successful. - public Result AppendChild(in Path child) - { - return AppendChild(child.GetString()); - } - - /// - /// Combines 2 s into a single path. - /// - /// If is empty, this 's IsNormalized flag will - /// be set to the value of 's flag. - /// Otherwise the flag will be set to the value of 's flag. - /// The first path to combine. - /// The second path to combine. - /// : The operation was successful.
- /// : The IsNormalized flag of either - /// or is not .
- public Result Combine(in Path path1, in Path path2) - { - int path1Length = path1.GetLength(); - int path2Length = path2.GetLength(); - - Result rc = Preallocate(path1Length + SeparatorLength + path2Length + NullTerminatorLength); - if (rc.IsFailure()) return rc; - - rc = Initialize(in path1); - if (rc.IsFailure()) return rc; - - if (IsEmpty()) - { - rc = Initialize(in path2); - if (rc.IsFailure()) return rc; - } - else - { - rc = AppendChild(in path2); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - /// - /// Removes the last entry from this . - /// - /// This function does not modify the IsNormalized flag. - /// : The operation was successful.
- /// : The path before calling this function was - /// one of ".", "..", "/" or "\".
- public Result RemoveChild() - { - // Make sure the Path has a buffer that we can write to. - if (_writeBuffer is null) - { - int oldLength = StringUtils.GetLength(_string); - - if (oldLength > 0) - { - ReadOnlySpan oldString = _string; - Result rc = Preallocate(oldLength); - if (rc.IsFailure()) return rc; - - StringUtils.Copy(_writeBuffer, oldString, oldLength + NullTerminatorLength); - } - } - - Span path = GetWriteBuffer(); - int originalLength = StringUtils.GetLength(path); - - // We don't handle the current directory or root directory. - if (originalLength == 1 && path[0] == DirectorySeparator) - return ResultFs.NotImplemented.Log(); - - if (originalLength == 1 && path[0] == Dot) - return ResultFs.NotImplemented.Log(); - - // Now look backward through the path for the first separator and terminate the string there. - int currentPos = originalLength; - - // Ignore a trailing slash. - if (originalLength > 0 && - (path[currentPos - 1] == DirectorySeparator || path[currentPos - 1] == AltDirectorySeparator)) - { - currentPos--; - } - - if (currentPos > 0) - { - currentPos--; - - while (currentPos >= 0 && path[currentPos] != NullTerminator) - { - if (path[currentPos] == DirectorySeparator || path[currentPos] == AltDirectorySeparator) - { - // Don't leave a trailing slash unless the resulting path is the root directory. - if (currentPos == 0) - { - path[1] = NullTerminator; - currentPos = 1; - } - else - { - path[currentPos] = NullTerminator; - } - - break; + path[currentPos] = NullTerminator; } - currentPos--; + break; } - } - if (currentPos <= 0) - return ResultFs.NotImplemented.Log(); - - return Result.Success; - } - - /// - /// Normalizes the current path according to the provided . - /// - /// If this 's IsNormalized flag is set, this function does nothing. - /// The IsNormalized flag will be set if this function returns successfully. - /// Flags that specify what types of paths are allowed. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is in an invalid format for the specified .
- public Result Normalize(PathFlags flags) - { - if (_isNormalized) - return Result.Success; - - Result rc = PathFormatter.IsNormalized(out bool isNormalized, out _, _string, flags); - if (rc.IsFailure()) return rc; - - if (isNormalized) - { - _isNormalized = true; - return Result.Success; - } - - int bufferLength = _writeBufferLength; - - if (flags.IsRelativePathAllowed() && PathUtility.IsPathRelative(_string)) - bufferLength += 2; - - if (flags.IsWindowsPathAllowed() && WindowsPath.IsWindowsPath(_string, true)) - bufferLength += 1; - - int alignedBufferLength = Alignment.AlignUpPow2(bufferLength, WriteBufferAlignmentLength); - - byte[] rentedArray = null; - try - { - rentedArray = ArrayPool.Shared.Rent(alignedBufferLength); - - rc = PathFormatter.Normalize(rentedArray, GetWriteBuffer(), flags); - if (rc.IsFailure()) return rc; - - SetModifiableBuffer(Shared.Move(ref rentedArray), alignedBufferLength); - _isNormalized = true; - return Result.Success; - } - finally - { - if (rentedArray is not null) - { - ArrayPool.Shared.Return(rentedArray); - } + currentPos--; } } - public override readonly string ToString() => StringUtils.Utf8ZToString(_string); + if (currentPos <= 0) + return ResultFs.NotImplemented.Log(); - public override bool Equals(object obj) => throw new NotSupportedException(); - public override int GetHashCode() => throw new NotImplementedException(); + return Result.Success; } - public static class PathFunctions + /// + /// Normalizes the current path according to the provided . + /// + /// If this 's IsNormalized flag is set, this function does nothing. + /// The IsNormalized flag will be set if this function returns successfully. + /// Flags that specify what types of paths are allowed. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is in an invalid format for the specified .
+ public Result Normalize(PathFlags flags) { - /// - /// Initializes a with the provided basic path. - /// The provided path must be a normalized basic path starting with a directory separator - /// and not containing any sort of prefix such as a mount name. - /// - /// The to initialize. - /// The string used to initialize the . - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is in an invalid format or is not normalized.
- public static Result SetUpFixedPath(ref Path path, ReadOnlySpan pathBuffer) + if (_isNormalized) + return Result.Success; + + Result rc = PathFormatter.IsNormalized(out bool isNormalized, out _, _string, flags); + if (rc.IsFailure()) return rc; + + if (isNormalized) { - Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, pathBuffer); - if (rc.IsFailure()) return rc; - - if (!isNormalized) - return ResultFs.InvalidPathFormat.Log(); - - rc = path.SetShallowBuffer(pathBuffer); - if (rc.IsFailure()) return rc; - + _isNormalized = true; return Result.Success; } - // Only a small number of format strings are used with these functions, so we can hard code them all easily. + int bufferLength = _writeBufferLength; - // /%s - /// - /// Initializes a using the format string /%s - /// - /// The to be initialized. - /// The buffer that will contain the built string. - /// The first entry in the generated path. - /// : The operation was successful.
- /// : was too small to contain the built path.
- internal static Result SetUpFixedPathSingleEntry(ref Path path, Span pathBuffer, - ReadOnlySpan entryName) + if (flags.IsRelativePathAllowed() && PathUtility.IsPathRelative(_string)) + bufferLength += 2; + + if (flags.IsWindowsPathAllowed() && WindowsPath.IsWindowsPath(_string, true)) + bufferLength += 1; + + int alignedBufferLength = Alignment.AlignUpPow2(bufferLength, WriteBufferAlignmentLength); + + byte[] rentedArray = null; + try { - var sb = new U8StringBuilder(pathBuffer); - sb.Append((byte)'/').Append(entryName); + rentedArray = ArrayPool.Shared.Rent(alignedBufferLength); - if (sb.Overflowed) - return ResultFs.InvalidArgument.Log(); + rc = PathFormatter.Normalize(rentedArray, GetWriteBuffer(), flags); + if (rc.IsFailure()) return rc; - return SetUpFixedPath(ref path, pathBuffer); + SetModifiableBuffer(Shared.Move(ref rentedArray), alignedBufferLength); + _isNormalized = true; + return Result.Success; } - - // /%s/%s - /// - /// Initializes a using the format string /%s/%s - /// - /// The to be initialized. - /// The buffer that will contain the built string. - /// The first entry in the generated path. - /// The second entry in the generated path. - /// : The operation was successful.
- /// : was too small to contain the built path.
- internal static Result SetUpFixedPathDoubleEntry(ref Path path, Span pathBuffer, - ReadOnlySpan entryName1, ReadOnlySpan entryName2) + finally { - var sb = new U8StringBuilder(pathBuffer); - sb.Append((byte)'/').Append(entryName1) - .Append((byte)'/').Append(entryName2); - - if (sb.Overflowed) - return ResultFs.InvalidArgument.Log(); - - return SetUpFixedPath(ref path, pathBuffer); - } - - // /%016llx - /// - /// Initializes a using the format string /%016llx - /// - /// The to be initialized. - /// The buffer that will contain the built string. - /// The save data ID to insert into the path. - /// : The operation was successful.
- /// : was too small to contain the built path.
- internal static Result SetUpFixedPathSaveId(ref Path path, Span pathBuffer, ulong saveDataId) - { - var sb = new U8StringBuilder(pathBuffer); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); - - if (sb.Overflowed) - return ResultFs.InvalidArgument.Log(); - - return SetUpFixedPath(ref path, pathBuffer); - } - - // /%08x.meta - /// - /// Initializes a using the format string /%08x.meta - /// - /// The to be initialized. - /// The buffer that will contain the built string. - /// The to insert into the path. - /// : The operation was successful.
- /// : was too small to contain the built path.
- internal static Result SetUpFixedPathSaveMetaName(ref Path path, Span pathBuffer, uint metaType) - { - ReadOnlySpan metaExtension = new[] { (byte)'.', (byte)'m', (byte)'e', (byte)'t', (byte)'a' }; // ".meta" - - var sb = new U8StringBuilder(pathBuffer); - sb.Append((byte)'/').AppendFormat(metaType, 'x', 8).Append(metaExtension); - - if (sb.Overflowed) - return ResultFs.InvalidArgument.Log(); - - return SetUpFixedPath(ref path, pathBuffer); - } - - // /saveMeta/%016llx - /// - /// Initializes a using the format string /saveMeta/%016llx - /// - /// The to be initialized. - /// The buffer that will contain the built string. - /// The save data ID to insert into the path. - /// : The operation was successful.
- /// : was too small to contain the built path.
- internal static Result SetUpFixedPathSaveMetaDir(ref Path path, Span pathBuffer, ulong saveDataId) - { - ReadOnlySpan metaDirectoryName = new[] + if (rentedArray is not null) { + ArrayPool.Shared.Return(rentedArray); + } + } + } + + public override readonly string ToString() => StringUtils.Utf8ZToString(_string); + + public override bool Equals(object obj) => throw new NotSupportedException(); + public override int GetHashCode() => throw new NotImplementedException(); +} + +public static class PathFunctions +{ + /// + /// Initializes a with the provided basic path. + /// The provided path must be a normalized basic path starting with a directory separator + /// and not containing any sort of prefix such as a mount name. + /// + /// The to initialize. + /// The string used to initialize the . + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is in an invalid format or is not normalized.
+ public static Result SetUpFixedPath(ref Path path, ReadOnlySpan pathBuffer) + { + Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, pathBuffer); + if (rc.IsFailure()) return rc; + + if (!isNormalized) + return ResultFs.InvalidPathFormat.Log(); + + rc = path.SetShallowBuffer(pathBuffer); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + // Only a small number of format strings are used with these functions, so we can hard code them all easily. + + // /%s + /// + /// Initializes a using the format string /%s + /// + /// The to be initialized. + /// The buffer that will contain the built string. + /// The first entry in the generated path. + /// : The operation was successful.
+ /// : was too small to contain the built path.
+ internal static Result SetUpFixedPathSingleEntry(ref Path path, Span pathBuffer, + ReadOnlySpan entryName) + { + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').Append(entryName); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /%s/%s + /// + /// Initializes a using the format string /%s/%s + /// + /// The to be initialized. + /// The buffer that will contain the built string. + /// The first entry in the generated path. + /// The second entry in the generated path. + /// : The operation was successful.
+ /// : was too small to contain the built path.
+ internal static Result SetUpFixedPathDoubleEntry(ref Path path, Span pathBuffer, + ReadOnlySpan entryName1, ReadOnlySpan entryName2) + { + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').Append(entryName1) + .Append((byte)'/').Append(entryName2); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /%016llx + /// + /// Initializes a using the format string /%016llx + /// + /// The to be initialized. + /// The buffer that will contain the built string. + /// The save data ID to insert into the path. + /// : The operation was successful.
+ /// : was too small to contain the built path.
+ internal static Result SetUpFixedPathSaveId(ref Path path, Span pathBuffer, ulong saveDataId) + { + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /%08x.meta + /// + /// Initializes a using the format string /%08x.meta + /// + /// The to be initialized. + /// The buffer that will contain the built string. + /// The to insert into the path. + /// : The operation was successful.
+ /// : was too small to contain the built path.
+ internal static Result SetUpFixedPathSaveMetaName(ref Path path, Span pathBuffer, uint metaType) + { + ReadOnlySpan metaExtension = new[] { (byte)'.', (byte)'m', (byte)'e', (byte)'t', (byte)'a' }; // ".meta" + + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').AppendFormat(metaType, 'x', 8).Append(metaExtension); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /saveMeta/%016llx + /// + /// Initializes a using the format string /saveMeta/%016llx + /// + /// The to be initialized. + /// The buffer that will contain the built string. + /// The save data ID to insert into the path. + /// : The operation was successful.
+ /// : was too small to contain the built path.
+ internal static Result SetUpFixedPathSaveMetaDir(ref Path path, Span pathBuffer, ulong saveDataId) + { + ReadOnlySpan metaDirectoryName = new[] + { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', (byte)'a', (byte)'/' }; - var sb = new U8StringBuilder(pathBuffer); - sb.Append(metaDirectoryName).AppendFormat(saveDataId, 'x', 16); + var sb = new U8StringBuilder(pathBuffer); + sb.Append(metaDirectoryName).AppendFormat(saveDataId, 'x', 16); - if (sb.Overflowed) - return ResultFs.InvalidArgument.Log(); + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); - return SetUpFixedPath(ref path, pathBuffer); - } + return SetUpFixedPath(ref path, pathBuffer); } } diff --git a/src/LibHac/Fs/Common/PathFormatter.cs b/src/LibHac/Fs/Common/PathFormatter.cs index 6798b699..43638349 100644 --- a/src/LibHac/Fs/Common/PathFormatter.cs +++ b/src/LibHac/Fs/Common/PathFormatter.cs @@ -9,627 +9,626 @@ using LibHac.Util; using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// Contains functions for working with path formatting and normalization. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public static class PathFormatter { - /// - /// Contains functions for working with path formatting and normalization. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public static class PathFormatter + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Result CheckHostName(ReadOnlySpan name) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Result CheckHostName(ReadOnlySpan name) + if (name.Length == 2 && name[0] == Dot && name[1] == Dot) + return ResultFs.InvalidPathFormat.Log(); + + for (int i = 0; i < name.Length; i++) { - if (name.Length == 2 && name[0] == Dot && name[1] == Dot) + if (name[i] == ':' || name[i] == '$') return ResultFs.InvalidPathFormat.Log(); - - for (int i = 0; i < name.Length; i++) - { - if (name[i] == ':' || name[i] == '$') - return ResultFs.InvalidPathFormat.Log(); - } - - return Result.Success; } - private static Result CheckSharedName(ReadOnlySpan name) + return Result.Success; + } + + private static Result CheckSharedName(ReadOnlySpan name) + { + if (name.Length == 1 && name[0] == Dot) + return ResultFs.InvalidPathFormat.Log(); + + if (name.Length == 2 && name[0] == Dot && name[1] == Dot) + return ResultFs.InvalidPathFormat.Log(); + + for (int i = 0; i < name.Length; i++) { - if (name.Length == 1 && name[0] == Dot) + if (name[i] == ':') return ResultFs.InvalidPathFormat.Log(); - - if (name.Length == 2 && name[0] == Dot && name[1] == Dot) - return ResultFs.InvalidPathFormat.Log(); - - for (int i = 0; i < name.Length; i++) - { - if (name[i] == ':') - return ResultFs.InvalidPathFormat.Log(); - } - - return Result.Success; } - public static Result ParseMountName(out ReadOnlySpan newPath, out int mountNameLength, - Span outMountNameBuffer, ReadOnlySpan path) + return Result.Success; + } + + public static Result ParseMountName(out ReadOnlySpan newPath, out int mountNameLength, + Span outMountNameBuffer, ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + UnsafeHelpers.SkipParamInit(out mountNameLength); + newPath = default; + + int maxMountLength = outMountNameBuffer.Length == 0 + ? PathTools.MountNameLengthMax + 1 + : Math.Min(outMountNameBuffer.Length, PathTools.MountNameLengthMax + 1); + + int mountLength; + for (mountLength = 0; mountLength < maxMountLength && path.At(mountLength) != 0; mountLength++) { - Assert.SdkRequiresNotNull(path); + byte c = path[mountLength]; - UnsafeHelpers.SkipParamInit(out mountNameLength); - newPath = default; - - int maxMountLength = outMountNameBuffer.Length == 0 - ? PathTools.MountNameLengthMax + 1 - : Math.Min(outMountNameBuffer.Length, PathTools.MountNameLengthMax + 1); - - int mountLength; - for (mountLength = 0; mountLength < maxMountLength && path.At(mountLength) != 0; mountLength++) + if (c == DriveSeparator) { - byte c = path[mountLength]; - - if (c == DriveSeparator) - { - mountLength++; - break; - } - - if (c == DirectorySeparator || c == AltDirectorySeparator) - { - newPath = path; - mountNameLength = 0; - - return Result.Success; - } + mountLength++; + break; } - if (mountLength <= 2 || path[mountLength - 1] != DriveSeparator) + if (c == DirectorySeparator || c == AltDirectorySeparator) { newPath = path; mountNameLength = 0; return Result.Success; } + } - for (int i = 0; i < mountLength; i++) + if (mountLength <= 2 || path[mountLength - 1] != DriveSeparator) + { + newPath = path; + mountNameLength = 0; + + return Result.Success; + } + + for (int i = 0; i < mountLength; i++) + { + if (path.At(i) is (byte)'*' or (byte)'?' or (byte)'<' or (byte)'>' or (byte)'|') + return ResultFs.InvalidCharacter.Log(); + } + + if (!outMountNameBuffer.IsEmpty) + { + if (mountLength >= outMountNameBuffer.Length) + return ResultFs.TooLongPath.Log(); + + path.Slice(0, mountLength).CopyTo(outMountNameBuffer); + outMountNameBuffer[mountLength] = NullTerminator; + } + + newPath = path.Slice(mountLength); + mountNameLength = mountLength; + return Result.Success; + } + + public static Result SkipMountName(out ReadOnlySpan newPath, out int mountNameLength, + ReadOnlySpan path) + { + return ParseMountName(out newPath, out mountNameLength, Span.Empty, path); + } + + private static Result ParseWindowsPathImpl(out ReadOnlySpan newPath, out int windowsPathLength, + Span normalizeBuffer, ReadOnlySpan path, bool hasMountName) + { + Assert.SdkRequiresNotNull(path); + + UnsafeHelpers.SkipParamInit(out windowsPathLength); + newPath = default; + + if (normalizeBuffer.Length != 0) + normalizeBuffer[0] = NullTerminator; + + ReadOnlySpan currentPath = path; + + if (hasMountName && path.At(0) == DirectorySeparator) + { + if (path.At(1) == AltDirectorySeparator && path.At(2) == AltDirectorySeparator) { - if (path.At(i) is (byte)'*' or (byte)'?' or (byte)'<' or (byte)'>' or (byte)'|') - return ResultFs.InvalidCharacter.Log(); + if (normalizeBuffer.Length == 0) + return ResultFs.NotNormalized.Log(); + + currentPath = path.Slice(1); + } + else if (path.Length != 0 && WindowsPath.IsWindowsDrive(path.Slice(1))) + { + if (normalizeBuffer.Length == 0) + return ResultFs.NotNormalized.Log(); + + currentPath = path.Slice(1); + } + } + + if (WindowsPath.IsWindowsDrive(currentPath)) + { + int winPathLength; + for (winPathLength = 2; currentPath.At(winPathLength) != NullTerminator; winPathLength++) + { + if (currentPath[winPathLength] == DirectorySeparator || + currentPath[winPathLength] == AltDirectorySeparator) + { + break; + } } - if (!outMountNameBuffer.IsEmpty) + if (normalizeBuffer.IsEmpty) { - if (mountLength >= outMountNameBuffer.Length) + for (int i = 0; i < winPathLength; i++) + { + if (currentPath[i] == '\\') + return ResultFs.NotNormalized.Log(); + } + } + + if (!normalizeBuffer.IsEmpty) + { + if (winPathLength >= normalizeBuffer.Length) return ResultFs.TooLongPath.Log(); - path.Slice(0, mountLength).CopyTo(outMountNameBuffer); - outMountNameBuffer[mountLength] = NullTerminator; + currentPath.Slice(0, winPathLength).CopyTo(normalizeBuffer); + normalizeBuffer[winPathLength] = NullTerminator; + PathUtility.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator, + DirectorySeparator); } - newPath = path.Slice(mountLength); - mountNameLength = mountLength; + newPath = currentPath.Slice(winPathLength); + windowsPathLength = winPathLength; return Result.Success; } - public static Result SkipMountName(out ReadOnlySpan newPath, out int mountNameLength, - ReadOnlySpan path) + if (WindowsPath.IsDosDevicePath(currentPath)) { - return ParseMountName(out newPath, out mountNameLength, Span.Empty, path); - } + int dosPathLength = WindowsPath.GetDosDevicePathPrefixLength(); - private static Result ParseWindowsPathImpl(out ReadOnlySpan newPath, out int windowsPathLength, - Span normalizeBuffer, ReadOnlySpan path, bool hasMountName) - { - Assert.SdkRequiresNotNull(path); - - UnsafeHelpers.SkipParamInit(out windowsPathLength); - newPath = default; - - if (normalizeBuffer.Length != 0) - normalizeBuffer[0] = NullTerminator; - - ReadOnlySpan currentPath = path; - - if (hasMountName && path.At(0) == DirectorySeparator) + if (WindowsPath.IsWindowsDrive(currentPath.Slice(dosPathLength))) { - if (path.At(1) == AltDirectorySeparator && path.At(2) == AltDirectorySeparator) - { - if (normalizeBuffer.Length == 0) - return ResultFs.NotNormalized.Log(); - - currentPath = path.Slice(1); - } - else if (path.Length != 0 && WindowsPath.IsWindowsDrive(path.Slice(1))) - { - if (normalizeBuffer.Length == 0) - return ResultFs.NotNormalized.Log(); - - currentPath = path.Slice(1); - } + dosPathLength += 2; + } + else + { + dosPathLength--; } - if (WindowsPath.IsWindowsDrive(currentPath)) + if (!normalizeBuffer.IsEmpty) { - int winPathLength; - for (winPathLength = 2; currentPath.At(winPathLength) != NullTerminator; winPathLength++) - { - if (currentPath[winPathLength] == DirectorySeparator || - currentPath[winPathLength] == AltDirectorySeparator) - { - break; - } - } + if (dosPathLength >= normalizeBuffer.Length) + return ResultFs.TooLongPath.Log(); - if (normalizeBuffer.IsEmpty) - { - for (int i = 0; i < winPathLength; i++) - { - if (currentPath[i] == '\\') - return ResultFs.NotNormalized.Log(); - } - } - - if (!normalizeBuffer.IsEmpty) - { - if (winPathLength >= normalizeBuffer.Length) - return ResultFs.TooLongPath.Log(); - - currentPath.Slice(0, winPathLength).CopyTo(normalizeBuffer); - normalizeBuffer[winPathLength] = NullTerminator; - PathUtility.Replace(normalizeBuffer.Slice(0, winPathLength), AltDirectorySeparator, - DirectorySeparator); - } - - newPath = currentPath.Slice(winPathLength); - windowsPathLength = winPathLength; - return Result.Success; + currentPath.Slice(0, dosPathLength).CopyTo(normalizeBuffer); + normalizeBuffer[dosPathLength] = NullTerminator; + PathUtility.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator, + AltDirectorySeparator); } - if (WindowsPath.IsDosDevicePath(currentPath)) - { - int dosPathLength = WindowsPath.GetDosDevicePathPrefixLength(); - - if (WindowsPath.IsWindowsDrive(currentPath.Slice(dosPathLength))) - { - dosPathLength += 2; - } - else - { - dosPathLength--; - } - - if (!normalizeBuffer.IsEmpty) - { - if (dosPathLength >= normalizeBuffer.Length) - return ResultFs.TooLongPath.Log(); - - currentPath.Slice(0, dosPathLength).CopyTo(normalizeBuffer); - normalizeBuffer[dosPathLength] = NullTerminator; - PathUtility.Replace(normalizeBuffer.Slice(0, dosPathLength), DirectorySeparator, - AltDirectorySeparator); - } - - newPath = currentPath.Slice(dosPathLength); - windowsPathLength = dosPathLength; - return Result.Success; - } - - if (WindowsPath.IsUncPath(currentPath, false, true)) - { - Result rc; - - ReadOnlySpan finalPath = currentPath; - - if (currentPath.At(2) == DirectorySeparator || currentPath.At(2) == AltDirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - - int currentComponentOffset = 0; - int pos; - for (pos = 2; currentPath.At(pos) != NullTerminator; pos++) - { - if (currentPath.At(pos) == DirectorySeparator || currentPath.At(pos) == AltDirectorySeparator) - { - if (currentComponentOffset != 0) - { - rc = CheckSharedName( - currentPath.Slice(currentComponentOffset, pos - currentComponentOffset)); - if (rc.IsFailure()) return rc; - - finalPath = currentPath.Slice(pos); - break; - } - - if (currentPath.At(pos + 1) == DirectorySeparator || currentPath.At(pos + 1) == AltDirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - - rc = CheckHostName(currentPath.Slice(2, pos - 2)); - if (rc.IsFailure()) return rc; - - currentComponentOffset = pos + 1; - } - } - - if (currentComponentOffset == pos) - return ResultFs.InvalidPathFormat.Log(); - - if (currentComponentOffset != 0 && finalPath == currentPath) - { - rc = CheckSharedName(currentPath.Slice(currentComponentOffset, pos - currentComponentOffset)); - if (rc.IsFailure()) return rc; - - finalPath = currentPath.Slice(pos); - } - - ref byte currentPathStart = ref MemoryMarshal.GetReference(currentPath); - ref byte finalPathStart = ref MemoryMarshal.GetReference(finalPath); - int uncPrefixLength = (int)Unsafe.ByteOffset(ref currentPathStart, ref finalPathStart); - - if (normalizeBuffer.IsEmpty) - { - for (int i = 0; i < uncPrefixLength; i++) - { - if (currentPath[i] == DirectorySeparator) - return ResultFs.NotNormalized.Log(); - } - } - - if (!normalizeBuffer.IsEmpty) - { - if (uncPrefixLength >= normalizeBuffer.Length) - return ResultFs.TooLongPath.Log(); - - currentPath.Slice(0, uncPrefixLength).CopyTo(normalizeBuffer); - normalizeBuffer[uncPrefixLength] = NullTerminator; - PathUtility.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator); - } - - newPath = finalPath; - windowsPathLength = uncPrefixLength; - return Result.Success; - } - - newPath = path; - windowsPathLength = 0; + newPath = currentPath.Slice(dosPathLength); + windowsPathLength = dosPathLength; return Result.Success; } - public static Result ParseWindowsPath(out ReadOnlySpan newPath, out int windowsPathLength, - Span normalizeBuffer, ReadOnlySpan path, bool hasMountName) - { - return ParseWindowsPathImpl(out newPath, out windowsPathLength, normalizeBuffer, path, hasMountName); - } - - public static Result SkipWindowsPath(out ReadOnlySpan newPath, out int windowsPathLength, - out bool isNormalized, ReadOnlySpan path, bool hasMountName) - { - isNormalized = true; - - Result rc = ParseWindowsPathImpl(out newPath, out windowsPathLength, Span.Empty, path, hasMountName); - if (!rc.IsSuccess()) - { - if (ResultFs.NotNormalized.Includes(rc)) - { - isNormalized = false; - } - else - { - return rc; - } - } - - return Result.Success; - } - - private static Result ParseRelativeDotPathImpl(out ReadOnlySpan newPath, out int length, - Span relativePathBuffer, ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - UnsafeHelpers.SkipParamInit(out length); - newPath = default; - - if (relativePathBuffer.Length != 0) - relativePathBuffer[0] = NullTerminator; - - if (path.At(0) == Dot && (path.At(1) == NullTerminator || path.At(1) == DirectorySeparator || - path.At(1) == AltDirectorySeparator)) - { - if (relativePathBuffer.Length >= 2) - { - relativePathBuffer[0] = Dot; - relativePathBuffer[1] = NullTerminator; - } - - newPath = path.Slice(1); - length = 1; - return Result.Success; - } - - if (path.At(0) == Dot && path.At(1) == Dot) - return ResultFs.DirectoryUnobtainable.Log(); - - newPath = path; - length = 0; - return Result.Success; - } - - public static Result ParseRelativeDotPath(out ReadOnlySpan newPath, out int length, - Span relativePathBuffer, ReadOnlySpan path) - { - return ParseRelativeDotPathImpl(out newPath, out length, relativePathBuffer, path); - } - - public static Result SkipRelativeDotPath(out ReadOnlySpan newPath, out int length, - ReadOnlySpan path) - { - return ParseRelativeDotPathImpl(out newPath, out length, Span.Empty, path); - } - - public static Result IsNormalized(out bool isNormalized, out int normalizedLength, ReadOnlySpan path, - PathFlags flags) - { - UnsafeHelpers.SkipParamInit(out isNormalized, out normalizedLength); - - Result rc = PathUtility.CheckUtf8(path); - if (rc.IsFailure()) return rc; - - ReadOnlySpan buffer = path; - int totalLength = 0; - - if (path.At(0) == NullTerminator) - { - if (!flags.IsEmptyPathAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - isNormalized = true; - normalizedLength = 0; - return Result.Success; - } - - if (path.At(0) != DirectorySeparator && - !flags.IsWindowsPathAllowed() && - !flags.IsRelativePathAllowed() && - !flags.IsMountNameAllowed()) - { - return ResultFs.InvalidPathFormat.Log(); - } - - if (WindowsPath.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - bool hasMountName = false; - - rc = SkipMountName(out buffer, out int mountNameLength, buffer); - if (rc.IsFailure()) return rc; - - if (mountNameLength != 0) - { - if (!flags.IsMountNameAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - totalLength += mountNameLength; - hasMountName = true; - } - - if (buffer.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(buffer) && - !WindowsPath.IsWindowsPath(buffer, false)) - { - if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(buffer.At(0)).IsSuccess()) - return ResultFs.InvalidPathFormat.Log(); - - isNormalized = false; - return Result.Success; - } - - bool isRelativePath = false; - - rc = SkipRelativeDotPath(out buffer, out int relativePathLength, buffer); - if (rc.IsFailure()) return rc; - - if (relativePathLength != 0) - { - if (!flags.IsRelativePathAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - totalLength += relativePathLength; - - if (buffer.At(0) == NullTerminator) - { - isNormalized = true; - normalizedLength = totalLength; - return Result.Success; - } - - isRelativePath = true; - } - - rc = SkipWindowsPath(out buffer, out int windowsPathLength, out bool isNormalizedWin, buffer, hasMountName); - if (rc.IsFailure()) return rc; - - if (!isNormalizedWin) - { - if (!flags.IsWindowsPathAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - isNormalized = false; - return Result.Success; - } - - if (windowsPathLength != 0) - { - if (!flags.IsWindowsPathAllowed()) - return ResultFs.InvalidPathFormat.Log(); - - totalLength += windowsPathLength; - - if (isRelativePath) - return ResultFs.InvalidPathFormat.Log(); - - if (buffer.At(0) == NullTerminator) - { - isNormalized = false; - return Result.Success; - } - - for (int i = 0; i < buffer.Length; i++) - { - if (buffer[i] == AltDirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - } - } - - if (PathNormalizer.IsParentDirectoryPathReplacementNeeded(buffer)) - return ResultFs.DirectoryUnobtainable.Log(); - - rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, buffer, - flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()); - if (rc.IsFailure()) return rc; - - if (isBackslashContained && !flags.IsBackslashAllowed()) - { - isNormalized = false; - return Result.Success; - } - - rc = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer); - if (rc.IsFailure()) return rc; - - totalLength += length; - normalizedLength = totalLength; - return Result.Success; - } - - public static Result Normalize(Span outputBuffer, ReadOnlySpan path, PathFlags flags) + if (WindowsPath.IsUncPath(currentPath, false, true)) { Result rc; - ReadOnlySpan src = path; - int currentPos = 0; - bool isWindowsPath = false; + ReadOnlySpan finalPath = currentPath; - if (path.At(0) == NullTerminator) + if (currentPath.At(2) == DirectorySeparator || currentPath.At(2) == AltDirectorySeparator) + return ResultFs.InvalidPathFormat.Log(); + + int currentComponentOffset = 0; + int pos; + for (pos = 2; currentPath.At(pos) != NullTerminator; pos++) { - if (!flags.IsEmptyPathAllowed()) - return ResultFs.InvalidPathFormat.Log(); + if (currentPath.At(pos) == DirectorySeparator || currentPath.At(pos) == AltDirectorySeparator) + { + if (currentComponentOffset != 0) + { + rc = CheckSharedName( + currentPath.Slice(currentComponentOffset, pos - currentComponentOffset)); + if (rc.IsFailure()) return rc; - if (outputBuffer.Length != 0) - outputBuffer[0] = NullTerminator; + finalPath = currentPath.Slice(pos); + break; + } - return Result.Success; + if (currentPath.At(pos + 1) == DirectorySeparator || currentPath.At(pos + 1) == AltDirectorySeparator) + return ResultFs.InvalidPathFormat.Log(); + + rc = CheckHostName(currentPath.Slice(2, pos - 2)); + if (rc.IsFailure()) return rc; + + currentComponentOffset = pos + 1; + } } - bool hasMountName = false; + if (currentComponentOffset == pos) + return ResultFs.InvalidPathFormat.Log(); - if (flags.IsMountNameAllowed()) + if (currentComponentOffset != 0 && finalPath == currentPath) { - rc = ParseMountName(out src, out int mountNameLength, outputBuffer.Slice(currentPos), src); + rc = CheckSharedName(currentPath.Slice(currentComponentOffset, pos - currentComponentOffset)); if (rc.IsFailure()) return rc; - currentPos += mountNameLength; - hasMountName = mountNameLength != 0; + finalPath = currentPath.Slice(pos); } - bool isDriveRelative = false; + ref byte currentPathStart = ref MemoryMarshal.GetReference(currentPath); + ref byte finalPathStart = ref MemoryMarshal.GetReference(finalPath); + int uncPrefixLength = (int)Unsafe.ByteOffset(ref currentPathStart, ref finalPathStart); - if (src.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(src) && - !WindowsPath.IsWindowsPath(src, false)) + if (normalizeBuffer.IsEmpty) { - if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(src.At(0)).IsSuccess()) - return ResultFs.InvalidPathFormat.Log(); - - outputBuffer[currentPos++] = Dot; - isDriveRelative = true; + for (int i = 0; i < uncPrefixLength; i++) + { + if (currentPath[i] == DirectorySeparator) + return ResultFs.NotNormalized.Log(); + } } - if (flags.IsRelativePathAllowed()) + if (!normalizeBuffer.IsEmpty) { - if (currentPos >= outputBuffer.Length) + if (uncPrefixLength >= normalizeBuffer.Length) return ResultFs.TooLongPath.Log(); - rc = ParseRelativeDotPath(out src, out int relativePathLength, outputBuffer.Slice(currentPos), src); - if (rc.IsFailure()) return rc; - - currentPos += relativePathLength; - - if (src.At(0) == NullTerminator) - { - if (currentPos >= outputBuffer.Length) - return ResultFs.TooLongPath.Log(); - - outputBuffer[currentPos] = NullTerminator; - return Result.Success; - } + currentPath.Slice(0, uncPrefixLength).CopyTo(normalizeBuffer); + normalizeBuffer[uncPrefixLength] = NullTerminator; + PathUtility.Replace(normalizeBuffer.Slice(0, uncPrefixLength), DirectorySeparator, AltDirectorySeparator); } - if (flags.IsWindowsPathAllowed()) - { - ReadOnlySpan originalPath = src; - - if (currentPos >= outputBuffer.Length) - return ResultFs.TooLongPath.Log(); - - rc = ParseWindowsPath(out src, out int windowsPathLength, outputBuffer.Slice(currentPos), src, - hasMountName); - if (rc.IsFailure()) return rc; - - currentPos += windowsPathLength; - - if (src.At(0) == NullTerminator) - { - // Note: Bug is in the original code. Should be "currentPos + 2" - if (currentPos + 1 >= outputBuffer.Length) - return ResultFs.TooLongPath.Log(); - - outputBuffer[currentPos] = DirectorySeparator; - outputBuffer[currentPos + 1] = NullTerminator; - return Result.Success; - } - - int skippedLength = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(originalPath), - ref MemoryMarshal.GetReference(src)); - - if (skippedLength > 0) - isWindowsPath = true; - } - - rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, src, - flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()); - if (rc.IsFailure()) return rc; - - byte[] srcBufferSlashReplaced = null; - try - { - if (isBackslashContained && flags.IsWindowsPathAllowed()) - { - srcBufferSlashReplaced = ArrayPool.Shared.Rent(path.Length); - - StringUtils.Copy(srcBufferSlashReplaced, path); - PathUtility.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator); - - int srcOffset = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(path), - ref MemoryMarshal.GetReference(src)); - - src = srcBufferSlashReplaced.AsSpan(srcOffset); - } - - rc = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - finally - { - if (srcBufferSlashReplaced is not null) - { - ArrayPool.Shared.Return(srcBufferSlashReplaced); - } - } - } - - public static Result CheckPathFormat(ReadOnlySpan path, PathFlags flags) - { + newPath = finalPath; + windowsPathLength = uncPrefixLength; return Result.Success; } + + newPath = path; + windowsPathLength = 0; + return Result.Success; + } + + public static Result ParseWindowsPath(out ReadOnlySpan newPath, out int windowsPathLength, + Span normalizeBuffer, ReadOnlySpan path, bool hasMountName) + { + return ParseWindowsPathImpl(out newPath, out windowsPathLength, normalizeBuffer, path, hasMountName); + } + + public static Result SkipWindowsPath(out ReadOnlySpan newPath, out int windowsPathLength, + out bool isNormalized, ReadOnlySpan path, bool hasMountName) + { + isNormalized = true; + + Result rc = ParseWindowsPathImpl(out newPath, out windowsPathLength, Span.Empty, path, hasMountName); + if (!rc.IsSuccess()) + { + if (ResultFs.NotNormalized.Includes(rc)) + { + isNormalized = false; + } + else + { + return rc; + } + } + + return Result.Success; + } + + private static Result ParseRelativeDotPathImpl(out ReadOnlySpan newPath, out int length, + Span relativePathBuffer, ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + UnsafeHelpers.SkipParamInit(out length); + newPath = default; + + if (relativePathBuffer.Length != 0) + relativePathBuffer[0] = NullTerminator; + + if (path.At(0) == Dot && (path.At(1) == NullTerminator || path.At(1) == DirectorySeparator || + path.At(1) == AltDirectorySeparator)) + { + if (relativePathBuffer.Length >= 2) + { + relativePathBuffer[0] = Dot; + relativePathBuffer[1] = NullTerminator; + } + + newPath = path.Slice(1); + length = 1; + return Result.Success; + } + + if (path.At(0) == Dot && path.At(1) == Dot) + return ResultFs.DirectoryUnobtainable.Log(); + + newPath = path; + length = 0; + return Result.Success; + } + + public static Result ParseRelativeDotPath(out ReadOnlySpan newPath, out int length, + Span relativePathBuffer, ReadOnlySpan path) + { + return ParseRelativeDotPathImpl(out newPath, out length, relativePathBuffer, path); + } + + public static Result SkipRelativeDotPath(out ReadOnlySpan newPath, out int length, + ReadOnlySpan path) + { + return ParseRelativeDotPathImpl(out newPath, out length, Span.Empty, path); + } + + public static Result IsNormalized(out bool isNormalized, out int normalizedLength, ReadOnlySpan path, + PathFlags flags) + { + UnsafeHelpers.SkipParamInit(out isNormalized, out normalizedLength); + + Result rc = PathUtility.CheckUtf8(path); + if (rc.IsFailure()) return rc; + + ReadOnlySpan buffer = path; + int totalLength = 0; + + if (path.At(0) == NullTerminator) + { + if (!flags.IsEmptyPathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + isNormalized = true; + normalizedLength = 0; + return Result.Success; + } + + if (path.At(0) != DirectorySeparator && + !flags.IsWindowsPathAllowed() && + !flags.IsRelativePathAllowed() && + !flags.IsMountNameAllowed()) + { + return ResultFs.InvalidPathFormat.Log(); + } + + if (WindowsPath.IsWindowsPath(path, false) && !flags.IsWindowsPathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + bool hasMountName = false; + + rc = SkipMountName(out buffer, out int mountNameLength, buffer); + if (rc.IsFailure()) return rc; + + if (mountNameLength != 0) + { + if (!flags.IsMountNameAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + totalLength += mountNameLength; + hasMountName = true; + } + + if (buffer.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(buffer) && + !WindowsPath.IsWindowsPath(buffer, false)) + { + if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(buffer.At(0)).IsSuccess()) + return ResultFs.InvalidPathFormat.Log(); + + isNormalized = false; + return Result.Success; + } + + bool isRelativePath = false; + + rc = SkipRelativeDotPath(out buffer, out int relativePathLength, buffer); + if (rc.IsFailure()) return rc; + + if (relativePathLength != 0) + { + if (!flags.IsRelativePathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + totalLength += relativePathLength; + + if (buffer.At(0) == NullTerminator) + { + isNormalized = true; + normalizedLength = totalLength; + return Result.Success; + } + + isRelativePath = true; + } + + rc = SkipWindowsPath(out buffer, out int windowsPathLength, out bool isNormalizedWin, buffer, hasMountName); + if (rc.IsFailure()) return rc; + + if (!isNormalizedWin) + { + if (!flags.IsWindowsPathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + isNormalized = false; + return Result.Success; + } + + if (windowsPathLength != 0) + { + if (!flags.IsWindowsPathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + totalLength += windowsPathLength; + + if (isRelativePath) + return ResultFs.InvalidPathFormat.Log(); + + if (buffer.At(0) == NullTerminator) + { + isNormalized = false; + return Result.Success; + } + + for (int i = 0; i < buffer.Length; i++) + { + if (buffer[i] == AltDirectorySeparator) + { + isNormalized = false; + return Result.Success; + } + } + } + + if (PathNormalizer.IsParentDirectoryPathReplacementNeeded(buffer)) + return ResultFs.DirectoryUnobtainable.Log(); + + rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, buffer, + flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()); + if (rc.IsFailure()) return rc; + + if (isBackslashContained && !flags.IsBackslashAllowed()) + { + isNormalized = false; + return Result.Success; + } + + rc = PathNormalizer.IsNormalized(out isNormalized, out int length, buffer); + if (rc.IsFailure()) return rc; + + totalLength += length; + normalizedLength = totalLength; + return Result.Success; + } + + public static Result Normalize(Span outputBuffer, ReadOnlySpan path, PathFlags flags) + { + Result rc; + + ReadOnlySpan src = path; + int currentPos = 0; + bool isWindowsPath = false; + + if (path.At(0) == NullTerminator) + { + if (!flags.IsEmptyPathAllowed()) + return ResultFs.InvalidPathFormat.Log(); + + if (outputBuffer.Length != 0) + outputBuffer[0] = NullTerminator; + + return Result.Success; + } + + bool hasMountName = false; + + if (flags.IsMountNameAllowed()) + { + rc = ParseMountName(out src, out int mountNameLength, outputBuffer.Slice(currentPos), src); + if (rc.IsFailure()) return rc; + + currentPos += mountNameLength; + hasMountName = mountNameLength != 0; + } + + bool isDriveRelative = false; + + if (src.At(0) != DirectorySeparator && !PathUtility.IsPathStartWithCurrentDirectory(src) && + !WindowsPath.IsWindowsPath(src, false)) + { + if (!flags.IsRelativePathAllowed() || !PathUtility.CheckInvalidCharacter(src.At(0)).IsSuccess()) + return ResultFs.InvalidPathFormat.Log(); + + outputBuffer[currentPos++] = Dot; + isDriveRelative = true; + } + + if (flags.IsRelativePathAllowed()) + { + if (currentPos >= outputBuffer.Length) + return ResultFs.TooLongPath.Log(); + + rc = ParseRelativeDotPath(out src, out int relativePathLength, outputBuffer.Slice(currentPos), src); + if (rc.IsFailure()) return rc; + + currentPos += relativePathLength; + + if (src.At(0) == NullTerminator) + { + if (currentPos >= outputBuffer.Length) + return ResultFs.TooLongPath.Log(); + + outputBuffer[currentPos] = NullTerminator; + return Result.Success; + } + } + + if (flags.IsWindowsPathAllowed()) + { + ReadOnlySpan originalPath = src; + + if (currentPos >= outputBuffer.Length) + return ResultFs.TooLongPath.Log(); + + rc = ParseWindowsPath(out src, out int windowsPathLength, outputBuffer.Slice(currentPos), src, + hasMountName); + if (rc.IsFailure()) return rc; + + currentPos += windowsPathLength; + + if (src.At(0) == NullTerminator) + { + // Note: Bug is in the original code. Should be "currentPos + 2" + if (currentPos + 1 >= outputBuffer.Length) + return ResultFs.TooLongPath.Log(); + + outputBuffer[currentPos] = DirectorySeparator; + outputBuffer[currentPos + 1] = NullTerminator; + return Result.Success; + } + + int skippedLength = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(originalPath), + ref MemoryMarshal.GetReference(src)); + + if (skippedLength > 0) + isWindowsPath = true; + } + + rc = PathUtility.CheckInvalidBackslash(out bool isBackslashContained, src, + flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()); + if (rc.IsFailure()) return rc; + + byte[] srcBufferSlashReplaced = null; + try + { + if (isBackslashContained && flags.IsWindowsPathAllowed()) + { + srcBufferSlashReplaced = ArrayPool.Shared.Rent(path.Length); + + StringUtils.Copy(srcBufferSlashReplaced, path); + PathUtility.Replace(srcBufferSlashReplaced, AltDirectorySeparator, DirectorySeparator); + + int srcOffset = (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(path), + ref MemoryMarshal.GetReference(src)); + + src = srcBufferSlashReplaced.AsSpan(srcOffset); + } + + rc = PathNormalizer.Normalize(outputBuffer.Slice(currentPos), out _, src, isWindowsPath, isDriveRelative); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + finally + { + if (srcBufferSlashReplaced is not null) + { + ArrayPool.Shared.Return(srcBufferSlashReplaced); + } + } + } + + public static Result CheckPathFormat(ReadOnlySpan path, PathFlags flags) + { + return Result.Success; } } diff --git a/src/LibHac/Fs/Common/PathNormalizer.cs b/src/LibHac/Fs/Common/PathNormalizer.cs index 4a835650..5e14cc5b 100644 --- a/src/LibHac/Fs/Common/PathNormalizer.cs +++ b/src/LibHac/Fs/Common/PathNormalizer.cs @@ -6,367 +6,366 @@ using static LibHac.Fs.PathUtility; using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// Contains functions for doing with basic path normalization. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public static class PathNormalizer { - /// - /// Contains functions for doing with basic path normalization. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public static class PathNormalizer + private enum PathState { - private enum PathState + Initial, + Normal, + FirstSeparator, + Separator, + CurrentDir, + ParentDir + } + + public static Result Normalize(Span outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, + bool isDriveRelativePath) + { + UnsafeHelpers.SkipParamInit(out length); + + ReadOnlySpan currentPath = path; + int totalLength = 0; + int i = 0; + + if (!IsSeparator(path.At(0))) { - Initial, - Normal, - FirstSeparator, - Separator, - CurrentDir, - ParentDir + if (!isDriveRelativePath) + return ResultFs.InvalidPathFormat.Log(); + + outputBuffer[totalLength++] = DirectorySeparator; } - public static Result Normalize(Span outputBuffer, out int length, ReadOnlySpan path, bool isWindowsPath, - bool isDriveRelativePath) + var convertedPath = new RentedArray(); + try { - UnsafeHelpers.SkipParamInit(out length); - - ReadOnlySpan currentPath = path; - int totalLength = 0; - int i = 0; - - if (!IsSeparator(path.At(0))) + // Check if parent directory path replacement is needed. + if (IsParentDirectoryPathReplacementNeeded(currentPath)) { - if (!isDriveRelativePath) - return ResultFs.InvalidPathFormat.Log(); + // Allocate a buffer to hold the replacement path. + convertedPath = new RentedArray(PathTools.MaxPathLength + 1); - outputBuffer[totalLength++] = DirectorySeparator; + // Replace the path. + ReplaceParentDirectoryPath(convertedPath.Span, currentPath); + + // Set current path to be the replacement path. + currentPath = new U8Span(convertedPath.Span); } - var convertedPath = new RentedArray(); - try + bool skipNextSeparator = false; + + while (!IsNul(currentPath.At(i))) { - // Check if parent directory path replacement is needed. - if (IsParentDirectoryPathReplacementNeeded(currentPath)) + if (IsSeparator(currentPath[i])) { - // Allocate a buffer to hold the replacement path. - convertedPath = new RentedArray(PathTools.MaxPathLength + 1); - - // Replace the path. - ReplaceParentDirectoryPath(convertedPath.Span, currentPath); - - // Set current path to be the replacement path. - currentPath = new U8Span(convertedPath.Span); - } - - bool skipNextSeparator = false; - - while (!IsNul(currentPath.At(i))) - { - if (IsSeparator(currentPath[i])) + do { - do - { - i++; - } while (IsSeparator(currentPath.At(i))); + i++; + } while (IsSeparator(currentPath.At(i))); - if (IsNul(currentPath.At(i))) - break; + if (IsNul(currentPath.At(i))) + break; - if (!skipNextSeparator) - { - if (totalLength + 1 == outputBuffer.Length) - { - outputBuffer[totalLength] = NullTerminator; - length = totalLength; - - return ResultFs.TooLongPath.Log(); - } - - outputBuffer[totalLength++] = DirectorySeparator; - } - - skipNextSeparator = false; - } - - int dirLen = 0; - while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen))) + if (!skipNextSeparator) { - dirLen++; - } - - if (IsCurrentDirectory(currentPath.Slice(i))) - { - skipNextSeparator = true; - } - else if (IsParentDirectory(currentPath.Slice(i))) - { - Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator); - - if (!isWindowsPath) - Assert.SdkAssert(outputBuffer[0] == DirectorySeparator); - - if (totalLength == 1) + if (totalLength + 1 == outputBuffer.Length) { - if (!isWindowsPath) - return ResultFs.DirectoryUnobtainable.Log(); - - totalLength--; - } - else - { - totalLength -= 2; - - do - { - if (outputBuffer[totalLength] == DirectorySeparator) - break; - - totalLength--; - } while (totalLength != 0); - } - - if (!isWindowsPath) - Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator); - - Assert.SdkAssert(totalLength < outputBuffer.Length); - } - else - { - if (totalLength + dirLen + 1 > outputBuffer.Length) - { - int copyLen = outputBuffer.Length - 1 - totalLength; - - for (int j = 0; j < copyLen; j++) - { - outputBuffer[totalLength++] = currentPath[i + j]; - } - outputBuffer[totalLength] = NullTerminator; length = totalLength; + return ResultFs.TooLongPath.Log(); } - for (int j = 0; j < dirLen; j++) + outputBuffer[totalLength++] = DirectorySeparator; + } + + skipNextSeparator = false; + } + + int dirLen = 0; + while (!IsSeparator(currentPath.At(i + dirLen)) && !IsNul(currentPath.At(i + dirLen))) + { + dirLen++; + } + + if (IsCurrentDirectory(currentPath.Slice(i))) + { + skipNextSeparator = true; + } + else if (IsParentDirectory(currentPath.Slice(i))) + { + Assert.SdkAssert(outputBuffer[totalLength - 1] == DirectorySeparator); + + if (!isWindowsPath) + Assert.SdkAssert(outputBuffer[0] == DirectorySeparator); + + if (totalLength == 1) + { + if (!isWindowsPath) + return ResultFs.DirectoryUnobtainable.Log(); + + totalLength--; + } + else + { + totalLength -= 2; + + do + { + if (outputBuffer[totalLength] == DirectorySeparator) + break; + + totalLength--; + } while (totalLength != 0); + } + + if (!isWindowsPath) + Assert.SdkAssert(outputBuffer[totalLength] == DirectorySeparator); + + Assert.SdkAssert(totalLength < outputBuffer.Length); + } + else + { + if (totalLength + dirLen + 1 > outputBuffer.Length) + { + int copyLen = outputBuffer.Length - 1 - totalLength; + + for (int j = 0; j < copyLen; j++) { outputBuffer[totalLength++] = currentPath[i + j]; } + + outputBuffer[totalLength] = NullTerminator; + length = totalLength; + return ResultFs.TooLongPath.Log(); } - i += dirLen; + for (int j = 0; j < dirLen; j++) + { + outputBuffer[totalLength++] = currentPath[i + j]; + } } - if (skipNextSeparator) - totalLength--; - - if (totalLength == 0 && outputBuffer.Length != 0) - { - totalLength = 1; - outputBuffer[0] = DirectorySeparator; - } - - // Note: This bug is in the original code. They probably meant to put "totalLength + 1" - if (totalLength - 1 > outputBuffer.Length) - return ResultFs.TooLongPath.Log(); - - outputBuffer[totalLength] = NullTerminator; - - Result rc = IsNormalized(out bool isNormalized, out _, outputBuffer); - if (rc.IsFailure()) return rc; - - Assert.SdkAssert(isNormalized); - - length = totalLength; - return Result.Success; + i += dirLen; } - finally + + if (skipNextSeparator) + totalLength--; + + if (totalLength == 0 && outputBuffer.Length != 0) { - convertedPath.Dispose(); + totalLength = 1; + outputBuffer[0] = DirectorySeparator; } + + // Note: This bug is in the original code. They probably meant to put "totalLength + 1" + if (totalLength - 1 > outputBuffer.Length) + return ResultFs.TooLongPath.Log(); + + outputBuffer[totalLength] = NullTerminator; + + Result rc = IsNormalized(out bool isNormalized, out _, outputBuffer); + if (rc.IsFailure()) return rc; + + Assert.SdkAssert(isNormalized); + + length = totalLength; + return Result.Success; } - - /// - /// Checks if a given path is normalized. Path must be a basic path, starting with a directory separator - /// and not containing any sort of prefix such as a mount name. - /// - /// When this function returns , - /// contains if the path is normalized or if it is not. - /// Contents are undefined if the function does not return . - /// - /// When this function returns and - /// is , contains the length of the normalized path. - /// Contents are undefined if the function does not return - /// or is . - /// - /// The path to check. - /// : The operation was successful.
- /// : The path contains an invalid character.
- /// : The path is not in a valid format.
- public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan path) + finally { - UnsafeHelpers.SkipParamInit(out isNormalized, out length); + convertedPath.Dispose(); + } + } - var state = PathState.Initial; - int pathLength = 0; + /// + /// Checks if a given path is normalized. Path must be a basic path, starting with a directory separator + /// and not containing any sort of prefix such as a mount name. + /// + /// When this function returns , + /// contains if the path is normalized or if it is not. + /// Contents are undefined if the function does not return . + /// + /// When this function returns and + /// is , contains the length of the normalized path. + /// Contents are undefined if the function does not return + /// or is . + /// + /// The path to check. + /// : The operation was successful.
+ /// : The path contains an invalid character.
+ /// : The path is not in a valid format.
+ public static Result IsNormalized(out bool isNormalized, out int length, ReadOnlySpan path) + { + UnsafeHelpers.SkipParamInit(out isNormalized, out length); - for (int i = 0; i < path.Length; i++) + var state = PathState.Initial; + int pathLength = 0; + + for (int i = 0; i < path.Length; i++) + { + byte c = path[i]; + if (c == NullTerminator) break; + + pathLength++; + + if (state != PathState.Initial) { - byte c = path[i]; - if (c == NullTerminator) break; - - pathLength++; - - if (state != PathState.Initial) - { - Result rc = CheckInvalidCharacter(c); - if (rc.IsFailure()) return rc; - } - - switch (state) - { - case PathState.Initial: - if (c != DirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - - state = PathState.FirstSeparator; - - break; - case PathState.Normal: - - if (c == DirectorySeparator) - state = PathState.Separator; - - break; - case PathState.FirstSeparator: - case PathState.Separator: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? PathState.CurrentDir : PathState.Normal; - break; - case PathState.CurrentDir: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = c == Dot ? PathState.ParentDir : PathState.Normal; - break; - case PathState.ParentDir: - if (c == DirectorySeparator) - { - isNormalized = false; - return Result.Success; - } - - state = PathState.Normal; - break; - // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis - default: - Abort.UnexpectedDefault(); - break; - } + Result rc = CheckInvalidCharacter(c); + if (rc.IsFailure()) return rc; } switch (state) { case PathState.Initial: - return ResultFs.InvalidPathFormat.Log(); - case PathState.Normal: - case PathState.FirstSeparator: - isNormalized = true; + if (c != DirectorySeparator) + return ResultFs.InvalidPathFormat.Log(); + + state = PathState.FirstSeparator; + break; + case PathState.Normal: + + if (c == DirectorySeparator) + state = PathState.Separator; + + break; + case PathState.FirstSeparator: case PathState.Separator: + if (c == DirectorySeparator) + { + isNormalized = false; + return Result.Success; + } + + state = c == Dot ? PathState.CurrentDir : PathState.Normal; + break; case PathState.CurrentDir: + if (c == DirectorySeparator) + { + isNormalized = false; + return Result.Success; + } + + state = c == Dot ? PathState.ParentDir : PathState.Normal; + break; case PathState.ParentDir: - isNormalized = false; + if (c == DirectorySeparator) + { + isNormalized = false; + return Result.Success; + } + + state = PathState.Normal; break; // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis default: Abort.UnexpectedDefault(); break; } - - length = pathLength; - return Result.Success; } - - /// - /// Checks if a path begins with / or \ and contains any of these patterns: - /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. - /// - public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan path) + switch (state) { - if (path.Length == 0 || (path[0] != DirectorySeparator && path[0] != AltDirectorySeparator)) - return false; + case PathState.Initial: + return ResultFs.InvalidPathFormat.Log(); + case PathState.Normal: + case PathState.FirstSeparator: + isNormalized = true; + break; + case PathState.Separator: + case PathState.CurrentDir: + case PathState.ParentDir: + isNormalized = false; + break; + // ReSharper disable once UnreachableSwitchCaseDueToIntegerAnalysis + default: + Abort.UnexpectedDefault(); + break; + } - for (int i = 0; i < path.Length - 2 && path[i] != NullTerminator; i++) + length = pathLength; + return Result.Success; + } + + + /// + /// Checks if a path begins with / or \ and contains any of these patterns: + /// "/..\", "\..\", "\../", "\..0" where '0' is the null terminator. + /// + public static bool IsParentDirectoryPathReplacementNeeded(ReadOnlySpan path) + { + if (path.Length == 0 || (path[0] != DirectorySeparator && path[0] != AltDirectorySeparator)) + return false; + + for (int i = 0; i < path.Length - 2 && path[i] != NullTerminator; i++) + { + byte c3 = path.At(i + 3); + + if (path[i] == AltDirectorySeparator && + path[i + 1] == Dot && + path[i + 2] == Dot && + (c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator)) { - byte c3 = path.At(i + 3); - - if (path[i] == AltDirectorySeparator && - path[i + 1] == Dot && - path[i + 2] == Dot && - (c3 == DirectorySeparator || c3 == AltDirectorySeparator || c3 == NullTerminator)) - { - return true; - } - - if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) && - path[i + 1] == Dot && - path[i + 2] == Dot && - c3 == AltDirectorySeparator) - { - return true; - } + return true; } - return false; + if ((path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) && + path[i + 1] == Dot && + path[i + 2] == Dot && + c3 == AltDirectorySeparator) + { + return true; + } } - private static void ReplaceParentDirectoryPath(Span dest, ReadOnlySpan source) - { - dest[0] = DirectorySeparator; + return false; + } - int i = 1; - while (source.Length > i && source[i] != NullTerminator) + private static void ReplaceParentDirectoryPath(Span dest, ReadOnlySpan source) + { + dest[0] = DirectorySeparator; + + int i = 1; + while (source.Length > i && source[i] != NullTerminator) + { + if (source.Length > i + 2 && + (source[i - 1] == DirectorySeparator || source[i - 1] == AltDirectorySeparator) && + source[i + 0] == Dot && + source[i + 1] == Dot && + (source[i + 2] == DirectorySeparator || source[i + 2] == AltDirectorySeparator)) { - if (source.Length > i + 2 && - (source[i - 1] == DirectorySeparator || source[i - 1] == AltDirectorySeparator) && - source[i + 0] == Dot && - source[i + 1] == Dot && - (source[i + 2] == DirectorySeparator || source[i + 2] == AltDirectorySeparator)) + dest[i - 1] = DirectorySeparator; + dest[i + 0] = Dot; + dest[i + 1] = Dot; + dest[i + 2] = DirectorySeparator; + i += 3; + } + else + { + if (source.Length > i + 1 && + source[i - 1] == AltDirectorySeparator && + source[i + 0] == Dot && + source[i + 1] == Dot && + (source.Length == i + 2 || source[i + 2] == NullTerminator)) { dest[i - 1] = DirectorySeparator; dest[i + 0] = Dot; dest[i + 1] = Dot; - dest[i + 2] = DirectorySeparator; - i += 3; + i += 2; + break; } - else - { - if (source.Length > i + 1 && - source[i - 1] == AltDirectorySeparator && - source[i + 0] == Dot && - source[i + 1] == Dot && - (source.Length == i + 2 || source[i + 2] == NullTerminator)) - { - dest[i - 1] = DirectorySeparator; - dest[i + 0] = Dot; - dest[i + 1] = Dot; - i += 2; - break; - } - dest[i] = source[i]; - i++; - } + dest[i] = source[i]; + i++; } - - dest[i] = NullTerminator; } + + dest[i] = NullTerminator; } } diff --git a/src/LibHac/Fs/Common/PathUtility.cs b/src/LibHac/Fs/Common/PathUtility.cs index 2c40ee51..e6f1f708 100644 --- a/src/LibHac/Fs/Common/PathUtility.cs +++ b/src/LibHac/Fs/Common/PathUtility.cs @@ -6,244 +6,243 @@ using LibHac.Util; using static LibHac.Fs.StringTraits; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// Contains various utility functions for working with paths. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public static class PathUtility { - /// - /// Contains various utility functions for working with paths. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public static class PathUtility + public static void Replace(Span buffer, byte currentChar, byte newChar) { - public static void Replace(Span buffer, byte currentChar, byte newChar) - { - Assert.SdkRequiresNotNull(buffer); + Assert.SdkRequiresNotNull(buffer); - for (int i = 0; i < buffer.Length; i++) + for (int i = 0; i < buffer.Length; i++) + { + if (buffer[i] == currentChar) { - if (buffer[i] == currentChar) - { - buffer[i] = newChar; - } + buffer[i] = newChar; } } - - public static bool IsCurrentDirectory(ReadOnlySpan path) - { - if (path.Length < 1) - return false; - - return path[0] == Dot && - (path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator); - } - - public static bool IsParentDirectory(ReadOnlySpan path) - { - if (path.Length < 2) - return false; - - return path[0] == Dot && - path[1] == Dot && - (path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator); - } - - public static bool IsSeparator(byte c) - { - return c == DirectorySeparator; - } - - public static bool IsNul(byte c) - { - return c == NullTerminator; - } - - public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan path) - { - UnsafeHelpers.SkipParamInit(out fspPath); - - int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1); - - if (length >= PathTool.EntryNameLengthMax + 1) - return ResultFs.TooLongPath.Log(); - - Result rc = PathFormatter.SkipMountName(out ReadOnlySpan pathWithoutMountName, out _, - new U8Span(path)); - if (rc.IsFailure()) return rc; - - if (!WindowsPath.IsWindowsPath(pathWithoutMountName, true)) - { - Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator); - } - else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator) - { - SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator; - SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator; - } - - return Result.Success; - } - - public static bool IsDirectoryPath(ReadOnlySpan path) - { - if (path.Length < 1 || path[0] == NullTerminator) - return false; - - int length = StringUtils.GetLength(path); - return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator; - } - - public static bool IsDirectoryPath(in FspPath path) - { - return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path)); - } - - public static Result CheckUtf8(ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - uint utf8Buffer = 0; - Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); - - ReadOnlySpan currentChar = path; - - while (currentChar.Length > 0 && currentChar[0] != NullTerminator) - { - utf8BufferSpan.Clear(); - - CharacterEncodingResult result = - CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar); - - if (result != CharacterEncodingResult.Success) - return ResultFs.InvalidPathFormat.Log(); - - result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan); - - if (result != CharacterEncodingResult.Success) - return ResultFs.InvalidPathFormat.Log(); - } - - return Result.Success; - } - - public static Result CheckInvalidCharacter(byte c) - { - /* - The optimized code is equivalent to this: - - ReadOnlySpan invalidChars = new[] - {(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'}; - - for (int i = 0; i < invalidChars.Length; i++) - { - if (c == invalidChars[i]) - return ResultFs.InvalidCharacter.Log(); - } - - return Result.Success; - */ - - const ulong mask = (1ul << (byte)':') | - (1ul << (byte)'*') | - (1ul << (byte)'?') | - (1ul << (byte)'<') | - (1ul << (byte)'>'); - - if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|') - return ResultFs.InvalidCharacter.Log(); - - return Result.Success; - } - - public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan path, bool allowBackslash) - { - containsBackslash = false; - - for (int i = 0; i < path.Length && path[i] != NullTerminator; i++) - { - if (path[i] == '\\') - { - containsBackslash = true; - - if (!allowBackslash) - return ResultFs.InvalidCharacter.Log(); - } - } - - return Result.Success; - } - - public static Result CheckEntryNameBytes(ReadOnlySpan path, int maxEntryLength) - { - Assert.SdkRequiresNotNull(path); - - int currentEntryLength = 0; - - for (int i = 0; i < path.Length && path[i] != NullTerminator; i++) - { - currentEntryLength++; - - if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) - currentEntryLength = 0; - - // Note: The original does use >= instead of > - if (currentEntryLength >= maxEntryLength) - return ResultFs.TooLongPath.Log(); - } - - return Result.Success; - } - - public static bool IsSubPath(ReadOnlySpan lhs, ReadOnlySpan rhs) - { - Assert.SdkRequiresNotNull(lhs); - Assert.SdkRequiresNotNull(rhs); - - if (WindowsPath.IsUncPath(lhs) && !WindowsPath.IsUncPath(rhs)) - return false; - - if (!WindowsPath.IsUncPath(lhs) && WindowsPath.IsUncPath(rhs)) - return false; - - if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator && - rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator) - return true; - - if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator && - lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator) - return true; - - for (int i = 0; ; i++) - { - if (lhs.At(i) == NullTerminator) - { - return rhs.At(i) == DirectorySeparator; - } - else if (rhs.At(i) == NullTerminator) - { - return lhs.At(i) == DirectorySeparator; - } - else if (lhs.At(i) != rhs.At(i)) - { - return false; - } - } - } - - public static bool IsPathAbsolute(ReadOnlySpan path) - { - if (WindowsPath.IsWindowsPath(path, false)) - return true; - - return path.At(0) == DirectorySeparator; - } - - public static bool IsPathRelative(ReadOnlySpan path) - { - return path.At(0) != NullTerminator && !IsPathAbsolute(path); - } - - public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) - { - return IsCurrentDirectory(path) || IsParentDirectory(path); - } + } + + public static bool IsCurrentDirectory(ReadOnlySpan path) + { + if (path.Length < 1) + return false; + + return path[0] == Dot && + (path.Length < 2 || path[1] == NullTerminator || path[1] == DirectorySeparator); + } + + public static bool IsParentDirectory(ReadOnlySpan path) + { + if (path.Length < 2) + return false; + + return path[0] == Dot && + path[1] == Dot && + (path.Length < 3 || path[2] == NullTerminator || path[2] == DirectorySeparator); + } + + public static bool IsSeparator(byte c) + { + return c == DirectorySeparator; + } + + public static bool IsNul(byte c) + { + return c == NullTerminator; + } + + public static Result ConvertToFspPath(out FspPath fspPath, ReadOnlySpan path) + { + UnsafeHelpers.SkipParamInit(out fspPath); + + int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref fspPath), path, PathTool.EntryNameLengthMax + 1); + + if (length >= PathTool.EntryNameLengthMax + 1) + return ResultFs.TooLongPath.Log(); + + Result rc = PathFormatter.SkipMountName(out ReadOnlySpan pathWithoutMountName, out _, + new U8Span(path)); + if (rc.IsFailure()) return rc; + + if (!WindowsPath.IsWindowsPath(pathWithoutMountName, true)) + { + Replace(SpanHelpers.AsByteSpan(ref fspPath).Slice(0, 0x300), AltDirectorySeparator, DirectorySeparator); + } + else if (fspPath.Str[0] == DirectorySeparator && fspPath.Str[1] == DirectorySeparator) + { + SpanHelpers.AsByteSpan(ref fspPath)[0] = AltDirectorySeparator; + SpanHelpers.AsByteSpan(ref fspPath)[1] = AltDirectorySeparator; + } + + return Result.Success; + } + + public static bool IsDirectoryPath(ReadOnlySpan path) + { + if (path.Length < 1 || path[0] == NullTerminator) + return false; + + int length = StringUtils.GetLength(path); + return path[length - 1] == DirectorySeparator || path[length - 1] == AltDirectorySeparator; + } + + public static bool IsDirectoryPath(in FspPath path) + { + return IsDirectoryPath(SpanHelpers.AsReadOnlyByteSpan(in path)); + } + + public static Result CheckUtf8(ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + uint utf8Buffer = 0; + Span utf8BufferSpan = SpanHelpers.AsByteSpan(ref utf8Buffer); + + ReadOnlySpan currentChar = path; + + while (currentChar.Length > 0 && currentChar[0] != NullTerminator) + { + utf8BufferSpan.Clear(); + + CharacterEncodingResult result = + CharacterEncoding.PickOutCharacterFromUtf8String(utf8BufferSpan, ref currentChar); + + if (result != CharacterEncodingResult.Success) + return ResultFs.InvalidPathFormat.Log(); + + result = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out _, utf8BufferSpan); + + if (result != CharacterEncodingResult.Success) + return ResultFs.InvalidPathFormat.Log(); + } + + return Result.Success; + } + + public static Result CheckInvalidCharacter(byte c) + { + /* + The optimized code is equivalent to this: + + ReadOnlySpan invalidChars = new[] + {(byte) ':', (byte) '*', (byte) '?', (byte) '<', (byte) '>', (byte) '|'}; + + for (int i = 0; i < invalidChars.Length; i++) + { + if (c == invalidChars[i]) + return ResultFs.InvalidCharacter.Log(); + } + + return Result.Success; + */ + + const ulong mask = (1ul << (byte)':') | + (1ul << (byte)'*') | + (1ul << (byte)'?') | + (1ul << (byte)'<') | + (1ul << (byte)'>'); + + if (c <= 0x3Fu && ((1ul << c) & mask) != 0 || c == (byte)'|') + return ResultFs.InvalidCharacter.Log(); + + return Result.Success; + } + + public static Result CheckInvalidBackslash(out bool containsBackslash, ReadOnlySpan path, bool allowBackslash) + { + containsBackslash = false; + + for (int i = 0; i < path.Length && path[i] != NullTerminator; i++) + { + if (path[i] == '\\') + { + containsBackslash = true; + + if (!allowBackslash) + return ResultFs.InvalidCharacter.Log(); + } + } + + return Result.Success; + } + + public static Result CheckEntryNameBytes(ReadOnlySpan path, int maxEntryLength) + { + Assert.SdkRequiresNotNull(path); + + int currentEntryLength = 0; + + for (int i = 0; i < path.Length && path[i] != NullTerminator; i++) + { + currentEntryLength++; + + if (path[i] == DirectorySeparator || path[i] == AltDirectorySeparator) + currentEntryLength = 0; + + // Note: The original does use >= instead of > + if (currentEntryLength >= maxEntryLength) + return ResultFs.TooLongPath.Log(); + } + + return Result.Success; + } + + public static bool IsSubPath(ReadOnlySpan lhs, ReadOnlySpan rhs) + { + Assert.SdkRequiresNotNull(lhs); + Assert.SdkRequiresNotNull(rhs); + + if (WindowsPath.IsUncPath(lhs) && !WindowsPath.IsUncPath(rhs)) + return false; + + if (!WindowsPath.IsUncPath(lhs) && WindowsPath.IsUncPath(rhs)) + return false; + + if (lhs.At(0) == DirectorySeparator && lhs.At(1) == NullTerminator && + rhs.At(0) == DirectorySeparator && rhs.At(1) != NullTerminator) + return true; + + if (rhs.At(0) == DirectorySeparator && rhs.At(1) == NullTerminator && + lhs.At(0) == DirectorySeparator && lhs.At(1) != NullTerminator) + return true; + + for (int i = 0; ; i++) + { + if (lhs.At(i) == NullTerminator) + { + return rhs.At(i) == DirectorySeparator; + } + else if (rhs.At(i) == NullTerminator) + { + return lhs.At(i) == DirectorySeparator; + } + else if (lhs.At(i) != rhs.At(i)) + { + return false; + } + } + } + + public static bool IsPathAbsolute(ReadOnlySpan path) + { + if (WindowsPath.IsWindowsPath(path, false)) + return true; + + return path.At(0) == DirectorySeparator; + } + + public static bool IsPathRelative(ReadOnlySpan path) + { + return path.At(0) != NullTerminator && !IsPathAbsolute(path); + } + + public static bool IsPathStartWithCurrentDirectory(ReadOnlySpan path) + { + return IsCurrentDirectory(path) || IsParentDirectory(path); } } diff --git a/src/LibHac/Fs/Common/WindowsPath.cs b/src/LibHac/Fs/Common/WindowsPath.cs index 8c72e0df..a3bb2d63 100644 --- a/src/LibHac/Fs/Common/WindowsPath.cs +++ b/src/LibHac/Fs/Common/WindowsPath.cs @@ -5,254 +5,253 @@ using LibHac.Util; using static LibHac.Util.CharacterEncoding; // ReSharper disable once CheckNamespace -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// Contains functions for working with Windows paths. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public static class WindowsPath { - /// - /// Contains functions for working with Windows paths. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public static class WindowsPath + private const int WindowsDriveLength = 2; + private const int UncPathPrefixLength = 2; + private const int DosDevicePathPrefixLength = 4; + + public static int GetCodePointByteLength(byte firstCodeUnit) { - private const int WindowsDriveLength = 2; - private const int UncPathPrefixLength = 2; - private const int DosDevicePathPrefixLength = 4; + if ((firstCodeUnit & 0x80) == 0x00) return 1; + if ((firstCodeUnit & 0xE0) == 0xC0) return 2; + if ((firstCodeUnit & 0xF0) == 0xE0) return 3; + if ((firstCodeUnit & 0xF8) == 0xF0) return 4; + return 0; + } - public static int GetCodePointByteLength(byte firstCodeUnit) + private static bool IsUncPathImpl(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < UncPathPrefixLength) + return false; + + if (checkForwardSlash && path[0] == '/' && path[1] == '/') + return true; + + return checkBackSlash && path[0] == '\\' && path[1] == '\\'; + } + + private static bool IsUncPathImpl(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < UncPathPrefixLength) + return false; + + if (checkForwardSlash && path[0] == '/' && path[1] == '/') + return true; + + return checkBackSlash && path[0] == '\\' && path[1] == '\\'; + } + + private static int GetUncPathPrefixLengthImpl(ReadOnlySpan path, bool checkForwardSlash) + { + Assert.SdkRequiresNotNull(path); + + int length; + int separatorCount = 0; + + for (length = 0; length < path.Length && path[length] != 0; length++) { - if ((firstCodeUnit & 0x80) == 0x00) return 1; - if ((firstCodeUnit & 0xE0) == 0xC0) return 2; - if ((firstCodeUnit & 0xF0) == 0xE0) return 3; - if ((firstCodeUnit & 0xF8) == 0xF0) return 4; - return 0; + if (checkForwardSlash && path[length] == '/') + ++separatorCount; + + if (path[length] == '\\') + ++separatorCount; + + if (separatorCount == 4) + return length; } - private static bool IsUncPathImpl(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + return length; + } + + private static int GetUncPathPrefixLengthImpl(ReadOnlySpan path, bool checkForwardSlash) + { + Assert.SdkRequiresNotNull(path); + + int length; + int separatorCount = 0; + + for (length = 0; length < path.Length && path[length] != 0; length++) { - Assert.SdkRequiresNotNull(path); + if (checkForwardSlash && path[length] == '/') + ++separatorCount; - if ((uint)path.Length < UncPathPrefixLength) - return false; + if (path[length] == '\\') + ++separatorCount; - if (checkForwardSlash && path[0] == '/' && path[1] == '/') - return true; - - return checkBackSlash && path[0] == '\\' && path[1] == '\\'; + if (separatorCount == 4) + return length; } - private static bool IsUncPathImpl(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + return length; + } + + private static bool IsDosDevicePathImpl(ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < DosDevicePathPrefixLength) + return false; + + return path[0] == '\\' && + path[1] == '\\' && + (path[2] == '.' || path[2] == '?') && + (path[3] == '/' || path[3] == '\\'); + } + + private static bool IsDosDevicePathImpl(ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < DosDevicePathPrefixLength) + return false; + + return path[0] == '\\' && + path[1] == '\\' && + (path[2] == '.' || path[2] == '?') && + (path[3] == '/' || path[3] == '\\'); + } + + public static bool IsWindowsDrive(ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < WindowsDriveLength) + return false; + + // Mask lowercase letters to uppercase and check if it's in range + return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':'; + // return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':'; + } + + public static bool IsUncPath(ReadOnlySpan path) + { + return IsUncPathImpl(path, true, true); + } + + public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) + { + return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); + } + + public static int GetUncPathPrefixLength(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePath(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); + } + + public static int GetDosDevicePathPrefixLength() + { + return DosDevicePathPrefixLength; + } + + public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) + { + return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); + } + + public static int GetWindowsSkipLength(ReadOnlySpan path) + { + if (IsDosDevicePath(path)) + return GetDosDevicePathPrefixLength(); + + if (IsWindowsDrive(path)) + return WindowsDriveLength; + + if (IsUncPath(path)) + return GetUncPathPrefixLength(path); + + return 0; + } + + public static bool IsDosDelimiterW(char c) + { + return c == '/' || c == '\\'; + } + + public static bool IsWindowsDriveW(ReadOnlySpan path) + { + Assert.SdkRequiresNotNull(path); + + if ((uint)path.Length < WindowsDriveLength) + return false; + + // Mask lowercase letters to uppercase and check if it's in range + return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':'; + // return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':'; + } + + public static bool IsUncPathW(ReadOnlySpan path) + { + return IsUncPathImpl(path, true, true); + } + + public static int GetUncPathPrefixLengthW(ReadOnlySpan path) + { + return GetUncPathPrefixLengthImpl(path, true); + } + + public static bool IsDosDevicePathW(ReadOnlySpan path) + { + return IsDosDevicePathImpl(path); + } + + public static bool IsWindowsPathW(ReadOnlySpan path) + { + return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); + } + + public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) + { + Assert.SdkRequiresNotNull(path); + + ReadOnlySpan currentChar = path; + int currentNameLength = 0; + int currentPathLength = 0; + + while (currentChar.Length > 1 && currentChar[0] != 0) { - Assert.SdkRequiresNotNull(path); + int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2; - if ((uint)path.Length < UncPathPrefixLength) - return false; + int utf8Buffer = 0; + CharacterEncodingResult result = + PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar); - if (checkForwardSlash && path[0] == '/' && path[1] == '/') - return true; + if (result != CharacterEncodingResult.Success) + return ResultFs.InvalidPathFormat.Log(); - return checkBackSlash && path[0] == '\\' && path[1] == '\\'; + result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer)); + + if (result != CharacterEncodingResult.Success) + return ResultFs.InvalidPathFormat.Log(); + + currentNameLength += utf16CodeUnitCount; + currentPathLength += utf16CodeUnitCount; + + if (pathChar == '/' || pathChar == '\\') + currentNameLength = 0; + + if (maxNameLength > 0 && currentNameLength > maxNameLength) + return ResultFs.TooLongPath.Log(); + + if (maxPathLength > 0 && currentPathLength > maxPathLength) + return ResultFs.TooLongPath.Log(); } - private static int GetUncPathPrefixLengthImpl(ReadOnlySpan path, bool checkForwardSlash) - { - Assert.SdkRequiresNotNull(path); - - int length; - int separatorCount = 0; - - for (length = 0; length < path.Length && path[length] != 0; length++) - { - if (checkForwardSlash && path[length] == '/') - ++separatorCount; - - if (path[length] == '\\') - ++separatorCount; - - if (separatorCount == 4) - return length; - } - - return length; - } - - private static int GetUncPathPrefixLengthImpl(ReadOnlySpan path, bool checkForwardSlash) - { - Assert.SdkRequiresNotNull(path); - - int length; - int separatorCount = 0; - - for (length = 0; length < path.Length && path[length] != 0; length++) - { - if (checkForwardSlash && path[length] == '/') - ++separatorCount; - - if (path[length] == '\\') - ++separatorCount; - - if (separatorCount == 4) - return length; - } - - return length; - } - - private static bool IsDosDevicePathImpl(ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - if ((uint)path.Length < DosDevicePathPrefixLength) - return false; - - return path[0] == '\\' && - path[1] == '\\' && - (path[2] == '.' || path[2] == '?') && - (path[3] == '/' || path[3] == '\\'); - } - - private static bool IsDosDevicePathImpl(ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - if ((uint)path.Length < DosDevicePathPrefixLength) - return false; - - return path[0] == '\\' && - path[1] == '\\' && - (path[2] == '.' || path[2] == '?') && - (path[3] == '/' || path[3] == '\\'); - } - - public static bool IsWindowsDrive(ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - if ((uint)path.Length < WindowsDriveLength) - return false; - - // Mask lowercase letters to uppercase and check if it's in range - return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':'; - // return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':'; - } - - public static bool IsUncPath(ReadOnlySpan path) - { - return IsUncPathImpl(path, true, true); - } - - public static bool IsUncPath(ReadOnlySpan path, bool checkForwardSlash, bool checkBackSlash) - { - return IsUncPathImpl(path, checkForwardSlash, checkBackSlash); - } - - public static int GetUncPathPrefixLength(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePath(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static int GetDosDevicePathPrefixLength() - { - return DosDevicePathPrefixLength; - } - - public static bool IsWindowsPath(ReadOnlySpan path, bool checkForwardSlash) - { - return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, checkForwardSlash, true); - } - - public static int GetWindowsSkipLength(ReadOnlySpan path) - { - if (IsDosDevicePath(path)) - return GetDosDevicePathPrefixLength(); - - if (IsWindowsDrive(path)) - return WindowsDriveLength; - - if (IsUncPath(path)) - return GetUncPathPrefixLength(path); - - return 0; - } - - public static bool IsDosDelimiterW(char c) - { - return c == '/' || c == '\\'; - } - - public static bool IsWindowsDriveW(ReadOnlySpan path) - { - Assert.SdkRequiresNotNull(path); - - if ((uint)path.Length < WindowsDriveLength) - return false; - - // Mask lowercase letters to uppercase and check if it's in range - return ((0b1101_1111 & path[0]) - 'A' <= 'Z' - 'A') && path[1] == ':'; - // return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') && path[1] == ':'; - } - - public static bool IsUncPathW(ReadOnlySpan path) - { - return IsUncPathImpl(path, true, true); - } - - public static int GetUncPathPrefixLengthW(ReadOnlySpan path) - { - return GetUncPathPrefixLengthImpl(path, true); - } - - public static bool IsDosDevicePathW(ReadOnlySpan path) - { - return IsDosDevicePathImpl(path); - } - - public static bool IsWindowsPathW(ReadOnlySpan path) - { - return IsWindowsDriveW(path) || IsUncPathW(path) || IsDosDevicePathW(path); - } - - public static Result CheckCharacterCountForWindows(ReadOnlySpan path, int maxNameLength, int maxPathLength) - { - Assert.SdkRequiresNotNull(path); - - ReadOnlySpan currentChar = path; - int currentNameLength = 0; - int currentPathLength = 0; - - while (currentChar.Length > 1 && currentChar[0] != 0) - { - int utf16CodeUnitCount = GetCodePointByteLength(currentChar[0]) < 4 ? 1 : 2; - - int utf8Buffer = 0; - CharacterEncodingResult result = - PickOutCharacterFromUtf8String(SpanHelpers.AsByteSpan(ref utf8Buffer), ref currentChar); - - if (result != CharacterEncodingResult.Success) - return ResultFs.InvalidPathFormat.Log(); - - result = ConvertCharacterUtf8ToUtf32(out uint pathChar, SpanHelpers.AsReadOnlyByteSpan(in utf8Buffer)); - - if (result != CharacterEncodingResult.Success) - return ResultFs.InvalidPathFormat.Log(); - - currentNameLength += utf16CodeUnitCount; - currentPathLength += utf16CodeUnitCount; - - if (pathChar == '/' || pathChar == '\\') - currentNameLength = 0; - - if (maxNameLength > 0 && currentNameLength > maxNameLength) - return ResultFs.TooLongPath.Log(); - - if (maxPathLength > 0 && currentPathLength > maxPathLength) - return ResultFs.TooLongPath.Log(); - } - - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/Fs/CommonPaths.cs b/src/LibHac/Fs/CommonPaths.cs index cc5d9e7b..ff564dac 100644 --- a/src/LibHac/Fs/CommonPaths.cs +++ b/src/LibHac/Fs/CommonPaths.cs @@ -1,33 +1,32 @@ using System; using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +// Todo: Migrate to LibHac.Fs.Impl.CommonMountNames and remove +internal static class CommonPaths { - // Todo: Migrate to LibHac.Fs.Impl.CommonMountNames and remove - internal static class CommonPaths - { - public const char ReservedMountNamePrefixCharacter = '@'; + public const char ReservedMountNamePrefixCharacter = '@'; - public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc"); - public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent"); - public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent"); - public static readonly U8String ContentStorageSdCardMountName = new U8String("@SdCardContent"); - public static readonly U8String BisCalibrationFilePartitionMountName = new U8String("@CalibFile"); - public static readonly U8String BisSafeModePartitionMountName = new U8String("@Safe"); - public static readonly U8String BisUserPartitionMountName = new U8String("@User"); - public static readonly U8String BisSystemPartitionMountName = new U8String("@System"); - public static readonly U8String SdCardFileSystemMountName = new U8String("@Sdcard"); - public static readonly U8String HostRootFileSystemMountName = new U8String("@Host"); - public static readonly U8String RegisteredUpdatePartitionMountName = new U8String("@RegUpdate"); + public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc"); + public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent"); + public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent"); + public static readonly U8String ContentStorageSdCardMountName = new U8String("@SdCardContent"); + public static readonly U8String BisCalibrationFilePartitionMountName = new U8String("@CalibFile"); + public static readonly U8String BisSafeModePartitionMountName = new U8String("@Safe"); + public static readonly U8String BisUserPartitionMountName = new U8String("@User"); + public static readonly U8String BisSystemPartitionMountName = new U8String("@System"); + public static readonly U8String SdCardFileSystemMountName = new U8String("@Sdcard"); + public static readonly U8String HostRootFileSystemMountName = new U8String("@Host"); + public static readonly U8String RegisteredUpdatePartitionMountName = new U8String("@RegUpdate"); - public const char GameCardFileSystemMountNameUpdateSuffix = 'U'; - public const char GameCardFileSystemMountNameNormalSuffix = 'N'; - public const char GameCardFileSystemMountNameSecureSuffix = 'S'; + public const char GameCardFileSystemMountNameUpdateSuffix = 'U'; + public const char GameCardFileSystemMountNameNormalSuffix = 'N'; + public const char GameCardFileSystemMountNameSecureSuffix = 'S'; - public static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo - new[] - { + public static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo + new[] + { (byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o' - }; - } + }; } diff --git a/src/LibHac/Fs/DirectoryEntry.cs b/src/LibHac/Fs/DirectoryEntry.cs index c47a98d3..07c46b11 100644 --- a/src/LibHac/Fs/DirectoryEntry.cs +++ b/src/LibHac/Fs/DirectoryEntry.cs @@ -3,47 +3,46 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.FsSystem; -namespace LibHac.Fs +namespace LibHac.Fs; + +public class DirectoryEntryEx { - public class DirectoryEntryEx + public string Name { get; set; } + public string FullPath { get; set; } + public NxFileAttributes Attributes { get; set; } + public DirectoryEntryType Type { get; set; } + public long Size { get; set; } + + public DirectoryEntryEx(string name, string fullPath, DirectoryEntryType type, long size) { - public string Name { get; set; } - public string FullPath { get; set; } - public NxFileAttributes Attributes { get; set; } - public DirectoryEntryType Type { get; set; } - public long Size { get; set; } - - public DirectoryEntryEx(string name, string fullPath, DirectoryEntryType type, long size) - { - Name = name; - FullPath = PathTools.Normalize(fullPath); - Type = type; - Size = size; - } - } - - [StructLayout(LayoutKind.Explicit)] - public struct DirectoryEntry - { - [FieldOffset(0)] private byte _name; - [FieldOffset(0x301)] public NxFileAttributes Attributes; - [FieldOffset(0x304)] public DirectoryEntryType Type; - [FieldOffset(0x308)] public long Size; - - public Span Name => SpanHelpers.CreateSpan(ref _name, PathTools.MaxPathLength + 1); - } - - public enum DirectoryEntryType : byte - { - Directory, - File - } - - [Flags] - public enum NxFileAttributes : byte - { - None = 0, - Directory = 1 << 0, - Archive = 1 << 1 + Name = name; + FullPath = PathTools.Normalize(fullPath); + Type = type; + Size = size; } } + +[StructLayout(LayoutKind.Explicit)] +public struct DirectoryEntry +{ + [FieldOffset(0)] private byte _name; + [FieldOffset(0x301)] public NxFileAttributes Attributes; + [FieldOffset(0x304)] public DirectoryEntryType Type; + [FieldOffset(0x308)] public long Size; + + public Span Name => SpanHelpers.CreateSpan(ref _name, PathTools.MaxPathLength + 1); +} + +public enum DirectoryEntryType : byte +{ + Directory, + File +} + +[Flags] +public enum NxFileAttributes : byte +{ + None = 0, + Directory = 1 << 0, + Archive = 1 << 1 +} diff --git a/src/LibHac/Fs/DirectoryHandle.cs b/src/LibHac/Fs/DirectoryHandle.cs index cea5b81b..8390a5af 100644 --- a/src/LibHac/Fs/DirectoryHandle.cs +++ b/src/LibHac/Fs/DirectoryHandle.cs @@ -1,25 +1,24 @@ using System; using LibHac.Fs.Fsa; -namespace LibHac.Fs +namespace LibHac.Fs; + +public readonly struct DirectoryHandle : IDisposable { - public readonly struct DirectoryHandle : IDisposable + internal readonly Impl.DirectoryAccessor Directory; + + public bool IsValid => Directory is not null; + + internal DirectoryHandle(Impl.DirectoryAccessor directory) { - internal readonly Impl.DirectoryAccessor Directory; + Directory = directory; + } - public bool IsValid => Directory is not null; - - internal DirectoryHandle(Impl.DirectoryAccessor directory) + public void Dispose() + { + if (IsValid) { - Directory = directory; - } - - public void Dispose() - { - if (IsValid) - { - Directory.GetParent().Hos.Fs.CloseDirectory(this); - } + Directory.GetParent().Hos.Fs.CloseDirectory(this); } } -} \ No newline at end of file +} diff --git a/src/LibHac/Fs/EncryptionSeed.cs b/src/LibHac/Fs/EncryptionSeed.cs index f8d592c3..add168b4 100644 --- a/src/LibHac/Fs/EncryptionSeed.cs +++ b/src/LibHac/Fs/EncryptionSeed.cs @@ -3,27 +3,26 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct EncryptionSeed : IEquatable { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct EncryptionSeed : IEquatable + private readonly Key128 Key; + + public readonly ReadOnlySpan Value => SpanHelpers.AsReadOnlyByteSpan(in this); + + public EncryptionSeed(ReadOnlySpan bytes) { - private readonly Key128 Key; - - public readonly ReadOnlySpan Value => SpanHelpers.AsReadOnlyByteSpan(in this); - - public EncryptionSeed(ReadOnlySpan bytes) - { - Key = new Key128(bytes); - } - - public override string ToString() => Key.ToString(); - - public override bool Equals(object obj) => obj is EncryptionSeed key && Equals(key); - public bool Equals(EncryptionSeed other) => Key.Equals(other.Key); - public override int GetHashCode() => Key.GetHashCode(); - public static bool operator ==(EncryptionSeed left, EncryptionSeed right) => left.Equals(right); - public static bool operator !=(EncryptionSeed left, EncryptionSeed right) => !(left == right); + Key = new Key128(bytes); } + + public override string ToString() => Key.ToString(); + + public override bool Equals(object obj) => obj is EncryptionSeed key && Equals(key); + public bool Equals(EncryptionSeed other) => Key.Equals(other.Key); + public override int GetHashCode() => Key.GetHashCode(); + public static bool operator ==(EncryptionSeed left, EncryptionSeed right) => left.Equals(right); + public static bool operator !=(EncryptionSeed left, EncryptionSeed right) => !(left == right); } diff --git a/src/LibHac/Fs/ErrorInfo.cs b/src/LibHac/Fs/ErrorInfo.cs index befd6bdc..e6ad9679 100644 --- a/src/LibHac/Fs/ErrorInfo.cs +++ b/src/LibHac/Fs/ErrorInfo.cs @@ -1,24 +1,23 @@ using System.Runtime.InteropServices; using LibHac.Fat; -namespace LibHac.Fs -{ - [StructLayout(LayoutKind.Explicit, Size = 0x80)] - public struct FileSystemProxyErrorInfo - { - [FieldOffset(0x00)] public int RomFsRemountForDataCorruptionCount; - [FieldOffset(0x04)] public int RomFsUnrecoverableDataCorruptionByRemountCount; - [FieldOffset(0x08)] public FatError FatError; - [FieldOffset(0x28)] public int RomFsRecoveredByInvalidateCacheCount; - [FieldOffset(0x2C)] public int SaveDataIndexCount; - } +namespace LibHac.Fs; - [StructLayout(LayoutKind.Explicit, Size = 0x10)] - public struct StorageErrorInfo - { - [FieldOffset(0x00)] public int NumActivationFailures; - [FieldOffset(0x04)] public int NumActivationErrorCorrections; - [FieldOffset(0x08)] public int NumReadWriteFailures; - [FieldOffset(0x0C)] public int NumReadWriteErrorCorrections; - } +[StructLayout(LayoutKind.Explicit, Size = 0x80)] +public struct FileSystemProxyErrorInfo +{ + [FieldOffset(0x00)] public int RomFsRemountForDataCorruptionCount; + [FieldOffset(0x04)] public int RomFsUnrecoverableDataCorruptionByRemountCount; + [FieldOffset(0x08)] public FatError FatError; + [FieldOffset(0x28)] public int RomFsRecoveredByInvalidateCacheCount; + [FieldOffset(0x2C)] public int SaveDataIndexCount; +} + +[StructLayout(LayoutKind.Explicit, Size = 0x10)] +public struct StorageErrorInfo +{ + [FieldOffset(0x00)] public int NumActivationFailures; + [FieldOffset(0x04)] public int NumActivationErrorCorrections; + [FieldOffset(0x08)] public int NumReadWriteFailures; + [FieldOffset(0x0C)] public int NumReadWriteErrorCorrections; } diff --git a/src/LibHac/Fs/FileHandle.cs b/src/LibHac/Fs/FileHandle.cs index 45bfbd34..2e90a547 100644 --- a/src/LibHac/Fs/FileHandle.cs +++ b/src/LibHac/Fs/FileHandle.cs @@ -1,25 +1,24 @@ using System; using LibHac.Fs.Fsa; -namespace LibHac.Fs +namespace LibHac.Fs; + +public readonly struct FileHandle : IDisposable { - public readonly struct FileHandle : IDisposable + internal readonly Impl.FileAccessor File; + + public bool IsValid => File is not null; + + internal FileHandle(Impl.FileAccessor file) { - internal readonly Impl.FileAccessor File; + File = file; + } - public bool IsValid => File is not null; - - internal FileHandle(Impl.FileAccessor file) + public void Dispose() + { + if (IsValid) { - File = file; - } - - public void Dispose() - { - if (IsValid) - { - File.Hos.Fs.CloseFile(this); - } + File.Hos.Fs.CloseFile(this); } } } diff --git a/src/LibHac/Fs/FileOptions.cs b/src/LibHac/Fs/FileOptions.cs index 0693760e..9837019e 100644 --- a/src/LibHac/Fs/FileOptions.cs +++ b/src/LibHac/Fs/FileOptions.cs @@ -1,43 +1,42 @@ using System; -namespace LibHac.Fs +namespace LibHac.Fs; + +public readonly struct ReadOption { - public readonly struct ReadOption + public readonly int Value; + + public ReadOption(int value) { - public readonly int Value; - - public ReadOption(int value) - { - Value = value; - } - - public static ReadOption None => default; + Value = value; } - public readonly struct WriteOption - { - public readonly WriteOptionFlag Flags; - - public WriteOption(int flags) - { - Flags = (WriteOptionFlag)flags; - } - - public WriteOption(WriteOptionFlag flags) - { - Flags = flags; - } - - public bool HasFlushFlag() => Flags.HasFlag(WriteOptionFlag.Flush); - - public static WriteOption None => new WriteOption(WriteOptionFlag.None); - public static WriteOption Flush => new WriteOption(WriteOptionFlag.Flush); - } - - [Flags] - public enum WriteOptionFlag - { - None = 0, - Flush = 1 - } + public static ReadOption None => default; +} + +public readonly struct WriteOption +{ + public readonly WriteOptionFlag Flags; + + public WriteOption(int flags) + { + Flags = (WriteOptionFlag)flags; + } + + public WriteOption(WriteOptionFlag flags) + { + Flags = flags; + } + + public bool HasFlushFlag() => Flags.HasFlag(WriteOptionFlag.Flush); + + public static WriteOption None => new WriteOption(WriteOptionFlag.None); + public static WriteOption Flush => new WriteOption(WriteOptionFlag.Flush); +} + +[Flags] +public enum WriteOptionFlag +{ + None = 0, + Flush = 1 } diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index a9a91d7b..898a1702 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -4,61 +4,60 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using LibHac.FsSystem; -namespace LibHac.Fs +namespace LibHac.Fs; + +public class FileSystemClient : IDisposable { - public class FileSystemClient : IDisposable + internal FileSystemClientGlobals Globals; + + public FileSystemClientImpl Impl => new FileSystemClientImpl(this); + internal HorizonClient Hos => Globals.Hos; + + public FileSystemClient(HorizonClient horizonClient) { - internal FileSystemClientGlobals Globals; - - public FileSystemClientImpl Impl => new FileSystemClientImpl(this); - internal HorizonClient Hos => Globals.Hos; - - public FileSystemClient(HorizonClient horizonClient) - { - Globals.Initialize(this, horizonClient); - } - - public void Dispose() - { - Globals.Dispose(); - } + Globals.Initialize(this, horizonClient); } - [NonCopyable] - internal struct FileSystemClientGlobals : IDisposable + public void Dispose() { - public HorizonClient Hos; - public object InitMutex; - public AccessLogGlobals AccessLog; - public UserMountTableGlobals UserMountTable; - public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject; - public FsContextHandlerGlobals FsContextHandler; - public ResultHandlingUtilityGlobals ResultHandlingUtility; - public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem; - - public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient) - { - Hos = horizonClient; - InitMutex = new object(); - AccessLog.Initialize(fsClient); - UserMountTable.Initialize(fsClient); - FsContextHandler.Initialize(fsClient); - DirectorySaveDataFileSystem.Initialize(fsClient); - } - - public void Dispose() - { - FileSystemProxyServiceObject.Dispose(); - } - } - - // Functions in the nn::fs::detail namespace use this struct. - public readonly struct FileSystemClientImpl - { - internal readonly FileSystemClient Fs; - internal HorizonClient Hos => Fs.Hos; - internal ref FileSystemClientGlobals Globals => ref Fs.Globals; - - internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient; + Globals.Dispose(); } } + +[NonCopyable] +internal struct FileSystemClientGlobals : IDisposable +{ + public HorizonClient Hos; + public object InitMutex; + public AccessLogGlobals AccessLog; + public UserMountTableGlobals UserMountTable; + public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject; + public FsContextHandlerGlobals FsContextHandler; + public ResultHandlingUtilityGlobals ResultHandlingUtility; + public DirectorySaveDataFileSystemGlobals DirectorySaveDataFileSystem; + + public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient) + { + Hos = horizonClient; + InitMutex = new object(); + AccessLog.Initialize(fsClient); + UserMountTable.Initialize(fsClient); + FsContextHandler.Initialize(fsClient); + DirectorySaveDataFileSystem.Initialize(fsClient); + } + + public void Dispose() + { + FileSystemProxyServiceObject.Dispose(); + } +} + +// Functions in the nn::fs::detail namespace use this struct. +public readonly struct FileSystemClientImpl +{ + internal readonly FileSystemClient Fs; + internal HorizonClient Hos => Fs.Hos; + internal ref FileSystemClientGlobals Globals => ref Fs.Globals; + + internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient; +} diff --git a/src/LibHac/Fs/FileSystemClientUtils.cs b/src/LibHac/Fs/FileSystemClientUtils.cs index 6e3a2dc4..524bec45 100644 --- a/src/LibHac/Fs/FileSystemClientUtils.cs +++ b/src/LibHac/Fs/FileSystemClientUtils.cs @@ -5,235 +5,234 @@ using LibHac.Common; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class FileSystemClientUtils { - public static class FileSystemClientUtils + public static Result CopyDirectory(this FileSystemClient fs, string sourcePath, string destPath, + CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null) { - public static Result CopyDirectory(this FileSystemClient fs, string sourcePath, string destPath, - CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null) + Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + try { - Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; - - try + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) { - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) { - string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); - string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + fs.EnsureDirectoryExists(subDstPath); - if (entry.Type == DirectoryEntryType.Directory) - { - fs.EnsureDirectoryExists(subDstPath); + rc = fs.CopyDirectory(subSrcPath, subDstPath, options, logger); + if (rc.IsFailure()) return rc; + } - rc = fs.CopyDirectory(subSrcPath, subDstPath, options, logger); - if (rc.IsFailure()) return rc; - } + if (entry.Type == DirectoryEntryType.File) + { + logger?.LogMessage(subSrcPath); + fs.CreateOrOverwriteFile(subDstPath, entry.Size, options); - if (entry.Type == DirectoryEntryType.File) - { - logger?.LogMessage(subSrcPath); - fs.CreateOrOverwriteFile(subDstPath, entry.Size, options); - - rc = fs.CopyFile(subSrcPath, subDstPath, logger); - if (rc.IsFailure()) return rc; - } + rc = fs.CopyFile(subSrcPath, subDstPath, logger); + if (rc.IsFailure()) return rc; } } - finally - { - if (sourceHandle.IsValid) - fs.CloseDirectory(sourceHandle); - } - - return Result.Success; + } + finally + { + if (sourceHandle.IsValid) + fs.CloseDirectory(sourceHandle); } - public static Result CopyFile(this FileSystemClient fs, string sourcePath, string destPath, IProgressReport logger = null) + return Result.Success; + } + + public static Result CopyFile(this FileSystemClient fs, string sourcePath, string destPath, IProgressReport logger = null) + { + Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); + if (rc.IsFailure()) return rc; + + try { - Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); + rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), + OpenMode.Write | OpenMode.AllowAppend); if (rc.IsFailure()) return rc; try { - rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), - OpenMode.Write | OpenMode.AllowAppend); + const int maxBufferSize = 0x10000; + + rc = fs.GetFileSize(out long fileSize, sourceHandle); if (rc.IsFailure()) return rc; + int bufferSize = (int)Math.Min(maxBufferSize, fileSize); + + logger?.SetTotal(fileSize); + + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); try { - const int maxBufferSize = 0x10000; - - rc = fs.GetFileSize(out long fileSize, sourceHandle); - if (rc.IsFailure()) return rc; - - int bufferSize = (int)Math.Min(maxBufferSize, fileSize); - - logger?.SetTotal(fileSize); - - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try + for (long offset = 0; offset < fileSize; offset += bufferSize) { - for (long offset = 0; offset < fileSize; offset += bufferSize) - { - int toRead = (int)Math.Min(fileSize - offset, bufferSize); - Span buf = buffer.AsSpan(0, toRead); + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span buf = buffer.AsSpan(0, toRead); - rc = fs.ReadFile(out long _, sourceHandle, offset, buf); - if (rc.IsFailure()) return rc; + rc = fs.ReadFile(out long _, sourceHandle, offset, buf); + if (rc.IsFailure()) return rc; - rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); - if (rc.IsFailure()) return rc; + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); + if (rc.IsFailure()) return rc; - logger?.ReportAdd(toRead); - } + logger?.ReportAdd(toRead); } - finally - { - ArrayPool.Shared.Return(buffer); - logger?.SetTotal(0); - } - - rc = fs.FlushFile(destHandle); - if (rc.IsFailure()) return rc; } finally { - if (destHandle.IsValid) - fs.CloseFile(destHandle); + ArrayPool.Shared.Return(buffer); + logger?.SetTotal(0); } - } - finally - { - if (sourceHandle.IsValid) - fs.CloseFile(sourceHandle); - } - return Result.Success; - } - - public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path) - { - return fs.EnumerateEntries(path, "*"); - } - - public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path, string searchPattern) - { - return fs.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories); - } - - public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path, string searchPattern, SearchOptions searchOptions) - { - bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive); - bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories); - - fs.OpenDirectory(out DirectoryHandle sourceHandle, path.ToU8Span(), OpenDirectoryMode.All).ThrowIfFailure(); - - try - { - while (true) - { - DirectoryEntry dirEntry = default; - - fs.ReadDirectory(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry), sourceHandle); - if (entriesRead == 0) break; - - DirectoryEntryEx entry = FileSystemExtensions.GetDirectoryEntryEx(ref dirEntry, path); - - if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) - { - yield return entry; - } - - if (entry.Type != DirectoryEntryType.Directory || !recurse) continue; - - IEnumerable subEntries = - fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, searchOptions); - - foreach (DirectoryEntryEx subEntry in subEntries) - { - yield return subEntry; - } - } - } - finally - { - if (sourceHandle.IsValid) - fs.CloseDirectory(sourceHandle); - } - } - - public static bool DirectoryExists(this FileSystemClient fs, string path) - { - Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); - - return (rc.IsSuccess() && type == DirectoryEntryType.Directory); - } - - public static bool FileExists(this FileSystemClient fs, string path) - { - Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); - - return (rc.IsSuccess() && type == DirectoryEntryType.File); - } - - public static void EnsureDirectoryExists(this FileSystemClient fs, string path) - { - path = PathTools.Normalize(path); - if (fs.DirectoryExists(path)) return; - - PathTools.GetMountNameLength(path, out int mountNameLength).ThrowIfFailure(); - - // Find the first subdirectory in the path that doesn't exist - int i; - for (i = path.Length - 1; i > mountNameLength + 2; i--) - { - if (path[i] == '/') - { - string subPath = path.Substring(0, i); - - if (fs.DirectoryExists(subPath)) - { - break; - } - } - } - - // path[i] will be a '/', so skip that character - i++; - - // loop until `path.Length - 1` so CreateDirectory won't be called multiple - // times on path if the last character in the path is a '/' - for (; i < path.Length - 1; i++) - { - if (path[i] == '/') - { - string subPath = path.Substring(0, i); - - fs.CreateDirectory(subPath.ToU8Span()); - } - } - - fs.CreateDirectory(path.ToU8Span()); - } - - public static Result CreateOrOverwriteFile(this FileSystemClient fs, string path, long size) - { - return fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None); - } - - public static Result CreateOrOverwriteFile(this FileSystemClient fs, string path, long size, CreateFileOptions options) - { - path = PathTools.Normalize(path); - var u8Path = path.ToU8Span(); - - if (fs.FileExists(path)) - { - Result rc = fs.DeleteFile(u8Path); + rc = fs.FlushFile(destHandle); if (rc.IsFailure()) return rc; } + finally + { + if (destHandle.IsValid) + fs.CloseFile(destHandle); + } + } + finally + { + if (sourceHandle.IsValid) + fs.CloseFile(sourceHandle); + } - return fs.CreateFile(u8Path, size, CreateFileOptions.None); + return Result.Success; + } + + public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path) + { + return fs.EnumerateEntries(path, "*"); + } + + public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path, string searchPattern) + { + return fs.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories); + } + + public static IEnumerable EnumerateEntries(this FileSystemClient fs, string path, string searchPattern, SearchOptions searchOptions) + { + bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive); + bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories); + + fs.OpenDirectory(out DirectoryHandle sourceHandle, path.ToU8Span(), OpenDirectoryMode.All).ThrowIfFailure(); + + try + { + while (true) + { + DirectoryEntry dirEntry = default; + + fs.ReadDirectory(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry), sourceHandle); + if (entriesRead == 0) break; + + DirectoryEntryEx entry = FileSystemExtensions.GetDirectoryEntryEx(ref dirEntry, path); + + if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) + { + yield return entry; + } + + if (entry.Type != DirectoryEntryType.Directory || !recurse) continue; + + IEnumerable subEntries = + fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, searchOptions); + + foreach (DirectoryEntryEx subEntry in subEntries) + { + yield return subEntry; + } + } + } + finally + { + if (sourceHandle.IsValid) + fs.CloseDirectory(sourceHandle); } } + + public static bool DirectoryExists(this FileSystemClient fs, string path) + { + Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); + + return (rc.IsSuccess() && type == DirectoryEntryType.Directory); + } + + public static bool FileExists(this FileSystemClient fs, string path) + { + Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); + + return (rc.IsSuccess() && type == DirectoryEntryType.File); + } + + public static void EnsureDirectoryExists(this FileSystemClient fs, string path) + { + path = PathTools.Normalize(path); + if (fs.DirectoryExists(path)) return; + + PathTools.GetMountNameLength(path, out int mountNameLength).ThrowIfFailure(); + + // Find the first subdirectory in the path that doesn't exist + int i; + for (i = path.Length - 1; i > mountNameLength + 2; i--) + { + if (path[i] == '/') + { + string subPath = path.Substring(0, i); + + if (fs.DirectoryExists(subPath)) + { + break; + } + } + } + + // path[i] will be a '/', so skip that character + i++; + + // loop until `path.Length - 1` so CreateDirectory won't be called multiple + // times on path if the last character in the path is a '/' + for (; i < path.Length - 1; i++) + { + if (path[i] == '/') + { + string subPath = path.Substring(0, i); + + fs.CreateDirectory(subPath.ToU8Span()); + } + } + + fs.CreateDirectory(path.ToU8Span()); + } + + public static Result CreateOrOverwriteFile(this FileSystemClient fs, string path, long size) + { + return fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None); + } + + public static Result CreateOrOverwriteFile(this FileSystemClient fs, string path, long size, CreateFileOptions options) + { + path = PathTools.Normalize(path); + var u8Path = path.ToU8Span(); + + if (fs.FileExists(path)) + { + Result rc = fs.DeleteFile(u8Path); + if (rc.IsFailure()) return rc; + } + + return fs.CreateFile(u8Path, size, CreateFileOptions.None); + } } diff --git a/src/LibHac/Fs/FileTimeStamp.cs b/src/LibHac/Fs/FileTimeStamp.cs index cda15cec..afeccd6f 100644 --- a/src/LibHac/Fs/FileTimeStamp.cs +++ b/src/LibHac/Fs/FileTimeStamp.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Sequential, Size = 0x20)] +public struct FileTimeStampRaw { - [StructLayout(LayoutKind.Sequential, Size = 0x20)] - public struct FileTimeStampRaw - { - public long Created; - public long Accessed; - public long Modified; - public bool IsLocalTime; - } + public long Created; + public long Accessed; + public long Modified; + public bool IsLocalTime; } diff --git a/src/LibHac/Fs/FsContext.cs b/src/LibHac/Fs/FsContext.cs index f2f51fc7..01f60ef6 100644 --- a/src/LibHac/Fs/FsContext.cs +++ b/src/LibHac/Fs/FsContext.cs @@ -1,130 +1,129 @@ using System; -namespace LibHac.Fs +namespace LibHac.Fs; + +public enum AbortSpecifier { - public enum AbortSpecifier + Default, + Abort, + Return +} + +internal struct FsContextHandlerGlobals +{ + public bool IsAutoAbortEnabled; + public FsContext DefaultContext; + public FsContext AllReturnContext; + + public void Initialize(FileSystemClient fsClient) { - Default, - Abort, - Return - } - - internal struct FsContextHandlerGlobals - { - public bool IsAutoAbortEnabled; - public FsContext DefaultContext; - public FsContext AllReturnContext; - - public void Initialize(FileSystemClient fsClient) - { - DefaultContext = new FsContext(fsClient, FsContextHandler.DefaultResultHandler); - AllReturnContext = new FsContext(fsClient, FsContextHandler.AllReturnResultHandler); - } - } - - public delegate AbortSpecifier ResultHandler(FileSystemClient fs, Result result); - - public static class FsContextHandler - { - [ThreadStatic] - private static FsContext _currentThreadContext; - - internal static AbortSpecifier DefaultResultHandler(FileSystemClient fs, Result result) - { - if (fs.Globals.FsContextHandler.IsAutoAbortEnabled) - return AbortSpecifier.Default; - else - return AbortSpecifier.Return; - } - - internal static AbortSpecifier AllReturnResultHandler(FileSystemClient fs, Result result) - { - return AbortSpecifier.Return; - } - - public static void SetEnabledAutoAbort(this FileSystemClient fs, bool isEnabled) - { - fs.Globals.FsContextHandler.IsAutoAbortEnabled = isEnabled; - } - - public static void SetDefaultFsContextResultHandler(this FileSystemClient fs, - ResultHandler resultHandler) - { - fs.Globals.FsContextHandler.DefaultContext.SetHandler(resultHandler ?? DefaultResultHandler); - } - - public static ref FsContext GetCurrentThreadFsContext(this FileSystemClient fs) - { - ref FsContext context = ref _currentThreadContext; - if (context.IsValid) - { - return ref context; - } - - return ref fs.Globals.FsContextHandler.DefaultContext; - } - - public static void SetCurrentThreadFsContext(this FileSystemClient fs, FsContext context) - { - _currentThreadContext = context; - } - - public static bool IsResolubleAccessFailureResult(Result result) - { - return ResultFs.GameCardAccessFailed.Includes(result); - } - - public static bool IsAutoAbortPolicyCustomized(this FileSystemClient fs) - { - ref FsContextHandlerGlobals g = ref fs.Globals.FsContextHandler; - return !g.IsAutoAbortEnabled || GetCurrentThreadFsContext(fs).GetHandler() != DefaultResultHandler; - } - } - - public struct FsContext - { - private readonly FileSystemClient _fsClient; - private ResultHandler _resultHandler; - - internal bool IsValid => _fsClient is not null; - - public FsContext(FileSystemClient fsClient, ResultHandler resultHandler) - { - _fsClient = fsClient; - _resultHandler = resultHandler; - } - - public AbortSpecifier HandleResult(Result result) - { - return _resultHandler(_fsClient, result); - } - - public ResultHandler GetHandler() - { - return _resultHandler; - } - - public void SetHandler(ResultHandler handler) - { - _resultHandler = handler; - } - } - - public struct ScopedAutoAbortDisabler - { - private FileSystemClient _fsClient; - private FsContext _prevContext; - - public ScopedAutoAbortDisabler(FileSystemClient fs) - { - _fsClient = fs; - _prevContext = fs.GetCurrentThreadFsContext(); - fs.SetCurrentThreadFsContext(fs.Globals.FsContextHandler.AllReturnContext); - } - - public void Dispose() - { - _fsClient.SetCurrentThreadFsContext(_prevContext); - } + DefaultContext = new FsContext(fsClient, FsContextHandler.DefaultResultHandler); + AllReturnContext = new FsContext(fsClient, FsContextHandler.AllReturnResultHandler); + } +} + +public delegate AbortSpecifier ResultHandler(FileSystemClient fs, Result result); + +public static class FsContextHandler +{ + [ThreadStatic] + private static FsContext _currentThreadContext; + + internal static AbortSpecifier DefaultResultHandler(FileSystemClient fs, Result result) + { + if (fs.Globals.FsContextHandler.IsAutoAbortEnabled) + return AbortSpecifier.Default; + else + return AbortSpecifier.Return; + } + + internal static AbortSpecifier AllReturnResultHandler(FileSystemClient fs, Result result) + { + return AbortSpecifier.Return; + } + + public static void SetEnabledAutoAbort(this FileSystemClient fs, bool isEnabled) + { + fs.Globals.FsContextHandler.IsAutoAbortEnabled = isEnabled; + } + + public static void SetDefaultFsContextResultHandler(this FileSystemClient fs, + ResultHandler resultHandler) + { + fs.Globals.FsContextHandler.DefaultContext.SetHandler(resultHandler ?? DefaultResultHandler); + } + + public static ref FsContext GetCurrentThreadFsContext(this FileSystemClient fs) + { + ref FsContext context = ref _currentThreadContext; + if (context.IsValid) + { + return ref context; + } + + return ref fs.Globals.FsContextHandler.DefaultContext; + } + + public static void SetCurrentThreadFsContext(this FileSystemClient fs, FsContext context) + { + _currentThreadContext = context; + } + + public static bool IsResolubleAccessFailureResult(Result result) + { + return ResultFs.GameCardAccessFailed.Includes(result); + } + + public static bool IsAutoAbortPolicyCustomized(this FileSystemClient fs) + { + ref FsContextHandlerGlobals g = ref fs.Globals.FsContextHandler; + return !g.IsAutoAbortEnabled || GetCurrentThreadFsContext(fs).GetHandler() != DefaultResultHandler; + } +} + +public struct FsContext +{ + private readonly FileSystemClient _fsClient; + private ResultHandler _resultHandler; + + internal bool IsValid => _fsClient is not null; + + public FsContext(FileSystemClient fsClient, ResultHandler resultHandler) + { + _fsClient = fsClient; + _resultHandler = resultHandler; + } + + public AbortSpecifier HandleResult(Result result) + { + return _resultHandler(_fsClient, result); + } + + public ResultHandler GetHandler() + { + return _resultHandler; + } + + public void SetHandler(ResultHandler handler) + { + _resultHandler = handler; + } +} + +public struct ScopedAutoAbortDisabler +{ + private FileSystemClient _fsClient; + private FsContext _prevContext; + + public ScopedAutoAbortDisabler(FileSystemClient fs) + { + _fsClient = fs; + _prevContext = fs.GetCurrentThreadFsContext(); + fs.SetCurrentThreadFsContext(fs.Globals.FsContextHandler.AllReturnContext); + } + + public void Dispose() + { + _fsClient.SetCurrentThreadFsContext(_prevContext); } } diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index ea0ca1ad..cac7c8bf 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -1,265 +1,264 @@ using System; using LibHac.Fs.Fsa; -namespace LibHac.Fs +namespace LibHac.Fs; + +public enum BisPartitionId { - public enum BisPartitionId - { - BootPartition1Root = 0, - BootPartition2Root = 10, - UserDataRoot = 20, - BootConfigAndPackage2Part1 = 21, - BootConfigAndPackage2Part2 = 22, - BootConfigAndPackage2Part3 = 23, - BootConfigAndPackage2Part4 = 24, - BootConfigAndPackage2Part5 = 25, - BootConfigAndPackage2Part6 = 26, - CalibrationBinary = 27, - CalibrationFile = 28, - SafeMode = 29, - User = 30, - System = 31, - SystemProperEncryption = 32, - SystemProperPartition = 33, - SignedSystemPartitionOnSafeMode = 34 - } - - public enum ContentStorageId - { - System = 0, - User = 1, - SdCard = 2 - } - - public enum GameCardPartition - { - Update = 0, - Normal = 1, - Secure = 2, - Logo = 3 - } - - public enum GameCardPartitionRaw - { - NormalReadOnly = 0, - SecureReadOnly = 1, - RootWriteOnly = 2 - } - - public enum SaveDataSpaceId : byte - { - System = 0, - User = 1, - SdSystem = 2, - Temporary = 3, - SdCache = 4, - ProperSystem = 100, - SafeMode = 101, - BisAuto = 127 - } - - public enum CustomStorageId - { - System = 0, - SdCard = 1 - } - - public enum ContentType - { - Meta = 0, - Control = 1, - Manual = 2, - Logo = 3, - Data = 4 - } - - public enum FileSystemProxyType - { - Code = 0, - Rom = 1, - Logo = 2, - Control = 3, - Manual = 4, - Meta = 5, - Data = 6, - Package = 7, - RegisteredUpdate = 8 - } - - public enum SaveDataMetaType : byte - { - None = 0, - Thumbnail = 1, - ExtensionContext = 2 - } - - public enum SaveDataState : byte - { - Normal = 0, - Processing = 1, - State2 = 2, - MarkedForDeletion = 3, - Extending = 4, - ImportSuspended = 5 - } - - public enum ImageDirectoryId - { - Nand = 0, - SdCard = 1 - } - - public enum CloudBackupWorkStorageId - { - Nand = 0, - SdCard = 1 - } - - public enum BaseFileSystemId - { - ImageDirectoryNand = 0, - ImageDirectorySdCard = 1, - TemporaryDirectory = 2 - } - - /// - /// Specifies which operations are available on an . - /// - [Flags] - public enum OpenMode - { - Read = 1, - Write = 2, - AllowAppend = 4, - ReadWrite = Read | Write, - All = Read | Write | AllowAppend - } - - [Flags] - public enum ReadOptionFlag - { - None = 0 - } - - public enum OperationId - { - FillZero = 0, - DestroySignature = 1, - InvalidateCache = 2, - QueryRange = 3, - QueryUnpreparedRange = 4, - QueryLazyLoadCompletionRate = 5, - SetLazyLoadPriority = 6, - ReadyLazyLoadFile = 10001 - } - - public enum SaveDataType : byte - { - System = 0, - Account = 1, - Bcat = 2, - Device = 3, - Temporary = 4, - Cache = 5, - SystemBcat = 6 - } - - public enum SaveDataRank : byte - { - Primary = 0, - Secondary = 1 - } - - [Flags] - public enum SaveDataFlags - { - None = 0, - KeepAfterResettingSystemSaveData = 1 << 0, - KeepAfterRefurbishment = 1 << 1, - KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2, - NeedsSecureDelete = 1 << 3, - Restore = 1 << 4 - } - - [Flags] - public enum CommitOptionFlag - { - None = 0, - ClearRestoreFlag = 1, - SetRestoreFlag = 2 - } - - public enum SdmmcPort - { - Mmc = 0, - SdCard = 1, - GcAsic = 2 - } - - public enum CacheStorageTargetMedia - { - None = 0, - Nand = 1, - SdCard = 2 - } - - public enum SimulatingDeviceDetectionMode - { - NoSimulation = 0, - DeviceAttached = 1, - DeviceRemoved = 2 - } - - public enum SimulatingDeviceAccessFailureEventType - { - None = 0, - AccessTimeoutFailure = 1, - AccessFailure = 2, - DataCorruption = 3 - } - - [Flags] - public enum SimulatingDeviceTargetOperation - { - None = 0, - Read = 1 << 0, - Write = 1 << 1 - } - - public enum FsStackUsageThreadType - { - MainThread = 0, - IpcWorker = 1, - PipelineWorker = 2 - } - - public enum MmcPartition - { - UserData = 0, - BootPartition1 = 1, - BootPartition2 = 2 - } - - public enum MmcSpeedMode - { - Identification = 0, - LegacySpeed = 1, - HighSpeed = 2, - Hs200 = 3, - Hs400 = 4, - Unknown = 5 - } - - public enum SdCardSpeedMode - { - Identification = 0, - DefaultSpeed = 1, - HighSpeed = 2, - Sdr12 = 3, - Sdr25 = 4, - Sdr50 = 5, - Sdr104 = 6, - Ddr50 = 7, - Unknown = 8, - } + BootPartition1Root = 0, + BootPartition2Root = 10, + UserDataRoot = 20, + BootConfigAndPackage2Part1 = 21, + BootConfigAndPackage2Part2 = 22, + BootConfigAndPackage2Part3 = 23, + BootConfigAndPackage2Part4 = 24, + BootConfigAndPackage2Part5 = 25, + BootConfigAndPackage2Part6 = 26, + CalibrationBinary = 27, + CalibrationFile = 28, + SafeMode = 29, + User = 30, + System = 31, + SystemProperEncryption = 32, + SystemProperPartition = 33, + SignedSystemPartitionOnSafeMode = 34 +} + +public enum ContentStorageId +{ + System = 0, + User = 1, + SdCard = 2 +} + +public enum GameCardPartition +{ + Update = 0, + Normal = 1, + Secure = 2, + Logo = 3 +} + +public enum GameCardPartitionRaw +{ + NormalReadOnly = 0, + SecureReadOnly = 1, + RootWriteOnly = 2 +} + +public enum SaveDataSpaceId : byte +{ + System = 0, + User = 1, + SdSystem = 2, + Temporary = 3, + SdCache = 4, + ProperSystem = 100, + SafeMode = 101, + BisAuto = 127 +} + +public enum CustomStorageId +{ + System = 0, + SdCard = 1 +} + +public enum ContentType +{ + Meta = 0, + Control = 1, + Manual = 2, + Logo = 3, + Data = 4 +} + +public enum FileSystemProxyType +{ + Code = 0, + Rom = 1, + Logo = 2, + Control = 3, + Manual = 4, + Meta = 5, + Data = 6, + Package = 7, + RegisteredUpdate = 8 +} + +public enum SaveDataMetaType : byte +{ + None = 0, + Thumbnail = 1, + ExtensionContext = 2 +} + +public enum SaveDataState : byte +{ + Normal = 0, + Processing = 1, + State2 = 2, + MarkedForDeletion = 3, + Extending = 4, + ImportSuspended = 5 +} + +public enum ImageDirectoryId +{ + Nand = 0, + SdCard = 1 +} + +public enum CloudBackupWorkStorageId +{ + Nand = 0, + SdCard = 1 +} + +public enum BaseFileSystemId +{ + ImageDirectoryNand = 0, + ImageDirectorySdCard = 1, + TemporaryDirectory = 2 +} + +/// +/// Specifies which operations are available on an . +/// +[Flags] +public enum OpenMode +{ + Read = 1, + Write = 2, + AllowAppend = 4, + ReadWrite = Read | Write, + All = Read | Write | AllowAppend +} + +[Flags] +public enum ReadOptionFlag +{ + None = 0 +} + +public enum OperationId +{ + FillZero = 0, + DestroySignature = 1, + InvalidateCache = 2, + QueryRange = 3, + QueryUnpreparedRange = 4, + QueryLazyLoadCompletionRate = 5, + SetLazyLoadPriority = 6, + ReadyLazyLoadFile = 10001 +} + +public enum SaveDataType : byte +{ + System = 0, + Account = 1, + Bcat = 2, + Device = 3, + Temporary = 4, + Cache = 5, + SystemBcat = 6 +} + +public enum SaveDataRank : byte +{ + Primary = 0, + Secondary = 1 +} + +[Flags] +public enum SaveDataFlags +{ + None = 0, + KeepAfterResettingSystemSaveData = 1 << 0, + KeepAfterRefurbishment = 1 << 1, + KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2, + NeedsSecureDelete = 1 << 3, + Restore = 1 << 4 +} + +[Flags] +public enum CommitOptionFlag +{ + None = 0, + ClearRestoreFlag = 1, + SetRestoreFlag = 2 +} + +public enum SdmmcPort +{ + Mmc = 0, + SdCard = 1, + GcAsic = 2 +} + +public enum CacheStorageTargetMedia +{ + None = 0, + Nand = 1, + SdCard = 2 +} + +public enum SimulatingDeviceDetectionMode +{ + NoSimulation = 0, + DeviceAttached = 1, + DeviceRemoved = 2 +} + +public enum SimulatingDeviceAccessFailureEventType +{ + None = 0, + AccessTimeoutFailure = 1, + AccessFailure = 2, + DataCorruption = 3 +} + +[Flags] +public enum SimulatingDeviceTargetOperation +{ + None = 0, + Read = 1 << 0, + Write = 1 << 1 +} + +public enum FsStackUsageThreadType +{ + MainThread = 0, + IpcWorker = 1, + PipelineWorker = 2 +} + +public enum MmcPartition +{ + UserData = 0, + BootPartition1 = 1, + BootPartition2 = 2 +} + +public enum MmcSpeedMode +{ + Identification = 0, + LegacySpeed = 1, + HighSpeed = 2, + Hs200 = 3, + Hs400 = 4, + Unknown = 5 +} + +public enum SdCardSpeedMode +{ + Identification = 0, + DefaultSpeed = 1, + HighSpeed = 2, + Sdr12 = 3, + Sdr25 = 4, + Sdr50 = 5, + Sdr104 = 6, + Ddr50 = 7, + Unknown = 8, } diff --git a/src/LibHac/Fs/Fsa/DirectoryAccessor.cs b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs index e4a63733..bf8962e0 100644 --- a/src/LibHac/Fs/Fsa/DirectoryAccessor.cs +++ b/src/LibHac/Fs/Fsa/DirectoryAccessor.cs @@ -3,37 +3,36 @@ using LibHac.Common; using LibHac.Fs.Fsa; // ReSharper disable once CheckNamespace -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal class DirectoryAccessor : IDisposable { - internal class DirectoryAccessor : IDisposable + private UniqueRef _directory; + private FileSystemAccessor _parentFileSystem; + + public DirectoryAccessor(ref UniqueRef directory, FileSystemAccessor parentFileSystem) { - private UniqueRef _directory; - private FileSystemAccessor _parentFileSystem; + _directory = new UniqueRef(ref directory); + _parentFileSystem = parentFileSystem; + } - public DirectoryAccessor(ref UniqueRef directory, FileSystemAccessor parentFileSystem) - { - _directory = new UniqueRef(ref directory); - _parentFileSystem = parentFileSystem; - } + public void Dispose() + { + _directory.Reset(); + _parentFileSystem.NotifyCloseDirectory(this); - public void Dispose() - { - _directory.Reset(); - _parentFileSystem.NotifyCloseDirectory(this); + _directory.Destroy(); + } - _directory.Destroy(); - } + public FileSystemAccessor GetParent() => _parentFileSystem; - public FileSystemAccessor GetParent() => _parentFileSystem; + public Result Read(out long entriesRead, Span entryBuffer) + { + return _directory.Get.Read(out entriesRead, entryBuffer); + } - public Result Read(out long entriesRead, Span entryBuffer) - { - return _directory.Get.Read(out entriesRead, entryBuffer); - } - - public Result GetEntryCount(out long entryCount) - { - return _directory.Get.GetEntryCount(out entryCount); - } + public Result GetEntryCount(out long entryCount) + { + return _directory.Get.GetEntryCount(out entryCount); } } diff --git a/src/LibHac/Fs/Fsa/FileAccessor.cs b/src/LibHac/Fs/Fsa/FileAccessor.cs index 5204fc94..6f61137c 100644 --- a/src/LibHac/Fs/Fsa/FileAccessor.cs +++ b/src/LibHac/Fs/Fsa/FileAccessor.cs @@ -6,223 +6,222 @@ using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; // ReSharper disable once CheckNamespace -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal enum WriteState { - internal enum WriteState + None, + NeedsFlush, + Failed, +} + +internal class FileAccessor : IDisposable +{ + private const string NeedFlushMessage = "Error: nn::fs::CloseFile() failed because the file was not flushed.\n"; + + private UniqueRef _file; + private FileSystemAccessor _parentFileSystem; + private WriteState _writeState; + private Result _lastResult; + private OpenMode _openMode; + private FilePathHash _filePathHash; + // ReSharper disable once NotAccessedField.Local + private int _pathHashIndex; + + internal HorizonClient Hos { get; } + + public FileAccessor(HorizonClient hosClient, ref UniqueRef file, FileSystemAccessor parentFileSystem, + OpenMode mode) { - None, - NeedsFlush, - Failed, + Hos = hosClient; + + _file = new UniqueRef(ref file); + _parentFileSystem = parentFileSystem; + _writeState = WriteState.None; + _lastResult = Result.Success; + _openMode = mode; } - internal class FileAccessor : IDisposable + public void Dispose() { - private const string NeedFlushMessage = "Error: nn::fs::CloseFile() failed because the file was not flushed.\n"; - - private UniqueRef _file; - private FileSystemAccessor _parentFileSystem; - private WriteState _writeState; - private Result _lastResult; - private OpenMode _openMode; - private FilePathHash _filePathHash; - // ReSharper disable once NotAccessedField.Local - private int _pathHashIndex; - - internal HorizonClient Hos { get; } - - public FileAccessor(HorizonClient hosClient, ref UniqueRef file, FileSystemAccessor parentFileSystem, - OpenMode mode) + if (_lastResult.IsSuccess() && _writeState == WriteState.NeedsFlush) { - Hos = hosClient; - - _file = new UniqueRef(ref file); - _parentFileSystem = parentFileSystem; - _writeState = WriteState.None; - _lastResult = Result.Success; - _openMode = mode; + Hos.Fs.Impl.LogErrorMessage(ResultFs.NeedFlush.Value, NeedFlushMessage); + Abort.DoAbort(ResultFs.NeedFlush.Value); } - public void Dispose() + _parentFileSystem?.NotifyCloseFile(this); + + _file.Destroy(); + } + + public OpenMode GetOpenMode() => _openMode; + public WriteState GetWriteState() => _writeState; + public FileSystemAccessor GetParent() => _parentFileSystem; + + public void SetFilePathHash(FilePathHash filePathHash, int index) + { + _filePathHash = filePathHash; + _pathHashIndex = index; + } + + private Result UpdateLastResult(Result result) + { + if (!ResultFs.UsableSpaceNotEnough.Includes(result)) + _lastResult = result; + + return result; + } + + public Result ReadWithoutCacheAccessLog(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + return _file.Get.Read(out bytesRead, offset, destination, in option); + } + + private Result ReadWithCacheAccessLog(out long bytesRead, long offset, Span destination, + in ReadOption option, bool usePathCache, bool useDataCache) + { + throw new NotImplementedException(); + } + + public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + Result rc; + Span logBuffer = stackalloc byte[0x50]; + var handle = new FileHandle(this); + + if (_lastResult.IsFailure()) { - if (_lastResult.IsSuccess() && _writeState == WriteState.NeedsFlush) + if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle)) { - Hos.Fs.Impl.LogErrorMessage(ResultFs.NeedFlush.Value, NeedFlushMessage); - Abort.DoAbort(ResultFs.NeedFlush.Value); + Tick start = Hos.Os.GetSystemTick(); + rc = _lastResult; + Tick end = Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogOffset).AppendFormat(offset) + .Append(LogSize).AppendFormat(destination.Length) + .Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc)); + + Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer), + nameof(UserFile.ReadFile)); } - _parentFileSystem?.NotifyCloseFile(this); - - _file.Destroy(); + return _lastResult; } - public OpenMode GetOpenMode() => _openMode; - public WriteState GetWriteState() => _writeState; - public FileSystemAccessor GetParent() => _parentFileSystem; + // ReSharper disable ConditionIsAlwaysTrueOrFalse + bool usePathCache = _parentFileSystem is not null && _filePathHash.Data != 0; - public void SetFilePathHash(FilePathHash filePathHash, int index) + // Todo: Call IsGlobalFileDataCacheEnabled +#pragma warning disable 162 + bool useDataCache = false && _parentFileSystem is not null && _parentFileSystem.IsFileDataCacheAttachable(); +#pragma warning restore 162 + if (usePathCache || useDataCache) { - _filePathHash = filePathHash; - _pathHashIndex = index; + return ReadWithCacheAccessLog(out bytesRead, offset, destination, in option, usePathCache, + useDataCache); } - - private Result UpdateLastResult(Result result) + else { - if (!ResultFs.UsableSpaceNotEnough.Includes(result)) - _lastResult = result; + if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle)) + { + Tick start = Hos.Os.GetSystemTick(); + rc = ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option); + Tick end = Hos.Os.GetSystemTick(); - return result; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogOffset).AppendFormat(offset) + .Append(LogSize).AppendFormat(destination.Length) + .Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc)); + + Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer), + nameof(UserFile.ReadFile)); + } + else + { + rc = ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option); + } + + return rc; } + // ReSharper restore ConditionIsAlwaysTrueOrFalse + } - public Result ReadWithoutCacheAccessLog(out long bytesRead, long offset, Span destination, - in ReadOption option) + public Result Write(long offset, ReadOnlySpan source, in WriteOption option) + { + if (_lastResult.IsFailure()) + return _lastResult; + + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + if (_filePathHash.Data != 0) { - return _file.Get.Read(out bytesRead, offset, destination, in option); + throw new NotImplementedException(); + } + else + { + Result rc = UpdateLastResult(_file.Get.Write(offset, source, in option)); + if (rc.IsFailure()) return rc; } - private Result ReadWithCacheAccessLog(out long bytesRead, long offset, Span destination, - in ReadOption option, bool usePathCache, bool useDataCache) + setter.Set(option.HasFlushFlag() ? WriteState.None : WriteState.NeedsFlush); + return Result.Success; + } + + public Result Flush() + { + if (_lastResult.IsFailure()) + return _lastResult; + + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + Result rc = UpdateLastResult(_file.Get.Flush()); + if (rc.IsFailure()) return rc; + + setter.Set(WriteState.None); + return Result.Success; + } + + public Result SetSize(long size) + { + if (_lastResult.IsFailure()) + return _lastResult; + + WriteState oldWriteState = _writeState; + using ScopedSetter setter = + ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); + + Result rc = UpdateLastResult(_file.Get.SetSize(size)); + if (rc.IsFailure()) return rc; + + if (_filePathHash.Data != 0) { throw new NotImplementedException(); } - public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); + setter.Set(oldWriteState); + return Result.Success; + } - Result rc; - Span logBuffer = stackalloc byte[0x50]; - var handle = new FileHandle(this); + public Result GetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); - if (_lastResult.IsFailure()) - { - if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = Hos.Os.GetSystemTick(); - rc = _lastResult; - Tick end = Hos.Os.GetSystemTick(); + if (_lastResult.IsFailure()) + return _lastResult; - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogOffset).AppendFormat(offset) - .Append(LogSize).AppendFormat(destination.Length) - .Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc)); + return _file.Get.GetSize(out size); + } - Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer), - nameof(UserFile.ReadFile)); - } - - return _lastResult; - } - - // ReSharper disable ConditionIsAlwaysTrueOrFalse - bool usePathCache = _parentFileSystem is not null && _filePathHash.Data != 0; - - // Todo: Call IsGlobalFileDataCacheEnabled -#pragma warning disable 162 - bool useDataCache = false && _parentFileSystem is not null && _parentFileSystem.IsFileDataCacheAttachable(); -#pragma warning restore 162 - if (usePathCache || useDataCache) - { - return ReadWithCacheAccessLog(out bytesRead, offset, destination, in option, usePathCache, - useDataCache); - } - else - { - if (Hos.Fs.Impl.IsEnabledAccessLog() && Hos.Fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = Hos.Os.GetSystemTick(); - rc = ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option); - Tick end = Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogOffset).AppendFormat(offset) - .Append(LogSize).AppendFormat(destination.Length) - .Append(LogReadSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in bytesRead, rc)); - - Hos.Fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(logBuffer), - nameof(UserFile.ReadFile)); - } - else - { - rc = ReadWithoutCacheAccessLog(out bytesRead, offset, destination, in option); - } - - return rc; - } - // ReSharper restore ConditionIsAlwaysTrueOrFalse - } - - public Result Write(long offset, ReadOnlySpan source, in WriteOption option) - { - if (_lastResult.IsFailure()) - return _lastResult; - - using ScopedSetter setter = - ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); - - if (_filePathHash.Data != 0) - { - throw new NotImplementedException(); - } - else - { - Result rc = UpdateLastResult(_file.Get.Write(offset, source, in option)); - if (rc.IsFailure()) return rc; - } - - setter.Set(option.HasFlushFlag() ? WriteState.None : WriteState.NeedsFlush); - return Result.Success; - } - - public Result Flush() - { - if (_lastResult.IsFailure()) - return _lastResult; - - using ScopedSetter setter = - ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); - - Result rc = UpdateLastResult(_file.Get.Flush()); - if (rc.IsFailure()) return rc; - - setter.Set(WriteState.None); - return Result.Success; - } - - public Result SetSize(long size) - { - if (_lastResult.IsFailure()) - return _lastResult; - - WriteState oldWriteState = _writeState; - using ScopedSetter setter = - ScopedSetter.MakeScopedSetter(ref _writeState, WriteState.Failed); - - Result rc = UpdateLastResult(_file.Get.SetSize(size)); - if (rc.IsFailure()) return rc; - - if (_filePathHash.Data != 0) - { - throw new NotImplementedException(); - } - - setter.Set(oldWriteState); - return Result.Success; - } - - public Result GetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - if (_lastResult.IsFailure()) - return _lastResult; - - return _file.Get.GetSize(out size); - } - - public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return _file.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } + public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return _file.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); } } diff --git a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs index e1ff6b24..bd113e67 100644 --- a/src/LibHac/Fs/Fsa/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Fsa/FileSystemAccessor.cs @@ -8,669 +8,668 @@ using LibHac.Util; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; // ReSharper disable once CheckNamespace -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal class FileSystemAccessor : IDisposable { - internal class FileSystemAccessor : IDisposable + private const string EmptyMountNameMessage = "Error: Mount failed because the mount name was empty.\n"; + private const string TooLongMountNameMessage = "Error: Mount failed because the mount name was too long. The mount name was \"{0}\".\n"; + private const string FileNotClosedMessage = "Error: Unmount failed because not all files were closed.\n"; + private const string DirectoryNotClosedMessage = "Error: Unmount failed because not all directories were closed.\n"; + private const string InvalidFsEntryObjectMessage = "Invalid file or directory object."; + + private MountName _mountName; + private UniqueRef _fileSystem; + private LinkedList _openFiles; + private LinkedList _openDirectories; + private SdkMutexType _openListLock; + private UniqueRef _mountNameGenerator; + private UniqueRef _saveDataAttributeGetter; + private bool _isAccessLogEnabled; + private bool _isDataCacheAttachable; + private bool _isPathCacheAttachable; + private bool _isPathCacheAttached; + private IMultiCommitTarget _multiCommitTarget; + private PathFlags _pathFlags; + private Optional _dataId; + + internal HorizonClient Hos { get; } + + public FileSystemAccessor(HorizonClient hosClient, U8Span name, IMultiCommitTarget multiCommitTarget, + ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, + ref UniqueRef saveAttributeGetter) { - private const string EmptyMountNameMessage = "Error: Mount failed because the mount name was empty.\n"; - private const string TooLongMountNameMessage = "Error: Mount failed because the mount name was too long. The mount name was \"{0}\".\n"; - private const string FileNotClosedMessage = "Error: Unmount failed because not all files were closed.\n"; - private const string DirectoryNotClosedMessage = "Error: Unmount failed because not all directories were closed.\n"; - private const string InvalidFsEntryObjectMessage = "Invalid file or directory object."; + Hos = hosClient; - private MountName _mountName; - private UniqueRef _fileSystem; - private LinkedList _openFiles; - private LinkedList _openDirectories; - private SdkMutexType _openListLock; - private UniqueRef _mountNameGenerator; - private UniqueRef _saveDataAttributeGetter; - private bool _isAccessLogEnabled; - private bool _isDataCacheAttachable; - private bool _isPathCacheAttachable; - private bool _isPathCacheAttached; - private IMultiCommitTarget _multiCommitTarget; - private PathFlags _pathFlags; - private Optional _dataId; + _fileSystem = new UniqueRef(ref fileSystem); + _openFiles = new LinkedList(); + _openDirectories = new LinkedList(); + _openListLock.Initialize(); + _mountNameGenerator = new UniqueRef(ref mountNameGenerator); + _saveDataAttributeGetter = new UniqueRef(ref saveAttributeGetter); + _multiCommitTarget = multiCommitTarget; - internal HorizonClient Hos { get; } - - public FileSystemAccessor(HorizonClient hosClient, U8Span name, IMultiCommitTarget multiCommitTarget, - ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, - ref UniqueRef saveAttributeGetter) + if (name.IsEmpty()) { - Hos = hosClient; - - _fileSystem = new UniqueRef(ref fileSystem); - _openFiles = new LinkedList(); - _openDirectories = new LinkedList(); - _openListLock.Initialize(); - _mountNameGenerator = new UniqueRef(ref mountNameGenerator); - _saveDataAttributeGetter = new UniqueRef(ref saveAttributeGetter); - _multiCommitTarget = multiCommitTarget; - - if (name.IsEmpty()) - { - Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, EmptyMountNameMessage); - Abort.DoAbort(ResultFs.InvalidMountName.Value); - } - - int mountLength = StringUtils.Copy(_mountName.Name, name, PathTool.MountNameLengthMax + 1); - - if (mountLength > PathTool.MountNameLengthMax) - { - Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, TooLongMountNameMessage, - name.ToString()); - Abort.DoAbort(ResultFs.InvalidMountName.Value); - } - - if (StringUtils.Compare(_mountName.Name, CommonMountNames.HostRootFileSystemMountName) == 0) - { - _pathFlags.AllowWindowsPath(); - } + Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, EmptyMountNameMessage); + Abort.DoAbort(ResultFs.InvalidMountName.Value); } - public void Dispose() + int mountLength = StringUtils.Copy(_mountName.Name, name, PathTool.MountNameLengthMax + 1); + + if (mountLength > PathTool.MountNameLengthMax) { - using (ScopedLock.Lock(ref _openListLock)) - { - DumpUnclosedAccessorList(OpenMode.All, OpenDirectoryMode.All); - - if (_openFiles.Count != 0) - { - Hos.Fs.Impl.LogErrorMessage(ResultFs.FileNotClosed.Value, FileNotClosedMessage); - Abort.DoAbort(ResultFs.FileNotClosed.Value); - } - - if (_openDirectories.Count != 0) - { - Hos.Fs.Impl.LogErrorMessage(ResultFs.DirectoryNotClosed.Value, DirectoryNotClosedMessage); - Abort.DoAbort(ResultFs.DirectoryNotClosed.Value); - } - - if (_isPathCacheAttached) - { - throw new NotImplementedException(); - } - } - - _saveDataAttributeGetter.Destroy(); - _mountNameGenerator.Destroy(); - _fileSystem.Destroy(); + Hos.Fs.Impl.LogErrorMessage(ResultFs.InvalidMountName.Value, TooLongMountNameMessage, + name.ToString()); + Abort.DoAbort(ResultFs.InvalidMountName.Value); } - private static void Remove(LinkedList list, T item) + if (StringUtils.Compare(_mountName.Name, CommonMountNames.HostRootFileSystemMountName) == 0) { - LinkedListNode node = list.Find(item); + _pathFlags.AllowWindowsPath(); + } + } - if (node is not null) + public void Dispose() + { + using (ScopedLock.Lock(ref _openListLock)) + { + DumpUnclosedAccessorList(OpenMode.All, OpenDirectoryMode.All); + + if (_openFiles.Count != 0) { - list.Remove(node); - return; + Hos.Fs.Impl.LogErrorMessage(ResultFs.FileNotClosed.Value, FileNotClosedMessage); + Abort.DoAbort(ResultFs.FileNotClosed.Value); } - Assert.SdkAssert(false, InvalidFsEntryObjectMessage); - } - - public void SetAccessLog(bool isEnabled) => _isAccessLogEnabled = isEnabled; - public void SetFileDataCacheAttachable(bool isAttachable) => _isDataCacheAttachable = isAttachable; - public void SetPathBasedFileDataCacheAttachable(bool isAttachable) => _isPathCacheAttachable = isAttachable; - - public bool IsEnabledAccessLog() => _isAccessLogEnabled; - public bool IsFileDataCacheAttachable() => _isDataCacheAttachable; - public bool IsPathBasedFileDataCacheAttachable() => _isPathCacheAttachable; - - public void AttachPathBasedFileDataCache() - { - if (_isPathCacheAttachable) - _isPathCacheAttached = true; - } - - public Optional GetDataId() => _dataId; - public void SetDataId(Optional dataId) => _dataId = dataId; - - public Result SetUpPath(ref Path path, U8Span pathBuffer) - { - Result rc = PathFormatter.IsNormalized(out bool isNormalized, out _, pathBuffer, _pathFlags); - - if (rc.IsSuccess() && isNormalized) + if (_openDirectories.Count != 0) { - path.SetShallowBuffer(pathBuffer); + Hos.Fs.Impl.LogErrorMessage(ResultFs.DirectoryNotClosed.Value, DirectoryNotClosedMessage); + Abort.DoAbort(ResultFs.DirectoryNotClosed.Value); } - else - { - if (_pathFlags.IsWindowsPathAllowed()) - { - rc = path.InitializeWithReplaceForwardSlashes(pathBuffer); - if (rc.IsFailure()) return rc; - } - else - { - rc = path.InitializeWithReplaceBackslash(pathBuffer); - if (rc.IsFailure()) return rc; - } - - rc = path.Normalize(_pathFlags); - if (rc.IsFailure()) return rc; - } - - if (path.GetLength() > PathTool.EntryNameLengthMax) - return ResultFs.TooLongPath.Log(); - - return Result.Success; - } - - public Result CreateFile(U8Span path, long size, CreateFileOptions option) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; if (_isPathCacheAttached) { throw new NotImplementedException(); } + } + + _saveDataAttributeGetter.Destroy(); + _mountNameGenerator.Destroy(); + _fileSystem.Destroy(); + } + + private static void Remove(LinkedList list, T item) + { + LinkedListNode node = list.Find(item); + + if (node is not null) + { + list.Remove(node); + return; + } + + Assert.SdkAssert(false, InvalidFsEntryObjectMessage); + } + + public void SetAccessLog(bool isEnabled) => _isAccessLogEnabled = isEnabled; + public void SetFileDataCacheAttachable(bool isAttachable) => _isDataCacheAttachable = isAttachable; + public void SetPathBasedFileDataCacheAttachable(bool isAttachable) => _isPathCacheAttachable = isAttachable; + + public bool IsEnabledAccessLog() => _isAccessLogEnabled; + public bool IsFileDataCacheAttachable() => _isDataCacheAttachable; + public bool IsPathBasedFileDataCacheAttachable() => _isPathCacheAttachable; + + public void AttachPathBasedFileDataCache() + { + if (_isPathCacheAttachable) + _isPathCacheAttached = true; + } + + public Optional GetDataId() => _dataId; + public void SetDataId(Optional dataId) => _dataId = dataId; + + public Result SetUpPath(ref Path path, U8Span pathBuffer) + { + Result rc = PathFormatter.IsNormalized(out bool isNormalized, out _, pathBuffer, _pathFlags); + + if (rc.IsSuccess() && isNormalized) + { + path.SetShallowBuffer(pathBuffer); + } + else + { + if (_pathFlags.IsWindowsPathAllowed()) + { + rc = path.InitializeWithReplaceForwardSlashes(pathBuffer); + if (rc.IsFailure()) return rc; + } else { - rc = _fileSystem.Get.CreateFile(in pathNormalized, size, option); + rc = path.InitializeWithReplaceBackslash(pathBuffer); if (rc.IsFailure()) return rc; } - return Result.Success; + rc = path.Normalize(_pathFlags); + if (rc.IsFailure()) return rc; } - public Result DeleteFile(U8Span path) + if (path.GetLength() > PathTool.EntryNameLengthMax) + return ResultFs.TooLongPath.Log(); + + return Result.Success; + } + + public Result CreateFile(U8Span path, long size, CreateFileOptions option) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.Get.CreateFile(in pathNormalized, size, option); if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.DeleteFile(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; } - public Result CreateDirectory(U8Span path) + return Result.Success; + } + + public Result DeleteFile(U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.DeleteFile(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CreateDirectory(U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.CreateDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result DeleteDirectory(U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.CreateDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result DeleteDirectoryRecursively(U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.DeleteDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CleanDirectoryRecursively(U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.CleanDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result RenameFile(U8Span currentPath, U8Span newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); + if (rc.IsFailure()) return rc; + + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), newPath); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.Get.RenameFile(in currentPathNormalized, in newPathNormalized); if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.CreateDirectory(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; } - public Result DeleteDirectory(U8Span path) + return Result.Success; + } + + public Result RenameDirectory(U8Span currentPath, U8Span newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); + if (rc.IsFailure()) return rc; + + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), newPath); + if (rc.IsFailure()) return rc; + + if (_isPathCacheAttached) { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); + throw new NotImplementedException(); + } + else + { + rc = _fileSystem.Get.RenameDirectory(in currentPathNormalized, in newPathNormalized); if (rc.IsFailure()) return rc; + } + return Result.Success; + } - rc = _fileSystem.Get.CreateDirectory(in pathNormalized); - if (rc.IsFailure()) return rc; + public Result GetEntryType(out DirectoryEntryType entryType, U8Span path) + { + UnsafeHelpers.SkipParamInit(out entryType); - return Result.Success; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.GetEntryType(out entryType, in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result GetFreeSpaceSize(out long freeSpace, U8Span path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.GetFreeSpaceSize(out freeSpace, in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result GetTotalSpaceSize(out long totalSpace, U8Span path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.GetTotalSpaceSize(out totalSpace, in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result OpenFile(ref UniqueRef outFile, U8Span path, OpenMode mode) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(); + rc = _fileSystem.Get.OpenFile(ref file.Ref(), in pathNormalized, mode); + if (rc.IsFailure()) return rc; + + var accessor = new FileAccessor(Hos, ref file.Ref(), this, mode); + + using (ScopedLock.Lock(ref _openListLock)) + { + _openFiles.AddLast(accessor); } - public Result DeleteDirectoryRecursively(U8Span path) + if (_isPathCacheAttached) { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.DeleteDirectoryRecursively(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result CleanDirectoryRecursively(U8Span path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.CleanDirectoryRecursively(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result RenameFile(U8Span currentPath, U8Span newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); - if (rc.IsFailure()) return rc; - - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), newPath); - if (rc.IsFailure()) return rc; - - if (_isPathCacheAttached) + if (mode.HasFlag(OpenMode.AllowAppend)) { throw new NotImplementedException(); } else - { - rc = _fileSystem.Get.RenameFile(in currentPathNormalized, in newPathNormalized); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - public Result RenameDirectory(U8Span currentPath, U8Span newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); - if (rc.IsFailure()) return rc; - - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), newPath); - if (rc.IsFailure()) return rc; - - if (_isPathCacheAttached) { throw new NotImplementedException(); } - else + } + + outFile.Reset(accessor); + return Result.Success; + } + + public Result OpenDirectory(ref UniqueRef outDirectory, U8Span path, OpenDirectoryMode mode) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + using var directory = new UniqueRef(); + rc = _fileSystem.Get.OpenDirectory(ref directory.Ref(), in pathNormalized, mode); + if (rc.IsFailure()) return rc; + + var accessor = new DirectoryAccessor(ref directory.Ref(), this); + + using (ScopedLock.Lock(ref _openListLock)) + { + _openDirectories.AddLast(accessor); + } + + outDirectory.Reset(accessor); + return Result.Success; + } + + public Result Commit() + { + static bool HasOpenWriteModeFiles(LinkedList list) + { + for (LinkedListNode file = list.First; file is not null; file = file.Next) { - rc = _fileSystem.Get.RenameDirectory(in currentPathNormalized, in newPathNormalized); - if (rc.IsFailure()) return rc; - } - return Result.Success; - } - - public Result GetEntryType(out DirectoryEntryType entryType, U8Span path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.GetEntryType(out entryType, in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result GetFreeSpaceSize(out long freeSpace, U8Span path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.GetFreeSpaceSize(out freeSpace, in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result GetTotalSpaceSize(out long totalSpace, U8Span path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.GetTotalSpaceSize(out totalSpace, in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result OpenFile(ref UniqueRef outFile, U8Span path, OpenMode mode) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(); - rc = _fileSystem.Get.OpenFile(ref file.Ref(), in pathNormalized, mode); - if (rc.IsFailure()) return rc; - - var accessor = new FileAccessor(Hos, ref file.Ref(), this, mode); - - using (ScopedLock.Lock(ref _openListLock)) - { - _openFiles.AddLast(accessor); - } - - if (_isPathCacheAttached) - { - if (mode.HasFlag(OpenMode.AllowAppend)) + if (file.Value.GetOpenMode().HasFlag(OpenMode.Write)) { - throw new NotImplementedException(); - } - else - { - throw new NotImplementedException(); + return true; } } - outFile.Reset(accessor); - return Result.Success; + return false; } - public Result OpenDirectory(ref UniqueRef outDirectory, U8Span path, OpenDirectoryMode mode) + using (ScopedLock.Lock(ref _openListLock)) { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + DumpUnclosedAccessorList(OpenMode.Write, 0); - using var directory = new UniqueRef(); - rc = _fileSystem.Get.OpenDirectory(ref directory.Ref(), in pathNormalized, mode); - if (rc.IsFailure()) return rc; + if (HasOpenWriteModeFiles(_openFiles)) + return ResultFs.WriteModeFileNotClosed.Log(); + } - var accessor = new DirectoryAccessor(ref directory.Ref(), this); + return _fileSystem.Get.Commit(); + } - using (ScopedLock.Lock(ref _openListLock)) + public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.GetFileTimeStampRaw(out timeStamp, in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public U8Span GetName() + { + return new U8Span(_mountName.Name); + } + + public Result GetCommonMountName(Span nameBuffer) + { + if (!_mountNameGenerator.HasValue) + return ResultFs.PreconditionViolation.Log(); + + return _mountNameGenerator.Get.GenerateCommonMountName(nameBuffer); + } + + public Result GetSaveDataAttribute(out SaveDataAttribute attribute) + { + UnsafeHelpers.SkipParamInit(out attribute); + + if (!_saveDataAttributeGetter.HasValue) + return ResultFs.PreconditionViolation.Log(); + + Result rc = _saveDataAttributeGetter.Get.GetSaveDataAttribute(out attribute); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public SharedRef GetMultiCommitTarget() + { + if (_multiCommitTarget is not null) + { + return _multiCommitTarget.GetMultiCommitTarget(); + } + else + { + return new SharedRef(); + } + } + + public void PurgeFileDataCache(FileDataCacheAccessor cacheAccessor) + { + cacheAccessor.Purge(_fileSystem.Get); + } + + internal void NotifyCloseFile(FileAccessor file) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _openListLock); + Remove(_openFiles, file); + } + + internal void NotifyCloseDirectory(DirectoryAccessor directory) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _openListLock); + Remove(_openDirectories, directory); + } + + private static ReadOnlySpan LogFsModuleName => new[] { (byte)'$', (byte)'f', (byte)'s' }; // "$fs" + + private static ReadOnlySpan LogFsErrorInfo => // "------ FS ERROR INFORMATION ------\n" + new[] + { + (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)' ', (byte)'F', + (byte)'S', (byte)' ', (byte)'E', (byte)'R', (byte)'R', (byte)'O', (byte)'R', (byte)' ', + (byte)'I', (byte)'N', (byte)'F', (byte)'O', (byte)'R', (byte)'M', (byte)'A', (byte)'T', + (byte)'I', (byte)'O', (byte)'N', (byte)' ', (byte)'-', (byte)'-', (byte)'-', (byte)'-', + (byte)'-', (byte)'-', (byte)'\\', (byte)'n' + }; + + private static ReadOnlySpan LogFileNotClosed => // "Error: File not closed" + new[] + { + (byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'F', + (byte)'i', (byte)'l', (byte)'e', (byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ', + (byte)'c', (byte)'l', (byte)'o', (byte)'s', (byte)'e', (byte)'d' + }; + + private static ReadOnlySpan LogDirectoryNotClosed => // "Error: Directory not closed" + new[] + { + (byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'D', + (byte)'i', (byte)'r', (byte)'e', (byte)'c', (byte)'t', (byte)'o', (byte)'r', (byte)'y', + (byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ', (byte)'c', (byte)'l', (byte)'o', + (byte)'s', (byte)'e', (byte)'d' + }; + + private static ReadOnlySpan LogMountName => // " (mount_name: "" + new[] + { + (byte)' ', (byte)'(', (byte)'m', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)'_', + (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ', (byte)'"' + }; + + private static ReadOnlySpan LogCount => // "", count: " + new[] + { + (byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t', + (byte)':', (byte)' ' + }; + + public static ReadOnlySpan LogLineEnd => new[] { (byte)')', (byte)'\\', (byte)'n' }; // ")\n" + + public static ReadOnlySpan LogOrOperator => new[] { (byte)' ', (byte)'|', (byte)' ' }; // " | " + + private static ReadOnlySpan LogOpenModeRead => // "OpenMode_Read" + new[] + { + (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', + (byte)'_', (byte)'R', (byte)'e', (byte)'a', (byte)'d' + }; + + private static ReadOnlySpan LogOpenModeWrite => // "OpenMode_Write" + new[] + { + (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', + (byte)'_', (byte)'W', (byte)'r', (byte)'i', (byte)'t', (byte)'e' + }; + + private static ReadOnlySpan LogOpenModeAppend => // "OpenMode_AllowAppend" + new[] + { + (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', + (byte)'_', (byte)'A', (byte)'l', (byte)'l', (byte)'o', (byte)'w', (byte)'A', (byte)'p', + (byte)'p', (byte)'e', (byte)'n', (byte)'d' + }; + + private static ReadOnlySpan LogHandle => // " handle: 0x" + new[] + { + (byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)'h', (byte)'a', (byte)'n', + (byte)'d', (byte)'l', (byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x' + }; + + private static ReadOnlySpan LogOpenMode => // ", open_mode: " + new[] + { + (byte)',', (byte)' ', (byte)'o', (byte)'p', (byte)'e', (byte)'n', (byte)'_', (byte)'m', + (byte)'o', (byte)'d', (byte)'e', (byte)':', (byte)' ' + }; + + private static ReadOnlySpan LogSize => // ", size: " + new[] + { + (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', (byte)' ' + }; + + private void DumpUnclosedAccessorList(OpenMode fileOpenModeMask, OpenDirectoryMode directoryOpenModeMask) + { + static int GetOpenFileCount(LinkedList list, OpenMode mask) + { + int count = 0; + + for (LinkedListNode file = list.First; file is not null; file = file.Next) { - _openDirectories.AddLast(accessor); + if ((file.Value.GetOpenMode() & mask) != 0) + count++; } - outDirectory.Reset(accessor); - return Result.Success; + return count; } - public Result Commit() + Span stringBuffer = stackalloc byte[0xA0]; + Span openModeStringBuffer = stackalloc byte[0x40]; + + int openFileCount = GetOpenFileCount(_openFiles, fileOpenModeMask); + + if (openFileCount > 0 || directoryOpenModeMask != 0 && _openDirectories.Count != 0) { - static bool HasOpenWriteModeFiles(LinkedList list) + Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, LogFsErrorInfo); + } + + if (openFileCount > 0) + { + var sb = new U8StringBuilder(stringBuffer, true); + sb.Append(LogFileNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount) + .AppendFormat(openFileCount).Append(LogLineEnd); + + Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer); + sb.Dispose(); + + for (LinkedListNode file = _openFiles.First; file is not null; file = file.Next) { - for (LinkedListNode file = list.First; file is not null; file = file.Next) + OpenMode openMode = file.Value.GetOpenMode(); + + if ((openMode & fileOpenModeMask) == 0) + continue; + + Result rc = file.Value.GetSize(out long fileSize); + if (rc.IsFailure()) + fileSize = -1; + + var openModeString = new U8StringBuilder(openModeStringBuffer); + + ReadOnlySpan readModeString = openMode.HasFlag(OpenMode.Read) ? LogOpenModeRead : default; + openModeString.Append(readModeString); + Assert.SdkAssert(!openModeString.Overflowed); + + if (openMode.HasFlag(OpenMode.Write)) { - if (file.Value.GetOpenMode().HasFlag(OpenMode.Write)) - { - return true; - } - } + if (openModeString.Length > 0) + sb.Append(LogOrOperator); - return false; - } - - using (ScopedLock.Lock(ref _openListLock)) - { - DumpUnclosedAccessorList(OpenMode.Write, 0); - - if (HasOpenWriteModeFiles(_openFiles)) - return ResultFs.WriteModeFileNotClosed.Log(); - } - - return _fileSystem.Get.Commit(); - } - - public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.GetFileTimeStampRaw(out timeStamp, in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public U8Span GetName() - { - return new U8Span(_mountName.Name); - } - - public Result GetCommonMountName(Span nameBuffer) - { - if (!_mountNameGenerator.HasValue) - return ResultFs.PreconditionViolation.Log(); - - return _mountNameGenerator.Get.GenerateCommonMountName(nameBuffer); - } - - public Result GetSaveDataAttribute(out SaveDataAttribute attribute) - { - UnsafeHelpers.SkipParamInit(out attribute); - - if (!_saveDataAttributeGetter.HasValue) - return ResultFs.PreconditionViolation.Log(); - - Result rc = _saveDataAttributeGetter.Get.GetSaveDataAttribute(out attribute); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public SharedRef GetMultiCommitTarget() - { - if (_multiCommitTarget is not null) - { - return _multiCommitTarget.GetMultiCommitTarget(); - } - else - { - return new SharedRef(); - } - } - - public void PurgeFileDataCache(FileDataCacheAccessor cacheAccessor) - { - cacheAccessor.Purge(_fileSystem.Get); - } - - internal void NotifyCloseFile(FileAccessor file) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _openListLock); - Remove(_openFiles, file); - } - - internal void NotifyCloseDirectory(DirectoryAccessor directory) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _openListLock); - Remove(_openDirectories, directory); - } - - private static ReadOnlySpan LogFsModuleName => new[] { (byte)'$', (byte)'f', (byte)'s' }; // "$fs" - - private static ReadOnlySpan LogFsErrorInfo => // "------ FS ERROR INFORMATION ------\n" - new[] - { - (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)' ', (byte)'F', - (byte)'S', (byte)' ', (byte)'E', (byte)'R', (byte)'R', (byte)'O', (byte)'R', (byte)' ', - (byte)'I', (byte)'N', (byte)'F', (byte)'O', (byte)'R', (byte)'M', (byte)'A', (byte)'T', - (byte)'I', (byte)'O', (byte)'N', (byte)' ', (byte)'-', (byte)'-', (byte)'-', (byte)'-', - (byte)'-', (byte)'-', (byte)'\\', (byte)'n' - }; - - private static ReadOnlySpan LogFileNotClosed => // "Error: File not closed" - new[] - { - (byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'F', - (byte)'i', (byte)'l', (byte)'e', (byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ', - (byte)'c', (byte)'l', (byte)'o', (byte)'s', (byte)'e', (byte)'d' - }; - - private static ReadOnlySpan LogDirectoryNotClosed => // "Error: Directory not closed" - new[] - { - (byte)'E', (byte)'r', (byte)'r', (byte)'o', (byte)'r', (byte)':', (byte)' ', (byte)'D', - (byte)'i', (byte)'r', (byte)'e', (byte)'c', (byte)'t', (byte)'o', (byte)'r', (byte)'y', - (byte)' ', (byte)'n', (byte)'o', (byte)'t', (byte)' ', (byte)'c', (byte)'l', (byte)'o', - (byte)'s', (byte)'e', (byte)'d' - }; - - private static ReadOnlySpan LogMountName => // " (mount_name: "" - new[] - { - (byte)' ', (byte)'(', (byte)'m', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)'_', - (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ', (byte)'"' - }; - - private static ReadOnlySpan LogCount => // "", count: " - new[] - { - (byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t', - (byte)':', (byte)' ' - }; - - public static ReadOnlySpan LogLineEnd => new[] { (byte)')', (byte)'\\', (byte)'n' }; // ")\n" - - public static ReadOnlySpan LogOrOperator => new[] { (byte)' ', (byte)'|', (byte)' ' }; // " | " - - private static ReadOnlySpan LogOpenModeRead => // "OpenMode_Read" - new[] - { - (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', - (byte)'_', (byte)'R', (byte)'e', (byte)'a', (byte)'d' - }; - - private static ReadOnlySpan LogOpenModeWrite => // "OpenMode_Write" - new[] - { - (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', - (byte)'_', (byte)'W', (byte)'r', (byte)'i', (byte)'t', (byte)'e' - }; - - private static ReadOnlySpan LogOpenModeAppend => // "OpenMode_AllowAppend" - new[] - { - (byte)'O', (byte)'p', (byte)'e', (byte)'n', (byte)'M', (byte)'o', (byte)'d', (byte)'e', - (byte)'_', (byte)'A', (byte)'l', (byte)'l', (byte)'o', (byte)'w', (byte)'A', (byte)'p', - (byte)'p', (byte)'e', (byte)'n', (byte)'d' - }; - - private static ReadOnlySpan LogHandle => // " handle: 0x" - new[] - { - (byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)' ', (byte)'h', (byte)'a', (byte)'n', - (byte)'d', (byte)'l', (byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x' - }; - - private static ReadOnlySpan LogOpenMode => // ", open_mode: " - new[] - { - (byte)',', (byte)' ', (byte)'o', (byte)'p', (byte)'e', (byte)'n', (byte)'_', (byte)'m', - (byte)'o', (byte)'d', (byte)'e', (byte)':', (byte)' ' - }; - - private static ReadOnlySpan LogSize => // ", size: " - new[] - { - (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', (byte)' ' - }; - - private void DumpUnclosedAccessorList(OpenMode fileOpenModeMask, OpenDirectoryMode directoryOpenModeMask) - { - static int GetOpenFileCount(LinkedList list, OpenMode mask) - { - int count = 0; - - for (LinkedListNode file = list.First; file is not null; file = file.Next) - { - if ((file.Value.GetOpenMode() & mask) != 0) - count++; - } - - return count; - } - - Span stringBuffer = stackalloc byte[0xA0]; - Span openModeStringBuffer = stackalloc byte[0x40]; - - int openFileCount = GetOpenFileCount(_openFiles, fileOpenModeMask); - - if (openFileCount > 0 || directoryOpenModeMask != 0 && _openDirectories.Count != 0) - { - Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, LogFsErrorInfo); - } - - if (openFileCount > 0) - { - var sb = new U8StringBuilder(stringBuffer, true); - sb.Append(LogFileNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount) - .AppendFormat(openFileCount).Append(LogLineEnd); - - Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer); - sb.Dispose(); - - for (LinkedListNode file = _openFiles.First; file is not null; file = file.Next) - { - OpenMode openMode = file.Value.GetOpenMode(); - - if ((openMode & fileOpenModeMask) == 0) - continue; - - Result rc = file.Value.GetSize(out long fileSize); - if (rc.IsFailure()) - fileSize = -1; - - var openModeString = new U8StringBuilder(openModeStringBuffer); - - ReadOnlySpan readModeString = openMode.HasFlag(OpenMode.Read) ? LogOpenModeRead : default; - openModeString.Append(readModeString); + openModeString.Append(LogOpenModeWrite); Assert.SdkAssert(!openModeString.Overflowed); - - if (openMode.HasFlag(OpenMode.Write)) - { - if (openModeString.Length > 0) - sb.Append(LogOrOperator); - - openModeString.Append(LogOpenModeWrite); - Assert.SdkAssert(!openModeString.Overflowed); - } - - if (openMode.HasFlag(OpenMode.AllowAppend)) - { - if (openModeString.Length > 0) - sb.Append(LogOrOperator); - - openModeString.Append(LogOpenModeAppend); - Assert.SdkAssert(!openModeString.Overflowed); - } - - var fileInfoString = new U8StringBuilder(stringBuffer, true); - fileInfoString.Append(LogHandle).AppendFormat(file.Value.GetHashCode(), 'x', 16).Append(LogOpenMode) - .Append(openModeString.Buffer).Append(LogSize).AppendFormat(fileSize).Append((byte)'\n'); - - Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, fileInfoString.Buffer); - fileInfoString.Dispose(); } - } - if (directoryOpenModeMask != 0 && _openDirectories.Count != 0) - { - var sb = new U8StringBuilder(stringBuffer, true); - sb.Append(LogDirectoryNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount) - .AppendFormat(_openDirectories.Count).Append(LogLineEnd); - - Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer); - sb.Dispose(); - - for (LinkedListNode dir = _openDirectories.First; dir is not null; dir = dir.Next) + if (openMode.HasFlag(OpenMode.AllowAppend)) { - var dirInfoString = new U8StringBuilder(stringBuffer, true); - dirInfoString.Append(LogHandle).AppendFormat(dir.Value.GetHashCode(), 'x', 16).Append((byte)'\n'); + if (openModeString.Length > 0) + sb.Append(LogOrOperator); - Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, dirInfoString.Buffer); - dirInfoString.Dispose(); + openModeString.Append(LogOpenModeAppend); + Assert.SdkAssert(!openModeString.Overflowed); } + + var fileInfoString = new U8StringBuilder(stringBuffer, true); + fileInfoString.Append(LogHandle).AppendFormat(file.Value.GetHashCode(), 'x', 16).Append(LogOpenMode) + .Append(openModeString.Buffer).Append(LogSize).AppendFormat(fileSize).Append((byte)'\n'); + + Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, fileInfoString.Buffer); + fileInfoString.Dispose(); + } + } + + if (directoryOpenModeMask != 0 && _openDirectories.Count != 0) + { + var sb = new U8StringBuilder(stringBuffer, true); + sb.Append(LogDirectoryNotClosed).Append(LogMountName).Append(GetName()).Append(LogCount) + .AppendFormat(_openDirectories.Count).Append(LogLineEnd); + + Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, sb.Buffer); + sb.Dispose(); + + for (LinkedListNode dir = _openDirectories.First; dir is not null; dir = dir.Next) + { + var dirInfoString = new U8StringBuilder(stringBuffer, true); + dirInfoString.Append(LogHandle).AppendFormat(dir.Value.GetHashCode(), 'x', 16).Append((byte)'\n'); + + Hos.Diag.Impl.LogImpl(LogFsModuleName, LogSeverity.Error, dirInfoString.Buffer); + dirInfoString.Dispose(); } } } diff --git a/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs b/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs index 95bb183c..8eb2d3cd 100644 --- a/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IAttributeFileSystem.cs @@ -1,31 +1,30 @@ -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +// ReSharper disable once InconsistentNaming +public abstract class IAttributeFileSystem : IFileSystem { - // ReSharper disable once InconsistentNaming - public abstract class IAttributeFileSystem : IFileSystem + public Result CreateDirectory(in Path path, NxFileAttributes archiveAttribute) { - public Result CreateDirectory(in Path path, NxFileAttributes archiveAttribute) - { - return DoCreateDirectory(in path, archiveAttribute); - } - - public Result GetFileAttributes(out NxFileAttributes attributes, in Path path) - { - return DoGetFileAttributes(out attributes, in path); - } - - public Result SetFileAttributes(in Path path, NxFileAttributes attributes) - { - return DoSetFileAttributes(in path, attributes); - } - - public Result GetFileSize(out long fileSize, in Path path) - { - return DoGetFileSize(out fileSize, in path); - } - - protected abstract Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute); - protected abstract Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path); - protected abstract Result DoSetFileAttributes(in Path path, NxFileAttributes attributes); - protected abstract Result DoGetFileSize(out long fileSize, in Path path); + return DoCreateDirectory(in path, archiveAttribute); } + + public Result GetFileAttributes(out NxFileAttributes attributes, in Path path) + { + return DoGetFileAttributes(out attributes, in path); + } + + public Result SetFileAttributes(in Path path, NxFileAttributes attributes) + { + return DoSetFileAttributes(in path, attributes); + } + + public Result GetFileSize(out long fileSize, in Path path) + { + return DoGetFileSize(out fileSize, in path); + } + + protected abstract Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute); + protected abstract Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path); + protected abstract Result DoSetFileAttributes(in Path path, NxFileAttributes attributes); + protected abstract Result DoGetFileSize(out long fileSize, in Path path); } diff --git a/src/LibHac/Fs/Fsa/IDirectory.cs b/src/LibHac/Fs/Fsa/IDirectory.cs index dd90bc37..4207cd41 100644 --- a/src/LibHac/Fs/Fsa/IDirectory.cs +++ b/src/LibHac/Fs/Fsa/IDirectory.cs @@ -1,49 +1,48 @@ using System; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +// ReSharper disable once InconsistentNaming +/// +/// Provides an interface for enumerating the child entries of a directory. +/// +public abstract class IDirectory : IDisposable { - // ReSharper disable once InconsistentNaming /// - /// Provides an interface for enumerating the child entries of a directory. + /// Retrieves the next entries that this directory contains. Does not search subdirectories. /// - public abstract class IDirectory : IDisposable + /// The number of s that + /// were read into . + /// The buffer the entries will be read into. + /// The of the requested operation. + /// With each call of , the object will + /// continue to iterate through all the entries it contains. + /// Each call will attempt to read as many entries as the buffer can contain. + /// Once all the entries have been read, all subsequent calls to will + /// read 0 entries into the buffer. + public Result Read(out long entriesRead, Span entryBuffer) { - /// - /// Retrieves the next entries that this directory contains. Does not search subdirectories. - /// - /// The number of s that - /// were read into . - /// The buffer the entries will be read into. - /// The of the requested operation. - /// With each call of , the object will - /// continue to iterate through all the entries it contains. - /// Each call will attempt to read as many entries as the buffer can contain. - /// Once all the entries have been read, all subsequent calls to will - /// read 0 entries into the buffer. - public Result Read(out long entriesRead, Span entryBuffer) + if (entryBuffer.IsEmpty) { - if (entryBuffer.IsEmpty) - { - entriesRead = 0; - return Result.Success; - } - - return DoRead(out entriesRead, entryBuffer); + entriesRead = 0; + return Result.Success; } - /// - /// Retrieves the number of file system entries that this directory contains. Does not search subdirectories. - /// - /// The number of child entries the directory contains. - /// The of the requested operation. - public Result GetEntryCount(out long entryCount) - { - return DoGetEntryCount(out entryCount); - } - - protected abstract Result DoRead(out long entriesRead, Span entryBuffer); - protected abstract Result DoGetEntryCount(out long entryCount); - - public virtual void Dispose() { } + return DoRead(out entriesRead, entryBuffer); } + + /// + /// Retrieves the number of file system entries that this directory contains. Does not search subdirectories. + /// + /// The number of child entries the directory contains. + /// The of the requested operation. + public Result GetEntryCount(out long entryCount) + { + return DoGetEntryCount(out entryCount); + } + + protected abstract Result DoRead(out long entriesRead, Span entryBuffer); + protected abstract Result DoGetEntryCount(out long entryCount); + + public virtual void Dispose() { } } diff --git a/src/LibHac/Fs/Fsa/IFile.cs b/src/LibHac/Fs/Fsa/IFile.cs index 2660128c..c9417e07 100644 --- a/src/LibHac/Fs/Fsa/IFile.cs +++ b/src/LibHac/Fs/Fsa/IFile.cs @@ -2,223 +2,222 @@ using System.Runtime.CompilerServices; using LibHac.Common; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +// ReSharper disable once InconsistentNaming +/// +/// Provides an interface for reading and writing a sequence of bytes. +/// +/// is similar to , and has a few main differences: +/// +/// - allows an to be set that controls read, write +/// and append permissions for the file. +/// +/// - If the cannot read or write as many bytes as requested, it will read +/// or write as many bytes as it can and return that number of bytes to the caller. +/// +/// - If is called on an offset past the end of the , +/// the mode is set and the file supports expansion, +/// the file will be expanded so that it is large enough to contain the written data. +public abstract class IFile : IDisposable { - // ReSharper disable once InconsistentNaming /// - /// Provides an interface for reading and writing a sequence of bytes. + /// Reads a sequence of bytes from the current . /// - /// is similar to , and has a few main differences: - /// - /// - allows an to be set that controls read, write - /// and append permissions for the file. - /// - /// - If the cannot read or write as many bytes as requested, it will read - /// or write as many bytes as it can and return that number of bytes to the caller. - /// - /// - If is called on an offset past the end of the , - /// the mode is set and the file supports expansion, - /// the file will be expanded so that it is large enough to contain the written data. - public abstract class IFile : IDisposable + /// If the operation returns successfully, The total number of bytes read into + /// the buffer. This can be less than the size of the buffer if the IFile is too short to fulfill the request. + /// The offset in the at which to begin reading. + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be no larger than the length of the buffer. + /// Options for reading from the . + /// The of the requested operation. + public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) { - /// - /// Reads a sequence of bytes from the current . - /// - /// If the operation returns successfully, The total number of bytes read into - /// the buffer. This can be less than the size of the buffer if the IFile is too short to fulfill the request. - /// The offset in the at which to begin reading. - /// The buffer where the read bytes will be stored. - /// The number of bytes read will be no larger than the length of the buffer. - /// Options for reading from the . - /// The of the requested operation. - public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) + UnsafeHelpers.SkipParamInit(out bytesRead); + + if (Unsafe.IsNullRef(ref bytesRead)) + return ResultFs.NullptrArgument.Log(); + + if (destination.IsEmpty) { - UnsafeHelpers.SkipParamInit(out bytesRead); - - if (Unsafe.IsNullRef(ref bytesRead)) - return ResultFs.NullptrArgument.Log(); - - if (destination.IsEmpty) - { - bytesRead = 0; - return Result.Success; - } - - if (offset < 0) - return ResultFs.OutOfRange.Log(); - - if (long.MaxValue - offset < destination.Length) - return ResultFs.OutOfRange.Log(); - - return DoRead(out bytesRead, offset, destination, in option); - } - - /// - /// Reads a sequence of bytes from the current with no s. - /// - /// If the operation returns successfully, The total number of bytes read into - /// the buffer. This can be less than the size of the buffer if the IFile is too short to fulfill the request. - /// The offset in the at which to begin reading. - /// The buffer where the read bytes will be stored. - /// The number of bytes read will be no larger than the length of the buffer. - /// The of the requested operation. - public Result Read(out long bytesRead, long offset, Span destination) - { - return Read(out bytesRead, offset, destination, ReadOption.None); - } - - /// - /// Writes a sequence of bytes to the current . - /// - /// The offset in the at which to begin writing. - /// The buffer containing the bytes to be written. - /// Options for writing to the . - /// The of the requested operation. - public Result Write(long offset, ReadOnlySpan source, in WriteOption option) - { - if (source.IsEmpty) - { - if (option.HasFlushFlag()) - { - Result rc = Flush(); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - if (offset < 0) - return ResultFs.OutOfRange.Log(); - - if (long.MaxValue - offset < source.Length) - return ResultFs.OutOfRange.Log(); - - return DoWrite(offset, source, in option); - } - - /// - /// Causes any buffered data to be written to the underlying device. - /// - public Result Flush() - { - return DoFlush(); - } - - /// - /// Sets the size of the file in bytes. - /// - /// The desired size of the file in bytes. - /// The of the requested operation. - public Result SetSize(long size) - { - if (size < 0) - return ResultFs.OutOfRange.Log(); - - return DoSetSize(size); - } - - /// - /// Gets the number of bytes in the file. - /// - /// If the operation returns successfully, the length of the file in bytes. - /// The of the requested operation. - public Result GetSize(out long size) - { - return DoGetSize(out size); - } - - /// - /// Performs various operations on the file. Used to extend the functionality of the interface. - /// - /// A buffer that will contain the response from the operation. - /// The operation to be performed. - /// The offset of the range to operate on. - /// The size of the range to operate on. - /// An input buffer. Size may vary depending on the operation performed. - /// The of the requested operation. - public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return DoOperateRange(outBuffer, operationId, offset, size, inBuffer); - } - - /// - /// Performs various operations on the file. Used to extend the functionality of the interface. - /// - /// The operation to be performed. - /// The offset of the range to operate on. - /// The size of the range to operate on. - /// The of the requested operation. - public Result OperateRange(OperationId operationId, long offset, long size) - { - return DoOperateRange(Span.Empty, operationId, offset, size, ReadOnlySpan.Empty); - } - - protected Result DryRead(out long readableBytes, long offset, long size, in ReadOption option, - OpenMode openMode) - { - UnsafeHelpers.SkipParamInit(out readableBytes); - - // Check that we can read. - if (!openMode.HasFlag(OpenMode.Read)) - return ResultFs.ReadUnpermitted.Log(); - - // Get the file size, and validate our offset. - Result rc = GetSize(out long fileSize); - if (rc.IsFailure()) return rc; - - if (offset > fileSize) - return ResultFs.OutOfRange.Log(); - - readableBytes = Math.Min(fileSize - offset, size); + bytesRead = 0; return Result.Success; } - protected Result DrySetSize(long size, OpenMode openMode) - { - // Check that we can write. - if (!openMode.HasFlag(OpenMode.Write)) - return ResultFs.WriteUnpermitted.Log(); + if (offset < 0) + return ResultFs.OutOfRange.Log(); - return Result.Success; - } + if (long.MaxValue - offset < destination.Length) + return ResultFs.OutOfRange.Log(); - protected Result DryWrite(out bool needsAppend, long offset, long size, in WriteOption option, - OpenMode openMode) - { - UnsafeHelpers.SkipParamInit(out needsAppend); - - // Check that we can write. - if (!openMode.HasFlag(OpenMode.Write)) - return ResultFs.WriteUnpermitted.Log(); - - // Get the file size. - Result rc = GetSize(out long fileSize); - if (rc.IsFailure()) return rc; - - if (fileSize < offset + size) - { - if (!openMode.HasFlag(OpenMode.AllowAppend)) - return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log(); - - needsAppend = true; - } - else - { - needsAppend = false; - } - - return Result.Success; - } - - protected abstract Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option); - protected abstract Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option); - protected abstract Result DoFlush(); - protected abstract Result DoSetSize(long size); - protected abstract Result DoGetSize(out long size); - protected abstract Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer); - - public virtual void Dispose() { } + return DoRead(out bytesRead, offset, destination, in option); } + + /// + /// Reads a sequence of bytes from the current with no s. + /// + /// If the operation returns successfully, The total number of bytes read into + /// the buffer. This can be less than the size of the buffer if the IFile is too short to fulfill the request. + /// The offset in the at which to begin reading. + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be no larger than the length of the buffer. + /// The of the requested operation. + public Result Read(out long bytesRead, long offset, Span destination) + { + return Read(out bytesRead, offset, destination, ReadOption.None); + } + + /// + /// Writes a sequence of bytes to the current . + /// + /// The offset in the at which to begin writing. + /// The buffer containing the bytes to be written. + /// Options for writing to the . + /// The of the requested operation. + public Result Write(long offset, ReadOnlySpan source, in WriteOption option) + { + if (source.IsEmpty) + { + if (option.HasFlushFlag()) + { + Result rc = Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + if (offset < 0) + return ResultFs.OutOfRange.Log(); + + if (long.MaxValue - offset < source.Length) + return ResultFs.OutOfRange.Log(); + + return DoWrite(offset, source, in option); + } + + /// + /// Causes any buffered data to be written to the underlying device. + /// + public Result Flush() + { + return DoFlush(); + } + + /// + /// Sets the size of the file in bytes. + /// + /// The desired size of the file in bytes. + /// The of the requested operation. + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.OutOfRange.Log(); + + return DoSetSize(size); + } + + /// + /// Gets the number of bytes in the file. + /// + /// If the operation returns successfully, the length of the file in bytes. + /// The of the requested operation. + public Result GetSize(out long size) + { + return DoGetSize(out size); + } + + /// + /// Performs various operations on the file. Used to extend the functionality of the interface. + /// + /// A buffer that will contain the response from the operation. + /// The operation to be performed. + /// The offset of the range to operate on. + /// The size of the range to operate on. + /// An input buffer. Size may vary depending on the operation performed. + /// The of the requested operation. + public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return DoOperateRange(outBuffer, operationId, offset, size, inBuffer); + } + + /// + /// Performs various operations on the file. Used to extend the functionality of the interface. + /// + /// The operation to be performed. + /// The offset of the range to operate on. + /// The size of the range to operate on. + /// The of the requested operation. + public Result OperateRange(OperationId operationId, long offset, long size) + { + return DoOperateRange(Span.Empty, operationId, offset, size, ReadOnlySpan.Empty); + } + + protected Result DryRead(out long readableBytes, long offset, long size, in ReadOption option, + OpenMode openMode) + { + UnsafeHelpers.SkipParamInit(out readableBytes); + + // Check that we can read. + if (!openMode.HasFlag(OpenMode.Read)) + return ResultFs.ReadUnpermitted.Log(); + + // Get the file size, and validate our offset. + Result rc = GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + if (offset > fileSize) + return ResultFs.OutOfRange.Log(); + + readableBytes = Math.Min(fileSize - offset, size); + return Result.Success; + } + + protected Result DrySetSize(long size, OpenMode openMode) + { + // Check that we can write. + if (!openMode.HasFlag(OpenMode.Write)) + return ResultFs.WriteUnpermitted.Log(); + + return Result.Success; + } + + protected Result DryWrite(out bool needsAppend, long offset, long size, in WriteOption option, + OpenMode openMode) + { + UnsafeHelpers.SkipParamInit(out needsAppend); + + // Check that we can write. + if (!openMode.HasFlag(OpenMode.Write)) + return ResultFs.WriteUnpermitted.Log(); + + // Get the file size. + Result rc = GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + if (fileSize < offset + size) + { + if (!openMode.HasFlag(OpenMode.AllowAppend)) + return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log(); + + needsAppend = true; + } + else + { + needsAppend = false; + } + + return Result.Success; + } + + protected abstract Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option); + protected abstract Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option); + protected abstract Result DoFlush(); + protected abstract Result DoSetSize(long size); + protected abstract Result DoGetSize(out long size); + protected abstract Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer); + + public virtual void Dispose() { } } diff --git a/src/LibHac/Fs/Fsa/IFileSystem.cs b/src/LibHac/Fs/Fsa/IFileSystem.cs index 809a3632..54561471 100644 --- a/src/LibHac/Fs/Fsa/IFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IFileSystem.cs @@ -2,375 +2,374 @@ using LibHac.Common; using LibHac.FsSystem; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +// ReSharper disable once InconsistentNaming +/// +/// Provides an interface for accessing a file system. / is used as the path delimiter. +/// +public abstract class IFileSystem : IDisposable { - // ReSharper disable once InconsistentNaming /// - /// Provides an interface for accessing a file system. / is used as the path delimiter. + /// Creates or overwrites a file at the specified path. /// - public abstract class IFileSystem : IDisposable + /// The full path of the file to create. + /// The initial size of the created file. + /// Flags to control how the file is created. + /// Should usually be + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the file.
+ public Result CreateFile(in Path path, long size, CreateFileOptions option) { - /// - /// Creates or overwrites a file at the specified path. - /// - /// The full path of the file to create. - /// The initial size of the created file. - /// Flags to control how the file is created. - /// Should usually be - /// : The operation was successful.
- /// : The parent directory of the specified path does not exist.
- /// : Specified path already exists as either a file or directory.
- /// : Insufficient free space to create the file.
- public Result CreateFile(in Path path, long size, CreateFileOptions option) - { - if (size < 0) - return ResultFs.OutOfRange.Log(); + if (size < 0) + return ResultFs.OutOfRange.Log(); - return DoCreateFile(in path, size, option); - } - - /// - /// Creates or overwrites a file at the specified path. - /// - /// The full path of the file to create. - /// The initial size of the created file. - /// Should usually be - /// : The operation was successful.
- /// : The parent directory of the specified path does not exist.
- /// : Specified path already exists as either a file or directory.
- /// : Insufficient free space to create the file.
- public Result CreateFile(in Path path, long size) - { - return CreateFile(in path, size, CreateFileOptions.None); - } - - /// - /// Deletes the specified file. - /// - /// The full path of the file to delete. - /// : The operation was successful.
- /// : The specified path does not exist or is a directory.
- public Result DeleteFile(in Path path) - { - return DoDeleteFile(in path); - } - - /// - /// Creates all directories and subdirectories in the specified path unless they already exist. - /// - /// The full path of the directory to create. - /// : The operation was successful.
- /// : The parent directory of the specified path does not exist.
- /// : Specified path already exists as either a file or directory.
- /// : Insufficient free space to create the directory.
- public Result CreateDirectory(in Path path) - { - return DoCreateDirectory(in path); - } - - /// - /// Deletes the specified directory. - /// - /// The full path of the directory to delete. - /// : The operation was successful.
- /// : The specified path does not exist or is a file.
- /// : The specified directory is not empty.
- public Result DeleteDirectory(in Path path) - { - return DoDeleteDirectory(in path); - } - - /// - /// Deletes the specified directory and any subdirectories and files in the directory. - /// - /// The full path of the directory to delete. - /// : The operation was successful.
- /// : The specified path does not exist or is a file.
- public Result DeleteDirectoryRecursively(in Path path) - { - return DoDeleteDirectoryRecursively(in path); - } - - /// - /// Deletes any subdirectories and files in the specified directory. - /// - /// The full path of the directory to clean. - /// : The operation was successful.
- /// : The specified path does not exist or is a file.
- public Result CleanDirectoryRecursively(in Path path) - { - return DoCleanDirectoryRecursively(in path); - } - - /// - /// Renames or moves a file to a new location. - /// - /// The current full path of the file to rename. - /// The new full path of the file. - /// : The operation was successful.
- /// : does not exist or is a directory.
- /// : 's parent directory does not exist.
- /// : already exists as either a file or directory.
- /// - /// If and are the same, this function does nothing and returns successfully. - /// - public Result RenameFile(in Path currentPath, in Path newPath) - { - return DoRenameFile(in currentPath, in newPath); - } - - /// - /// Renames or moves a directory to a new location. - /// - /// The full path of the directory to rename. - /// The new full path of the directory. - /// : The operation was successful.
- /// : does not exist or is a file.
- /// : 's parent directory does not exist.
- /// : already exists as either a file or directory.
- /// : Either or is a subpath of the other.
- /// - /// If and are the same, this function does nothing and returns . - /// - public Result RenameDirectory(in Path currentPath, in Path newPath) - { - return DoRenameDirectory(in currentPath, in newPath); - } - - /// - /// Determines whether the specified path is a file or directory, or does not exist. - /// - /// If the operation returns successfully, contains the of the file. - /// The full path to check. - /// : The operation was successful.
- /// : The specified path does not exist.
- public Result GetEntryType(out DirectoryEntryType entryType, in Path path) - { - return DoGetEntryType(out entryType, in path); - } - - /// - /// Determines whether the specified path is a file or directory, or does not exist. - /// - /// If the operation returns successfully, contains the of the file. - /// The full path to check. - /// : The operation was successful.
- /// : The specified path does not exist.
- public Result GetEntryType(out DirectoryEntryType entryType, U8Span path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - using var pathNormalized = new Path(); - Result rs = pathNormalized.InitializeWithNormalization(path); - if (rs.IsFailure()) return rs; - - return DoGetEntryType(out entryType, in pathNormalized); - } - - /// - /// Gets the amount of available free space on a drive, in bytes. - /// - /// If the operation returns successfully, the amount of free space available on the drive, in bytes. - /// The path of the drive to query. Unused in almost all cases. - /// The of the requested operation. - public Result GetFreeSpaceSize(out long freeSpace, in Path path) - { - return DoGetFreeSpaceSize(out freeSpace, in path); - } - - /// - /// Gets the total size of storage space on a drive, in bytes. - /// - /// If the operation returns successfully, the total size of the drive, in bytes. - /// The path of the drive to query. Unused in almost all cases. - /// The of the requested operation. - public Result GetTotalSpaceSize(out long totalSpace, in Path path) - { - return DoGetTotalSpaceSize(out totalSpace, in path); - } - - /// - /// Opens an instance for the specified path. - /// - /// If the operation returns successfully, - /// An instance for the specified path. - /// The full path of the file to open. - /// Specifies the access permissions of the created . - /// : The operation was successful.
- /// : The specified path does not exist or is a directory.
- /// : When opening as , - /// the file is already opened as .
- public Result OpenFile(ref UniqueRef file, U8Span path, OpenMode mode) - { - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - using var pathNormalized = new Path(); - Result rs = pathNormalized.InitializeWithNormalization(path); - if (rs.IsFailure()) return rs; - - return DoOpenFile(ref file, in pathNormalized, mode); - } - - /// - /// Opens an instance for the specified path. - /// - /// If the operation returns successfully, - /// An instance for the specified path. - /// The full path of the file to open. - /// Specifies the access permissions of the created . - /// : The operation was successful.
- /// : The specified path does not exist or is a directory.
- /// : When opening as , - /// the file is already opened as .
- public Result OpenFile(ref UniqueRef file, in Path path, OpenMode mode) - { - if ((mode & OpenMode.ReadWrite) == 0 || (mode & ~OpenMode.All) != 0) - return ResultFs.InvalidOpenMode.Log(); - - return DoOpenFile(ref file, in path, mode); - } - - /// - /// Creates an instance for enumerating the specified directory. - /// - /// - /// The directory's full path. - /// Specifies which sub-entries should be enumerated. - /// : The operation was successful.
- /// : The specified path does not exist or is a file.
- public Result OpenDirectory(ref UniqueRef outDirectory, in Path path, OpenDirectoryMode mode) - { - if ((mode & OpenDirectoryMode.All) == 0 || - (mode & ~(OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize)) != 0) - return ResultFs.InvalidOpenMode.Log(); - - return DoOpenDirectory(ref outDirectory, in path, mode); - } - - /// - /// Commits any changes to a transactional file system. - /// Does nothing if called on a non-transactional file system. - /// - /// The of the requested operation. - public Result Commit() => DoCommit(); - - public Result CommitProvisionally(long counter) => DoCommitProvisionally(counter); - - public Result Rollback() => DoRollback(); - - public Result Flush() => DoFlush(); - - /// - /// Gets the creation, last accessed, and last modified timestamps of a file or directory. - /// - /// If the operation returns successfully, the timestamps for the specified file or directory. - /// These value are expressed as Unix timestamps. - /// The path of the file or directory. - /// : The operation was successful.
- /// : The specified path does not exist.
- public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - return DoGetFileTimeStampRaw(out timeStamp, in path); - } - - /// - /// Performs a query on the specified file. - /// - /// This method allows implementers of to accept queries and operations - /// not included in the IFileSystem interface itself. - /// The buffer for receiving data from the query operation. - /// May be unused depending on the query type. - /// The buffer for sending data to the query operation. - /// May be unused depending on the query type. - /// The type of query to perform. - /// The full path of the file to query. - /// The of the requested operation. - public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) - { - return DoQueryEntry(outBuffer, inBuffer, queryId, path); - } - - protected abstract Result DoCreateFile(in Path path, long size, CreateFileOptions option); - protected abstract Result DoDeleteFile(in Path path); - protected abstract Result DoCreateDirectory(in Path path); - protected abstract Result DoDeleteDirectory(in Path path); - protected abstract Result DoDeleteDirectoryRecursively(in Path path); - protected abstract Result DoCleanDirectoryRecursively(in Path path); - protected abstract Result DoRenameFile(in Path currentPath, in Path newPath); - protected abstract Result DoRenameDirectory(in Path currentPath, in Path newPath); - protected abstract Result DoGetEntryType(out DirectoryEntryType entryType, in Path path); - - protected virtual Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); - return ResultFs.NotImplemented.Log(); - } - - protected virtual Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - return ResultFs.NotImplemented.Log(); - } - - protected abstract Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); - protected abstract Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode); - protected abstract Result DoCommit(); - - protected virtual Result DoCommitProvisionally(long counter) => ResultFs.NotImplemented.Log(); - protected virtual Result DoRollback() => ResultFs.NotImplemented.Log(); - protected virtual Result DoFlush() => ResultFs.NotImplemented.Log(); - - protected virtual Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - return ResultFs.NotImplemented.Log(); - } - - protected virtual Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) => ResultFs.NotImplemented.Log(); - - public virtual void Dispose() { } + return DoCreateFile(in path, size, option); } /// - /// Specifies which types of entries are returned when enumerating an . + /// Creates or overwrites a file at the specified path. /// - [Flags] - public enum OpenDirectoryMode + /// The full path of the file to create. + /// The initial size of the created file. + /// Should usually be + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the file.
+ public Result CreateFile(in Path path, long size) { - Directory = 1 << 0, - File = 1 << 1, - NoFileSize = 1 << 31, - All = Directory | File + return CreateFile(in path, size, CreateFileOptions.None); } /// - /// Optional file creation flags. + /// Deletes the specified file. /// - [Flags] - public enum CreateFileOptions + /// The full path of the file to delete. + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ public Result DeleteFile(in Path path) { - None = 0, - /// - /// On a , creates a concatenation file. - /// - CreateConcatenationFile = 1 << 0 + return DoDeleteFile(in path); } - public enum QueryId + /// + /// Creates all directories and subdirectories in the specified path unless they already exist. + /// + /// The full path of the directory to create. + /// : The operation was successful.
+ /// : The parent directory of the specified path does not exist.
+ /// : Specified path already exists as either a file or directory.
+ /// : Insufficient free space to create the directory.
+ public Result CreateDirectory(in Path path) { - /// - /// Turns a folder in a into a concatenation file by - /// setting the directory's archive flag. - /// - SetConcatenationFileAttribute = 0, - UpdateMac = 1, - IsSignedSystemPartitionOnSdCardValid = 2, - QueryUnpreparedFileInformation = 3 + return DoCreateDirectory(in path); } + + /// + /// Deletes the specified directory. + /// + /// The full path of the directory to delete. + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ /// : The specified directory is not empty.
+ public Result DeleteDirectory(in Path path) + { + return DoDeleteDirectory(in path); + } + + /// + /// Deletes the specified directory and any subdirectories and files in the directory. + /// + /// The full path of the directory to delete. + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ public Result DeleteDirectoryRecursively(in Path path) + { + return DoDeleteDirectoryRecursively(in path); + } + + /// + /// Deletes any subdirectories and files in the specified directory. + /// + /// The full path of the directory to clean. + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ public Result CleanDirectoryRecursively(in Path path) + { + return DoCleanDirectoryRecursively(in path); + } + + /// + /// Renames or moves a file to a new location. + /// + /// The current full path of the file to rename. + /// The new full path of the file. + /// : The operation was successful.
+ /// : does not exist or is a directory.
+ /// : 's parent directory does not exist.
+ /// : already exists as either a file or directory.
+ /// + /// If and are the same, this function does nothing and returns successfully. + /// + public Result RenameFile(in Path currentPath, in Path newPath) + { + return DoRenameFile(in currentPath, in newPath); + } + + /// + /// Renames or moves a directory to a new location. + /// + /// The full path of the directory to rename. + /// The new full path of the directory. + /// : The operation was successful.
+ /// : does not exist or is a file.
+ /// : 's parent directory does not exist.
+ /// : already exists as either a file or directory.
+ /// : Either or is a subpath of the other.
+ /// + /// If and are the same, this function does nothing and returns . + /// + public Result RenameDirectory(in Path currentPath, in Path newPath) + { + return DoRenameDirectory(in currentPath, in newPath); + } + + /// + /// Determines whether the specified path is a file or directory, or does not exist. + /// + /// If the operation returns successfully, contains the of the file. + /// The full path to check. + /// : The operation was successful.
+ /// : The specified path does not exist.
+ public Result GetEntryType(out DirectoryEntryType entryType, in Path path) + { + return DoGetEntryType(out entryType, in path); + } + + /// + /// Determines whether the specified path is a file or directory, or does not exist. + /// + /// If the operation returns successfully, contains the of the file. + /// The full path to check. + /// : The operation was successful.
+ /// : The specified path does not exist.
+ public Result GetEntryType(out DirectoryEntryType entryType, U8Span path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + using var pathNormalized = new Path(); + Result rs = pathNormalized.InitializeWithNormalization(path); + if (rs.IsFailure()) return rs; + + return DoGetEntryType(out entryType, in pathNormalized); + } + + /// + /// Gets the amount of available free space on a drive, in bytes. + /// + /// If the operation returns successfully, the amount of free space available on the drive, in bytes. + /// The path of the drive to query. Unused in almost all cases. + /// The of the requested operation. + public Result GetFreeSpaceSize(out long freeSpace, in Path path) + { + return DoGetFreeSpaceSize(out freeSpace, in path); + } + + /// + /// Gets the total size of storage space on a drive, in bytes. + /// + /// If the operation returns successfully, the total size of the drive, in bytes. + /// The path of the drive to query. Unused in almost all cases. + /// The of the requested operation. + public Result GetTotalSpaceSize(out long totalSpace, in Path path) + { + return DoGetTotalSpaceSize(out totalSpace, in path); + } + + /// + /// Opens an instance for the specified path. + /// + /// If the operation returns successfully, + /// An instance for the specified path. + /// The full path of the file to open. + /// Specifies the access permissions of the created . + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ /// : When opening as , + /// the file is already opened as .
+ public Result OpenFile(ref UniqueRef file, U8Span path, OpenMode mode) + { + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + using var pathNormalized = new Path(); + Result rs = pathNormalized.InitializeWithNormalization(path); + if (rs.IsFailure()) return rs; + + return DoOpenFile(ref file, in pathNormalized, mode); + } + + /// + /// Opens an instance for the specified path. + /// + /// If the operation returns successfully, + /// An instance for the specified path. + /// The full path of the file to open. + /// Specifies the access permissions of the created . + /// : The operation was successful.
+ /// : The specified path does not exist or is a directory.
+ /// : When opening as , + /// the file is already opened as .
+ public Result OpenFile(ref UniqueRef file, in Path path, OpenMode mode) + { + if ((mode & OpenMode.ReadWrite) == 0 || (mode & ~OpenMode.All) != 0) + return ResultFs.InvalidOpenMode.Log(); + + return DoOpenFile(ref file, in path, mode); + } + + /// + /// Creates an instance for enumerating the specified directory. + /// + /// + /// The directory's full path. + /// Specifies which sub-entries should be enumerated. + /// : The operation was successful.
+ /// : The specified path does not exist or is a file.
+ public Result OpenDirectory(ref UniqueRef outDirectory, in Path path, OpenDirectoryMode mode) + { + if ((mode & OpenDirectoryMode.All) == 0 || + (mode & ~(OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize)) != 0) + return ResultFs.InvalidOpenMode.Log(); + + return DoOpenDirectory(ref outDirectory, in path, mode); + } + + /// + /// Commits any changes to a transactional file system. + /// Does nothing if called on a non-transactional file system. + /// + /// The of the requested operation. + public Result Commit() => DoCommit(); + + public Result CommitProvisionally(long counter) => DoCommitProvisionally(counter); + + public Result Rollback() => DoRollback(); + + public Result Flush() => DoFlush(); + + /// + /// Gets the creation, last accessed, and last modified timestamps of a file or directory. + /// + /// If the operation returns successfully, the timestamps for the specified file or directory. + /// These value are expressed as Unix timestamps. + /// The path of the file or directory. + /// : The operation was successful.
+ /// : The specified path does not exist.
+ public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return DoGetFileTimeStampRaw(out timeStamp, in path); + } + + /// + /// Performs a query on the specified file. + /// + /// This method allows implementers of to accept queries and operations + /// not included in the IFileSystem interface itself. + /// The buffer for receiving data from the query operation. + /// May be unused depending on the query type. + /// The buffer for sending data to the query operation. + /// May be unused depending on the query type. + /// The type of query to perform. + /// The full path of the file to query. + /// The of the requested operation. + public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) + { + return DoQueryEntry(outBuffer, inBuffer, queryId, path); + } + + protected abstract Result DoCreateFile(in Path path, long size, CreateFileOptions option); + protected abstract Result DoDeleteFile(in Path path); + protected abstract Result DoCreateDirectory(in Path path); + protected abstract Result DoDeleteDirectory(in Path path); + protected abstract Result DoDeleteDirectoryRecursively(in Path path); + protected abstract Result DoCleanDirectoryRecursively(in Path path); + protected abstract Result DoRenameFile(in Path currentPath, in Path newPath); + protected abstract Result DoRenameDirectory(in Path currentPath, in Path newPath); + protected abstract Result DoGetEntryType(out DirectoryEntryType entryType, in Path path); + + protected virtual Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + return ResultFs.NotImplemented.Log(); + } + + protected virtual Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + return ResultFs.NotImplemented.Log(); + } + + protected abstract Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); + protected abstract Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode); + protected abstract Result DoCommit(); + + protected virtual Result DoCommitProvisionally(long counter) => ResultFs.NotImplemented.Log(); + protected virtual Result DoRollback() => ResultFs.NotImplemented.Log(); + protected virtual Result DoFlush() => ResultFs.NotImplemented.Log(); + + protected virtual Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + return ResultFs.NotImplemented.Log(); + } + + protected virtual Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) => ResultFs.NotImplemented.Log(); + + public virtual void Dispose() { } +} + +/// +/// Specifies which types of entries are returned when enumerating an . +/// +[Flags] +public enum OpenDirectoryMode +{ + Directory = 1 << 0, + File = 1 << 1, + NoFileSize = 1 << 31, + All = Directory | File +} + +/// +/// Optional file creation flags. +/// +[Flags] +public enum CreateFileOptions +{ + None = 0, + /// + /// On a , creates a concatenation file. + /// + CreateConcatenationFile = 1 << 0 +} + +public enum QueryId +{ + /// + /// Turns a folder in a into a concatenation file by + /// setting the directory's archive flag. + /// + SetConcatenationFileAttribute = 0, + UpdateMac = 1, + IsSignedSystemPartitionOnSdCardValid = 2, + QueryUnpreparedFileInformation = 3 } diff --git a/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs b/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs index 176a9264..dbf6a2d5 100644 --- a/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs +++ b/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs @@ -1,10 +1,9 @@ using LibHac.Common; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +public interface IMultiCommitTarget { - public interface IMultiCommitTarget - { - SharedRef GetMultiCommitTarget(); - } + SharedRef GetMultiCommitTarget(); } diff --git a/src/LibHac/Fs/Fsa/MountTable.cs b/src/LibHac/Fs/Fsa/MountTable.cs index 1b16d7a4..a08897fa 100644 --- a/src/LibHac/Fs/Fsa/MountTable.cs +++ b/src/LibHac/Fs/Fsa/MountTable.cs @@ -8,159 +8,158 @@ using LibHac.Os; using LibHac.Util; // ReSharper disable once CheckNamespace -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +/// +/// Holds a list of s that are indexed by their name. +/// These may be retrieved or removed using their name as a key. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class MountTable : IDisposable { - /// - /// Holds a list of s that are indexed by their name. - /// These may be retrieved or removed using their name as a key. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class MountTable : IDisposable + private LinkedList _fileSystemList; + private SdkMutexType _mutex; + + // LibHac addition + private FileSystemClient _fsClient; + + public MountTable(FileSystemClient fsClient) { - private LinkedList _fileSystemList; - private SdkMutexType _mutex; + _fileSystemList = new LinkedList(); + _mutex = new SdkMutexType(); + _mutex.Initialize(); - // LibHac addition - private FileSystemClient _fsClient; + _fsClient = fsClient; + } - public MountTable(FileSystemClient fsClient) + // Note: The original class does not have a destructor + public void Dispose() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + LinkedListNode currentEntry = _fileSystemList.First; + + while (currentEntry is not null) { - _fileSystemList = new LinkedList(); - _mutex = new SdkMutexType(); - _mutex.Initialize(); + FileSystemAccessor accessor = currentEntry.Value; + _fileSystemList.Remove(currentEntry); + accessor?.Dispose(); - _fsClient = fsClient; + currentEntry = _fileSystemList.First; } - // Note: The original class does not have a destructor - public void Dispose() + _fileSystemList = null; + _fsClient = null; + } + + private static bool Matches(FileSystemAccessor accessor, U8Span name) + { + return StringUtils.Compare(accessor.GetName(), name, Unsafe.SizeOf()) == 0; + } + + public Result Mount(ref UniqueRef fileSystem) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (!CanAcceptMountName(fileSystem.Get.GetName())) + return ResultFs.MountNameAlreadyExists.Log(); + + _fileSystemList.AddLast(fileSystem.Release()); + return Result.Success; + } + + public Result Find(out FileSystemAccessor accessor, U8Span name) + { + UnsafeHelpers.SkipParamInit(out accessor); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (LinkedListNode currentNode = _fileSystemList.First; + currentNode is not null; + currentNode = currentNode.Next) { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - LinkedListNode currentEntry = _fileSystemList.First; - - while (currentEntry is not null) - { - FileSystemAccessor accessor = currentEntry.Value; - _fileSystemList.Remove(currentEntry); - accessor?.Dispose(); - - currentEntry = _fileSystemList.First; - } - - _fileSystemList = null; - _fsClient = null; - } - - private static bool Matches(FileSystemAccessor accessor, U8Span name) - { - return StringUtils.Compare(accessor.GetName(), name, Unsafe.SizeOf()) == 0; - } - - public Result Mount(ref UniqueRef fileSystem) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - if (!CanAcceptMountName(fileSystem.Get.GetName())) - return ResultFs.MountNameAlreadyExists.Log(); - - _fileSystemList.AddLast(fileSystem.Release()); + if (!Matches(currentNode.Value, name)) continue; + accessor = currentNode.Value; return Result.Success; } - public Result Find(out FileSystemAccessor accessor, U8Span name) + return ResultFs.NotMounted.Log(); + } + + public void Unmount(U8Span name) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (LinkedListNode currentNode = _fileSystemList.First; + currentNode is not null; + currentNode = currentNode.Next) { - UnsafeHelpers.SkipParamInit(out accessor); - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - for (LinkedListNode currentNode = _fileSystemList.First; - currentNode is not null; - currentNode = currentNode.Next) + if (Matches(currentNode.Value, name)) { - if (!Matches(currentNode.Value, name)) continue; - accessor = currentNode.Value; - return Result.Success; + _fileSystemList.Remove(currentNode); + currentNode.Value.Dispose(); + return; } - - return ResultFs.NotMounted.Log(); } - public void Unmount(U8Span name) + _fsClient.Impl.LogErrorMessage(ResultFs.NotMounted.Value, + "Error: Unmount failed because the mount name was not mounted. The mount name is \"{0}\".\n", + name.ToString()); + + Abort.DoAbortUnlessSuccess(ResultFs.NotMounted.Value); + } + + private bool CanAcceptMountName(U8Span name) + { + Assert.SdkAssert(_mutex.IsLockedByCurrentThread()); + + for (LinkedListNode currentNode = _fileSystemList.First; + currentNode is not null; + currentNode = currentNode.Next) { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - for (LinkedListNode currentNode = _fileSystemList.First; - currentNode is not null; - currentNode = currentNode.Next) - { - if (Matches(currentNode.Value, name)) - { - _fileSystemList.Remove(currentNode); - currentNode.Value.Dispose(); - return; - } - } - - _fsClient.Impl.LogErrorMessage(ResultFs.NotMounted.Value, - "Error: Unmount failed because the mount name was not mounted. The mount name is \"{0}\".\n", - name.ToString()); - - Abort.DoAbortUnlessSuccess(ResultFs.NotMounted.Value); + if (Matches(currentNode.Value, name)) + return false; } - private bool CanAcceptMountName(U8Span name) + return true; + } + + public int GetDataIdCount() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + int count = 0; + + for (LinkedListNode currentNode = _fileSystemList.First; + currentNode is not null; + currentNode = currentNode.Next) { - Assert.SdkAssert(_mutex.IsLockedByCurrentThread()); - - for (LinkedListNode currentNode = _fileSystemList.First; - currentNode is not null; - currentNode = currentNode.Next) - { - if (Matches(currentNode.Value, name)) - return false; - } - - return true; + if (currentNode.Value.GetDataId().HasValue) + count++; } - public int GetDataIdCount() + return count; + } + + public Result ListDataId(out int dataIdCount, Span dataIdBuffer) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + int count = 0; + + for (LinkedListNode currentNode = _fileSystemList.First; + currentNode is not null && count < dataIdBuffer.Length; + currentNode = currentNode.Next) { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + Optional dataId = currentNode.Value.GetDataId(); - int count = 0; - - for (LinkedListNode currentNode = _fileSystemList.First; - currentNode is not null; - currentNode = currentNode.Next) + if (dataId.HasValue) { - if (currentNode.Value.GetDataId().HasValue) - count++; + dataIdBuffer[count] = dataId.Value; + count++; } - - return count; } - public Result ListDataId(out int dataIdCount, Span dataIdBuffer) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - int count = 0; - - for (LinkedListNode currentNode = _fileSystemList.First; - currentNode is not null && count < dataIdBuffer.Length; - currentNode = currentNode.Next) - { - Optional dataId = currentNode.Value.GetDataId(); - - if (dataId.HasValue) - { - dataIdBuffer[count] = dataId.Value; - count++; - } - } - - dataIdCount = count; - return Result.Success; - } + dataIdCount = count; + return Result.Success; } } diff --git a/src/LibHac/Fs/Fsa/MountUtility.cs b/src/LibHac/Fs/Fsa/MountUtility.cs index 8b836121..da334694 100644 --- a/src/LibHac/Fs/Fsa/MountUtility.cs +++ b/src/LibHac/Fs/Fsa/MountUtility.cs @@ -8,251 +8,250 @@ using LibHac.Util; using static LibHac.Fs.StringTraits; using static LibHac.Fs.Impl.AccessLogStrings; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +public static class MountUtility { - public static class MountUtility + internal static Result GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, U8Span path) { - internal static Result GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, U8Span path) + UnsafeHelpers.SkipParamInit(out mountName); + subPath = default; + + int mountLen = 0; + int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax); + + if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUncPath(path)) { - UnsafeHelpers.SkipParamInit(out mountName); - subPath = default; - - int mountLen = 0; - int maxMountLen = Math.Min(path.Length, PathTools.MountNameLengthMax); - - if (WindowsPath.IsWindowsDrive(path) || WindowsPath.IsUncPath(path)) - { - StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName); - mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator; - - subPath = path; - return Result.Success; - } - - for (int i = 0; i <= maxMountLen; i++) - { - if (path[i] == PathTools.MountSeparator) - { - mountLen = i; - break; - } - } - - if (mountLen == 0) - return ResultFs.InvalidMountName.Log(); - - if (mountLen > maxMountLen) - return ResultFs.InvalidMountName.Log(); - - if (mountLen <= 0) - return ResultFs.InvalidMountName.Log(); - - U8Span subPathTemp = path.Slice(mountLen + 1); - - if (subPathTemp.Length == 0 || - (subPathTemp[0] != DirectorySeparator && subPathTemp[0] != AltDirectorySeparator)) - return ResultFs.InvalidPathFormat.Log(); - - path.Value.Slice(0, mountLen).CopyTo(mountName.Name); - mountName.Name[mountLen] = StringTraits.NullTerminator; - subPath = subPathTemp; + StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName); + mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator; + subPath = path; return Result.Success; } - public static bool IsValidMountName(this FileSystemClientImpl fs, U8Span name) + for (int i = 0; i <= maxMountLen; i++) { - if (name.IsEmpty()) + if (path[i] == PathTools.MountSeparator) + { + mountLen = i; + break; + } + } + + if (mountLen == 0) + return ResultFs.InvalidMountName.Log(); + + if (mountLen > maxMountLen) + return ResultFs.InvalidMountName.Log(); + + if (mountLen <= 0) + return ResultFs.InvalidMountName.Log(); + + U8Span subPathTemp = path.Slice(mountLen + 1); + + if (subPathTemp.Length == 0 || + (subPathTemp[0] != DirectorySeparator && subPathTemp[0] != AltDirectorySeparator)) + return ResultFs.InvalidPathFormat.Log(); + + path.Value.Slice(0, mountLen).CopyTo(mountName.Name); + mountName.Name[mountLen] = StringTraits.NullTerminator; + subPath = subPathTemp; + + return Result.Success; + } + + public static bool IsValidMountName(this FileSystemClientImpl fs, U8Span name) + { + if (name.IsEmpty()) + return false; + + // Check for a single-letter mount name + if ((name.Length <= 1 || name[1] == 0) && + ('a' <= name[0] && name[0] <= 'z' || 'A' <= name[0] && name[0] <= 'Z')) + { + return false; + } + + // Check for mount or directory separators + int length = 0; + for (int i = 0; i < name.Length && name[i] != 0; i++) + { + if (name[i] == DriveSeparator || name[i] == DirectorySeparator) return false; - // Check for a single-letter mount name - if ((name.Length <= 1 || name[1] == 0) && - ('a' <= name[0] && name[0] <= 'z' || 'A' <= name[0] && name[0] <= 'Z')) - { + if (++length > PathTools.MountNameLengthMax) return false; - } - - // Check for mount or directory separators - int length = 0; - for (int i = 0; i < name.Length && name[i] != 0; i++) - { - if (name[i] == DriveSeparator || name[i] == DirectorySeparator) - return false; - - if (++length > PathTools.MountNameLengthMax) - return false; - } - - // Todo: VerifyUtf8String - return true; } - public static bool IsUsedReservedMountName(this FileSystemClientImpl fs, U8Span name) + // Todo: VerifyUtf8String + return true; + } + + public static bool IsUsedReservedMountName(this FileSystemClientImpl fs, U8Span name) + { + return name.Length > 0 && name[0] == CommonPaths.ReservedMountNamePrefixCharacter; + } + + internal static Result FindFileSystem(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, + out U8Span subPath, U8Span path) + { + UnsafeHelpers.SkipParamInit(out fileSystem); + subPath = default; + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + int hostMountNameLen = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName); + if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountNameLen) == 0) { - return name.Length > 0 && name[0] == CommonPaths.ReservedMountNamePrefixCharacter; + return ResultFs.NotMounted.Log(); } - internal static Result FindFileSystem(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, - out U8Span subPath, U8Span path) + Result rc = GetMountNameAndSubPath(out MountName mountName, out subPath, path); + if (rc.IsFailure()) return rc; + + return fs.Find(out fileSystem, new U8Span(mountName.Name)); + } + + public static Result CheckMountName(this FileSystemClientImpl fs, U8Span name) + { + if (name.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (fs.IsUsedReservedMountName(name)) + return ResultFs.InvalidMountName.Log(); + + if (!fs.IsValidMountName(name)) + return ResultFs.InvalidMountName.Log(); + + return Result.Success; + } + + public static Result CheckMountNameAcceptingReservedMountName(this FileSystemClientImpl fs, U8Span name) + { + if (name.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (!fs.IsValidMountName(name)) + return ResultFs.InvalidMountName.Log(); + + return Result.Success; + } + + public static Result Unmount(this FileSystemClientImpl fs, U8Span mountName) + { + Result rc = fs.Find(out FileSystemAccessor fileSystem, mountName); + if (rc.IsFailure()) return rc; + + if (fileSystem.IsFileDataCacheAttachable()) { - UnsafeHelpers.SkipParamInit(out fileSystem); - subPath = default; - - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - int hostMountNameLen = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName); - if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountNameLen) == 0) - { - return ResultFs.NotMounted.Log(); - } - - Result rc = GetMountNameAndSubPath(out MountName mountName, out subPath, path); - if (rc.IsFailure()) return rc; - - return fs.Find(out fileSystem, new U8Span(mountName.Name)); + // Todo: Data cache purge } - public static Result CheckMountName(this FileSystemClientImpl fs, U8Span name) + fs.Unregister(mountName); + return Result.Success; + } + + public static Result IsMounted(this FileSystemClientImpl fs, out bool isMounted, U8Span mountName) + { + UnsafeHelpers.SkipParamInit(out isMounted); + + Result rc = fs.Find(out _, mountName); + if (rc.IsFailure()) { - if (name.IsNull()) - return ResultFs.NullptrArgument.Log(); + if (!ResultFs.NotMounted.Includes(rc)) + return rc; - if (fs.IsUsedReservedMountName(name)) - return ResultFs.InvalidMountName.Log(); - - if (!fs.IsValidMountName(name)) - return ResultFs.InvalidMountName.Log(); - - return Result.Success; + isMounted = false; } - - public static Result CheckMountNameAcceptingReservedMountName(this FileSystemClientImpl fs, U8Span name) + else { - if (name.IsNull()) - return ResultFs.NullptrArgument.Log(); - - if (!fs.IsValidMountName(name)) - return ResultFs.InvalidMountName.Log(); - - return Result.Success; + isMounted = true; } - public static Result Unmount(this FileSystemClientImpl fs, U8Span mountName) + return Result.Success; + } + + public static void Unmount(this FileSystemClient fs, U8Span mountName) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) { - Result rc = fs.Find(out FileSystemAccessor fileSystem, mountName); - if (rc.IsFailure()) return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Unmount(mountName); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fileSystem.IsFileDataCacheAttachable()) - { - // Todo: Data cache purge - } + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append((byte)'"'); - fs.Unregister(mountName); - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); } - - public static Result IsMounted(this FileSystemClientImpl fs, out bool isMounted, U8Span mountName) + else { - UnsafeHelpers.SkipParamInit(out isMounted); - - Result rc = fs.Find(out _, mountName); - if (rc.IsFailure()) - { - if (!ResultFs.NotMounted.Includes(rc)) - return rc; - - isMounted = false; - } - else - { - isMounted = true; - } - - return Result.Success; + rc = fs.Impl.Unmount(mountName); } + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + } - public static void Unmount(this FileSystemClient fs, U8Span mountName) + public static bool IsMounted(this FileSystemClient fs, U8Span mountName) + { + Result rc; + bool isMounted; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) { - Result rc; - Span logBuffer = stackalloc byte[0x30]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.IsMounted(out isMounted, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.Unmount(mountName); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + ReadOnlySpan boolString = AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue(isMounted); + sb.Append(LogName).Append(mountName).Append(LogIsMounted).Append(boolString).Append((byte)'"'); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append((byte)'"'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.Unmount(mountName); - } - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); } - - public static bool IsMounted(this FileSystemClient fs, U8Span mountName) + else { - Result rc; - bool isMounted; - Span logBuffer = stackalloc byte[0x30]; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.IsMounted(out isMounted, mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - ReadOnlySpan boolString = AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue(isMounted); - sb.Append(LogName).Append(mountName).Append(LogIsMounted).Append(boolString).Append((byte)'"'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.IsMounted(out isMounted, mountName); - } - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - - return isMounted; + rc = fs.Impl.IsMounted(out isMounted, mountName); } + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); - public static Result ConvertToFsCommonPath(this FileSystemClient fs, U8SpanMutable commonPathBuffer, - U8Span path) - { - if (commonPathBuffer.IsNull()) - return ResultFs.NullptrArgument.Log(); + return isMounted; + } - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); + public static Result ConvertToFsCommonPath(this FileSystemClient fs, U8SpanMutable commonPathBuffer, + U8Span path) + { + if (commonPathBuffer.IsNull()) + return ResultFs.NullptrArgument.Log(); - Result rc = GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, path); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); - rc = fs.Impl.Find(out FileSystemAccessor fileSystem, new U8Span(mountName.Name)); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + Result rc = GetMountNameAndSubPath(out MountName mountName, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - rc = fileSystem.GetCommonMountName(commonPathBuffer.Value); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + rc = fs.Impl.Find(out FileSystemAccessor fileSystem, new U8Span(mountName.Name)); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - int mountNameLength = StringUtils.GetLength(commonPathBuffer); - int commonPathLength = StringUtils.GetLength(subPath); + rc = fileSystem.GetCommonMountName(commonPathBuffer.Value); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - if (mountNameLength + commonPathLength > commonPathBuffer.Length) - return ResultFs.TooLongPath.Log(); + int mountNameLength = StringUtils.GetLength(commonPathBuffer); + int commonPathLength = StringUtils.GetLength(subPath); - StringUtils.Copy(commonPathBuffer.Slice(commonPathLength), subPath); - return Result.Success; - } + if (mountNameLength + commonPathLength > commonPathBuffer.Length) + return ResultFs.TooLongPath.Log(); + + StringUtils.Copy(commonPathBuffer.Slice(commonPathLength), subPath); + return Result.Success; } } diff --git a/src/LibHac/Fs/Fsa/Registrar.cs b/src/LibHac/Fs/Fsa/Registrar.cs index 1529b00a..c523477c 100644 --- a/src/LibHac/Fs/Fsa/Registrar.cs +++ b/src/LibHac/Fs/Fsa/Registrar.cs @@ -3,113 +3,112 @@ using LibHac.Common; using LibHac.Fs.Impl; using LibHac.Util; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +public interface ICommonMountNameGenerator : IDisposable { - public interface ICommonMountNameGenerator : IDisposable + Result GenerateCommonMountName(Span nameBuffer); +} + +public interface ISaveDataAttributeGetter : IDisposable +{ + Result GetSaveDataAttribute(out SaveDataAttribute attribute); +} + +/// +/// Contains functions for registering and unregistering mounted s. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public static class Registrar +{ + public static Result Register(this FileSystemClient fs, U8Span name, ref UniqueRef fileSystem) { - Result GenerateCommonMountName(Span nameBuffer); + using var attributeGetter = new UniqueRef(); + using var mountNameGenerator = new UniqueRef(); + + using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, null, + ref fileSystem, ref mountNameGenerator.Ref(), ref attributeGetter.Ref())); + + Result rc = fs.Impl.Register(ref accessor.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } - public interface ISaveDataAttributeGetter : IDisposable + public static Result Register(this FileSystemClient fs, U8Span name, ref UniqueRef fileSystem, + ref UniqueRef mountNameGenerator) { - Result GetSaveDataAttribute(out SaveDataAttribute attribute); + using var attributeGetter = new UniqueRef(); + + using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, null, + ref fileSystem, ref mountNameGenerator, ref attributeGetter.Ref())); + + Result rc = fs.Impl.Register(ref accessor.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; } - /// - /// Contains functions for registering and unregistering mounted s. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public static class Registrar + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, + bool useDataCache, bool usePathCache) { - public static Result Register(this FileSystemClient fs, U8Span name, ref UniqueRef fileSystem) - { - using var attributeGetter = new UniqueRef(); - using var mountNameGenerator = new UniqueRef(); + using var attributeGetter = new UniqueRef(); - using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, null, - ref fileSystem, ref mountNameGenerator.Ref(), ref attributeGetter.Ref())); + Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, + ref attributeGetter.Ref(), useDataCache, usePathCache, new Optional()); + if (rc.IsFailure()) return rc.Miss(); - Result rc = fs.Impl.Register(ref accessor.Ref()); - if (rc.IsFailure()) return rc.Miss(); + return Result.Success; + } - return Result.Success; - } + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, + bool useDataCache, bool usePathCache, Optional dataId) + { + using var attributeGetter = new UniqueRef(); - public static Result Register(this FileSystemClient fs, U8Span name, ref UniqueRef fileSystem, - ref UniqueRef mountNameGenerator) - { - using var attributeGetter = new UniqueRef(); + Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, + ref attributeGetter.Ref(), useDataCache, usePathCache, dataId); + if (rc.IsFailure()) return rc.Miss(); - using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, null, - ref fileSystem, ref mountNameGenerator, ref attributeGetter.Ref())); + return Result.Success; + } - Result rc = fs.Impl.Register(ref accessor.Ref()); - if (rc.IsFailure()) return rc.Miss(); + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, + ref UniqueRef saveAttributeGetter, bool useDataCache, bool usePathCache) + { + Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, + ref saveAttributeGetter, useDataCache, usePathCache, new Optional()); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, - ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, - bool useDataCache, bool usePathCache) - { - using var attributeGetter = new UniqueRef(); + public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, + ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, + ref UniqueRef saveAttributeGetter, bool useDataCache, bool usePathCache, + Optional dataId) + { + using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, + multiCommitTarget, ref fileSystem, ref mountNameGenerator, ref saveAttributeGetter)); - Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, - ref attributeGetter.Ref(), useDataCache, usePathCache, new Optional()); - if (rc.IsFailure()) return rc.Miss(); + if (!accessor.HasValue) + return ResultFs.AllocationMemoryFailedInRegisterB.Log(); - return Result.Success; - } + accessor.Get.SetFileDataCacheAttachable(useDataCache); + accessor.Get.SetPathBasedFileDataCacheAttachable(usePathCache); + accessor.Get.SetDataId(dataId); - public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, - ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, - bool useDataCache, bool usePathCache, Optional dataId) - { - using var attributeGetter = new UniqueRef(); + Result rc = fs.Impl.Register(ref accessor.Ref()); + if (rc.IsFailure()) return rc.Miss(); - Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, - ref attributeGetter.Ref(), useDataCache, usePathCache, dataId); - if (rc.IsFailure()) return rc.Miss(); + return Result.Success; + } - return Result.Success; - } - - public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, - ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, - ref UniqueRef saveAttributeGetter, bool useDataCache, bool usePathCache) - { - Result rc = Register(fs, name, multiCommitTarget, ref fileSystem, ref mountNameGenerator, - ref saveAttributeGetter, useDataCache, usePathCache, new Optional()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result Register(this FileSystemClient fs, U8Span name, IMultiCommitTarget multiCommitTarget, - ref UniqueRef fileSystem, ref UniqueRef mountNameGenerator, - ref UniqueRef saveAttributeGetter, bool useDataCache, bool usePathCache, - Optional dataId) - { - using var accessor = new UniqueRef(new FileSystemAccessor(fs.Hos, name, - multiCommitTarget, ref fileSystem, ref mountNameGenerator, ref saveAttributeGetter)); - - if (!accessor.HasValue) - return ResultFs.AllocationMemoryFailedInRegisterB.Log(); - - accessor.Get.SetFileDataCacheAttachable(useDataCache); - accessor.Get.SetPathBasedFileDataCacheAttachable(usePathCache); - accessor.Get.SetDataId(dataId); - - Result rc = fs.Impl.Register(ref accessor.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static void Unregister(this FileSystemClient fs, U8Span name) - { - fs.Impl.Unregister(name); - } + public static void Unregister(this FileSystemClient fs, U8Span name) + { + fs.Impl.Unregister(name); } } diff --git a/src/LibHac/Fs/Fsa/UserDirectory.cs b/src/LibHac/Fs/Fsa/UserDirectory.cs index 0894d778..2b50045d 100644 --- a/src/LibHac/Fs/Fsa/UserDirectory.cs +++ b/src/LibHac/Fs/Fsa/UserDirectory.cs @@ -5,82 +5,81 @@ using LibHac.Fs.Impl; using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +[SkipLocalsInit] +public static class UserDirectory { - [SkipLocalsInit] - public static class UserDirectory + private static DirectoryAccessor Get(DirectoryHandle handle) { - private static DirectoryAccessor Get(DirectoryHandle handle) + return handle.Directory; + } + + public static Result ReadDirectory(this FileSystemClient fs, out long entriesRead, + Span entryBuffer, DirectoryHandle handle) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - return handle.Directory; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).Read(out entriesRead, entryBuffer); + Tick end = fs.Hos.Os.GetSystemTick(); + + Span buffer = stackalloc byte[0x50]; + var sb = new U8StringBuilder(buffer, true); + + sb.Append(LogEntryBufferCount).AppendFormat(entryBuffer.Length) + .Append(LogEntryCount).AppendFormat(entriesRead); + fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); + } + else + { + rc = Get(handle).Read(out entriesRead, entryBuffer); } - public static Result ReadDirectory(this FileSystemClient fs, out long entriesRead, - Span entryBuffer, DirectoryHandle handle) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetDirectoryEntryCount(this FileSystemClient fs, out long count, DirectoryHandle handle) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).GetEntryCount(out count); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).Read(out entriesRead, entryBuffer); - Tick end = fs.Hos.Os.GetSystemTick(); + Span buffer = stackalloc byte[0x50]; + var sb = new U8StringBuilder(buffer, true); - Span buffer = stackalloc byte[0x50]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogEntryBufferCount).AppendFormat(entryBuffer.Length) - .Append(LogEntryCount).AppendFormat(entriesRead); - fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); - } - else - { - rc = Get(handle).Read(out entriesRead, entryBuffer); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; + sb.Append(LogEntryCount).AppendFormat(count); + fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); + } + else + { + rc = Get(handle).GetEntryCount(out count); } - public static Result GetDirectoryEntryCount(this FileSystemClient fs, out long count, DirectoryHandle handle) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static void CloseDirectory(this FileSystemClient fs, DirectoryHandle handle) + { + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc; + Tick start = fs.Hos.Os.GetSystemTick(); + Get(handle).Dispose(); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).GetEntryCount(out count); - Tick end = fs.Hos.Os.GetSystemTick(); - - Span buffer = stackalloc byte[0x50]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogEntryCount).AppendFormat(count); - fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); - } - else - { - rc = Get(handle).GetEntryCount(out count); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty); } - - public static void CloseDirectory(this FileSystemClient fs, DirectoryHandle handle) + else { - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - Get(handle).Dispose(); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty); - } - else - { - Get(handle).Dispose(); - } + Get(handle).Dispose(); } } } diff --git a/src/LibHac/Fs/Fsa/UserFile.cs b/src/LibHac/Fs/Fsa/UserFile.cs index e7d00fb7..60b6598e 100644 --- a/src/LibHac/Fs/Fsa/UserFile.cs +++ b/src/LibHac/Fs/Fsa/UserFile.cs @@ -5,223 +5,222 @@ using LibHac.Fs.Impl; using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +[SkipLocalsInit] +public static class UserFile { - [SkipLocalsInit] - public static class UserFile + private static FileAccessor Get(FileHandle handle) { - private static FileAccessor Get(FileHandle handle) + return handle.File; + } + + private static Result ReadFileImpl(FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, + Span destination, in ReadOption option) + { + return Get(handle).Read(out bytesRead, offset, destination, in option); + } + + public static Result ReadFile(this FileSystemClient fs, FileHandle handle, long offset, Span destination, + in ReadOption option) + { + Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, in option); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (bytesRead == destination.Length) + return Result.Success; + + rc = ResultFs.OutOfRange.Log(); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result ReadFile(this FileSystemClient fs, FileHandle handle, long offset, Span destination) + { + Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, ReadOption.None); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (bytesRead == destination.Length) + return Result.Success; + + rc = ResultFs.OutOfRange.Log(); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, + Span destination, in ReadOption option) + { + Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, in option); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, + Span destination) + { + Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, ReadOption.None); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result WriteFile(this FileSystemClient fs, FileHandle handle, long offset, + ReadOnlySpan source, in WriteOption option) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - return handle.File; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).Write(offset, source, in option); + Tick end = fs.Hos.Os.GetSystemTick(); + + Span buffer = stackalloc byte[0x60]; + var sb = new U8StringBuilder(buffer, true); + + sb.Append(LogOffset).AppendFormat(offset).Append(LogSize).AppendFormat(source.Length); + + if (option.HasFlushFlag()) + sb.Append(LogWriteOptionFlush); + + fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); + sb.Dispose(); + } + else + { + rc = Get(handle).Write(offset, source, in option); } - private static Result ReadFileImpl(FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, - Span destination, in ReadOption option) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result FlushFile(this FileSystemClient fs, FileHandle handle) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - return Get(handle).Read(out bytesRead, offset, destination, in option); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).Flush(); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, handle, U8Span.Empty); + } + else + { + rc = Get(handle).Flush(); } - public static Result ReadFile(this FileSystemClient fs, FileHandle handle, long offset, Span destination, - in ReadOption option) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result SetFileSize(this FileSystemClient fs, FileHandle handle, long size) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, in option); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).SetSize(size); + Tick end = fs.Hos.Os.GetSystemTick(); - if (bytesRead == destination.Length) - return Result.Success; + Span buffer = stackalloc byte[0x20]; + var sb = new U8StringBuilder(buffer, true); - rc = ResultFs.OutOfRange.Log(); - fs.Impl.AbortIfNeeded(rc); - return rc; + sb.Append(LogSize).AppendFormat(size); + fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); + } + else + { + rc = Get(handle).SetSize(size); } - public static Result ReadFile(this FileSystemClient fs, FileHandle handle, long offset, Span destination) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetFileSize(this FileSystemClient fs, out long size, FileHandle handle) + { + Result rc; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, ReadOption.None); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Get(handle).GetSize(out size); + Tick end = fs.Hos.Os.GetSystemTick(); - if (bytesRead == destination.Length) - return Result.Success; + Span buffer = stackalloc byte[0x20]; + var sb = new U8StringBuilder(buffer, true); - rc = ResultFs.OutOfRange.Log(); - fs.Impl.AbortIfNeeded(rc); - return rc; + sb.Append(LogSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in size, rc)); + fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); + } + else + { + rc = Get(handle).GetSize(out size); } - public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, - Span destination, in ReadOption option) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static OpenMode GetFileOpenMode(this FileSystemClient fs, FileHandle handle) + { + OpenMode mode = Get(handle).GetOpenMode(); + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, in option); - fs.Impl.AbortIfNeeded(rc); - return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + Tick end = fs.Hos.Os.GetSystemTick(); + + Span buffer = stackalloc byte[0x20]; + var sb = new U8StringBuilder(buffer, true); + + sb.Append(LogOpenMode).AppendFormat((int)mode, 'X'); + fs.Impl.OutputAccessLog(Result.Success, start, end, handle, new U8Span(sb.Buffer)); } - public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle handle, long offset, - Span destination) + return mode; + } + + public static void CloseFile(this FileSystemClient fs, FileHandle handle) + { + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) { - Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, ReadOption.None); - fs.Impl.AbortIfNeeded(rc); - return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + Get(handle).Dispose(); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty); } - - public static Result WriteFile(this FileSystemClient fs, FileHandle handle, long offset, - ReadOnlySpan source, in WriteOption option) + else { - Result rc; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).Write(offset, source, in option); - Tick end = fs.Hos.Os.GetSystemTick(); - - Span buffer = stackalloc byte[0x60]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogOffset).AppendFormat(offset).Append(LogSize).AppendFormat(source.Length); - - if (option.HasFlushFlag()) - sb.Append(LogWriteOptionFlush); - - fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); - sb.Dispose(); - } - else - { - rc = Get(handle).Write(offset, source, in option); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result FlushFile(this FileSystemClient fs, FileHandle handle) - { - Result rc; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).Flush(); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, handle, U8Span.Empty); - } - else - { - rc = Get(handle).Flush(); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result SetFileSize(this FileSystemClient fs, FileHandle handle, long size) - { - Result rc; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).SetSize(size); - Tick end = fs.Hos.Os.GetSystemTick(); - - Span buffer = stackalloc byte[0x20]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogSize).AppendFormat(size); - fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); - } - else - { - rc = Get(handle).SetSize(size); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetFileSize(this FileSystemClient fs, out long size, FileHandle handle) - { - Result rc; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Get(handle).GetSize(out size); - Tick end = fs.Hos.Os.GetSystemTick(); - - Span buffer = stackalloc byte[0x20]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in size, rc)); - fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer)); - } - else - { - rc = Get(handle).GetSize(out size); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static OpenMode GetFileOpenMode(this FileSystemClient fs, FileHandle handle) - { - OpenMode mode = Get(handle).GetOpenMode(); - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - Tick end = fs.Hos.Os.GetSystemTick(); - - Span buffer = stackalloc byte[0x20]; - var sb = new U8StringBuilder(buffer, true); - - sb.Append(LogOpenMode).AppendFormat((int)mode, 'X'); - fs.Impl.OutputAccessLog(Result.Success, start, end, handle, new U8Span(sb.Buffer)); - } - - return mode; - } - - public static void CloseFile(this FileSystemClient fs, FileHandle handle) - { - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - Get(handle).Dispose(); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty); - } - else - { - Get(handle).Dispose(); - } - } - - public static Result QueryRange(this FileSystemClient fs, out QueryRangeInfo rangeInfo, FileHandle handle, - long offset, long size) - { - UnsafeHelpers.SkipParamInit(out rangeInfo); - - Result rc = Get(handle).OperateRange(SpanHelpers.AsByteSpan(ref rangeInfo), OperationId.QueryRange, offset, - size, ReadOnlySpan.Empty); - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result InvalidateCache(this FileSystemClient fs, FileHandle handle, long offset, long size) - { - Result rc = Get(handle).OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, - ReadOnlySpan.Empty); - - fs.Impl.AbortIfNeeded(rc); - return rc; + Get(handle).Dispose(); } } + + public static Result QueryRange(this FileSystemClient fs, out QueryRangeInfo rangeInfo, FileHandle handle, + long offset, long size) + { + UnsafeHelpers.SkipParamInit(out rangeInfo); + + Result rc = Get(handle).OperateRange(SpanHelpers.AsByteSpan(ref rangeInfo), OperationId.QueryRange, offset, + size, ReadOnlySpan.Empty); + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result InvalidateCache(this FileSystemClient fs, FileHandle handle, long offset, long size) + { + Result rc = Get(handle).OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); + + fs.Impl.AbortIfNeeded(rc); + return rc; + } } diff --git a/src/LibHac/Fs/Fsa/UserFileSystem.cs b/src/LibHac/Fs/Fsa/UserFileSystem.cs index 8f84ddee..c99c1fab 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystem.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystem.cs @@ -8,741 +8,740 @@ using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +[SkipLocalsInit] +public static class UserFileSystem { - [SkipLocalsInit] - public static class UserFileSystem + public static Result CreateFile(this FileSystemClient fs, U8Span path, long size) { - public static Result CreateFile(this FileSystemClient fs, U8Span path, long size) + return fs.CreateFile(path, size, CreateFileOptions.None); + } + + public static Result DeleteFile(this FileSystemClient fs, U8Span path) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) { - return fs.CreateFile(path, size, CreateFileOptions.None); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result DeleteFile(this FileSystemClient fs, U8Span path) + else { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.DeleteFile(subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.DeleteFile(subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result CreateDirectory(this FileSystemClient fs, U8Span path) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteFile(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.CreateDirectory(subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.CreateDirectory(subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result DeleteDirectory(this FileSystemClient fs, U8Span path) + else { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.DeleteDirectory(subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.DeleteDirectory(subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fileSystem.DeleteFile(subPath); } + fs.Impl.AbortIfNeeded(rc); + return rc; + } - public static Result DeleteDirectoryRecursively(this FileSystemClient fs, U8Span path) + public static Result CreateDirectory(this FileSystemClient fs, U8Span path) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.DeleteDirectoryRecursively(subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.DeleteDirectoryRecursively(subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result CleanDirectoryRecursively(this FileSystemClient fs, U8Span path) + else { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.CleanDirectoryRecursively(subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.CleanDirectoryRecursively(subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result RenameFile(this FileSystemClient fs, U8Span oldPath, U8Span newPath) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - Result rc; - U8Span currentSubPath, newSubPath; - FileSystemAccessor currentFileSystem, newFileSystem; - Span logBuffer = stackalloc byte[0x300]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CreateDirectory(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); - // Get the file system accessor for the current path - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Get the file system accessor for the new path - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Rename the file - if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - - rc = currentFileSystem != newFileSystem - ? ResultFs.RenameToOtherFileSystem.Log() - : currentFileSystem.RenameFile(currentSubPath, newSubPath); - - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = currentFileSystem != newFileSystem - ? ResultFs.RenameToOtherFileSystem.Log() - : currentFileSystem.RenameFile(currentSubPath, newSubPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result RenameDirectory(this FileSystemClient fs, U8Span oldPath, U8Span newPath) + else { - Result rc; - U8Span currentSubPath, newSubPath; - FileSystemAccessor currentFileSystem, newFileSystem; - Span logBuffer = stackalloc byte[0x300]; - - // Get the file system accessor for the current path - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Get the file system accessor for the new path - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Rename the directory - if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - - rc = currentFileSystem != newFileSystem - ? ResultFs.RenameToOtherFileSystem.Log() - : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); - - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = currentFileSystem != newFileSystem - ? ResultFs.RenameToOtherFileSystem.Log() - : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fileSystem.CreateDirectory(subPath); } + fs.Impl.AbortIfNeeded(rc); + return rc; + } - public static Result GetEntryType(this FileSystemClient fs, out DirectoryEntryType type, U8Span path) + public static Result DeleteDirectory(this FileSystemClient fs, U8Span path) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) { - UnsafeHelpers.SkipParamInit(out type); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append(LogEntryType) - .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.GetEntryType(out type, subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append(LogEntryType) - .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.GetEntryType(out type, subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result GetFreeSpaceSize(this FileSystemClient fs, out long freeSpace, U8Span path) + else { - UnsafeHelpers.SkipParamInit(out freeSpace); - - Result rc; - var subPath = U8Span.Empty; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsValidMountName(path)) - { - rc = fs.Impl.Find(out fileSystem, path); - if (rc.IsFailure()) return rc; - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - if (rc.IsFailure()) return rc; - } - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize) - .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - if (fs.Impl.IsValidMountName(path)) - { - rc = fs.Impl.Find(out fileSystem, path); - if (rc.IsFailure()) return rc; - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - if (rc.IsFailure()) return rc; - } - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize) - .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result OpenFile(this FileSystemClient fs, out FileHandle handle, U8Span path, OpenMode mode) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - UnsafeHelpers.SkipParamInit(out handle); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteDirectory(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(); - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.OpenFile(ref file.Ref(), subPath, mode); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, file.Get, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.OpenFile(ref file.Ref(), subPath, mode); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - handle = new FileHandle(file.Release()); - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result OpenFile(this FileSystemClient fs, out FileHandle handle, ref UniqueRef file, OpenMode mode) + else { - var accessor = new FileAccessor(fs.Hos, ref file, null, mode); - handle = new FileHandle(accessor); - - return Result.Success; + rc = fileSystem.DeleteDirectory(subPath); } + fs.Impl.AbortIfNeeded(rc); + return rc; + } - public static Result OpenDirectory(this FileSystemClient fs, out DirectoryHandle handle, U8Span path, - OpenDirectoryMode mode) + public static Result DeleteDirectoryRecursively(this FileSystemClient fs, U8Span path) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) { - UnsafeHelpers.SkipParamInit(out handle); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - using var accessor = new UniqueRef(); - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.OpenDirectory(ref accessor.Ref(), subPath, mode); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, accessor.Get, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.OpenDirectory(ref accessor.Ref(), subPath, mode); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - handle = new DirectoryHandle(accessor.Release()); - return Result.Success; + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); } - - private static Result CommitImpl(FileSystemClient fs, U8Span mountName, - [CallerMemberName] string functionName = "") + else { - Result rc; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x30]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.Find(out fileSystem, mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer), functionName); - } - else - { - rc = fs.Impl.Find(out fileSystem, mountName); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.Commit(); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer), functionName); - } - else - { - rc = fileSystem.Commit(); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result Commit(this FileSystemClient fs, ReadOnlySpan mountNames) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - // Todo: Add access log + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.DeleteDirectoryRecursively(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); - if (mountNames.Length > 10) - return ResultFs.InvalidCommitNameCount.Log(); + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.DeleteDirectoryRecursively(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } - if (mountNames.Length == 0) - return Result.Success; + public static Result CleanDirectoryRecursively(this FileSystemClient fs, U8Span path) + { + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; - using var commitManager = new SharedRef(); - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc = fileSystemProxy.Get.OpenMultiCommitManager(ref commitManager.Ref()); - if (rc.IsFailure()) return rc; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; - for (int i = 0; i < mountNames.Length; i++) + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CleanDirectoryRecursively(subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.CleanDirectoryRecursively(subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result RenameFile(this FileSystemClient fs, U8Span oldPath, U8Span newPath) + { + Result rc; + U8Span currentSubPath, newSubPath; + FileSystemAccessor currentFileSystem, newFileSystem; + Span logBuffer = stackalloc byte[0x300]; + + // Get the file system accessor for the current path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Get the file system accessor for the new path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Rename the file + if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameFile(currentSubPath, newSubPath); + + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameFile(currentSubPath, newSubPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result RenameDirectory(this FileSystemClient fs, U8Span oldPath, U8Span newPath) + { + Result rc; + U8Span currentSubPath, newSubPath; + FileSystemAccessor currentFileSystem, newFileSystem; + Span logBuffer = stackalloc byte[0x300]; + + // Get the file system accessor for the current path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(oldPath).Append(LogNewPath).Append(newPath).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out currentFileSystem, out currentSubPath, oldPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Get the file system accessor for the new path + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out newFileSystem, out newSubPath, newPath); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Rename the directory + if (fs.Impl.IsEnabledAccessLog() && currentFileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); + + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = currentFileSystem != newFileSystem + ? ResultFs.RenameToOtherFileSystem.Log() + : currentFileSystem.RenameDirectory(currentSubPath, newSubPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetEntryType(this FileSystemClient fs, out DirectoryEntryType type, U8Span path) + { + UnsafeHelpers.SkipParamInit(out type); + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogEntryType) + .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.GetEntryType(out type, subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append(LogEntryType) + .Append(idString.ToString(AccessLogImpl.DereferenceOutValue(in type, rc))); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.GetEntryType(out type, subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetFreeSpaceSize(this FileSystemClient fs, out long freeSpace, U8Span path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + Result rc; + var subPath = U8Span.Empty; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + if (fs.Impl.IsValidMountName(path)) { - rc = fs.Impl.Find(out FileSystemAccessor accessor, mountNames[i]); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystem = accessor.GetMultiCommitTarget(); - if (!fileSystem.HasValue) - return ResultFs.UnsupportedCommitTarget.Log(); - - rc = commitManager.Get.Add(ref fileSystem.Ref()); + rc = fs.Impl.Find(out fileSystem, path); if (rc.IsFailure()) return rc; } - - rc = commitManager.Get.Commit(); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public static Result Commit(this FileSystemClient fs, U8Span mountName, CommitOption option) - { - Result rc; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.Find(out fileSystem, mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogCommitOption).AppendFormat((int)option.Flags, 'X'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } else { - rc = fs.Impl.Find(out fileSystem, mountName); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = RunCommit(fs, option, fileSystem); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = RunCommit(fs, option, fileSystem); - } - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result RunCommit(FileSystemClient fs, CommitOption option, FileSystemAccessor fileSystem) - { - if ((option.Flags & (CommitOptionFlag.ClearRestoreFlag | CommitOptionFlag.SetRestoreFlag)) == 0) - { - return fileSystem.Commit(); - } - - if (option.Flags != CommitOptionFlag.ClearRestoreFlag && - option.Flags != CommitOptionFlag.SetRestoreFlag) - { - return ResultFs.InvalidCommitOption.Log(); - } - - Result rc = fileSystem.GetSaveDataAttribute(out SaveDataAttribute attribute); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); if (rc.IsFailure()) return rc; + } + Tick end = fs.Hos.Os.GetSystemTick(); - if (attribute.ProgramId == SaveData.InvalidProgramId) - attribute.ProgramId = SaveData.AutoResolveCallerProgramId; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); + logBuffer = sb.Buffer; - var extraDataMask = new SaveDataExtraData(); - extraDataMask.Flags = SaveDataFlags.Restore; - - var extraData = new SaveDataExtraData(); - extraDataMask.Flags = option.Flags == CommitOptionFlag.SetRestoreFlag - ? SaveDataFlags.Restore - : SaveDataFlags.None; - - return fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.User, in attribute, in extraData, - in extraDataMask); + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + if (fs.Impl.IsValidMountName(path)) + { + rc = fs.Impl.Find(out fileSystem, path); + if (rc.IsFailure()) return rc; + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + if (rc.IsFailure()) return rc; } } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result Commit(this FileSystemClient fs, U8Span mountName) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - return CommitImpl(fs, mountName); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc)); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.GetFreeSpaceSize(out freeSpace, subPath); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result OpenFile(this FileSystemClient fs, out FileHandle handle, U8Span path, OpenMode mode) + { + UnsafeHelpers.SkipParamInit(out handle); + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(); + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.OpenFile(ref file.Ref(), subPath, mode); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, file.Get, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.OpenFile(ref file.Ref(), subPath, mode); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + handle = new FileHandle(file.Release()); + return Result.Success; + } + + public static Result OpenFile(this FileSystemClient fs, out FileHandle handle, ref UniqueRef file, OpenMode mode) + { + var accessor = new FileAccessor(fs.Hos, ref file, null, mode); + handle = new FileHandle(accessor); + + return Result.Success; + } + + public static Result OpenDirectory(this FileSystemClient fs, out DirectoryHandle handle, U8Span path, + OpenDirectoryMode mode) + { + UnsafeHelpers.SkipParamInit(out handle); + + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var accessor = new UniqueRef(); + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.OpenDirectory(ref accessor.Ref(), subPath, mode); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, accessor.Get, new U8Span(logBuffer)); + } + else + { + rc = fileSystem.OpenDirectory(ref accessor.Ref(), subPath, mode); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + handle = new DirectoryHandle(accessor.Release()); + return Result.Success; + } + + private static Result CommitImpl(FileSystemClient fs, U8Span mountName, + [CallerMemberName] string functionName = "") + { + Result rc; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Find(out fileSystem, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append((byte)'"'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer), functionName); + } + else + { + rc = fs.Impl.Find(out fileSystem, mountName); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.Commit(); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer), functionName); + } + else + { + rc = fileSystem.Commit(); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result Commit(this FileSystemClient fs, ReadOnlySpan mountNames) + { + // Todo: Add access log + + if (mountNames.Length > 10) + return ResultFs.InvalidCommitNameCount.Log(); + + if (mountNames.Length == 0) + return Result.Success; + + using var commitManager = new SharedRef(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.OpenMultiCommitManager(ref commitManager.Ref()); + if (rc.IsFailure()) return rc; + + for (int i = 0; i < mountNames.Length; i++) + { + rc = fs.Impl.Find(out FileSystemAccessor accessor, mountNames[i]); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystem = accessor.GetMultiCommitTarget(); + if (!fileSystem.HasValue) + return ResultFs.UnsupportedCommitTarget.Log(); + + rc = commitManager.Get.Add(ref fileSystem.Ref()); + if (rc.IsFailure()) return rc; } - public static Result CommitSaveData(this FileSystemClient fs, U8Span mountName) + rc = commitManager.Get.Commit(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result Commit(this FileSystemClient fs, U8Span mountName, CommitOption option) + { + Result rc; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog()) { - return CommitImpl(fs, mountName); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Find(out fileSystem, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogCommitOption).AppendFormat((int)option.Flags, 'X'); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.Find(out fileSystem, mountName); + } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = RunCommit(fs, option, fileSystem); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = RunCommit(fs, option, fileSystem); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result RunCommit(FileSystemClient fs, CommitOption option, FileSystemAccessor fileSystem) + { + if ((option.Flags & (CommitOptionFlag.ClearRestoreFlag | CommitOptionFlag.SetRestoreFlag)) == 0) + { + return fileSystem.Commit(); + } + + if (option.Flags != CommitOptionFlag.ClearRestoreFlag && + option.Flags != CommitOptionFlag.SetRestoreFlag) + { + return ResultFs.InvalidCommitOption.Log(); + } + + Result rc = fileSystem.GetSaveDataAttribute(out SaveDataAttribute attribute); + if (rc.IsFailure()) return rc; + + if (attribute.ProgramId == SaveData.InvalidProgramId) + attribute.ProgramId = SaveData.AutoResolveCallerProgramId; + + var extraDataMask = new SaveDataExtraData(); + extraDataMask.Flags = SaveDataFlags.Restore; + + var extraData = new SaveDataExtraData(); + extraDataMask.Flags = option.Flags == CommitOptionFlag.SetRestoreFlag + ? SaveDataFlags.Restore + : SaveDataFlags.None; + + return fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.User, in attribute, in extraData, + in extraDataMask); } } + + public static Result Commit(this FileSystemClient fs, U8Span mountName) + { + return CommitImpl(fs, mountName); + } + + public static Result CommitSaveData(this FileSystemClient fs, U8Span mountName) + { + return CommitImpl(fs, mountName); + } } diff --git a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs index af078567..5ae24068 100644 --- a/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs +++ b/src/LibHac/Fs/Fsa/UserFileSystemPrivate.cs @@ -4,78 +4,77 @@ using LibHac.Fs.Impl; using LibHac.Os; using static LibHac.Fs.Impl.AccessLogStrings; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +public static class UserFileSystemPrivate { - public static class UserFileSystemPrivate + public static Result CreateFile(this FileSystemClient fs, U8Span path, long size, CreateFileOptions options) { - public static Result CreateFile(this FileSystemClient fs, U8Span path, long size, CreateFileOptions options) + Result rc; + U8Span subPath; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog()) { - Result rc; - U8Span subPath; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x300]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"'); + logBuffer = sb.Buffer; - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"'); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); - } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.CreateFile(subPath, size, options); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize).AppendFormat(size); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fileSystem.CreateFile(subPath, size, options); - } - fs.Impl.AbortIfNeeded(rc); - return rc; + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); } - - public static Result GetTotalSpaceSize(this FileSystemClient fs, out long totalSpace, U8Span path) + else { - UnsafeHelpers.SkipParamInit(out totalSpace); - - Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - rc = fileSystem.GetFreeSpaceSize(out totalSpace, subPath); - fs.Impl.AbortIfNeeded(rc); - return rc; + rc = fs.Impl.FindFileSystem(out fileSystem, out subPath, path); } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - public static Result SetConcatenationFileAttribute(this FileSystemClient fs, U8Span path) + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) { - Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fileSystem.CreateFile(subPath, size, options); + Tick end = fs.Hos.Os.GetSystemTick(); - rc = fileSystem.QueryEntry(Span.Empty, ReadOnlySpan.Empty, QueryId.SetConcatenationFileAttribute, subPath); - fs.Impl.AbortIfNeeded(rc); - return rc; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize).AppendFormat(size); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); } + else + { + rc = fileSystem.CreateFile(subPath, size, options); + } + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetTotalSpaceSize(this FileSystemClient fs, out long totalSpace, U8Span path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fileSystem.GetFreeSpaceSize(out totalSpace, subPath); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result SetConcatenationFileAttribute(this FileSystemClient fs, U8Span path) + { + Result rc = fs.Impl.FindFileSystem(out FileSystemAccessor fileSystem, out U8Span subPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = fileSystem.QueryEntry(Span.Empty, ReadOnlySpan.Empty, QueryId.SetConcatenationFileAttribute, subPath); + fs.Impl.AbortIfNeeded(rc); + return rc; } } diff --git a/src/LibHac/Fs/Fsa/UserMountTable.cs b/src/LibHac/Fs/Fsa/UserMountTable.cs index 6189c8d9..177e4382 100644 --- a/src/LibHac/Fs/Fsa/UserMountTable.cs +++ b/src/LibHac/Fs/Fsa/UserMountTable.cs @@ -3,48 +3,47 @@ using LibHac.Common; using LibHac.Fs.Impl; using LibHac.Ncm; -namespace LibHac.Fs.Fsa +namespace LibHac.Fs.Fsa; + +internal struct UserMountTableGlobals { - internal struct UserMountTableGlobals + public MountTable MountTable; + + public void Initialize(FileSystemClient fsClient) { - public MountTable MountTable; - - public void Initialize(FileSystemClient fsClient) - { - MountTable = new MountTable(fsClient); - } - } - - /// - /// Contains functions for adding, removing and retrieving s from the mount table. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal static class UserMountTable - { - public static Result Register(this FileSystemClientImpl fs, ref UniqueRef fileSystem) - { - return fs.Globals.UserMountTable.MountTable.Mount(ref fileSystem); - } - - public static Result Find(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, U8Span name) - { - return fs.Globals.UserMountTable.MountTable.Find(out fileSystem, name); - } - - public static void Unregister(this FileSystemClientImpl fs, U8Span name) - { - fs.Globals.UserMountTable.MountTable.Unmount(name); - } - - public static int GetMountedDataIdCount(this FileSystemClientImpl fs) - { - return fs.Globals.UserMountTable.MountTable.GetDataIdCount(); - } - - public static Result ListMountedDataId(this FileSystemClientImpl fs, out int dataIdCount, - Span dataIdBuffer) - { - return fs.Globals.UserMountTable.MountTable.ListDataId(out dataIdCount, dataIdBuffer); - } + MountTable = new MountTable(fsClient); + } +} + +/// +/// Contains functions for adding, removing and retrieving s from the mount table. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal static class UserMountTable +{ + public static Result Register(this FileSystemClientImpl fs, ref UniqueRef fileSystem) + { + return fs.Globals.UserMountTable.MountTable.Mount(ref fileSystem); + } + + public static Result Find(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, U8Span name) + { + return fs.Globals.UserMountTable.MountTable.Find(out fileSystem, name); + } + + public static void Unregister(this FileSystemClientImpl fs, U8Span name) + { + fs.Globals.UserMountTable.MountTable.Unmount(name); + } + + public static int GetMountedDataIdCount(this FileSystemClientImpl fs) + { + return fs.Globals.UserMountTable.MountTable.GetDataIdCount(); + } + + public static Result ListMountedDataId(this FileSystemClientImpl fs, out int dataIdCount, + Span dataIdBuffer) + { + return fs.Globals.UserMountTable.MountTable.ListDataId(out dataIdCount, dataIdBuffer); } } diff --git a/src/LibHac/Fs/GameCard.cs b/src/LibHac/Fs/GameCard.cs index 6a014e1f..81ff7bc4 100644 --- a/src/LibHac/Fs/GameCard.cs +++ b/src/LibHac/Fs/GameCard.cs @@ -1,44 +1,43 @@ using System; -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class GameCard { - public static class GameCard + public static long GetGameCardSizeBytes(GameCardSizeInternal size) => size switch { - public static long GetGameCardSizeBytes(GameCardSizeInternal size) => size switch - { - GameCardSizeInternal.Size1Gb => 0x3B800000, - GameCardSizeInternal.Size2Gb => 0x77000000, - GameCardSizeInternal.Size4Gb => 0xEE000000, - GameCardSizeInternal.Size8Gb => 0x1DC000000, - GameCardSizeInternal.Size16Gb => 0x3B8000000, - GameCardSizeInternal.Size32Gb => 0x770000000, - _ => 0 - }; + GameCardSizeInternal.Size1Gb => 0x3B800000, + GameCardSizeInternal.Size2Gb => 0x77000000, + GameCardSizeInternal.Size4Gb => 0xEE000000, + GameCardSizeInternal.Size8Gb => 0x1DC000000, + GameCardSizeInternal.Size16Gb => 0x3B8000000, + GameCardSizeInternal.Size32Gb => 0x770000000, + _ => 0 + }; - public static long CardPageToOffset(int page) - { - return (long)page << 9; - } - } - - public enum GameCardSizeInternal : byte + public static long CardPageToOffset(int page) { - Size1Gb = 0xFA, - Size2Gb = 0xF8, - Size4Gb = 0xF0, - Size8Gb = 0xE0, - Size16Gb = 0xE1, - Size32Gb = 0xE2 - } - - [Flags] - public enum GameCardAttribute : byte - { - None = 0, - AutoBootFlag = 1 << 0, - HistoryEraseFlag = 1 << 1, - RepairToolFlag = 1 << 2, - DifferentRegionCupToTerraDeviceFlag = 1 << 3, - DifferentRegionCupToGlobalDeviceFlag = 1 << 4 + return (long)page << 9; } } + +public enum GameCardSizeInternal : byte +{ + Size1Gb = 0xFA, + Size2Gb = 0xF8, + Size4Gb = 0xF0, + Size8Gb = 0xE0, + Size16Gb = 0xE1, + Size32Gb = 0xE2 +} + +[Flags] +public enum GameCardAttribute : byte +{ + None = 0, + AutoBootFlag = 1 << 0, + HistoryEraseFlag = 1 << 1, + RepairToolFlag = 1 << 2, + DifferentRegionCupToTerraDeviceFlag = 1 << 3, + DifferentRegionCupToGlobalDeviceFlag = 1 << 4 +} diff --git a/src/LibHac/Fs/Host.cs b/src/LibHac/Fs/Host.cs index 26ab6c46..511a37c6 100644 --- a/src/LibHac/Fs/Host.cs +++ b/src/LibHac/Fs/Host.cs @@ -1,31 +1,30 @@ using System; -namespace LibHac.Fs +namespace LibHac.Fs; + +public readonly struct MountHostOption { - public readonly struct MountHostOption + public readonly MountHostOptionFlag Flags; + + public MountHostOption(int flags) { - public readonly MountHostOptionFlag Flags; - - public MountHostOption(int flags) - { - Flags = (MountHostOptionFlag)flags; - } - - public MountHostOption(MountHostOptionFlag flags) - { - Flags = flags; - } - - public static MountHostOption None => new MountHostOption(MountHostOptionFlag.None); - - public static MountHostOption PseudoCaseSensitive => - new MountHostOption(MountHostOptionFlag.PseudoCaseSensitive); + Flags = (MountHostOptionFlag)flags; } - [Flags] - public enum MountHostOptionFlag + public MountHostOption(MountHostOptionFlag flags) { - None = 0, - PseudoCaseSensitive = 1 + Flags = flags; } + + public static MountHostOption None => new MountHostOption(MountHostOptionFlag.None); + + public static MountHostOption PseudoCaseSensitive => + new MountHostOption(MountHostOptionFlag.PseudoCaseSensitive); +} + +[Flags] +public enum MountHostOptionFlag +{ + None = 0, + PseudoCaseSensitive = 1 } diff --git a/src/LibHac/Fs/IStorage.cs b/src/LibHac/Fs/IStorage.cs index 017f24dd..053b8a0e 100644 --- a/src/LibHac/Fs/IStorage.cs +++ b/src/LibHac/Fs/IStorage.cs @@ -1,121 +1,120 @@ using System; using System.Runtime.CompilerServices; -namespace LibHac.Fs +namespace LibHac.Fs; + +// ReSharper disable once InconsistentNaming +/// +/// Provides an interface for reading and writing a sequence of bytes. +/// +/// +/// The official IStorage makes the Read etc. methods abstract and doesn't +/// have DoRead etc. methods. We're using them here so we can make sure +/// the object isn't disposed before calling the method implementation. +/// +public abstract class IStorage : IDisposable { - // ReSharper disable once InconsistentNaming /// - /// Provides an interface for reading and writing a sequence of bytes. + /// Reads a sequence of bytes from the current . /// - /// - /// The official IStorage makes the Read etc. methods abstract and doesn't - /// have DoRead etc. methods. We're using them here so we can make sure - /// the object isn't disposed before calling the method implementation. - /// - public abstract class IStorage : IDisposable + /// The offset in the at which to begin reading. + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be equal to the length of the buffer. + /// The of the operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Read(long offset, Span destination) { - /// - /// Reads a sequence of bytes from the current . - /// - /// The offset in the at which to begin reading. - /// The buffer where the read bytes will be stored. - /// The number of bytes read will be equal to the length of the buffer. - /// The of the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result Read(long offset, Span destination) - { - return DoRead(offset, destination); - } - - /// - /// Writes a sequence of bytes to the current . - /// - /// The offset in the at which to begin writing. - /// The buffer containing the bytes to be written. - /// The of the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result Write(long offset, ReadOnlySpan source) - { - return DoWrite(offset, source); - } - - /// - /// Causes any buffered data to be written to the underlying device. - /// - /// The of the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result Flush() - { - return DoFlush(); - } - - /// - /// Sets the size of the current . - /// - /// The desired size of the in bytes. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result SetSize(long size) - { - return DoSetSize(size); - } - - /// - /// Gets the number of bytes in the . - /// - /// If the operation returns successfully, the length of the file in bytes. - /// The of the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result GetSize(out long size) - { - return DoGetSize(out size); - } - - /// - /// Performs various operations on the file. Used to extend the functionality of the interface. - /// - /// A buffer that will contain the response from the operation. - /// The operation to be performed. - /// The offset of the range to operate on. - /// The size of the range to operate on. - /// An input buffer. Size may vary depending on the operation performed. - /// The of the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return DoOperateRange(outBuffer, operationId, offset, size, inBuffer); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckAccessRange(long offset, long size, long totalSize) - { - return offset >= 0 && - size >= 0 && - size <= totalSize && - offset <= totalSize - size; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckOffsetAndSize(long offset, long size) - { - return offset >= 0 && - size >= 0 && - offset <= offset + size; - } - - // Todo: Remove Do* methods - protected abstract Result DoRead(long offset, Span destination); - protected abstract Result DoWrite(long offset, ReadOnlySpan source); - protected abstract Result DoFlush(); - protected abstract Result DoSetSize(long size); - protected abstract Result DoGetSize(out long size); - - protected virtual Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } - - public virtual void Dispose() { } + return DoRead(offset, destination); } + + /// + /// Writes a sequence of bytes to the current . + /// + /// The offset in the at which to begin writing. + /// The buffer containing the bytes to be written. + /// The of the operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Write(long offset, ReadOnlySpan source) + { + return DoWrite(offset, source); + } + + /// + /// Causes any buffered data to be written to the underlying device. + /// + /// The of the operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result Flush() + { + return DoFlush(); + } + + /// + /// Sets the size of the current . + /// + /// The desired size of the in bytes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result SetSize(long size) + { + return DoSetSize(size); + } + + /// + /// Gets the number of bytes in the . + /// + /// If the operation returns successfully, the length of the file in bytes. + /// The of the operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result GetSize(out long size) + { + return DoGetSize(out size); + } + + /// + /// Performs various operations on the file. Used to extend the functionality of the interface. + /// + /// A buffer that will contain the response from the operation. + /// The operation to be performed. + /// The offset of the range to operate on. + /// The size of the range to operate on. + /// An input buffer. Size may vary depending on the operation performed. + /// The of the operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return DoOperateRange(outBuffer, operationId, offset, size, inBuffer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckAccessRange(long offset, long size, long totalSize) + { + return offset >= 0 && + size >= 0 && + size <= totalSize && + offset <= totalSize - size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckOffsetAndSize(long offset, long size) + { + return offset >= 0 && + size >= 0 && + offset <= offset + size; + } + + // Todo: Remove Do* methods + protected abstract Result DoRead(long offset, Span destination); + protected abstract Result DoWrite(long offset, ReadOnlySpan source); + protected abstract Result DoFlush(); + protected abstract Result DoSetSize(long size); + protected abstract Result DoGetSize(out long size); + + protected virtual Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } + + public virtual void Dispose() { } } diff --git a/src/LibHac/Fs/Impl/CommonMountNames.cs b/src/LibHac/Fs/Impl/CommonMountNames.cs index 00f6f108..a3850c88 100644 --- a/src/LibHac/Fs/Impl/CommonMountNames.cs +++ b/src/LibHac/Fs/Impl/CommonMountNames.cs @@ -1,81 +1,80 @@ using System; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +public static class CommonMountNames { - public static class CommonMountNames - { - public const char ReservedMountNamePrefixCharacter = '@'; + public const char ReservedMountNamePrefixCharacter = '@'; - // Filesystem names. - public static ReadOnlySpan HostRootFileSystemMountName => // "@Host" - new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t' }; + // Filesystem names. + public static ReadOnlySpan HostRootFileSystemMountName => // "@Host" + new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t' }; - public static ReadOnlySpan SdCardFileSystemMountName => // "@Sdcard" - new[] { (byte)'@', (byte)'S', (byte)'d', (byte)'c', (byte)'a', (byte)'r', (byte)'d' }; + public static ReadOnlySpan SdCardFileSystemMountName => // "@Sdcard" + new[] { (byte)'@', (byte)'S', (byte)'d', (byte)'c', (byte)'a', (byte)'r', (byte)'d' }; - public static ReadOnlySpan GameCardFileSystemMountName => // "@Gc" - new[] { (byte)'@', (byte)'G', (byte)'c' }; + public static ReadOnlySpan GameCardFileSystemMountName => // "@Gc" + new[] { (byte)'@', (byte)'G', (byte)'c' }; - public static ReadOnlySpan GameCardFileSystemMountNameUpdateSuffix => // "U" - new[] { (byte)'U' }; + public static ReadOnlySpan GameCardFileSystemMountNameUpdateSuffix => // "U" + new[] { (byte)'U' }; - public static ReadOnlySpan GameCardFileSystemMountNameNormalSuffix => // "N" - new[] { (byte)'N' }; + public static ReadOnlySpan GameCardFileSystemMountNameNormalSuffix => // "N" + new[] { (byte)'N' }; - public static ReadOnlySpan GameCardFileSystemMountNameSecureSuffix => // "S" - new[] { (byte)'S' }; + public static ReadOnlySpan GameCardFileSystemMountNameSecureSuffix => // "S" + new[] { (byte)'S' }; - // Built-in storage names. - public static ReadOnlySpan BisCalibrationFilePartitionMountName => // "@CalibFile" - new[] - { - (byte)'@', (byte)'C', (byte)'a', (byte)'l', (byte)'i', (byte)'b', (byte)'F', (byte)'i', - (byte)'l', (byte)'e' - }; + // Built-in storage names. + public static ReadOnlySpan BisCalibrationFilePartitionMountName => // "@CalibFile" + new[] + { + (byte)'@', (byte)'C', (byte)'a', (byte)'l', (byte)'i', (byte)'b', (byte)'F', (byte)'i', + (byte)'l', (byte)'e' + }; - public static ReadOnlySpan BisSafeModePartitionMountName => // "@Safe" - new[] { (byte)'@', (byte)'S', (byte)'a', (byte)'f', (byte)'e' }; + public static ReadOnlySpan BisSafeModePartitionMountName => // "@Safe" + new[] { (byte)'@', (byte)'S', (byte)'a', (byte)'f', (byte)'e' }; - public static ReadOnlySpan BisUserPartitionMountName => // "@User" - new[] { (byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r' }; + public static ReadOnlySpan BisUserPartitionMountName => // "@User" + new[] { (byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r' }; - public static ReadOnlySpan BisSystemPartitionMountName => // "@System" - new[] { (byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' }; + public static ReadOnlySpan BisSystemPartitionMountName => // "@System" + new[] { (byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' }; - //Content storage names. - public static ReadOnlySpan ContentStorageSystemMountName => // "@SystemContent" - new[] - { - (byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m', (byte)'C', - (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t' - }; + //Content storage names. + public static ReadOnlySpan ContentStorageSystemMountName => // "@SystemContent" + new[] + { + (byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m', (byte)'C', + (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t' + }; - public static ReadOnlySpan ContentStorageUserMountName => // "@UserContent" - new[] - { - (byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r', (byte)'C', (byte)'o', (byte)'n', - (byte)'t', (byte)'e', (byte)'n', (byte)'t' - }; + public static ReadOnlySpan ContentStorageUserMountName => // "@UserContent" + new[] + { + (byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r', (byte)'C', (byte)'o', (byte)'n', + (byte)'t', (byte)'e', (byte)'n', (byte)'t' + }; - public static ReadOnlySpan ContentStorageSdCardMountName => // "@SdCardContent" - new[] - { - (byte)'@', (byte)'S', (byte)'d', (byte)'C', (byte)'a', (byte)'r', (byte)'d', (byte)'C', - (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t' - }; + public static ReadOnlySpan ContentStorageSdCardMountName => // "@SdCardContent" + new[] + { + (byte)'@', (byte)'S', (byte)'d', (byte)'C', (byte)'a', (byte)'r', (byte)'d', (byte)'C', + (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t' + }; - // Registered update partition - public static ReadOnlySpan RegisteredUpdatePartitionMountName => // "@RegUpdate" - new[] - { - (byte)'@', (byte)'R', (byte)'e', (byte)'g', (byte)'U', (byte)'p', (byte)'d', (byte)'a', - (byte)'t', (byte)'e' - }; + // Registered update partition + public static ReadOnlySpan RegisteredUpdatePartitionMountName => // "@RegUpdate" + new[] + { + (byte)'@', (byte)'R', (byte)'e', (byte)'g', (byte)'U', (byte)'p', (byte)'d', (byte)'a', + (byte)'t', (byte)'e' + }; - public static ReadOnlySpan SdCardNintendoRootDirectoryName => // "Nintendo" - new[] - { - (byte)'N', (byte)'i', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'d', (byte)'o' - }; - } + public static ReadOnlySpan SdCardNintendoRootDirectoryName => // "Nintendo" + new[] + { + (byte)'N', (byte)'i', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'d', (byte)'o' + }; } diff --git a/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs b/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs index 0d1e2076..7c715c2b 100644 --- a/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs +++ b/src/LibHac/Fs/Impl/FileDataCacheAccessor.cs @@ -1,26 +1,25 @@ using System; using LibHac.Fs.Fsa; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal class FileDataCacheAccessor { - internal class FileDataCacheAccessor + private IFileDataCache _cache; + + public FileDataCacheAccessor(IFileDataCache cache) { - private IFileDataCache _cache; + _cache = cache; + } - public FileDataCacheAccessor(IFileDataCache cache) - { - _cache = cache; - } + public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, + ref FileDataCacheAccessResult cacheAccessResult) + { + return _cache.Read(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); + } - public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, - ref FileDataCacheAccessResult cacheAccessResult) - { - return _cache.Read(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); - } - - public void Purge(IFileSystem fileSystem) - { - _cache.Purge(fileSystem); - } + public void Purge(IFileSystem fileSystem) + { + _cache.Purge(fileSystem); } } diff --git a/src/LibHac/Fs/Impl/FilePathHash.cs b/src/LibHac/Fs/Impl/FilePathHash.cs index 65b1db8d..3f1531f8 100644 --- a/src/LibHac/Fs/Impl/FilePathHash.cs +++ b/src/LibHac/Fs/Impl/FilePathHash.cs @@ -1,7 +1,6 @@ -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +public struct FilePathHash { - public struct FilePathHash - { - public int Data; - } + public int Data; } diff --git a/src/LibHac/Fs/Impl/FileRegion.cs b/src/LibHac/Fs/Impl/FileRegion.cs index 0d062c47..2a3b8872 100644 --- a/src/LibHac/Fs/Impl/FileRegion.cs +++ b/src/LibHac/Fs/Impl/FileRegion.cs @@ -2,92 +2,91 @@ using LibHac.Diag; using LibHac.Util; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +public readonly struct FileRegion { - public readonly struct FileRegion + public readonly long Offset; + public readonly long Size; + + public FileRegion(long offset, long size) { - public readonly long Offset; - public readonly long Size; + Offset = offset; + Size = size; - public FileRegion(long offset, long size) - { - Offset = offset; - Size = size; + Abort.DoAbortUnless(size >= 0); + } - Abort.DoAbortUnless(size >= 0); - } + public long GetEndOffset() + { + return Offset + Size; + } - public long GetEndOffset() - { - return Offset + Size; - } + public bool Includes(FileRegion other) + { + return Offset <= other.Offset && other.GetEndOffset() <= GetEndOffset(); + } - public bool Includes(FileRegion other) - { - return Offset <= other.Offset && other.GetEndOffset() <= GetEndOffset(); - } + public bool Intersects(FileRegion other) + { + return HasIntersection(this, other); + } - public bool Intersects(FileRegion other) - { - return HasIntersection(this, other); - } + public FileRegion GetIntersection(FileRegion other) + { + return GetIntersection(this, other); + } - public FileRegion GetIntersection(FileRegion other) - { - return GetIntersection(this, other); - } + public FileRegion ExpandAndAlign(uint alignment) + { + long alignedStartOffset = Alignment.AlignDownPow2(Offset, alignment); + long alignedEndOffset = Alignment.AlignUpPow2(GetEndOffset(), alignment); + long alignedSize = alignedEndOffset - alignedStartOffset; - public FileRegion ExpandAndAlign(uint alignment) - { - long alignedStartOffset = Alignment.AlignDownPow2(Offset, alignment); - long alignedEndOffset = Alignment.AlignUpPow2(GetEndOffset(), alignment); - long alignedSize = alignedEndOffset - alignedStartOffset; + return new FileRegion(alignedStartOffset, alignedSize); + } - return new FileRegion(alignedStartOffset, alignedSize); - } + public FileRegion ShrinkAndAlign(uint alignment) + { + long alignedStartOffset = Alignment.AlignUpPow2(Offset, alignment); + long alignedEndOffset = Alignment.AlignDownPow2(GetEndOffset(), alignment); + long alignedSize = alignedEndOffset - alignedStartOffset; - public FileRegion ShrinkAndAlign(uint alignment) - { - long alignedStartOffset = Alignment.AlignUpPow2(Offset, alignment); - long alignedEndOffset = Alignment.AlignDownPow2(GetEndOffset(), alignment); - long alignedSize = alignedEndOffset - alignedStartOffset; + return new FileRegion(alignedStartOffset, alignedSize); + } - return new FileRegion(alignedStartOffset, alignedSize); - } + public FileRegion GetEndRegionWithSizeLimit(long size) + { + if (size >= Size) + return this; - public FileRegion GetEndRegionWithSizeLimit(long size) - { - if (size >= Size) - return this; + return new FileRegion(GetEndOffset() - size, size); + } - return new FileRegion(GetEndOffset() - size, size); - } + public static bool HasIntersection(FileRegion region1, FileRegion region2) + { + return region1.GetEndOffset() >= region2.Offset && + region2.GetEndOffset() >= region1.Offset; + } - public static bool HasIntersection(FileRegion region1, FileRegion region2) - { - return region1.GetEndOffset() >= region2.Offset && - region2.GetEndOffset() >= region1.Offset; - } + public static FileRegion GetIntersection(FileRegion region1, FileRegion region2) + { + if (!region1.Intersects(region2)) + return new FileRegion(); - public static FileRegion GetIntersection(FileRegion region1, FileRegion region2) - { - if (!region1.Intersects(region2)) - return new FileRegion(); + long intersectionStartOffset = Math.Max(region1.Offset, region2.Offset); + long intersectionEndOffset = Math.Min(region1.GetEndOffset(), region2.GetEndOffset()); + long intersectionSize = intersectionEndOffset - intersectionStartOffset; - long intersectionStartOffset = Math.Max(region1.Offset, region2.Offset); - long intersectionEndOffset = Math.Min(region1.GetEndOffset(), region2.GetEndOffset()); - long intersectionSize = intersectionEndOffset - intersectionStartOffset; + return new FileRegion(intersectionStartOffset, intersectionSize); + } - return new FileRegion(intersectionStartOffset, intersectionSize); - } + public static FileRegion GetInclusion(FileRegion region1, FileRegion region2) + { + long inclusionStartOffset = Math.Min(region1.Offset, region2.Offset); + long inclusionEndOffset = Math.Max(region1.GetEndOffset(), region2.GetEndOffset()); + long inclusionSize = inclusionEndOffset - inclusionStartOffset; - public static FileRegion GetInclusion(FileRegion region1, FileRegion region2) - { - long inclusionStartOffset = Math.Min(region1.Offset, region2.Offset); - long inclusionEndOffset = Math.Max(region1.GetEndOffset(), region2.GetEndOffset()); - long inclusionSize = inclusionEndOffset - inclusionStartOffset; - - return new FileRegion(inclusionStartOffset, inclusionSize); - } + return new FileRegion(inclusionStartOffset, inclusionSize); } } diff --git a/src/LibHac/Fs/Impl/IFileDataCache.cs b/src/LibHac/Fs/Impl/IFileDataCache.cs index 1b25b9eb..c2051423 100644 --- a/src/LibHac/Fs/Impl/IFileDataCache.cs +++ b/src/LibHac/Fs/Impl/IFileDataCache.cs @@ -4,89 +4,88 @@ using LibHac.Common.FixedArrays; using LibHac.Diag; using LibHac.Fs.Fsa; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +// ReSharper disable once InconsistentNaming +internal abstract class IFileDataCache : IDisposable { - // ReSharper disable once InconsistentNaming - internal abstract class IFileDataCache : IDisposable + public abstract void Dispose(); + + public abstract void Purge(IFileSystem fileSystem); + + protected abstract Result DoRead(IFile file, out long bytesRead, long offset, Span destination, + in ReadOption option, ref FileDataCacheAccessResult cacheAccessResult); + + public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, + ref FileDataCacheAccessResult cacheAccessResult) { - public abstract void Dispose(); + UnsafeHelpers.SkipParamInit(out bytesRead); - public abstract void Purge(IFileSystem fileSystem); - - protected abstract Result DoRead(IFile file, out long bytesRead, long offset, Span destination, - in ReadOption option, ref FileDataCacheAccessResult cacheAccessResult); - - public Result Read(IFile file, out long bytesRead, long offset, Span destination, in ReadOption option, - ref FileDataCacheAccessResult cacheAccessResult) + if (destination.Length == 0) { - UnsafeHelpers.SkipParamInit(out bytesRead); - - if (destination.Length == 0) - { - bytesRead = 0; - cacheAccessResult.SetFileDataCacheUsed(true); - return Result.Success; - } - - if (offset < 0) - return ResultFs.OutOfRange.Log(); - - if (destination.Length < 0) - return ResultFs.OutOfRange.Log(); - - if (long.MaxValue - offset < destination.Length) - return ResultFs.OutOfRange.Log(); - - return DoRead(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); + bytesRead = 0; + cacheAccessResult.SetFileDataCacheUsed(true); + return Result.Success; } + + if (offset < 0) + return ResultFs.OutOfRange.Log(); + + if (destination.Length < 0) + return ResultFs.OutOfRange.Log(); + + if (long.MaxValue - offset < destination.Length) + return ResultFs.OutOfRange.Log(); + + return DoRead(file, out bytesRead, offset, destination, in option, ref cacheAccessResult); + } +} + +internal struct FileDataCacheAccessResult +{ + private const int MaxRegionCount = 8; + + private int _regionCount; + private Array8 _regions; + private bool _isFileDataCacheUsed; + private bool _exceededMaxRegionCount; + + public bool IsFileDataCacheUsed() => _isFileDataCacheUsed; + public bool SetFileDataCacheUsed(bool useFileDataCache) => _isFileDataCacheUsed = useFileDataCache; + + public int GetCacheFetchedRegionCount() + { + Assert.SdkRequires(_isFileDataCacheUsed); + return _regionCount; } - internal struct FileDataCacheAccessResult + public bool ExceededMaxCacheFetchedRegionCount() => _exceededMaxRegionCount; + + public FileRegion GetCacheFetchedRegion(int index) { - private const int MaxRegionCount = 8; + Assert.SdkRequires(IsFileDataCacheUsed()); + Assert.SdkRequiresLessEqual(0, index); + Assert.SdkRequiresLess(index, _regionCount); - private int _regionCount; - private Array8 _regions; - private bool _isFileDataCacheUsed; - private bool _exceededMaxRegionCount; + return _regions[index]; + } - public bool IsFileDataCacheUsed() => _isFileDataCacheUsed; - public bool SetFileDataCacheUsed(bool useFileDataCache) => _isFileDataCacheUsed = useFileDataCache; + public void AddCacheFetchedRegion(FileRegion region) + { + _isFileDataCacheUsed = true; - public int GetCacheFetchedRegionCount() + if (region.Size == 0) + return; + + if (_regionCount >= MaxRegionCount) { - Assert.SdkRequires(_isFileDataCacheUsed); - return _regionCount; + _regions[MaxRegionCount - 1] = region; + _exceededMaxRegionCount = true; } - - public bool ExceededMaxCacheFetchedRegionCount() => _exceededMaxRegionCount; - - public FileRegion GetCacheFetchedRegion(int index) + else { - Assert.SdkRequires(IsFileDataCacheUsed()); - Assert.SdkRequiresLessEqual(0, index); - Assert.SdkRequiresLess(index, _regionCount); - - return _regions[index]; - } - - public void AddCacheFetchedRegion(FileRegion region) - { - _isFileDataCacheUsed = true; - - if (region.Size == 0) - return; - - if (_regionCount >= MaxRegionCount) - { - _regions[MaxRegionCount - 1] = region; - _exceededMaxRegionCount = true; - } - else - { - _regions[_regionCount] = region; - _regionCount++; - } + _regions[_regionCount] = region; + _regionCount++; } } } diff --git a/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs b/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs index 5911caf2..2faa88c6 100644 --- a/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs +++ b/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs @@ -1,53 +1,52 @@ using System; using System.Threading; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +/// +/// A wrapper for handling write access to a reader-writer lock. +/// +public class UniqueLock : IDisposable { - /// - /// A wrapper for handling write access to a reader-writer lock. - /// - public class UniqueLock : IDisposable + private ReaderWriterLockSlim _lock; + private bool _hasLock; + + public UniqueLock(ReaderWriterLockSlim readerWriterLock) { - private ReaderWriterLockSlim _lock; - private bool _hasLock; - - public UniqueLock(ReaderWriterLockSlim readerWriterLock) - { - _lock = readerWriterLock; - readerWriterLock.EnterWriteLock(); - _hasLock = true; - } - - public void Dispose() - { - if (_hasLock) - { - _lock.ExitWriteLock(); - } - } + _lock = readerWriterLock; + readerWriterLock.EnterWriteLock(); + _hasLock = true; } - /// - /// A wrapper for handling read access to a reader-writer lock. - /// - public class SharedLock : IDisposable + public void Dispose() { - private ReaderWriterLockSlim _lock; - private bool _hasLock; - - public SharedLock(ReaderWriterLockSlim readerWriterLock) + if (_hasLock) { - _lock = readerWriterLock; - readerWriterLock.EnterReadLock(); - _hasLock = true; - } - - public void Dispose() - { - if (_hasLock) - { - _lock.EnterReadLock(); - } + _lock.ExitWriteLock(); + } + } +} + +/// +/// A wrapper for handling read access to a reader-writer lock. +/// +public class SharedLock : IDisposable +{ + private ReaderWriterLockSlim _lock; + private bool _hasLock; + + public SharedLock(ReaderWriterLockSlim readerWriterLock) + { + _lock = readerWriterLock; + readerWriterLock.EnterReadLock(); + _hasLock = true; + } + + public void Dispose() + { + if (_hasLock) + { + _lock.EnterReadLock(); } } } diff --git a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs index 28cf0fee..09433d0d 100644 --- a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs +++ b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs @@ -1,43 +1,42 @@ using LibHac.Common; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal readonly struct SaveDataMetaPolicy { - internal readonly struct SaveDataMetaPolicy + private readonly SaveDataType _type; + private const int ThumbnailFileSize = 0x40060; + + public SaveDataMetaPolicy(SaveDataType saveType) { - private readonly SaveDataType _type; - private const int ThumbnailFileSize = 0x40060; + _type = saveType; + } - public SaveDataMetaPolicy(SaveDataType saveType) + public void GenerateMetaInfo(out SaveDataMetaInfo metaInfo) + { + UnsafeHelpers.SkipParamInit(out metaInfo); + + if (_type == SaveDataType.Account || _type == SaveDataType.Device) { - _type = saveType; + metaInfo.Type = SaveDataMetaType.Thumbnail; + metaInfo.Size = ThumbnailFileSize; } - - public void GenerateMetaInfo(out SaveDataMetaInfo metaInfo) + else { - UnsafeHelpers.SkipParamInit(out metaInfo); - - if (_type == SaveDataType.Account || _type == SaveDataType.Device) - { - metaInfo.Type = SaveDataMetaType.Thumbnail; - metaInfo.Size = ThumbnailFileSize; - } - else - { - metaInfo.Type = SaveDataMetaType.None; - metaInfo.Size = 0; - } - } - - public long GetSaveDataMetaSize() - { - GenerateMetaInfo(out SaveDataMetaInfo metaInfo); - return metaInfo.Size; - } - - public SaveDataMetaType GetSaveDataMetaType() - { - GenerateMetaInfo(out SaveDataMetaInfo metaInfo); - return metaInfo.Type; + metaInfo.Type = SaveDataMetaType.None; + metaInfo.Size = 0; } } + + public long GetSaveDataMetaSize() + { + GenerateMetaInfo(out SaveDataMetaInfo metaInfo); + return metaInfo.Size; + } + + public SaveDataMetaType GetSaveDataMetaType() + { + GenerateMetaInfo(out SaveDataMetaInfo metaInfo); + return metaInfo.Type; + } } diff --git a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs index ca6832f3..5c239659 100644 --- a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs @@ -2,53 +2,52 @@ using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +[StructLayout(LayoutKind.Sequential, Size = 0x20)] +public struct InitialDataAad { - [StructLayout(LayoutKind.Sequential, Size = 0x20)] - public struct InitialDataAad + public byte this[int i] { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); + readonly get => BytesRo[i]; + set => Bytes[i] = value; } - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct KeySeed - { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - } - - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct InitialDataMac - { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - } - - [StructLayout(LayoutKind.Sequential, Size = 0x20)] - public struct ImportReportInfo - { - public byte DiffChunkCount; - public byte DoubleDivisionDiffChunkCount; - public byte HalfDivisionDiffChunkCount; - public byte CompressionRate; - } + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); +} + +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct KeySeed +{ + public byte this[int i] + { + readonly get => BytesRo[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); +} + +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct InitialDataMac +{ + public byte this[int i] + { + readonly get => BytesRo[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); +} + +[StructLayout(LayoutKind.Sequential, Size = 0x20)] +public struct ImportReportInfo +{ + public byte DiffChunkCount; + public byte DoubleDivisionDiffChunkCount; + public byte HalfDivisionDiffChunkCount; + public byte CompressionRate; } diff --git a/src/LibHac/Fs/Impl/SdHandleManager.cs b/src/LibHac/Fs/Impl/SdHandleManager.cs index 35a9d76d..b6de7090 100644 --- a/src/LibHac/Fs/Impl/SdHandleManager.cs +++ b/src/LibHac/Fs/Impl/SdHandleManager.cs @@ -1,34 +1,33 @@ using LibHac.FsSrv; using LibHac.FsSrv.Storage; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +internal class SdHandleManager : IDeviceHandleManager { - internal class SdHandleManager : IDeviceHandleManager + public Result GetHandle(out StorageDeviceHandle handle) { - public Result GetHandle(out StorageDeviceHandle handle) - { - return GetCurrentSdCardHandle(out handle); - } + return GetCurrentSdCardHandle(out handle); + } - public bool IsValid(in StorageDeviceHandle handle) - { - // Note: Nintendo ignores the result here. - IsSdCardHandleValid(out bool isValid, in handle).IgnoreResult(); - return isValid; - } + public bool IsValid(in StorageDeviceHandle handle) + { + // Note: Nintendo ignores the result here. + IsSdCardHandleValid(out bool isValid, in handle).IgnoreResult(); + return isValid; + } - // Todo: Use FsSrv.Storage - private static Result GetCurrentSdCardHandle(out StorageDeviceHandle handle) - { - handle = new StorageDeviceHandle(1, StorageDevicePortId.SdCard); - return Result.Success; - } + // Todo: Use FsSrv.Storage + private static Result GetCurrentSdCardHandle(out StorageDeviceHandle handle) + { + handle = new StorageDeviceHandle(1, StorageDevicePortId.SdCard); + return Result.Success; + } - private static Result IsSdCardHandleValid(out bool isValid, in StorageDeviceHandle handle) - { - isValid = handle.PortId == StorageDevicePortId.SdCard; + private static Result IsSdCardHandleValid(out bool isValid, in StorageDeviceHandle handle) + { + isValid = handle.PortId == StorageDevicePortId.SdCard; - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs index 353266e4..7cb138d4 100644 --- a/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs +++ b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs @@ -4,70 +4,69 @@ using LibHac.Common; using LibHac.Sf; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +/// +/// An adapter for using an service object as an . Used +/// when receiving a Horizon IPC storage object so it can be used as an locally. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class StorageServiceObjectAdapter : IStorage { - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC storage object so it can be used as an locally. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class StorageServiceObjectAdapter : IStorage + private SharedRef _baseStorage; + + public StorageServiceObjectAdapter(ref SharedRef baseStorage) { - private SharedRef _baseStorage; + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } - public StorageServiceObjectAdapter(ref SharedRef baseStorage) + protected override Result DoRead(long offset, Span destination) + { + return _baseStorage.Get.Read(offset, new OutBuffer(destination), destination.Length); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return _baseStorage.Get.Write(offset, new InBuffer(source), source.Length); + } + + protected override Result DoFlush() + { + return _baseStorage.Get.Flush(); + } + + protected override Result DoSetSize(long size) + { + return _baseStorage.Get.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + return _baseStorage.Get.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) { - _baseStorage = SharedRef.CreateMove(ref baseStorage); - } + case OperationId.InvalidateCache: + return _baseStorage.Get.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); - protected override Result DoRead(long offset, Span destination) - { - return _baseStorage.Get.Read(offset, new OutBuffer(destination), destination.Length); - } + ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return _baseStorage.Get.Write(offset, new InBuffer(source), source.Length); - } - - protected override Result DoFlush() - { - return _baseStorage.Get.Flush(); - } - - protected override Result DoSetSize(long size) - { - return _baseStorage.Get.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - return _baseStorage.Get.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - switch (operationId) - { - case OperationId.InvalidateCache: - return _baseStorage.Get.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); - case OperationId.QueryRange: - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); - - ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); - - return _baseStorage.Get.OperateRange(out info, (int)OperationId.QueryRange, offset, size); - default: - return ResultFs.UnsupportedOperateRangeForStorageServiceObjectAdapter.Log(); - } - } - - public override void Dispose() - { - _baseStorage.Destroy(); - base.Dispose(); + return _baseStorage.Get.OperateRange(out info, (int)OperationId.QueryRange, offset, size); + default: + return ResultFs.UnsupportedOperateRangeForStorageServiceObjectAdapter.Log(); } } -} \ No newline at end of file + + public override void Dispose() + { + _baseStorage.Destroy(); + base.Dispose(); + } +} diff --git a/src/LibHac/Fs/InMemoryFileSystem.cs b/src/LibHac/Fs/InMemoryFileSystem.cs index 54693e20..cf635388 100644 --- a/src/LibHac/Fs/InMemoryFileSystem.cs +++ b/src/LibHac/Fs/InMemoryFileSystem.cs @@ -6,792 +6,791 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.Util; -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// A filesystem stored in-memory. Mainly used for testing. +/// +public class InMemoryFileSystem : IAttributeFileSystem { - /// - /// A filesystem stored in-memory. Mainly used for testing. - /// - public class InMemoryFileSystem : IAttributeFileSystem + private FileTable FsTable { get; } + + public InMemoryFileSystem() { - private FileTable FsTable { get; } + FsTable = new FileTable(); + } - public InMemoryFileSystem() + protected override Result DoCreateDirectory(in Path path) + { + return FsTable.AddDirectory(new U8Span(path.GetString())); + } + + protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute) + { + Result rc = FsTable.AddDirectory(new U8Span(path.GetString())); + if (rc.IsFailure()) return rc; + + rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir); + if (rc.IsFailure()) return rc; + + dir.Attributes = archiveAttribute; + return Result.Success; + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + Result rc = FsTable.AddFile(new U8Span(path.GetString())); + if (rc.IsFailure()) return rc; + + rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode file); + if (rc.IsFailure()) return rc; + + return file.File.SetSize(size); + } + + protected override Result DoDeleteDirectory(in Path path) + { + return FsTable.DeleteDirectory(new U8Span(path.GetString()), false); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + return FsTable.DeleteDirectory(new U8Span(path.GetString()), true); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + return FsTable.CleanDirectory(new U8Span(path.GetString())); + } + + protected override Result DoDeleteFile(in Path path) + { + return FsTable.DeleteFile(new U8Span(path.GetString())); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + Result rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dirNode); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(new MemoryDirectory(dirNode, mode)); + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + Result rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode fileNode); + if (rc.IsFailure()) return rc; + + outFile.Reset(new MemoryFile(mode, fileNode.File)); + + return Result.Success; + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + return FsTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + return FsTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + if (FsTable.GetFile(new U8Span(path.GetString()), out _).IsSuccess()) { - FsTable = new FileTable(); - } - - protected override Result DoCreateDirectory(in Path path) - { - return FsTable.AddDirectory(new U8Span(path.GetString())); - } - - protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute) - { - Result rc = FsTable.AddDirectory(new U8Span(path.GetString())); - if (rc.IsFailure()) return rc; - - rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir); - if (rc.IsFailure()) return rc; - - dir.Attributes = archiveAttribute; + entryType = DirectoryEntryType.File; return Result.Success; } - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + if (FsTable.GetDirectory(new U8Span(path.GetString()), out _).IsSuccess()) { - Result rc = FsTable.AddFile(new U8Span(path.GetString())); - if (rc.IsFailure()) return rc; - - rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode file); - if (rc.IsFailure()) return rc; - - return file.File.SetSize(size); - } - - protected override Result DoDeleteDirectory(in Path path) - { - return FsTable.DeleteDirectory(new U8Span(path.GetString()), false); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - return FsTable.DeleteDirectory(new U8Span(path.GetString()), true); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - return FsTable.CleanDirectory(new U8Span(path.GetString())); - } - - protected override Result DoDeleteFile(in Path path) - { - return FsTable.DeleteFile(new U8Span(path.GetString())); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - Result rc = FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dirNode); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new MemoryDirectory(dirNode, mode)); + entryType = DirectoryEntryType.Directory; return Result.Success; } - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - Result rc = FsTable.GetFile(new U8Span(path.GetString()), out FileNode fileNode); - if (rc.IsFailure()) return rc; + return ResultFs.PathNotFound.Log(); + } - outFile.Reset(new MemoryFile(mode, fileNode.File)); + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path) + { + UnsafeHelpers.SkipParamInit(out attributes); + + if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) + { + attributes = file.Attributes; + return Result.Success; + } + + if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess()) + { + attributes = dir.Attributes; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes) + { + if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) + { + file.Attributes = attributes; + return Result.Success; + } + + if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess()) + { + dir.Attributes = attributes; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoGetFileSize(out long fileSize, in Path path) + { + UnsafeHelpers.SkipParamInit(out fileSize); + + if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) + { + return file.File.GetSize(out fileSize); + } + + return ResultFs.PathNotFound.Log(); + } + + // todo: Make a more generic MemoryFile-type class + private class MemoryFile : IFile + { + private OpenMode Mode { get; } + private MemoryStreamAccessor BaseStream { get; } + + public MemoryFile(OpenMode mode, MemoryStreamAccessor buffer) + { + BaseStream = buffer; + Mode = mode; + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + if (!Mode.HasFlag(OpenMode.Read)) + { + bytesRead = 0; + return ResultFs.ReadUnpermitted.Log(); + } + + return BaseStream.Read(out bytesRead, offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + if (!Mode.HasFlag(OpenMode.Write)) + { + return ResultFs.WriteUnpermitted.Log(); + } + + return BaseStream.Write(offset, source, Mode.HasFlag(OpenMode.AllowAppend)); + } + + protected override Result DoFlush() + { + return BaseStream.Flush(); + } + + protected override Result DoSetSize(long size) + { + return BaseStream.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + return BaseStream.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + throw new NotImplementedException(); + } + } + + private class MemoryDirectory : IDirectory + { + private OpenDirectoryMode Mode { get; } + private DirectoryNode Directory { get; } + private DirectoryNode CurrentDir { get; set; } + private FileNode CurrentFile { get; set; } + + public MemoryDirectory(DirectoryNode directory, OpenDirectoryMode mode) + { + Mode = mode; + Directory = directory; + CurrentDir = directory.ChildDirectory; + CurrentFile = directory.ChildFile; + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + int i = 0; + + if (Mode.HasFlag(OpenDirectoryMode.Directory)) + { + while (CurrentDir != null && i < entryBuffer.Length) + { + ref DirectoryEntry entry = ref entryBuffer[i]; + + StringUtils.Copy(entry.Name, CurrentDir.Name); + entry.Name[PathTools.MaxPathLength] = 0; + + entry.Type = DirectoryEntryType.Directory; + entry.Attributes = CurrentDir.Attributes; + entry.Size = 0; + + i++; + CurrentDir = CurrentDir.Next; + } + } + + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + while (CurrentFile != null && i < entryBuffer.Length) + { + ref DirectoryEntry entry = ref entryBuffer[i]; + + StringUtils.Copy(entry.Name, CurrentFile.Name); + entry.Name[PathTools.MaxPathLength] = 0; + + entry.Type = DirectoryEntryType.File; + entry.Attributes = CurrentFile.Attributes; + + Result rc = CurrentFile.File.GetSize(out entry.Size); + if (rc.IsFailure()) + { + entriesRead = 0; + return rc; + } + + i++; + CurrentFile = CurrentFile.Next; + } + } + + entriesRead = i; return Result.Success; } - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + protected override Result DoGetEntryCount(out long entryCount) { - return FsTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); - } + long count = 0; - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - return FsTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - if (FsTable.GetFile(new U8Span(path.GetString()), out _).IsSuccess()) + if (Mode.HasFlag(OpenDirectoryMode.Directory)) { - entryType = DirectoryEntryType.File; - return Result.Success; + DirectoryNode current = Directory.ChildDirectory; + + while (current != null) + { + count++; + current = current.Next; + } } - if (FsTable.GetDirectory(new U8Span(path.GetString()), out _).IsSuccess()) + if (Mode.HasFlag(OpenDirectoryMode.File)) { - entryType = DirectoryEntryType.Directory; - return Result.Success; + FileNode current = Directory.ChildFile; + + while (current != null) + { + count++; + current = current.Next; + } } - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoCommit() - { + entryCount = count; return Result.Success; } + } - protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path) + // todo: Replace with a class that uses multiple byte arrays as backing memory + // so resizing doesn't involve copies + /// + /// Provides exclusive access to a object. + /// Used by to enable opening a file multiple times with differing permissions. + /// + private class MemoryStreamAccessor + { + private const int MemStreamMaxLength = int.MaxValue; + + private MemoryStream BaseStream { get; } + private object Locker { get; } = new object(); + + public MemoryStreamAccessor(MemoryStream stream) { - UnsafeHelpers.SkipParamInit(out attributes); - - if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) - { - attributes = file.Attributes; - return Result.Success; - } - - if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess()) - { - attributes = dir.Attributes; - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); + BaseStream = stream; } - protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes) + public Result Read(out long bytesRead, long offset, Span destination) { - if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) + lock (Locker) { - file.Attributes = attributes; - return Result.Success; - } - - if (FsTable.GetDirectory(new U8Span(path.GetString()), out DirectoryNode dir).IsSuccess()) - { - dir.Attributes = attributes; - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoGetFileSize(out long fileSize, in Path path) - { - UnsafeHelpers.SkipParamInit(out fileSize); - - if (FsTable.GetFile(new U8Span(path.GetString()), out FileNode file).IsSuccess()) - { - return file.File.GetSize(out fileSize); - } - - return ResultFs.PathNotFound.Log(); - } - - // todo: Make a more generic MemoryFile-type class - private class MemoryFile : IFile - { - private OpenMode Mode { get; } - private MemoryStreamAccessor BaseStream { get; } - - public MemoryFile(OpenMode mode, MemoryStreamAccessor buffer) - { - BaseStream = buffer; - Mode = mode; - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - if (!Mode.HasFlag(OpenMode.Read)) + if (offset > BaseStream.Length) { bytesRead = 0; - return ResultFs.ReadUnpermitted.Log(); + return ResultFs.OutOfRange.Log(); } - return BaseStream.Read(out bytesRead, offset, destination); - } + BaseStream.Position = offset; - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - if (!Mode.HasFlag(OpenMode.Write)) - { - return ResultFs.WriteUnpermitted.Log(); - } - - return BaseStream.Write(offset, source, Mode.HasFlag(OpenMode.AllowAppend)); - } - - protected override Result DoFlush() - { - return BaseStream.Flush(); - } - - protected override Result DoSetSize(long size) - { - return BaseStream.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - return BaseStream.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - throw new NotImplementedException(); - } - } - - private class MemoryDirectory : IDirectory - { - private OpenDirectoryMode Mode { get; } - private DirectoryNode Directory { get; } - private DirectoryNode CurrentDir { get; set; } - private FileNode CurrentFile { get; set; } - - public MemoryDirectory(DirectoryNode directory, OpenDirectoryMode mode) - { - Mode = mode; - Directory = directory; - CurrentDir = directory.ChildDirectory; - CurrentFile = directory.ChildFile; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - int i = 0; - - if (Mode.HasFlag(OpenDirectoryMode.Directory)) - { - while (CurrentDir != null && i < entryBuffer.Length) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - - StringUtils.Copy(entry.Name, CurrentDir.Name); - entry.Name[PathTools.MaxPathLength] = 0; - - entry.Type = DirectoryEntryType.Directory; - entry.Attributes = CurrentDir.Attributes; - entry.Size = 0; - - i++; - CurrentDir = CurrentDir.Next; - } - } - - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - while (CurrentFile != null && i < entryBuffer.Length) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - - StringUtils.Copy(entry.Name, CurrentFile.Name); - entry.Name[PathTools.MaxPathLength] = 0; - - entry.Type = DirectoryEntryType.File; - entry.Attributes = CurrentFile.Attributes; - - Result rc = CurrentFile.File.GetSize(out entry.Size); - if (rc.IsFailure()) - { - entriesRead = 0; - return rc; - } - - i++; - CurrentFile = CurrentFile.Next; - } - } - - entriesRead = i; - - return Result.Success; - } - - protected override Result DoGetEntryCount(out long entryCount) - { - long count = 0; - - if (Mode.HasFlag(OpenDirectoryMode.Directory)) - { - DirectoryNode current = Directory.ChildDirectory; - - while (current != null) - { - count++; - current = current.Next; - } - } - - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - FileNode current = Directory.ChildFile; - - while (current != null) - { - count++; - current = current.Next; - } - } - - entryCount = count; + bytesRead = BaseStream.Read(destination); return Result.Success; } } - // todo: Replace with a class that uses multiple byte arrays as backing memory - // so resizing doesn't involve copies - /// - /// Provides exclusive access to a object. - /// Used by to enable opening a file multiple times with differing permissions. - /// - private class MemoryStreamAccessor + public Result Write(long offset, ReadOnlySpan source, bool allowAppend) { - private const int MemStreamMaxLength = int.MaxValue; - - private MemoryStream BaseStream { get; } - private object Locker { get; } = new object(); - - public MemoryStreamAccessor(MemoryStream stream) + lock (Locker) { - BaseStream = stream; - } - - public Result Read(out long bytesRead, long offset, Span destination) - { - lock (Locker) + if (offset + source.Length > BaseStream.Length) { - if (offset > BaseStream.Length) + if (!allowAppend) { - bytesRead = 0; + return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log(); + } + + if (offset + source.Length > MemStreamMaxLength) return ResultFs.OutOfRange.Log(); - } - - BaseStream.Position = offset; - - bytesRead = BaseStream.Read(destination); - return Result.Success; } - } - public Result Write(long offset, ReadOnlySpan source, bool allowAppend) - { - lock (Locker) - { - if (offset + source.Length > BaseStream.Length) - { - if (!allowAppend) - { - return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log(); - } + BaseStream.Position = offset; - if (offset + source.Length > MemStreamMaxLength) - return ResultFs.OutOfRange.Log(); - } - - BaseStream.Position = offset; - - BaseStream.Write(source); - return Result.Success; - } - } - - public Result Flush() - { - return Result.Success; - } - - public Result SetSize(long size) - { - lock (Locker) - { - if (size > MemStreamMaxLength) - return ResultFs.OutOfRange.Log(); - - BaseStream.SetLength(size); - - return Result.Success; - } - } - - public Result GetSize(out long size) - { - size = BaseStream.Length; + BaseStream.Write(source); return Result.Success; } } - private class FileNode + public Result Flush() { - public MemoryStreamAccessor File { get; set; } - public DirectoryNode Parent { get; set; } - public U8String Name { get; set; } - public NxFileAttributes Attributes { get; set; } - public FileNode Next { get; set; } + return Result.Success; } - private class DirectoryNode + public Result SetSize(long size) { - private NxFileAttributes _attributes = NxFileAttributes.Directory; - - public NxFileAttributes Attributes + lock (Locker) { - get => _attributes; - set => _attributes = value | NxFileAttributes.Directory; - } + if (size > MemStreamMaxLength) + return ResultFs.OutOfRange.Log(); - public DirectoryNode Parent { get; set; } - public U8String Name { get; set; } - public DirectoryNode Next { get; set; } - public DirectoryNode ChildDirectory { get; set; } - public FileNode ChildFile { get; set; } + BaseStream.SetLength(size); + + return Result.Success; + } } - private class FileTable + public Result GetSize(out long size) { - private DirectoryNode Root; + size = BaseStream.Length; + return Result.Success; + } + } - public FileTable() + private class FileNode + { + public MemoryStreamAccessor File { get; set; } + public DirectoryNode Parent { get; set; } + public U8String Name { get; set; } + public NxFileAttributes Attributes { get; set; } + public FileNode Next { get; set; } + } + + private class DirectoryNode + { + private NxFileAttributes _attributes = NxFileAttributes.Directory; + + public NxFileAttributes Attributes + { + get => _attributes; + set => _attributes = value | NxFileAttributes.Directory; + } + + public DirectoryNode Parent { get; set; } + public U8String Name { get; set; } + public DirectoryNode Next { get; set; } + public DirectoryNode ChildDirectory { get; set; } + public FileNode ChildFile { get; set; } + } + + private class FileTable + { + private DirectoryNode Root; + + public FileTable() + { + Root = new DirectoryNode(); + Root.Name = new U8String(""); + } + + public Result AddFile(U8Span path) + { + var parentPath = new U8Span(PathTools.GetParentDirectory(path)); + + Result rc = FindDirectory(parentPath, out DirectoryNode parent); + if (rc.IsFailure()) return rc; + var fileName = new U8Span(PathTools.GetLastSegment(path)); + + + return AddFile(fileName, parent); + } + + public Result AddDirectory(U8Span path) + { + var parentPath = new U8Span(PathTools.GetParentDirectory(path)); + + Result rc = FindDirectory(parentPath, out DirectoryNode parent); + if (rc.IsFailure()) return rc; + + var dirName = new U8Span(PathTools.GetLastSegment(path)); + + return AddDirectory(dirName, parent); + } + + public Result GetFile(U8Span path, out FileNode file) + { + return FindFile(path, out file); + } + + public Result GetDirectory(U8Span path, out DirectoryNode dir) + { + return FindDirectory(path, out dir); + } + + public Result RenameDirectory(U8Span oldPath, U8Span newPath) + { + Result rc = FindDirectory(oldPath, out DirectoryNode directory); + if (rc.IsFailure()) return rc; + + var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath)); + + rc = FindDirectory(newParentPath, out DirectoryNode newParent); + if (rc.IsFailure()) return rc; + + var newName = new U8Span(PathTools.GetLastSegment(newPath)); + + if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _)) { - Root = new DirectoryNode(); - Root.Name = new U8String(""); + return ResultFs.PathAlreadyExists.Log(); } - public Result AddFile(U8Span path) + if (directory.Parent != newParent) { - var parentPath = new U8Span(PathTools.GetParentDirectory(path)); - - Result rc = FindDirectory(parentPath, out DirectoryNode parent); - if (rc.IsFailure()) return rc; - var fileName = new U8Span(PathTools.GetLastSegment(path)); - - - return AddFile(fileName, parent); - } - - public Result AddDirectory(U8Span path) - { - var parentPath = new U8Span(PathTools.GetParentDirectory(path)); - - Result rc = FindDirectory(parentPath, out DirectoryNode parent); - if (rc.IsFailure()) return rc; - - var dirName = new U8Span(PathTools.GetLastSegment(path)); - - return AddDirectory(dirName, parent); - } - - public Result GetFile(U8Span path, out FileNode file) - { - return FindFile(path, out file); - } - - public Result GetDirectory(U8Span path, out DirectoryNode dir) - { - return FindDirectory(path, out dir); - } - - public Result RenameDirectory(U8Span oldPath, U8Span newPath) - { - Result rc = FindDirectory(oldPath, out DirectoryNode directory); - if (rc.IsFailure()) return rc; - - var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath)); - - rc = FindDirectory(newParentPath, out DirectoryNode newParent); - if (rc.IsFailure()) return rc; - - var newName = new U8Span(PathTools.GetLastSegment(newPath)); - - if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _)) + if (!UnlinkDirectory(directory)) { - return ResultFs.PathAlreadyExists.Log(); + return ResultFs.PreconditionViolation.Log(); } - if (directory.Parent != newParent) - { - if (!UnlinkDirectory(directory)) - { - return ResultFs.PreconditionViolation.Log(); - } + LinkDirectory(directory, newParent); + } - LinkDirectory(directory, newParent); + if (StringUtils.Compare(directory.Name, newName) != 0) + { + directory.Name = newName.ToU8String(); + } + + return Result.Success; + } + + public Result RenameFile(U8Span oldPath, U8Span newPath) + { + Result rc = FindFile(oldPath, out FileNode file); + if (rc.IsFailure()) return rc; + + var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath)); + + rc = FindDirectory(newParentPath, out DirectoryNode newParent); + if (rc.IsFailure()) return rc; + + var newName = new U8Span(PathTools.GetLastSegment(newPath)); + + if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (file.Parent != newParent) + { + if (!UnlinkFile(file)) + { + return ResultFs.PreconditionViolation.Log(); } - if (StringUtils.Compare(directory.Name, newName) != 0) - { - directory.Name = newName.ToU8String(); - } + LinkFile(file, newParent); + } + + if (StringUtils.Compare(file.Name, newName) != 0) + { + file.Name = newName.ToU8String(); + } + + return Result.Success; + } + + public Result DeleteDirectory(U8Span path, bool recursive) + { + Result rc = FindDirectory(path, out DirectoryNode directory); + if (rc.IsFailure()) return rc; + + if (!recursive && (directory.ChildDirectory != null || directory.ChildFile != null)) + { + return ResultFs.DirectoryNotEmpty.Log(); + } + + UnlinkDirectory(directory); + return Result.Success; + } + + public Result DeleteFile(U8Span path) + { + Result rc = FindFile(path, out FileNode file); + if (rc.IsFailure()) return rc; + + UnlinkFile(file); + return Result.Success; + } + + public Result CleanDirectory(U8Span path) + { + Result rc = FindDirectory(path, out DirectoryNode directory); + if (rc.IsFailure()) return rc; + + directory.ChildDirectory = null; + directory.ChildFile = null; + + return Result.Success; + } + + private Result AddFile(U8Span name, DirectoryNode parent) + { + if (TryFindChildDirectory(name, parent, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (TryFindChildFile(name, parent, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + var newFileNode = new FileNode + { + Name = name.ToU8String(), + File = new MemoryStreamAccessor(new MemoryStream()) + }; + + LinkFile(newFileNode, parent); + return Result.Success; + } + + private Result AddDirectory(U8Span name, DirectoryNode parent) + { + if (TryFindChildDirectory(name, parent, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (TryFindChildFile(name, parent, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + var newDirNode = new DirectoryNode { Name = name.ToU8String() }; + + LinkDirectory(newDirNode, parent); + return Result.Success; + } + + private Result FindFile(U8Span path, out FileNode file) + { + var parentPath = new U8Span(PathTools.GetParentDirectory(path)); + + Result rc = FindDirectory(parentPath, out DirectoryNode parentNode); + if (rc.IsFailure()) + { + UnsafeHelpers.SkipParamInit(out file); + return rc; + } + + var fileName = new U8Span(PathTools.GetLastSegment(path)); + + if (TryFindChildFile(fileName, parentNode, out file)) + { return Result.Success; } - public Result RenameFile(U8Span oldPath, U8Span newPath) + return ResultFs.PathNotFound.Log(); + } + + private Result FindDirectory(U8Span path, out DirectoryNode directory) + { + var parser = new PathParser(path); + DirectoryNode current = Root; + + while (parser.MoveNext()) { - Result rc = FindFile(oldPath, out FileNode file); - if (rc.IsFailure()) return rc; + var currentDir = new U8Span(parser.GetCurrent()); - var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath)); + // End if we've hit a trailing separator + if (currentDir.IsEmpty() && parser.IsFinished()) break; - rc = FindDirectory(newParentPath, out DirectoryNode newParent); - if (rc.IsFailure()) return rc; - - var newName = new U8Span(PathTools.GetLastSegment(newPath)); - - if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _)) + if (!TryFindChildDirectory(currentDir, current, out DirectoryNode child)) { - return ResultFs.PathAlreadyExists.Log(); + UnsafeHelpers.SkipParamInit(out directory); + return ResultFs.PathNotFound.Log(); } - if (file.Parent != newParent) - { - if (!UnlinkFile(file)) - { - return ResultFs.PreconditionViolation.Log(); - } - - LinkFile(file, newParent); - } - - - if (StringUtils.Compare(file.Name, newName) != 0) - { - file.Name = newName.ToU8String(); - } - - return Result.Success; + current = child; } - public Result DeleteDirectory(U8Span path, bool recursive) - { - Result rc = FindDirectory(path, out DirectoryNode directory); - if (rc.IsFailure()) return rc; + directory = current; + return Result.Success; + } - if (!recursive && (directory.ChildDirectory != null || directory.ChildFile != null)) + private bool TryFindChildDirectory(U8Span name, DirectoryNode parent, out DirectoryNode child) + { + DirectoryNode currentChild = parent.ChildDirectory; + + while (currentChild != null) + { + if (StringUtils.Compare(name, currentChild.Name) == 0) { - return ResultFs.DirectoryNotEmpty.Log(); + child = currentChild; + return true; } - UnlinkDirectory(directory); - return Result.Success; + currentChild = currentChild.Next; } - public Result DeleteFile(U8Span path) - { - Result rc = FindFile(path, out FileNode file); - if (rc.IsFailure()) return rc; + UnsafeHelpers.SkipParamInit(out child); + return false; + } - UnlinkFile(file); - return Result.Success; + private bool TryFindChildFile(U8Span name, DirectoryNode parent, out FileNode child) + { + FileNode currentChild = parent.ChildFile; + + while (currentChild != null) + { + if (StringUtils.Compare(name, currentChild.Name) == 0) + { + child = currentChild; + return true; + } + + currentChild = currentChild.Next; } - public Result CleanDirectory(U8Span path) - { - Result rc = FindDirectory(path, out DirectoryNode directory); - if (rc.IsFailure()) return rc; + UnsafeHelpers.SkipParamInit(out child); + return false; + } - directory.ChildDirectory = null; - directory.ChildFile = null; + private void LinkDirectory(DirectoryNode dir, DirectoryNode parentDir) + { + Debug.Assert(dir.Parent == null); + Debug.Assert(dir.Next == null); - return Result.Success; - } + dir.Next = parentDir.ChildDirectory; + dir.Parent = parentDir; + parentDir.ChildDirectory = dir; + } - private Result AddFile(U8Span name, DirectoryNode parent) - { - if (TryFindChildDirectory(name, parent, out _)) - { - return ResultFs.PathAlreadyExists.Log(); - } + private bool UnlinkDirectory(DirectoryNode dir) + { + Debug.Assert(dir.Parent != null); - if (TryFindChildFile(name, parent, out _)) - { - return ResultFs.PathAlreadyExists.Log(); - } + DirectoryNode parent = dir.Parent; - var newFileNode = new FileNode - { - Name = name.ToU8String(), - File = new MemoryStreamAccessor(new MemoryStream()) - }; - - LinkFile(newFileNode, parent); - return Result.Success; - } - - private Result AddDirectory(U8Span name, DirectoryNode parent) - { - if (TryFindChildDirectory(name, parent, out _)) - { - return ResultFs.PathAlreadyExists.Log(); - } - - if (TryFindChildFile(name, parent, out _)) - { - return ResultFs.PathAlreadyExists.Log(); - } - - var newDirNode = new DirectoryNode { Name = name.ToU8String() }; - - LinkDirectory(newDirNode, parent); - return Result.Success; - } - - private Result FindFile(U8Span path, out FileNode file) - { - var parentPath = new U8Span(PathTools.GetParentDirectory(path)); - - Result rc = FindDirectory(parentPath, out DirectoryNode parentNode); - if (rc.IsFailure()) - { - UnsafeHelpers.SkipParamInit(out file); - return rc; - } - - var fileName = new U8Span(PathTools.GetLastSegment(path)); - - if (TryFindChildFile(fileName, parentNode, out file)) - { - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); - } - - private Result FindDirectory(U8Span path, out DirectoryNode directory) - { - var parser = new PathParser(path); - DirectoryNode current = Root; - - while (parser.MoveNext()) - { - var currentDir = new U8Span(parser.GetCurrent()); - - // End if we've hit a trailing separator - if (currentDir.IsEmpty() && parser.IsFinished()) break; - - if (!TryFindChildDirectory(currentDir, current, out DirectoryNode child)) - { - UnsafeHelpers.SkipParamInit(out directory); - return ResultFs.PathNotFound.Log(); - } - - current = child; - } - - directory = current; - return Result.Success; - } - - private bool TryFindChildDirectory(U8Span name, DirectoryNode parent, out DirectoryNode child) - { - DirectoryNode currentChild = parent.ChildDirectory; - - while (currentChild != null) - { - if (StringUtils.Compare(name, currentChild.Name) == 0) - { - child = currentChild; - return true; - } - - currentChild = currentChild.Next; - } - - UnsafeHelpers.SkipParamInit(out child); + if (parent.ChildDirectory == null) return false; + + if (parent.ChildDirectory == dir) + { + parent.ChildDirectory = dir.Next; + dir.Parent = null; + + return true; } - private bool TryFindChildFile(U8Span name, DirectoryNode parent, out FileNode child) - { - FileNode currentChild = parent.ChildFile; + DirectoryNode current = parent.ChildDirectory; - while (currentChild != null) + while (current != null) + { + if (current.Next == dir) { - if (StringUtils.Compare(name, currentChild.Name) == 0) - { - child = currentChild; - return true; - } - - currentChild = currentChild.Next; - } - - UnsafeHelpers.SkipParamInit(out child); - return false; - } - - private void LinkDirectory(DirectoryNode dir, DirectoryNode parentDir) - { - Debug.Assert(dir.Parent == null); - Debug.Assert(dir.Next == null); - - dir.Next = parentDir.ChildDirectory; - dir.Parent = parentDir; - parentDir.ChildDirectory = dir; - } - - private bool UnlinkDirectory(DirectoryNode dir) - { - Debug.Assert(dir.Parent != null); - - DirectoryNode parent = dir.Parent; - - if (parent.ChildDirectory == null) - return false; - - if (parent.ChildDirectory == dir) - { - parent.ChildDirectory = dir.Next; + current.Next = dir.Next; dir.Parent = null; return true; } - DirectoryNode current = parent.ChildDirectory; + current = current.Next; + } - while (current != null) - { - if (current.Next == dir) - { - current.Next = dir.Next; - dir.Parent = null; + return false; + } - return true; - } + private void LinkFile(FileNode file, DirectoryNode parentDir) + { + Debug.Assert(file.Parent == null); + Debug.Assert(file.Next == null); - current = current.Next; - } + file.Next = parentDir.ChildFile; + file.Parent = parentDir; + parentDir.ChildFile = file; + } + private bool UnlinkFile(FileNode file) + { + Debug.Assert(file.Parent != null); + + DirectoryNode parent = file.Parent; + + if (parent.ChildFile == null) return false; + + if (parent.ChildFile == file) + { + parent.ChildFile = file.Next; + file.Parent = null; + + return true; } - private void LinkFile(FileNode file, DirectoryNode parentDir) + FileNode current = parent.ChildFile; + + while (current != null) { - Debug.Assert(file.Parent == null); - Debug.Assert(file.Next == null); - - file.Next = parentDir.ChildFile; - file.Parent = parentDir; - parentDir.ChildFile = file; - } - - private bool UnlinkFile(FileNode file) - { - Debug.Assert(file.Parent != null); - - DirectoryNode parent = file.Parent; - - if (parent.ChildFile == null) - return false; - - if (parent.ChildFile == file) + if (current.Next == file) { - parent.ChildFile = file.Next; + current.Next = file.Next; file.Parent = null; return true; } - FileNode current = parent.ChildFile; - - while (current != null) - { - if (current.Next == file) - { - current.Next = file.Next; - file.Parent = null; - - return true; - } - - current = current.Next; - } - - return false; + current = current.Next; } + + return false; } } } diff --git a/src/LibHac/Fs/MemoryReportInfo.cs b/src/LibHac/Fs/MemoryReportInfo.cs index aeff07d7..dd199c74 100644 --- a/src/LibHac/Fs/MemoryReportInfo.cs +++ b/src/LibHac/Fs/MemoryReportInfo.cs @@ -1,20 +1,19 @@ using System.Runtime.InteropServices; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Sequential, Size = 0x80)] +public struct MemoryReportInfo { - [StructLayout(LayoutKind.Sequential, Size = 0x80)] - public struct MemoryReportInfo - { - public long PooledBufferFreeSizePeak; - public long PooledBufferRetriedCount; - public long PooledBufferReduceAllocationCount; - public long BufferManagerFreeSizePeak; - public long BufferManagerRetriedCount; - public long ExpHeapFreeSizePeak; - public long BufferPoolFreeSizePeak; - public long PatrolAllocateSuccessCount; - public long PatrolAllocateFailureCount; - public long BufferManagerTotalAllocatableSizePeak; - public long BufferPoolAllocateSizeMax; - } + public long PooledBufferFreeSizePeak; + public long PooledBufferRetriedCount; + public long PooledBufferReduceAllocationCount; + public long BufferManagerFreeSizePeak; + public long BufferManagerRetriedCount; + public long ExpHeapFreeSizePeak; + public long BufferPoolFreeSizePeak; + public long PatrolAllocateSuccessCount; + public long PatrolAllocateFailureCount; + public long BufferManagerTotalAllocatableSizePeak; + public long BufferPoolAllocateSizeMax; } diff --git a/src/LibHac/Fs/MemoryStorage.cs b/src/LibHac/Fs/MemoryStorage.cs index 0269eab5..196e685e 100644 --- a/src/LibHac/Fs/MemoryStorage.cs +++ b/src/LibHac/Fs/MemoryStorage.cs @@ -1,57 +1,56 @@ using System; -namespace LibHac.Fs +namespace LibHac.Fs; + +public class MemoryStorage : IStorage { - public class MemoryStorage : IStorage + private byte[] StorageBuffer { get; } + + public MemoryStorage(byte[] buffer) { - private byte[] StorageBuffer { get; } - - public MemoryStorage(byte[] buffer) - { - StorageBuffer = buffer; - } - - protected override Result DoRead(long offset, Span destination) - { - if (destination.Length == 0) - return Result.Success; - - if (!CheckAccessRange(offset, destination.Length, StorageBuffer.Length)) - return ResultFs.OutOfRange.Log(); - - StorageBuffer.AsSpan((int)offset, destination.Length).CopyTo(destination); + StorageBuffer = buffer; + } + protected override Result DoRead(long offset, Span destination) + { + if (destination.Length == 0) return Result.Success; - } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - if (source.Length == 0) - return Result.Success; + if (!CheckAccessRange(offset, destination.Length, StorageBuffer.Length)) + return ResultFs.OutOfRange.Log(); - if (!CheckAccessRange(offset, source.Length, StorageBuffer.Length)) - return ResultFs.OutOfRange.Log(); + StorageBuffer.AsSpan((int)offset, destination.Length).CopyTo(destination); - source.CopyTo(StorageBuffer.AsSpan((int)offset)); + return Result.Success; + } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + if (source.Length == 0) return Result.Success; - } - protected override Result DoFlush() - { - return Result.Success; - } + if (!CheckAccessRange(offset, source.Length, StorageBuffer.Length)) + return ResultFs.OutOfRange.Log(); - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedSetSizeForMemoryStorage.Log(); - } + source.CopyTo(StorageBuffer.AsSpan((int)offset)); - protected override Result DoGetSize(out long size) - { - size = StorageBuffer.Length; + return Result.Success; + } - return Result.Success; - } + protected override Result DoFlush() + { + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedSetSizeForMemoryStorage.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = StorageBuffer.Length; + + return Result.Success; } } diff --git a/src/LibHac/Fs/MountName.cs b/src/LibHac/Fs/MountName.cs index b5873c3a..8f6c8d35 100644 --- a/src/LibHac/Fs/MountName.cs +++ b/src/LibHac/Fs/MountName.cs @@ -3,14 +3,13 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs -{ - [StructLayout(LayoutKind.Sequential, Size = 16)] - [DebuggerDisplay("{ToString()}")] - internal struct MountName - { - public Span Name => SpanHelpers.AsByteSpan(ref this); +namespace LibHac.Fs; - public override string ToString() => new U8Span(Name).ToString(); - } +[StructLayout(LayoutKind.Sequential, Size = 16)] +[DebuggerDisplay("{ToString()}")] +internal struct MountName +{ + public Span Name => SpanHelpers.AsByteSpan(ref this); + + public override string ToString() => new U8Span(Name).ToString(); } diff --git a/src/LibHac/Fs/PathTool.cs b/src/LibHac/Fs/PathTool.cs index 56707d6c..95fefe21 100644 --- a/src/LibHac/Fs/PathTool.cs +++ b/src/LibHac/Fs/PathTool.cs @@ -1,9 +1,8 @@ -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class PathTool { - public static class PathTool - { - // These are kept in nn::fs, but C# requires them to be inside a class - internal const int EntryNameLengthMax = 0x300; - internal const int MountNameLengthMax = 15; - } + // These are kept in nn::fs, but C# requires them to be inside a class + internal const int EntryNameLengthMax = 0x300; + internal const int MountNameLengthMax = 15; } diff --git a/src/LibHac/Fs/ProgramIndexMapInfo.cs b/src/LibHac/Fs/ProgramIndexMapInfo.cs index 7e221870..a56ba198 100644 --- a/src/LibHac/Fs/ProgramIndexMapInfo.cs +++ b/src/LibHac/Fs/ProgramIndexMapInfo.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; using LibHac.Ncm; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Explicit, Size = 0x20)] +public struct ProgramIndexMapInfo { - [StructLayout(LayoutKind.Explicit, Size = 0x20)] - public struct ProgramIndexMapInfo - { - [FieldOffset(0x00)] public ProgramId ProgramId; - [FieldOffset(0x08)] public ProgramId MainProgramId; - [FieldOffset(0x10)] public byte ProgramIndex; - } + [FieldOffset(0x00)] public ProgramId ProgramId; + [FieldOffset(0x08)] public ProgramId MainProgramId; + [FieldOffset(0x10)] public byte ProgramIndex; } diff --git a/src/LibHac/Fs/QueryRangeInfo.cs b/src/LibHac/Fs/QueryRangeInfo.cs index 6cbb4bff..2a496a90 100644 --- a/src/LibHac/Fs/QueryRangeInfo.cs +++ b/src/LibHac/Fs/QueryRangeInfo.cs @@ -1,31 +1,30 @@ using System; using System.Runtime.InteropServices; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Sequential, Size = 0x40)] +public struct QueryRangeInfo { - [StructLayout(LayoutKind.Sequential, Size = 0x40)] - public struct QueryRangeInfo + public int AesCtrKeyType; + public int SpeedEmulationType; + + public void Clear() { - public int AesCtrKeyType; - public int SpeedEmulationType; + this = default; + } - public void Clear() - { - this = default; - } + public void Merge(in QueryRangeInfo other) + { + AesCtrKeyType |= other.AesCtrKeyType; + SpeedEmulationType |= other.SpeedEmulationType; + } - public void Merge(in QueryRangeInfo other) - { - AesCtrKeyType |= other.AesCtrKeyType; - SpeedEmulationType |= other.SpeedEmulationType; - } - - [Flags] - public enum AesCtrKeyTypeFlag - { - InternalKeyForSoftwareAes = 1 << 0, - InternalKeyForHardwareAes = 1 << 1, - ExternalKeyForHardwareAes = 1 << 2 - } + [Flags] + public enum AesCtrKeyTypeFlag + { + InternalKeyForSoftwareAes = 1 << 0, + InternalKeyForHardwareAes = 1 << 1, + ExternalKeyForHardwareAes = 1 << 2 } } diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index c3de5610..3235bd00 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -11,2048 +11,2047 @@ using System.Runtime.CompilerServices; -namespace LibHac.Fs +namespace LibHac.Fs; + +public static class ResultFs { - public static class ResultFs - { - public const int ModuleFs = 2; - - /// Error code: 2002-0000; Range: 0-999; Inner value: 0x2 - public static Result.Base HandledByAllProcess => new Result.Base(ModuleFs, 0, 999); - /// Specified path does not exist
Error code: 2002-0001; Inner value: 0x202
- public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1); - /// Specified path already exists
Error code: 2002-0002; Inner value: 0x402
- public static Result.Base PathAlreadyExists => new Result.Base(ModuleFs, 2); - /// Resource already in use (file already opened, savedata filesystem already mounted)
Error code: 2002-0007; Inner value: 0xe02
- public static Result.Base TargetLocked => new Result.Base(ModuleFs, 7); - /// Specified directory is not empty when trying to delete it
Error code: 2002-0008; Inner value: 0x1002
- public static Result.Base DirectoryNotEmpty => new Result.Base(ModuleFs, 8); - /// Error code: 2002-0013; Inner value: 0x1a02 - public static Result.Base DirectoryStatusChanged => new Result.Base(ModuleFs, 13); - - /// Error code: 2002-0030; Range: 30-45; Inner value: 0x3c02 - public static Result.Base UsableSpaceNotEnough => new Result.Base(ModuleFs, 30, 45); - /// Error code: 2002-0031; Inner value: 0x3e02 - public static Result.Base UsableSpaceNotEnoughForSaveData => new Result.Base(ModuleFs, 31); - /// Error code: 2002-0032; Inner value: 0x4002 - public static Result.Base UsableSpaceNotEnoughAfterDataErase => new Result.Base(ModuleFs, 32); - /// Error code: 2002-0033; Inner value: 0x4202 - public static Result.Base UsableSpaceNotEnoughForCacheStorage => new Result.Base(ModuleFs, 33); - - /// Error code: 2002-0034; Range: 34-38; Inner value: 0x4402 - public static Result.Base UsableSpaceNotEnoughForBis => new Result.Base(ModuleFs, 34, 38); - /// Error code: 2002-0035; Inner value: 0x4602 - public static Result.Base UsableSpaceNotEnoughForBisCalibration => new Result.Base(ModuleFs, 35); - /// Error code: 2002-0036; Inner value: 0x4802 - public static Result.Base UsableSpaceNotEnoughForBisSafe => new Result.Base(ModuleFs, 36); - /// Error code: 2002-0037; Inner value: 0x4a02 - public static Result.Base UsableSpaceNotEnoughForBisUser => new Result.Base(ModuleFs, 37); - /// Error code: 2002-0038; Inner value: 0x4c02 - public static Result.Base UsableSpaceNotEnoughForBisSystem => new Result.Base(ModuleFs, 38); - - /// Error code: 2002-0039; Inner value: 0x4e02 - public static Result.Base UsableSpaceNotEnoughForSdCard => new Result.Base(ModuleFs, 39); - - /// Error code: 2002-0050; Inner value: 0x6402 - public static Result.Base UnsupportedSdkVersion => new Result.Base(ModuleFs, 50); - /// Error code: 2002-0060; Inner value: 0x7802 - public static Result.Base MountNameAlreadyExists => new Result.Base(ModuleFs, 60); - /// Error code: 2002-0070; Inner value: 0x8c02 - public static Result.Base IndividualFileDataCacheAlreadyEnabled => new Result.Base(ModuleFs, 70); - - /// Error code: 2002-1000; Range: 1000-2999; Inner value: 0x7d002 - public static Result.Base HandledBySystemProcess { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 1000, 2999); } - /// Error code: 2002-1001; Inner value: 0x7d202 - public static Result.Base PartitionNotFound => new Result.Base(ModuleFs, 1001); - /// Error code: 2002-1002; Inner value: 0x7d402 - public static Result.Base TargetNotFound => new Result.Base(ModuleFs, 1002); - /// Error code: 2002-1003; Inner value: 0x7d602 - public static Result.Base MmcPatrolDataNotInitialized => new Result.Base(ModuleFs, 1003); - /// The requested external key was not found
Error code: 2002-1004; Inner value: 0x7d802
- public static Result.Base NcaExternalKeyUnavailable => new Result.Base(ModuleFs, 1004); - - /// Error code: 2002-2000; Range: 2000-2499; Inner value: 0xfa002 - public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } - /// Error code: 2002-2001; Inner value: 0xfa202 - public static Result.Base PortSdCardNoDevice => new Result.Base(ModuleFs, 2001); - /// Error code: 2002-2002; Inner value: 0xfa402 - public static Result.Base PortSdCardNotActivated => new Result.Base(ModuleFs, 2002); - /// Error code: 2002-2003; Inner value: 0xfa602 - public static Result.Base PortSdCardDeviceRemoved => new Result.Base(ModuleFs, 2003); - /// Error code: 2002-2004; Inner value: 0xfa802 - public static Result.Base PortSdCardNotAwakened => new Result.Base(ModuleFs, 2004); - - /// Error code: 2002-2032; Range: 2032-2126; Inner value: 0xfe002 - public static Result.Base PortSdCardCommunicationError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2032, 2126); } - /// Error code: 2002-2033; Range: 2033-2046; Inner value: 0xfe202 - public static Result.Base PortSdCardCommunicationNotAttained { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2033, 2046); } - /// Error code: 2002-2034; Inner value: 0xfe402 - public static Result.Base PortSdCardResponseIndexError => new Result.Base(ModuleFs, 2034); - /// Error code: 2002-2035; Inner value: 0xfe602 - public static Result.Base PortSdCardResponseEndBitError => new Result.Base(ModuleFs, 2035); - /// Error code: 2002-2036; Inner value: 0xfe802 - public static Result.Base PortSdCardResponseCrcError => new Result.Base(ModuleFs, 2036); - /// Error code: 2002-2037; Inner value: 0xfea02 - public static Result.Base PortSdCardResponseTimeoutError => new Result.Base(ModuleFs, 2037); - /// Error code: 2002-2038; Inner value: 0xfec02 - public static Result.Base PortSdCardDataEndBitError => new Result.Base(ModuleFs, 2038); - /// Error code: 2002-2039; Inner value: 0xfee02 - public static Result.Base PortSdCardDataCrcError => new Result.Base(ModuleFs, 2039); - /// Error code: 2002-2040; Inner value: 0xff002 - public static Result.Base PortSdCardDataTimeoutError => new Result.Base(ModuleFs, 2040); - /// Error code: 2002-2041; Inner value: 0xff202 - public static Result.Base PortSdCardAutoCommandResponseIndexError => new Result.Base(ModuleFs, 2041); - /// Error code: 2002-2042; Inner value: 0xff402 - public static Result.Base PortSdCardAutoCommandResponseEndBitError => new Result.Base(ModuleFs, 2042); - /// Error code: 2002-2043; Inner value: 0xff602 - public static Result.Base PortSdCardAutoCommandResponseCrcError => new Result.Base(ModuleFs, 2043); - /// Error code: 2002-2044; Inner value: 0xff802 - public static Result.Base PortSdCardAutoCommandResponseTimeoutError => new Result.Base(ModuleFs, 2044); - /// Error code: 2002-2045; Inner value: 0xffa02 - public static Result.Base PortSdCardCommandCompleteSoftwareTimeout => new Result.Base(ModuleFs, 2045); - /// Error code: 2002-2046; Inner value: 0xffc02 - public static Result.Base PortSdCardTransferCompleteSoftwareTimeout => new Result.Base(ModuleFs, 2046); - - /// Error code: 2002-2048; Range: 2048-2070; Inner value: 0x100002 - public static Result.Base PortSdCardDeviceStatusHasError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2048, 2070); } - /// Error code: 2002-2049; Inner value: 0x100202 - public static Result.Base PortSdCardDeviceStatusAddressOutOfRange => new Result.Base(ModuleFs, 2049); - /// Error code: 2002-2050; Inner value: 0x100402 - public static Result.Base PortSdCardDeviceStatusAddressMisaligned => new Result.Base(ModuleFs, 2050); - /// Error code: 2002-2051; Inner value: 0x100602 - public static Result.Base PortSdCardDeviceStatusBlockLenError => new Result.Base(ModuleFs, 2051); - /// Error code: 2002-2052; Inner value: 0x100802 - public static Result.Base PortSdCardDeviceStatusEraseSeqError => new Result.Base(ModuleFs, 2052); - /// Error code: 2002-2053; Inner value: 0x100a02 - public static Result.Base PortSdCardDeviceStatusEraseParam => new Result.Base(ModuleFs, 2053); - /// Error code: 2002-2054; Inner value: 0x100c02 - public static Result.Base PortSdCardDeviceStatusWpViolation => new Result.Base(ModuleFs, 2054); - /// Error code: 2002-2055; Inner value: 0x100e02 - public static Result.Base PortSdCardDeviceStatusLockUnlockFailed => new Result.Base(ModuleFs, 2055); - /// Error code: 2002-2056; Inner value: 0x101002 - public static Result.Base PortSdCardDeviceStatusComCrcError => new Result.Base(ModuleFs, 2056); - /// Error code: 2002-2057; Inner value: 0x101202 - public static Result.Base PortSdCardDeviceStatusIllegalCommand => new Result.Base(ModuleFs, 2057); - /// Error code: 2002-2058; Inner value: 0x101402 - public static Result.Base PortSdCardDeviceStatusDeviceEccFailed => new Result.Base(ModuleFs, 2058); - /// Error code: 2002-2059; Inner value: 0x101602 - public static Result.Base PortSdCardDeviceStatusCcError => new Result.Base(ModuleFs, 2059); - /// Error code: 2002-2060; Inner value: 0x101802 - public static Result.Base PortSdCardDeviceStatusError => new Result.Base(ModuleFs, 2060); - /// Error code: 2002-2061; Inner value: 0x101a02 - public static Result.Base PortSdCardDeviceStatusCidCsdOverwrite => new Result.Base(ModuleFs, 2061); - /// Error code: 2002-2062; Inner value: 0x101c02 - public static Result.Base PortSdCardDeviceStatusWpEraseSkip => new Result.Base(ModuleFs, 2062); - /// Error code: 2002-2063; Inner value: 0x101e02 - public static Result.Base PortSdCardDeviceStatusEraseReset => new Result.Base(ModuleFs, 2063); - /// Error code: 2002-2064; Inner value: 0x102002 - public static Result.Base PortSdCardDeviceStatusSwitchError => new Result.Base(ModuleFs, 2064); - - /// Error code: 2002-2072; Inner value: 0x103002 - public static Result.Base PortSdCardUnexpectedDeviceState => new Result.Base(ModuleFs, 2072); - /// Error code: 2002-2073; Inner value: 0x103202 - public static Result.Base PortSdCardUnexpectedDeviceCsdValue => new Result.Base(ModuleFs, 2073); - /// Error code: 2002-2074; Inner value: 0x103402 - public static Result.Base PortSdCardAbortTransactionSoftwareTimeout => new Result.Base(ModuleFs, 2074); - /// Error code: 2002-2075; Inner value: 0x103602 - public static Result.Base PortSdCardCommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleFs, 2075); - /// Error code: 2002-2076; Inner value: 0x103802 - public static Result.Base PortSdCardCommandInhibitDatSoftwareTimeout => new Result.Base(ModuleFs, 2076); - /// Error code: 2002-2077; Inner value: 0x103a02 - public static Result.Base PortSdCardBusySoftwareTimeout => new Result.Base(ModuleFs, 2077); - /// Error code: 2002-2078; Inner value: 0x103c02 - public static Result.Base PortSdCardIssueTuningCommandSoftwareTimeout => new Result.Base(ModuleFs, 2078); - /// Error code: 2002-2079; Inner value: 0x103e02 - public static Result.Base PortSdCardTuningFailed => new Result.Base(ModuleFs, 2079); - /// Error code: 2002-2080; Inner value: 0x104002 - public static Result.Base PortSdCardMmcInitializationSoftwareTimeout => new Result.Base(ModuleFs, 2080); - /// Error code: 2002-2081; Inner value: 0x104202 - public static Result.Base PortSdCardMmcNotSupportExtendedCsd => new Result.Base(ModuleFs, 2081); - /// Error code: 2002-2082; Inner value: 0x104402 - public static Result.Base PortSdCardUnexpectedMmcExtendedCsdValue => new Result.Base(ModuleFs, 2082); - /// Error code: 2002-2083; Inner value: 0x104602 - public static Result.Base PortSdCardMmcEraseSoftwareTimeout => new Result.Base(ModuleFs, 2083); - /// Error code: 2002-2084; Inner value: 0x104802 - public static Result.Base PortSdCardSdCardValidationError => new Result.Base(ModuleFs, 2084); - /// Error code: 2002-2085; Inner value: 0x104a02 - public static Result.Base PortSdCardSdCardInitializationSoftwareTimeout => new Result.Base(ModuleFs, 2085); - /// Error code: 2002-2086; Inner value: 0x104c02 - public static Result.Base PortSdCardSdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleFs, 2086); - /// Error code: 2002-2087; Inner value: 0x104e02 - public static Result.Base PortSdCardUnexpectedSdCardAcmdDisabled => new Result.Base(ModuleFs, 2087); - /// Error code: 2002-2088; Inner value: 0x105002 - public static Result.Base PortSdCardSdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleFs, 2088); - /// Error code: 2002-2089; Inner value: 0x105202 - public static Result.Base PortSdCardUnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleFs, 2089); - /// Error code: 2002-2090; Inner value: 0x105402 - public static Result.Base PortSdCardSdCardNotSupportAccessMode => new Result.Base(ModuleFs, 2090); - /// Error code: 2002-2091; Inner value: 0x105602 - public static Result.Base PortSdCardSdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleFs, 2091); - /// Error code: 2002-2092; Inner value: 0x105802 - public static Result.Base PortSdCardSdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleFs, 2092); - /// Error code: 2002-2093; Inner value: 0x105a02 - public static Result.Base PortSdCardSdCardCannotSwitchAccessMode => new Result.Base(ModuleFs, 2093); - /// Error code: 2002-2094; Inner value: 0x105c02 - public static Result.Base PortSdCardSdCardFailedSwitchAccessMode => new Result.Base(ModuleFs, 2094); - /// Error code: 2002-2095; Inner value: 0x105e02 - public static Result.Base PortSdCardSdCardUnacceptableCurrentConsumption => new Result.Base(ModuleFs, 2095); - /// Error code: 2002-2096; Inner value: 0x106002 - public static Result.Base PortSdCardSdCardNotReadyToVoltageSwitch => new Result.Base(ModuleFs, 2096); - /// Error code: 2002-2097; Inner value: 0x106202 - public static Result.Base PortSdCardSdCardNotCompleteVoltageSwitch => new Result.Base(ModuleFs, 2097); - - /// Error code: 2002-2128; Range: 2128-2158; Inner value: 0x10a002 - public static Result.Base PortSdCardHostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2128, 2158); } - /// Error code: 2002-2129; Inner value: 0x10a202 - public static Result.Base PortSdCardInternalClockStableSoftwareTimeout => new Result.Base(ModuleFs, 2129); - /// Error code: 2002-2130; Inner value: 0x10a402 - public static Result.Base PortSdCardSdHostStandardUnknownAutoCmdError => new Result.Base(ModuleFs, 2130); - /// Error code: 2002-2131; Inner value: 0x10a602 - public static Result.Base PortSdCardSdHostStandardUnknownError => new Result.Base(ModuleFs, 2131); - /// Error code: 2002-2132; Inner value: 0x10a802 - public static Result.Base PortSdCardSdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleFs, 2132); - /// Error code: 2002-2133; Inner value: 0x10aa02 - public static Result.Base PortSdCardSdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleFs, 2133); - /// Error code: 2002-2134; Inner value: 0x10ac02 - public static Result.Base PortSdCardSdHostStandardFailSwitchTo18V => new Result.Base(ModuleFs, 2134); - - /// Error code: 2002-2160; Range: 2160-2190; Inner value: 0x10e002 - public static Result.Base PortSdCardInternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2160, 2190); } - /// Error code: 2002-2161; Inner value: 0x10e202 - public static Result.Base PortSdCardNoWaitedInterrupt => new Result.Base(ModuleFs, 2161); - /// Error code: 2002-2162; Inner value: 0x10e402 - public static Result.Base PortSdCardWaitInterruptSoftwareTimeout => new Result.Base(ModuleFs, 2162); - - /// Error code: 2002-2192; Inner value: 0x112002 - public static Result.Base PortSdCardAbortCommandIssued => new Result.Base(ModuleFs, 2192); - /// Error code: 2002-2200; Inner value: 0x113002 - public static Result.Base PortSdCardNotSupported => new Result.Base(ModuleFs, 2200); - /// Error code: 2002-2201; Inner value: 0x113202 - public static Result.Base PortSdCardNotImplemented => new Result.Base(ModuleFs, 2201); - /// Error code: 2002-2496; Inner value: 0x138002 - public static Result.Base SdCardStorageDeviceInvalidated => new Result.Base(ModuleFs, 2496); - /// Error code: 2002-2497; Inner value: 0x138202 - public static Result.Base SdCardFormatWriteVerificationFailed => new Result.Base(ModuleFs, 2497); - /// Error code: 2002-2498; Inner value: 0x138402 - public static Result.Base SdCardFileSystemInvalidatedByRemoved => new Result.Base(ModuleFs, 2498); - /// Error code: 2002-2499; Inner value: 0x138602 - public static Result.Base SdCardDeviceUnknownError => new Result.Base(ModuleFs, 2499); - - /// Error code: 2002-2500; Range: 2500-2999; Inner value: 0x138802 - public static Result.Base GameCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2500, 2999); } - /// Error code: 2002-2503; Inner value: 0x138e02 - public static Result.Base GameCardPreconditionViolation => new Result.Base(ModuleFs, 2503); - /// Error code: 2002-2504; Inner value: 0x139002 - public static Result.Base GameCardNotImplemented => new Result.Base(ModuleFs, 2504); - /// Error code: 2002-2510; Inner value: 0x139c02 - public static Result.Base GameCardNoAvailableLockers => new Result.Base(ModuleFs, 2510); - /// Error code: 2002-2511; Inner value: 0x139e02 - public static Result.Base GameCardLockerIndexOutOfRange => new Result.Base(ModuleFs, 2511); - /// Error code: 2002-2520; Inner value: 0x13b002 - public static Result.Base GameCardNotInserted => new Result.Base(ModuleFs, 2520); - /// Error code: 2002-2521; Inner value: 0x13b202 - public static Result.Base InvalidGameCardIdInSpecificData => new Result.Base(ModuleFs, 2521); - /// Error code: 2002-2522; Inner value: 0x13b402 - public static Result.Base GameCardCardNotActivated => new Result.Base(ModuleFs, 2522); - /// Error code: 2002-2523; Inner value: 0x13b602 - public static Result.Base InvalidCommandForDeactivatedGameCardAsic => new Result.Base(ModuleFs, 2523); - /// Error code: 2002-2531; Inner value: 0x13c602 - public static Result.Base GameCardCardAccessTimeout => new Result.Base(ModuleFs, 2531); - /// Error code: 2002-2532; Inner value: 0x13c802 - public static Result.Base GameCardStatusFatalError => new Result.Base(ModuleFs, 2532); - /// Error code: 2002-2533; Inner value: 0x13ca02 - public static Result.Base GameCardReadFailure => new Result.Base(ModuleFs, 2533); - /// Error code: 2002-2536; Inner value: 0x13d002 - public static Result.Base GameCardRetryLimitHit => new Result.Base(ModuleFs, 2536); - /// Error code: 2002-2537; Inner value: 0x13d202 - public static Result.Base GameCardStatusRefreshRequested => new Result.Base(ModuleFs, 2537); - /// Error code: 2002-2538; Inner value: 0x13d402 - public static Result.Base GameCardStatusCrcErrorAndRefreshRequested => new Result.Base(ModuleFs, 2538); - /// Error code: 2002-2540; Inner value: 0x13d802 - public static Result.Base InvalidSecureAccess => new Result.Base(ModuleFs, 2540); - /// Error code: 2002-2541; Inner value: 0x13da02 - public static Result.Base InvalidNormalAccess => new Result.Base(ModuleFs, 2541); - /// Error code: 2002-2542; Inner value: 0x13dc02 - public static Result.Base InvalidAccessAcrossMode => new Result.Base(ModuleFs, 2542); - - /// Error code: 2002-2543; Range: 2543-2546; Inner value: 0x13de02 - public static Result.Base GameCardWrongCard { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2543, 2546); } - /// Error code: 2002-2544; Inner value: 0x13e002 - public static Result.Base InvalidGameCardInitialDataHash => new Result.Base(ModuleFs, 2544); - /// Error code: 2002-2545; Inner value: 0x13e202 - public static Result.Base InvalidGameCardInitialDataReservedArea => new Result.Base(ModuleFs, 2545); - /// Error code: 2002-2546; Inner value: 0x13e402 - public static Result.Base InvalidGameCardCertificateKekIndex => new Result.Base(ModuleFs, 2546); - - /// Error code: 2002-2548; Inner value: 0x13e802 - public static Result.Base InvalidGameCardModeForGetCardDeviceCertificate => new Result.Base(ModuleFs, 2548); - /// Error code: 2002-2549; Inner value: 0x13ea02 - public static Result.Base NotSupportedForGameCardSecurityMode => new Result.Base(ModuleFs, 2549); - /// Error code: 2002-2550; Inner value: 0x13ec02 - public static Result.Base Result2550 => new Result.Base(ModuleFs, 2550); - /// Error code: 2002-2551; Inner value: 0x13ee02 - public static Result.Base GameCardReadHeaderTryTimeoutForActivation => new Result.Base(ModuleFs, 2551); - /// Error code: 2002-2552; Inner value: 0x13f002 - public static Result.Base Result2552 => new Result.Base(ModuleFs, 2552); - /// Error code: 2002-2553; Inner value: 0x13f202 - public static Result.Base InvalidGameCardModeForGetChallengeCardExistence => new Result.Base(ModuleFs, 2553); - /// Error code: 2002-2554; Inner value: 0x13f402 - public static Result.Base InvalidGameCardHeader => new Result.Base(ModuleFs, 2554); - /// Error code: 2002-2555; Inner value: 0x13f602 - public static Result.Base InvalidGameCardCertificate => new Result.Base(ModuleFs, 2555); - /// Error code: 2002-2557; Inner value: 0x13fa02 - public static Result.Base Result2557 => new Result.Base(ModuleFs, 2557); - /// Error code: 2002-2558; Inner value: 0x13fc02 - public static Result.Base Result2558 => new Result.Base(ModuleFs, 2558); - - /// Error code: 2002-2565; Range: 2565-2595; Inner value: 0x140a02 - public static Result.Base GameCardCommunicationFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2565, 2595); } - - /// Error code: 2002-2599; Inner value: 0x144e02 - public static Result.Base GameCardInvalidStateTransition => new Result.Base(ModuleFs, 2599); - /// Error code: 2002-2600; Inner value: 0x145002 - public static Result.Base GameCardAsicInvalidTransitionToNormalMode => new Result.Base(ModuleFs, 2600); - /// Error code: 2002-2601; Inner value: 0x145202 - public static Result.Base GameCardAsicInvalidTransitionToSecureMode => new Result.Base(ModuleFs, 2601); - /// Error code: 2002-2602; Inner value: 0x145402 - public static Result.Base GameCardAsicInvalidTransitionToWriteMode => new Result.Base(ModuleFs, 2602); - /// Error code: 2002-2629; Inner value: 0x148a02 - public static Result.Base GameCardAsicInitializationFailureForWriterFirmware => new Result.Base(ModuleFs, 2629); - - /// Error code: 2002-2630; Range: 2630-2669; Inner value: 0x148c02 - public static Result.Base GameCardAsicInitializationFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2630, 2669); } - /// Error code: 2002-2631; Inner value: 0x148e02 - public static Result.Base GameCardAsicGetDeviceStatusFailure => new Result.Base(ModuleFs, 2631); - /// Error code: 2002-2632; Inner value: 0x149002 - public static Result.Base GameCardAsicActivationFailure => new Result.Base(ModuleFs, 2632); - /// Error code: 2002-2634; Inner value: 0x149402 - public static Result.Base GameCardAsicSetUserAsicFirmwareFailure => new Result.Base(ModuleFs, 2634); - /// Error code: 2002-2637; Inner value: 0x149a02 - public static Result.Base GameCardAsicGetAsicCertFailure => new Result.Base(ModuleFs, 2637); - /// Error code: 2002-2638; Inner value: 0x149c02 - public static Result.Base GameCardParseCertificateFailure => new Result.Base(ModuleFs, 2638); - /// Error code: 2002-2639; Inner value: 0x149e02 - public static Result.Base InvalidGameCardAsicCertificate => new Result.Base(ModuleFs, 2639); - /// Error code: 2002-2640; Inner value: 0x14a002 - public static Result.Base GameCardAsicSetEmmcEmbeddedSocCertificateFailure => new Result.Base(ModuleFs, 2640); - /// Error code: 2002-2645; Inner value: 0x14aa02 - public static Result.Base GameCardAsicGetAsicEncryptedMessageFailure => new Result.Base(ModuleFs, 2645); - /// Error code: 2002-2646; Inner value: 0x14ac02 - public static Result.Base GameCardAsicSetLibraryEncryptedMessageFailure => new Result.Base(ModuleFs, 2646); - /// Error code: 2002-2651; Inner value: 0x14b602 - public static Result.Base GameCardAsicGetAsicAuthenticationDataFailure => new Result.Base(ModuleFs, 2651); - /// Error code: 2002-2652; Inner value: 0x14b802 - public static Result.Base GameCardAsicSetAsicAuthenticationDataHashFailure => new Result.Base(ModuleFs, 2652); - /// Error code: 2002-2653; Inner value: 0x14ba02 - public static Result.Base GameCardAsicSetLibraryAuthenticationDataFailure => new Result.Base(ModuleFs, 2653); - /// Error code: 2002-2654; Inner value: 0x14bc02 - public static Result.Base GameCardAsicGetLibraryAuthenticationDataHashFailure => new Result.Base(ModuleFs, 2654); - /// Error code: 2002-2655; Inner value: 0x14be02 - public static Result.Base GameCardInvalidLibraryAuthenticationDataHash => new Result.Base(ModuleFs, 2655); - /// Error code: 2002-2658; Inner value: 0x14c402 - public static Result.Base GameCardAsicEnterSecureAsicModeFailure => new Result.Base(ModuleFs, 2658); - /// Error code: 2002-2659; Inner value: 0x14c602 - public static Result.Base GameCardAsicExchangeRandomValuesInSecureModeFailure => new Result.Base(ModuleFs, 2659); - /// Error code: 2002-2660; Inner value: 0x14c802 - public static Result.Base GameCardAsicChallengeCardExistenceFailure => new Result.Base(ModuleFs, 2660); - /// Error code: 2002-2663; Inner value: 0x14ce02 - public static Result.Base GameCardAsicActivationTimeout => new Result.Base(ModuleFs, 2663); - - /// Error code: 2002-2665; Range: 2665-2669; Inner value: 0x14d202 - public static Result.Base GameCardSplFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2665, 2669); } - /// Error code: 2002-2666; Inner value: 0x14d402 - public static Result.Base GameCardSplDecryptAesKeyFailure => new Result.Base(ModuleFs, 2666); - /// Error code: 2002-2667; Inner value: 0x14d602 - public static Result.Base GameCardSplDecryptAndStoreGcKeyFailure => new Result.Base(ModuleFs, 2667); - /// Error code: 2002-2668; Inner value: 0x14d802 - public static Result.Base GameCardSplGenerateRandomBytesFailure => new Result.Base(ModuleFs, 2668); - /// Error code: 2002-2669; Inner value: 0x14da02 - public static Result.Base GameCardSplDecryptGcMessageFailure => new Result.Base(ModuleFs, 2669); - - /// Error code: 2002-2671; Inner value: 0x14de02 - public static Result.Base GameCardAsicReadAsicRegisterFailure => new Result.Base(ModuleFs, 2671); - /// Error code: 2002-2672; Inner value: 0x14e002 - public static Result.Base GameCardAsicWriteAsicRegisterFailure => new Result.Base(ModuleFs, 2672); - /// Error code: 2002-2673; Inner value: 0x14e202 - public static Result.Base GameCardAsicEnableCardBusFailure => new Result.Base(ModuleFs, 2673); - /// Error code: 2002-2674; Inner value: 0x14e402 - public static Result.Base GameCardAsicGetCardHeaderFailure => new Result.Base(ModuleFs, 2674); - /// Error code: 2002-2675; Inner value: 0x14e602 - public static Result.Base GameCardAsicStatusError => new Result.Base(ModuleFs, 2675); - /// Error code: 2002-2676; Inner value: 0x14e802 - public static Result.Base GameCardAsicGetCardKeyAreaFailure => new Result.Base(ModuleFs, 2676); - /// Error code: 2002-2677; Inner value: 0x14ea02 - public static Result.Base GameCardAsicChangeDebugModeFailure => new Result.Base(ModuleFs, 2677); - /// Error code: 2002-2678; Inner value: 0x14ec02 - public static Result.Base GameCardAsicGetRmaInformationFailure => new Result.Base(ModuleFs, 2678); - /// Error code: 2002-2680; Inner value: 0x14f002 - public static Result.Base GameCardAsicStatusBit22Set => new Result.Base(ModuleFs, 2680); - /// Error code: 2002-2681; Inner value: 0x14f202 - public static Result.Base GameCardSecureValuesNotInitialized => new Result.Base(ModuleFs, 2681); - /// Error code: 2002-2692; Inner value: 0x150802 - public static Result.Base InvalidSecureGameCardCommand => new Result.Base(ModuleFs, 2692); - /// Error code: 2002-2693; Inner value: 0x150a02 - public static Result.Base InvalidWriteGameCardCommand => new Result.Base(ModuleFs, 2693); - /// Error code: 2002-2703; Inner value: 0x151e02 - public static Result.Base GameCardSetVoltageFailure => new Result.Base(ModuleFs, 2703); - /// Error code: 2002-2731; Inner value: 0x155602 - public static Result.Base GameCardCommandReadId1Failure => new Result.Base(ModuleFs, 2731); - /// Error code: 2002-2732; Inner value: 0x155802 - public static Result.Base GameCardCommandReadId2Failure => new Result.Base(ModuleFs, 2732); - /// Error code: 2002-2733; Inner value: 0x155a02 - public static Result.Base GameCardCommandReadId3Failure => new Result.Base(ModuleFs, 2733); - /// Error code: 2002-2735; Inner value: 0x155e02 - public static Result.Base GameCardCommandReadPageFailure => new Result.Base(ModuleFs, 2735); - /// Error code: 2002-2736; Inner value: 0x156002 - public static Result.Base GameCardCommandReadPageUnalignedFailure => new Result.Base(ModuleFs, 2736); - /// Error code: 2002-2737; Inner value: 0x156202 - public static Result.Base GameCardCommandWritePageFailure => new Result.Base(ModuleFs, 2737); - /// Error code: 2002-2738; Inner value: 0x156402 - public static Result.Base GameCardCommandRefreshFailure => new Result.Base(ModuleFs, 2738); - /// Error code: 2002-2739; Inner value: 0x156602 - public static Result.Base GameCardCommandUpdateKeyFailure => new Result.Base(ModuleFs, 2739); - /// Error code: 2002-2742; Inner value: 0x156c02 - public static Result.Base GameCardCommandReadCrcFailure => new Result.Base(ModuleFs, 2742); - /// Error code: 2002-2743; Inner value: 0x156e02 - public static Result.Base GameCardCommandEraseFailure => new Result.Base(ModuleFs, 2743); - /// Error code: 2002-2744; Inner value: 0x157002 - public static Result.Base GameCardCommandReadDevParamFailure => new Result.Base(ModuleFs, 2744); - /// Error code: 2002-2745; Inner value: 0x157202 - public static Result.Base GameCardCommandWriteDevParamFailure => new Result.Base(ModuleFs, 2745); - /// Error code: 2002-2901; Inner value: 0x16aa02 - public static Result.Base GameCardParameterError => new Result.Base(ModuleFs, 2901); - /// Error code: 2002-2902; Inner value: 0x16ac02 - public static Result.Base Result2902 => new Result.Base(ModuleFs, 2902); - /// Error code: 2002-2903; Inner value: 0x16ae02 - public static Result.Base Result2903 => new Result.Base(ModuleFs, 2903); - /// Error code: 2002-2904; Inner value: 0x16b002 - public static Result.Base Result2904 => new Result.Base(ModuleFs, 2904); - /// Error code: 2002-2905; Inner value: 0x16b202 - public static Result.Base Result2905 => new Result.Base(ModuleFs, 2905); - /// Error code: 2002-2906; Inner value: 0x16b402 - public static Result.Base Result2906 => new Result.Base(ModuleFs, 2906); - /// Error code: 2002-2950; Inner value: 0x170c02 - public static Result.Base InvalidGameCardStorageAttribute => new Result.Base(ModuleFs, 2950); - /// Error code: 2002-2951; Inner value: 0x170e02 - public static Result.Base GameCardNotInsertedOnGetHandle => new Result.Base(ModuleFs, 2951); - /// Error code: 2002-2952; Inner value: 0x171002 - public static Result.Base InvalidGameCardHandleOnRead => new Result.Base(ModuleFs, 2952); - /// Error code: 2002-2954; Inner value: 0x171402 - public static Result.Base InvalidGameCardHandleOnGetCardInfo => new Result.Base(ModuleFs, 2954); - /// Error code: 2002-2955; Inner value: 0x171602 - public static Result.Base InvalidGameCardHandleOnGetGameCardDeviceCertificate => new Result.Base(ModuleFs, 2955); - /// Error code: 2002-2956; Inner value: 0x171802 - public static Result.Base InvalidGameCardHandleOnGetGameCardImageHash => new Result.Base(ModuleFs, 2956); - /// Error code: 2002-2957; Inner value: 0x171a02 - public static Result.Base InvalidGameCardHandleOnChallengeCardExistence => new Result.Base(ModuleFs, 2957); - /// Error code: 2002-2958; Inner value: 0x171c02 - public static Result.Base InvalidGameCardHandleOnOnAcquireLock => new Result.Base(ModuleFs, 2958); - /// Error code: 2002-2959; Inner value: 0x171e02 - public static Result.Base InvalidGameCardModeOnAcquireSecureLock => new Result.Base(ModuleFs, 2959); - /// Error code: 2002-2960; Inner value: 0x172002 - public static Result.Base InvalidGameCardHandleOnOpenNormalPartition => new Result.Base(ModuleFs, 2960); - /// Error code: 2002-2961; Inner value: 0x172202 - public static Result.Base InvalidGameCardHandleOnOpenSecurePartition => new Result.Base(ModuleFs, 2961); - /// Error code: 2002-2962; Inner value: 0x172402 - public static Result.Base InvalidGameCardCompatibilityType => new Result.Base(ModuleFs, 2962); - /// Error code: 2002-2963; Inner value: 0x172602 - public static Result.Base GameCardsNotSupportedOnDeviceModel => new Result.Base(ModuleFs, 2963); - - /// Error code: 2002-3000; Range: 3000-7999; Inner value: 0x177002 - public static Result.Base Internal { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3000, 7999); } - /// Error code: 2002-3001; Inner value: 0x177202 - public static Result.Base NotImplemented => new Result.Base(ModuleFs, 3001); - /// Error code: 2002-3002; Inner value: 0x177402 - public static Result.Base UnsupportedVersion => new Result.Base(ModuleFs, 3002); - /// Error code: 2002-3003; Inner value: 0x177602 - public static Result.Base AlreadyExists => new Result.Base(ModuleFs, 3003); - /// Error code: 2002-3005; Inner value: 0x177a02 - public static Result.Base OutOfRange => new Result.Base(ModuleFs, 3005); - /// Error code: 2002-3099; Inner value: 0x183602 - public static Result.Base Result3099 => new Result.Base(ModuleFs, 3099); - /// Error code: 2002-3100; Inner value: 0x183802 - public static Result.Base SystemPartitionNotReady => new Result.Base(ModuleFs, 3100); - /// Error code: 2002-3101; Inner value: 0x183a02 - public static Result.Base StorageDeviceNotReady => new Result.Base(ModuleFs, 3101); - - /// Error code: 2002-3200; Range: 3200-3499; Inner value: 0x190002 - public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); } - /// Error code: 2002-3201; Inner value: 0x190202 - public static Result.Base AllocationMemoryFailedInPrFile2 => new Result.Base(ModuleFs, 3201); - /// Error code: 2002-3203; Inner value: 0x190602 - public static Result.Base AllocationMemoryFailedInFatFileSystemA => new Result.Base(ModuleFs, 3203); - /// Error code: 2002-3204; Inner value: 0x190802 - public static Result.Base AllocationMemoryFailedInFatFileSystemB => new Result.Base(ModuleFs, 3204); - /// Error code: 2002-3205; Inner value: 0x190a02 - public static Result.Base AllocationMemoryFailedInFatFileSystemC => new Result.Base(ModuleFs, 3205); - /// Error code: 2002-3206; Inner value: 0x190c02 - public static Result.Base AllocationMemoryFailedInFatFileSystemD => new Result.Base(ModuleFs, 3206); - /// Error code: 2002-3208; Inner value: 0x191002 - public static Result.Base AllocationMemoryFailedInFatFileSystemE => new Result.Base(ModuleFs, 3208); - /// Error code: 2002-3211; Inner value: 0x191602 - public static Result.Base AllocationMemoryFailedInFileSystemAccessorA => new Result.Base(ModuleFs, 3211); - /// Error code: 2002-3212; Inner value: 0x191802 - public static Result.Base AllocationMemoryFailedInFileSystemAccessorB => new Result.Base(ModuleFs, 3212); - /// Error code: 2002-3213; Inner value: 0x191a02 - public static Result.Base AllocationMemoryFailedInApplicationA => new Result.Base(ModuleFs, 3213); - /// Error code: 2002-3214; Inner value: 0x191c02 - public static Result.Base AllocationMemoryFailedInBcatSaveDataA => new Result.Base(ModuleFs, 3214); - /// Error code: 2002-3215; Inner value: 0x191e02 - public static Result.Base AllocationMemoryFailedInBisA => new Result.Base(ModuleFs, 3215); - /// Error code: 2002-3216; Inner value: 0x192002 - public static Result.Base AllocationMemoryFailedInBisB => new Result.Base(ModuleFs, 3216); - /// Error code: 2002-3217; Inner value: 0x192202 - public static Result.Base AllocationMemoryFailedInBisC => new Result.Base(ModuleFs, 3217); - /// Error code: 2002-3218; Inner value: 0x192402 - public static Result.Base AllocationMemoryFailedInCodeA => new Result.Base(ModuleFs, 3218); - /// Error code: 2002-3219; Inner value: 0x192602 - public static Result.Base AllocationMemoryFailedInContentA => new Result.Base(ModuleFs, 3219); - /// Error code: 2002-3220; Inner value: 0x192802 - public static Result.Base AllocationMemoryFailedInContentStorageA => new Result.Base(ModuleFs, 3220); - /// Error code: 2002-3221; Inner value: 0x192a02 - public static Result.Base AllocationMemoryFailedInContentStorageB => new Result.Base(ModuleFs, 3221); - /// Error code: 2002-3222; Inner value: 0x192c02 - public static Result.Base AllocationMemoryFailedInDataA => new Result.Base(ModuleFs, 3222); - /// Error code: 2002-3223; Inner value: 0x192e02 - public static Result.Base AllocationMemoryFailedInDataB => new Result.Base(ModuleFs, 3223); - /// Error code: 2002-3224; Inner value: 0x193002 - public static Result.Base AllocationMemoryFailedInDeviceSaveDataA => new Result.Base(ModuleFs, 3224); - /// Error code: 2002-3225; Inner value: 0x193202 - public static Result.Base AllocationMemoryFailedInGameCardA => new Result.Base(ModuleFs, 3225); - /// Error code: 2002-3226; Inner value: 0x193402 - public static Result.Base AllocationMemoryFailedInGameCardB => new Result.Base(ModuleFs, 3226); - /// Error code: 2002-3227; Inner value: 0x193602 - public static Result.Base AllocationMemoryFailedInGameCardC => new Result.Base(ModuleFs, 3227); - /// Error code: 2002-3228; Inner value: 0x193802 - public static Result.Base AllocationMemoryFailedInGameCardD => new Result.Base(ModuleFs, 3228); - /// Error code: 2002-3229; Inner value: 0x193a02 - public static Result.Base AllocationMemoryFailedInHostA => new Result.Base(ModuleFs, 3229); - /// Error code: 2002-3230; Inner value: 0x193c02 - public static Result.Base AllocationMemoryFailedInHostB => new Result.Base(ModuleFs, 3230); - /// Error code: 2002-3231; Inner value: 0x193e02 - public static Result.Base AllocationMemoryFailedInHostC => new Result.Base(ModuleFs, 3231); - /// Error code: 2002-3232; Inner value: 0x194002 - public static Result.Base AllocationMemoryFailedInImageDirectoryA => new Result.Base(ModuleFs, 3232); - /// Error code: 2002-3233; Inner value: 0x194202 - public static Result.Base AllocationMemoryFailedInLogoA => new Result.Base(ModuleFs, 3233); - /// Error code: 2002-3234; Inner value: 0x194402 - public static Result.Base AllocationMemoryFailedInRomA => new Result.Base(ModuleFs, 3234); - /// Error code: 2002-3235; Inner value: 0x194602 - public static Result.Base AllocationMemoryFailedInRomB => new Result.Base(ModuleFs, 3235); - /// Error code: 2002-3236; Inner value: 0x194802 - public static Result.Base AllocationMemoryFailedInRomC => new Result.Base(ModuleFs, 3236); - /// Error code: 2002-3237; Inner value: 0x194a02 - public static Result.Base AllocationMemoryFailedInRomD => new Result.Base(ModuleFs, 3237); - /// Error code: 2002-3238; Inner value: 0x194c02 - public static Result.Base AllocationMemoryFailedInRomE => new Result.Base(ModuleFs, 3238); - /// Error code: 2002-3239; Inner value: 0x194e02 - public static Result.Base AllocationMemoryFailedInRomF => new Result.Base(ModuleFs, 3239); - /// Error code: 2002-3242; Inner value: 0x195402 - public static Result.Base AllocationMemoryFailedInSaveDataManagementA => new Result.Base(ModuleFs, 3242); - /// Error code: 2002-3243; Inner value: 0x195602 - public static Result.Base AllocationMemoryFailedInSaveDataThumbnailA => new Result.Base(ModuleFs, 3243); - /// Error code: 2002-3244; Inner value: 0x195802 - public static Result.Base AllocationMemoryFailedInSdCardA => new Result.Base(ModuleFs, 3244); - /// Error code: 2002-3245; Inner value: 0x195a02 - public static Result.Base AllocationMemoryFailedInSdCardB => new Result.Base(ModuleFs, 3245); - /// Error code: 2002-3246; Inner value: 0x195c02 - public static Result.Base AllocationMemoryFailedInSystemSaveDataA => new Result.Base(ModuleFs, 3246); - /// Error code: 2002-3247; Inner value: 0x195e02 - public static Result.Base AllocationMemoryFailedInRomFsFileSystemA => new Result.Base(ModuleFs, 3247); - /// Error code: 2002-3248; Inner value: 0x196002 - public static Result.Base AllocationMemoryFailedInRomFsFileSystemB => new Result.Base(ModuleFs, 3248); - /// Error code: 2002-3249; Inner value: 0x196202 - public static Result.Base AllocationMemoryFailedInRomFsFileSystemC => new Result.Base(ModuleFs, 3249); - /// Error code: 2002-3251; Inner value: 0x196602 - public static Result.Base AllocationMemoryFailedInGuidPartitionTableA => new Result.Base(ModuleFs, 3251); - /// Error code: 2002-3252; Inner value: 0x196802 - public static Result.Base AllocationMemoryFailedInDeviceDetectionEventManagerA => new Result.Base(ModuleFs, 3252); - /// Error code: 2002-3253; Inner value: 0x196a02 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemServiceImplA => new Result.Base(ModuleFs, 3253); - /// Error code: 2002-3254; Inner value: 0x196c02 - public static Result.Base AllocationMemoryFailedInFileSystemProxyCoreImplB => new Result.Base(ModuleFs, 3254); - /// Error code: 2002-3255; Inner value: 0x196e02 - public static Result.Base AllocationMemoryFailedInSdCardProxyFileSystemCreatorA => new Result.Base(ModuleFs, 3255); - /// In ParseNsp allocating FileStorageBasedFileSystem
Error code: 2002-3256; Inner value: 0x197002
- public static Result.Base AllocationMemoryFailedInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 3256); - /// In ParseNca allocating FileStorageBasedFileSystem
Error code: 2002-3257; Inner value: 0x197202
- public static Result.Base AllocationMemoryFailedInNcaFileSystemServiceImplB => new Result.Base(ModuleFs, 3257); - /// In RegisterProgram allocating ProgramInfoNode
Error code: 2002-3258; Inner value: 0x197402
- public static Result.Base AllocationMemoryFailedInProgramRegistryManagerA => new Result.Base(ModuleFs, 3258); - /// Error code: 2002-3259; Inner value: 0x197602 - public static Result.Base AllocationMemoryFailedInSdmmcStorageServiceA => new Result.Base(ModuleFs, 3259); - /// Error code: 2002-3260; Inner value: 0x197802 - public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorA => new Result.Base(ModuleFs, 3260); - /// Error code: 2002-3261; Inner value: 0x197a02 - public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorB => new Result.Base(ModuleFs, 3261); - /// Error code: 2002-3262; Inner value: 0x197c02 - public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorC => new Result.Base(ModuleFs, 3262); - /// In Initialize allocating ProgramInfoNode
Error code: 2002-3264; Inner value: 0x198002
- public static Result.Base AllocationMemoryFailedFatFileSystemWithBufferA => new Result.Base(ModuleFs, 3264); - /// Error code: 2002-3265; Inner value: 0x198202 - public static Result.Base AllocationMemoryFailedInFatFileSystemCreatorA => new Result.Base(ModuleFs, 3265); - /// Error code: 2002-3266; Inner value: 0x198402 - public static Result.Base AllocationMemoryFailedInFatFileSystemCreatorB => new Result.Base(ModuleFs, 3266); - /// Error code: 2002-3267; Inner value: 0x198602 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorA => new Result.Base(ModuleFs, 3267); - /// Error code: 2002-3268; Inner value: 0x198802 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorB => new Result.Base(ModuleFs, 3268); - /// Error code: 2002-3269; Inner value: 0x198a02 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorC => new Result.Base(ModuleFs, 3269); - /// Error code: 2002-3270; Inner value: 0x198c02 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorD => new Result.Base(ModuleFs, 3270); - /// Error code: 2002-3271; Inner value: 0x198e02 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorE => new Result.Base(ModuleFs, 3271); - /// Error code: 2002-3272; Inner value: 0x199002 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorF => new Result.Base(ModuleFs, 3272); - /// Error code: 2002-3273; Inner value: 0x199202 - public static Result.Base AllocationMemoryFailedInGameCardManagerA => new Result.Base(ModuleFs, 3273); - /// Error code: 2002-3274; Inner value: 0x199402 - public static Result.Base AllocationMemoryFailedInGameCardManagerB => new Result.Base(ModuleFs, 3274); - /// Error code: 2002-3275; Inner value: 0x199602 - public static Result.Base AllocationMemoryFailedInGameCardManagerC => new Result.Base(ModuleFs, 3275); - /// Error code: 2002-3276; Inner value: 0x199802 - public static Result.Base AllocationMemoryFailedInGameCardManagerD => new Result.Base(ModuleFs, 3276); - /// Error code: 2002-3277; Inner value: 0x199a02 - public static Result.Base AllocationMemoryFailedInGameCardManagerE => new Result.Base(ModuleFs, 3277); - /// Error code: 2002-3278; Inner value: 0x199c02 - public static Result.Base AllocationMemoryFailedInGameCardManagerF => new Result.Base(ModuleFs, 3278); - /// Error code: 2002-3279; Inner value: 0x199e02 - public static Result.Base AllocationMemoryFailedInLocalFileSystemCreatorA => new Result.Base(ModuleFs, 3279); - /// In Create allocating PartitionFileSystemCore
Error code: 2002-3280; Inner value: 0x19a002
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemCreatorA => new Result.Base(ModuleFs, 3280); - /// Error code: 2002-3281; Inner value: 0x19a202 - public static Result.Base AllocationMemoryFailedInRomFileSystemCreatorA => new Result.Base(ModuleFs, 3281); - /// Error code: 2002-3282; Inner value: 0x19a402 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorA => new Result.Base(ModuleFs, 3282); - /// Error code: 2002-3283; Inner value: 0x19a602 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorB => new Result.Base(ModuleFs, 3283); - /// Error code: 2002-3284; Inner value: 0x19a802 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorC => new Result.Base(ModuleFs, 3284); - /// Error code: 2002-3285; Inner value: 0x19aa02 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorD => new Result.Base(ModuleFs, 3285); - /// Error code: 2002-3286; Inner value: 0x19ac02 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorE => new Result.Base(ModuleFs, 3286); - /// Error code: 2002-3288; Inner value: 0x19b002 - public static Result.Base AllocationMemoryFailedInStorageOnNcaCreatorA => new Result.Base(ModuleFs, 3288); - /// Error code: 2002-3289; Inner value: 0x19b202 - public static Result.Base AllocationMemoryFailedInStorageOnNcaCreatorB => new Result.Base(ModuleFs, 3289); - /// Error code: 2002-3290; Inner value: 0x19b402 - public static Result.Base AllocationMemoryFailedInSubDirectoryFileSystemCreatorA => new Result.Base(ModuleFs, 3290); - /// Error code: 2002-3291; Inner value: 0x19b602 - public static Result.Base AllocationMemoryFailedInTargetManagerFileSystemCreatorA => new Result.Base(ModuleFs, 3291); - /// Error code: 2002-3292; Inner value: 0x19b802 - public static Result.Base AllocationMemoryFailedInSaveDataIndexerA => new Result.Base(ModuleFs, 3292); - /// Error code: 2002-3293; Inner value: 0x19ba02 - public static Result.Base AllocationMemoryFailedInSaveDataIndexerB => new Result.Base(ModuleFs, 3293); - /// Error code: 2002-3294; Inner value: 0x19bc02 - public static Result.Base AllocationMemoryFailedInFileSystemBuddyHeapA => new Result.Base(ModuleFs, 3294); - /// Error code: 2002-3295; Inner value: 0x19be02 - public static Result.Base AllocationMemoryFailedInFileSystemBufferManagerA => new Result.Base(ModuleFs, 3295); - /// Error code: 2002-3296; Inner value: 0x19c002 - public static Result.Base AllocationMemoryFailedInBlockCacheBufferedStorageA => new Result.Base(ModuleFs, 3296); - /// Error code: 2002-3297; Inner value: 0x19c202 - public static Result.Base AllocationMemoryFailedInBlockCacheBufferedStorageB => new Result.Base(ModuleFs, 3297); - /// Error code: 2002-3298; Inner value: 0x19c402 - public static Result.Base AllocationMemoryFailedInDuplexStorageA => new Result.Base(ModuleFs, 3298); - /// Error code: 2002-3304; Inner value: 0x19d002 - public static Result.Base AllocationMemoryFailedInIntegrityVerificationStorageA => new Result.Base(ModuleFs, 3304); - /// Error code: 2002-3305; Inner value: 0x19d202 - public static Result.Base AllocationMemoryFailedInIntegrityVerificationStorageB => new Result.Base(ModuleFs, 3305); - /// Error code: 2002-3306; Inner value: 0x19d402 - public static Result.Base AllocationMemoryFailedInJournalStorageA => new Result.Base(ModuleFs, 3306); - /// Error code: 2002-3307; Inner value: 0x19d602 - public static Result.Base AllocationMemoryFailedInJournalStorageB => new Result.Base(ModuleFs, 3307); - /// Error code: 2002-3310; Inner value: 0x19dc02 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCoreA => new Result.Base(ModuleFs, 3310); - /// Error code: 2002-3311; Inner value: 0x19de02 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCoreB => new Result.Base(ModuleFs, 3311); - /// In Initialize allocating FileStorage
Error code: 2002-3312; Inner value: 0x19e002
- public static Result.Base AllocationMemoryFailedInAesXtsFileA => new Result.Base(ModuleFs, 3312); - /// In Initialize allocating AesXtsStorage
Error code: 2002-3313; Inner value: 0x19e202
- public static Result.Base AllocationMemoryFailedInAesXtsFileB => new Result.Base(ModuleFs, 3313); - /// In Initialize allocating AlignmentMatchingStoragePooledBuffer
Error code: 2002-3314; Inner value: 0x19e402
- public static Result.Base AllocationMemoryFailedInAesXtsFileC => new Result.Base(ModuleFs, 3314); - /// In Initialize allocating StorageFile
Error code: 2002-3315; Inner value: 0x19e602
- public static Result.Base AllocationMemoryFailedInAesXtsFileD => new Result.Base(ModuleFs, 3315); - /// Error code: 2002-3316; Inner value: 0x19e802 - public static Result.Base AllocationMemoryFailedInAesXtsFileSystemA => new Result.Base(ModuleFs, 3316); - /// Error code: 2002-3319; Inner value: 0x19ee02 - public static Result.Base AllocationMemoryFailedInConcatenationFileSystemA => new Result.Base(ModuleFs, 3319); - /// Error code: 2002-3320; Inner value: 0x19f002 - public static Result.Base AllocationMemoryFailedInConcatenationFileSystemB => new Result.Base(ModuleFs, 3320); - /// Error code: 2002-3321; Inner value: 0x19f202 - public static Result.Base AllocationMemoryFailedInDirectorySaveDataFileSystemA => new Result.Base(ModuleFs, 3321); - /// Error code: 2002-3322; Inner value: 0x19f402 - public static Result.Base AllocationMemoryFailedInLocalFileSystemA => new Result.Base(ModuleFs, 3322); - /// Error code: 2002-3323; Inner value: 0x19f602 - public static Result.Base AllocationMemoryFailedInLocalFileSystemB => new Result.Base(ModuleFs, 3323); - /// Error code: 2002-3341; Inner value: 0x1a1a02 - public static Result.Base AllocationMemoryFailedInNcaFileSystemDriverI => new Result.Base(ModuleFs, 3341); - /// In Initialize allocating PartitionFileSystemMetaCore
Error code: 2002-3347; Inner value: 0x1a2602
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemA => new Result.Base(ModuleFs, 3347); - /// In DoOpenFile allocating PartitionFile
Error code: 2002-3348; Inner value: 0x1a2802
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemB => new Result.Base(ModuleFs, 3348); - /// In DoOpenDirectory allocating PartitionDirectory
Error code: 2002-3349; Inner value: 0x1a2a02
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemC => new Result.Base(ModuleFs, 3349); - /// In Initialize allocating metadata buffer
Error code: 2002-3350; Inner value: 0x1a2c02
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemMetaA => new Result.Base(ModuleFs, 3350); - /// In Sha256 Initialize allocating metadata buffer
Error code: 2002-3351; Inner value: 0x1a2e02
- public static Result.Base AllocationMemoryFailedInPartitionFileSystemMetaB => new Result.Base(ModuleFs, 3351); - /// Error code: 2002-3352; Inner value: 0x1a3002 - public static Result.Base AllocationMemoryFailedInRomFsFileSystemD => new Result.Base(ModuleFs, 3352); - /// In Initialize allocating RootPathBuffer
Error code: 2002-3355; Inner value: 0x1a3602
- public static Result.Base AllocationMemoryFailedInSubdirectoryFileSystemA => new Result.Base(ModuleFs, 3355); - /// Error code: 2002-3356; Inner value: 0x1a3802 - public static Result.Base AllocationMemoryFailedInTmFileSystemA => new Result.Base(ModuleFs, 3356); - /// Error code: 2002-3357; Inner value: 0x1a3a02 - public static Result.Base AllocationMemoryFailedInTmFileSystemB => new Result.Base(ModuleFs, 3357); - /// Error code: 2002-3359; Inner value: 0x1a3e02 - public static Result.Base AllocationMemoryFailedInProxyFileSystemA => new Result.Base(ModuleFs, 3359); - /// Error code: 2002-3360; Inner value: 0x1a4002 - public static Result.Base AllocationMemoryFailedInProxyFileSystemB => new Result.Base(ModuleFs, 3360); - /// Error code: 2002-3362; Inner value: 0x1a4402 - public static Result.Base AllocationMemoryFailedInSaveDataExtraDataAccessorCacheManagerA => new Result.Base(ModuleFs, 3362); - /// Error code: 2002-3363; Inner value: 0x1a4602 - public static Result.Base AllocationMemoryFailedInNcaReaderA => new Result.Base(ModuleFs, 3363); - /// Error code: 2002-3365; Inner value: 0x1a4a02 - public static Result.Base AllocationMemoryFailedInRegisterA => new Result.Base(ModuleFs, 3365); - /// Error code: 2002-3366; Inner value: 0x1a4c02 - public static Result.Base AllocationMemoryFailedInRegisterB => new Result.Base(ModuleFs, 3366); - /// Error code: 2002-3367; Inner value: 0x1a4e02 - public static Result.Base AllocationMemoryFailedInPathNormalizer => new Result.Base(ModuleFs, 3367); - /// Error code: 2002-3375; Inner value: 0x1a5e02 - public static Result.Base AllocationMemoryFailedInDbmRomKeyValueStorage => new Result.Base(ModuleFs, 3375); - /// Error code: 2002-3376; Inner value: 0x1a6002 - public static Result.Base AllocationMemoryFailedInDbmHierarchicalRomFileTable => new Result.Base(ModuleFs, 3376); - /// Error code: 2002-3377; Inner value: 0x1a6202 - public static Result.Base AllocationMemoryFailedInRomFsFileSystemE => new Result.Base(ModuleFs, 3377); - /// Error code: 2002-3378; Inner value: 0x1a6402 - public static Result.Base AllocationMemoryFailedInISaveFileSystemA => new Result.Base(ModuleFs, 3378); - /// Error code: 2002-3379; Inner value: 0x1a6602 - public static Result.Base AllocationMemoryFailedInISaveFileSystemB => new Result.Base(ModuleFs, 3379); - /// Error code: 2002-3380; Inner value: 0x1a6802 - public static Result.Base AllocationMemoryFailedInRomOnFileA => new Result.Base(ModuleFs, 3380); - /// Error code: 2002-3381; Inner value: 0x1a6a02 - public static Result.Base AllocationMemoryFailedInRomOnFileB => new Result.Base(ModuleFs, 3381); - /// Error code: 2002-3382; Inner value: 0x1a6c02 - public static Result.Base AllocationMemoryFailedInRomOnFileC => new Result.Base(ModuleFs, 3382); - /// In Initialize
Error code: 2002-3383; Inner value: 0x1a6e02
- public static Result.Base AllocationMemoryFailedInAesXtsFileE => new Result.Base(ModuleFs, 3383); - /// Error code: 2002-3384; Inner value: 0x1a7002 - public static Result.Base AllocationMemoryFailedInAesXtsFileSystemB => new Result.Base(ModuleFs, 3384); - /// Error code: 2002-3385; Inner value: 0x1a7202 - public static Result.Base AllocationMemoryFailedInAesXtsFileSystemC => new Result.Base(ModuleFs, 3385); - /// Error code: 2002-3386; Inner value: 0x1a7402 - public static Result.Base AllocationMemoryFailedInReadOnlyFileSystemA => new Result.Base(ModuleFs, 3386); - /// In Create allocating AesXtsFileSystem
Error code: 2002-3394; Inner value: 0x1a8402
- public static Result.Base AllocationMemoryFailedInEncryptedFileSystemCreatorA => new Result.Base(ModuleFs, 3394); - /// Error code: 2002-3399; Inner value: 0x1a8e02 - public static Result.Base AllocationMemoryFailedInAesCtrCounterExtendedStorageA => new Result.Base(ModuleFs, 3399); - /// Error code: 2002-3400; Inner value: 0x1a9002 - public static Result.Base AllocationMemoryFailedInAesCtrCounterExtendedStorageB => new Result.Base(ModuleFs, 3400); - /// Error code: 2002-3406; Inner value: 0x1a9c02 - public static Result.Base AllocationMemoryFailedInSdmmcStorageServiceB => new Result.Base(ModuleFs, 3406); - /// In OpenFile or OpenDirectory
Error code: 2002-3407; Inner value: 0x1a9e02
- public static Result.Base AllocationMemoryFailedInFileSystemInterfaceAdapter => new Result.Base(ModuleFs, 3407); - /// Error code: 2002-3408; Inner value: 0x1aa002 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorG => new Result.Base(ModuleFs, 3408); - /// Error code: 2002-3409; Inner value: 0x1aa202 - public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorH => new Result.Base(ModuleFs, 3409); - /// Error code: 2002-3411; Inner value: 0x1aa602 - public static Result.Base AllocationMemoryFailedInBufferedStorageA => new Result.Base(ModuleFs, 3411); - /// Error code: 2002-3412; Inner value: 0x1aa802 - public static Result.Base AllocationMemoryFailedInIntegrityRomFsStorageA => new Result.Base(ModuleFs, 3412); - /// Error code: 2002-3416; Inner value: 0x1ab002 - public static Result.Base AllocationMemoryFailedInSaveDataFileSystemServiceImplB => new Result.Base(ModuleFs, 3416); - /// Error code: 2002-3420; Inner value: 0x1ab802 - public static Result.Base AllocationMemoryFailedNew => new Result.Base(ModuleFs, 3420); - /// Error code: 2002-3421; Inner value: 0x1aba02 - public static Result.Base AllocationMemoryFailedCreateShared => new Result.Base(ModuleFs, 3421); - /// Error code: 2002-3422; Inner value: 0x1abc02 - public static Result.Base AllocationMemoryFailedMakeUnique => new Result.Base(ModuleFs, 3422); - /// Error code: 2002-3423; Inner value: 0x1abe02 - public static Result.Base AllocationMemoryFailedAllocateShared => new Result.Base(ModuleFs, 3423); - /// Error code: 2002-3424; Inner value: 0x1ac002 - public static Result.Base AllocationPooledBufferNotEnoughSize => new Result.Base(ModuleFs, 3424); - /// Error code: 2002-3428; Inner value: 0x1ac802 - public static Result.Base AllocationMemoryFailedInWriteThroughCacheStorageA => new Result.Base(ModuleFs, 3428); - /// Error code: 2002-3429; Inner value: 0x1aca02 - public static Result.Base AllocationMemoryFailedInSaveDataTransferManagerA => new Result.Base(ModuleFs, 3429); - /// Error code: 2002-3430; Inner value: 0x1acc02 - public static Result.Base AllocationMemoryFailedInSaveDataTransferManagerB => new Result.Base(ModuleFs, 3430); - /// Error code: 2002-3431; Inner value: 0x1ace02 - public static Result.Base AllocationMemoryFailedInHtcFileSystemA => new Result.Base(ModuleFs, 3431); - /// Error code: 2002-3432; Inner value: 0x1ad002 - public static Result.Base AllocationMemoryFailedInHtcFileSystemB => new Result.Base(ModuleFs, 3432); - /// Error code: 2002-3433; Inner value: 0x1ad202 - public static Result.Base AllocationMemoryFailedInGameCardManagerG => new Result.Base(ModuleFs, 3433); - - /// Error code: 2002-3500; Range: 3500-3999; Inner value: 0x1b5802 - public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3500, 3999); } - /// Error code: 2002-3501; Inner value: 0x1b5a02 - public static Result.Base PortMmcNoDevice => new Result.Base(ModuleFs, 3501); - /// Error code: 2002-3502; Inner value: 0x1b5c02 - public static Result.Base PortMmcNotActivated => new Result.Base(ModuleFs, 3502); - /// Error code: 2002-3503; Inner value: 0x1b5e02 - public static Result.Base PortMmcDeviceRemoved => new Result.Base(ModuleFs, 3503); - /// Error code: 2002-3504; Inner value: 0x1b6002 - public static Result.Base PortMmcNotAwakened => new Result.Base(ModuleFs, 3504); - - /// Error code: 2002-3532; Range: 3532-3626; Inner value: 0x1b9802 - public static Result.Base PortMmcCommunicationError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3532, 3626); } - /// Error code: 2002-3533; Range: 3533-3546; Inner value: 0x1b9a02 - public static Result.Base PortMmcCommunicationNotAttained { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3533, 3546); } - /// Error code: 2002-3534; Inner value: 0x1b9c02 - public static Result.Base PortMmcResponseIndexError => new Result.Base(ModuleFs, 3534); - /// Error code: 2002-3535; Inner value: 0x1b9e02 - public static Result.Base PortMmcResponseEndBitError => new Result.Base(ModuleFs, 3535); - /// Error code: 2002-3536; Inner value: 0x1ba002 - public static Result.Base PortMmcResponseCrcError => new Result.Base(ModuleFs, 3536); - /// Error code: 2002-3537; Inner value: 0x1ba202 - public static Result.Base PortMmcResponseTimeoutError => new Result.Base(ModuleFs, 3537); - /// Error code: 2002-3538; Inner value: 0x1ba402 - public static Result.Base PortMmcDataEndBitError => new Result.Base(ModuleFs, 3538); - /// Error code: 2002-3539; Inner value: 0x1ba602 - public static Result.Base PortMmcDataCrcError => new Result.Base(ModuleFs, 3539); - /// Error code: 2002-3540; Inner value: 0x1ba802 - public static Result.Base PortMmcDataTimeoutError => new Result.Base(ModuleFs, 3540); - /// Error code: 2002-3541; Inner value: 0x1baa02 - public static Result.Base PortMmcAutoCommandResponseIndexError => new Result.Base(ModuleFs, 3541); - /// Error code: 2002-3542; Inner value: 0x1bac02 - public static Result.Base PortMmcAutoCommandResponseEndBitError => new Result.Base(ModuleFs, 3542); - /// Error code: 2002-3543; Inner value: 0x1bae02 - public static Result.Base PortMmcAutoCommandResponseCrcError => new Result.Base(ModuleFs, 3543); - /// Error code: 2002-3544; Inner value: 0x1bb002 - public static Result.Base PortMmcAutoCommandResponseTimeoutError => new Result.Base(ModuleFs, 3544); - /// Error code: 2002-3545; Inner value: 0x1bb202 - public static Result.Base PortMmcCommandCompleteSoftwareTimeout => new Result.Base(ModuleFs, 3545); - /// Error code: 2002-3546; Inner value: 0x1bb402 - public static Result.Base PortMmcTransferCompleteSoftwareTimeout => new Result.Base(ModuleFs, 3546); - - /// Error code: 2002-3548; Range: 3548-3570; Inner value: 0x1bb802 - public static Result.Base PortMmcDeviceStatusHasError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3548, 3570); } - /// Error code: 2002-3549; Inner value: 0x1bba02 - public static Result.Base PortMmcDeviceStatusAddressOutOfRange => new Result.Base(ModuleFs, 3549); - /// Error code: 2002-3550; Inner value: 0x1bbc02 - public static Result.Base PortMmcDeviceStatusAddressMisaligned => new Result.Base(ModuleFs, 3550); - /// Error code: 2002-3551; Inner value: 0x1bbe02 - public static Result.Base PortMmcDeviceStatusBlockLenError => new Result.Base(ModuleFs, 3551); - /// Error code: 2002-3552; Inner value: 0x1bc002 - public static Result.Base PortMmcDeviceStatusEraseSeqError => new Result.Base(ModuleFs, 3552); - /// Error code: 2002-3553; Inner value: 0x1bc202 - public static Result.Base PortMmcDeviceStatusEraseParam => new Result.Base(ModuleFs, 3553); - /// Error code: 2002-3554; Inner value: 0x1bc402 - public static Result.Base PortMmcDeviceStatusWpViolation => new Result.Base(ModuleFs, 3554); - /// Error code: 2002-3555; Inner value: 0x1bc602 - public static Result.Base PortMmcDeviceStatusLockUnlockFailed => new Result.Base(ModuleFs, 3555); - /// Error code: 2002-3556; Inner value: 0x1bc802 - public static Result.Base PortMmcDeviceStatusComCrcError => new Result.Base(ModuleFs, 3556); - /// Error code: 2002-3557; Inner value: 0x1bca02 - public static Result.Base PortMmcDeviceStatusIllegalCommand => new Result.Base(ModuleFs, 3557); - /// Error code: 2002-3558; Inner value: 0x1bcc02 - public static Result.Base PortMmcDeviceStatusDeviceEccFailed => new Result.Base(ModuleFs, 3558); - /// Error code: 2002-3559; Inner value: 0x1bce02 - public static Result.Base PortMmcDeviceStatusCcError => new Result.Base(ModuleFs, 3559); - /// Error code: 2002-3560; Inner value: 0x1bd002 - public static Result.Base PortMmcDeviceStatusError => new Result.Base(ModuleFs, 3560); - /// Error code: 2002-3561; Inner value: 0x1bd202 - public static Result.Base PortMmcDeviceStatusCidCsdOverwrite => new Result.Base(ModuleFs, 3561); - /// Error code: 2002-3562; Inner value: 0x1bd402 - public static Result.Base PortMmcDeviceStatusWpEraseSkip => new Result.Base(ModuleFs, 3562); - /// Error code: 2002-3563; Inner value: 0x1bd602 - public static Result.Base PortMmcDeviceStatusEraseReset => new Result.Base(ModuleFs, 3563); - /// Error code: 2002-3564; Inner value: 0x1bd802 - public static Result.Base PortMmcDeviceStatusSwitchError => new Result.Base(ModuleFs, 3564); - - /// Error code: 2002-3572; Inner value: 0x1be802 - public static Result.Base PortMmcUnexpectedDeviceState => new Result.Base(ModuleFs, 3572); - /// Error code: 2002-3573; Inner value: 0x1bea02 - public static Result.Base PortMmcUnexpectedDeviceCsdValue => new Result.Base(ModuleFs, 3573); - /// Error code: 2002-3574; Inner value: 0x1bec02 - public static Result.Base PortMmcAbortTransactionSoftwareTimeout => new Result.Base(ModuleFs, 3574); - /// Error code: 2002-3575; Inner value: 0x1bee02 - public static Result.Base PortMmcCommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleFs, 3575); - /// Error code: 2002-3576; Inner value: 0x1bf002 - public static Result.Base PortMmcCommandInhibitDatSoftwareTimeout => new Result.Base(ModuleFs, 3576); - /// Error code: 2002-3577; Inner value: 0x1bf202 - public static Result.Base PortMmcBusySoftwareTimeout => new Result.Base(ModuleFs, 3577); - /// Error code: 2002-3578; Inner value: 0x1bf402 - public static Result.Base PortMmcIssueTuningCommandSoftwareTimeout => new Result.Base(ModuleFs, 3578); - /// Error code: 2002-3579; Inner value: 0x1bf602 - public static Result.Base PortMmcTuningFailed => new Result.Base(ModuleFs, 3579); - /// Error code: 2002-3580; Inner value: 0x1bf802 - public static Result.Base PortMmcMmcInitializationSoftwareTimeout => new Result.Base(ModuleFs, 3580); - /// Error code: 2002-3581; Inner value: 0x1bfa02 - public static Result.Base PortMmcMmcNotSupportExtendedCsd => new Result.Base(ModuleFs, 3581); - /// Error code: 2002-3582; Inner value: 0x1bfc02 - public static Result.Base PortMmcUnexpectedMmcExtendedCsdValue => new Result.Base(ModuleFs, 3582); - /// Error code: 2002-3583; Inner value: 0x1bfe02 - public static Result.Base PortMmcMmcEraseSoftwareTimeout => new Result.Base(ModuleFs, 3583); - /// Error code: 2002-3584; Inner value: 0x1c0002 - public static Result.Base PortMmcSdCardValidationError => new Result.Base(ModuleFs, 3584); - /// Error code: 2002-3585; Inner value: 0x1c0202 - public static Result.Base PortMmcSdCardInitializationSoftwareTimeout => new Result.Base(ModuleFs, 3585); - /// Error code: 2002-3586; Inner value: 0x1c0402 - public static Result.Base PortMmcSdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleFs, 3586); - /// Error code: 2002-3587; Inner value: 0x1c0602 - public static Result.Base PortMmcUnexpectedSdCardAcmdDisabled => new Result.Base(ModuleFs, 3587); - /// Error code: 2002-3588; Inner value: 0x1c0802 - public static Result.Base PortMmcSdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleFs, 3588); - /// Error code: 2002-3589; Inner value: 0x1c0a02 - public static Result.Base PortMmcUnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleFs, 3589); - /// Error code: 2002-3590; Inner value: 0x1c0c02 - public static Result.Base PortMmcSdCardNotSupportAccessMode => new Result.Base(ModuleFs, 3590); - /// Error code: 2002-3591; Inner value: 0x1c0e02 - public static Result.Base PortMmcSdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleFs, 3591); - /// Error code: 2002-3592; Inner value: 0x1c1002 - public static Result.Base PortMmcSdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleFs, 3592); - /// Error code: 2002-3593; Inner value: 0x1c1202 - public static Result.Base PortMmcSdCardCannotSwitchAccessMode => new Result.Base(ModuleFs, 3593); - /// Error code: 2002-3594; Inner value: 0x1c1402 - public static Result.Base PortMmcSdCardFailedSwitchAccessMode => new Result.Base(ModuleFs, 3594); - /// Error code: 2002-3595; Inner value: 0x1c1602 - public static Result.Base PortMmcSdCardUnacceptableCurrentConsumption => new Result.Base(ModuleFs, 3595); - /// Error code: 2002-3596; Inner value: 0x1c1802 - public static Result.Base PortMmcSdCardNotReadyToVoltageSwitch => new Result.Base(ModuleFs, 3596); - /// Error code: 2002-3597; Inner value: 0x1c1a02 - public static Result.Base PortMmcSdCardNotCompleteVoltageSwitch => new Result.Base(ModuleFs, 3597); - - /// Error code: 2002-3628; Range: 3628-3658; Inner value: 0x1c5802 - public static Result.Base PortMmcHostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3628, 3658); } - /// Error code: 2002-3629; Inner value: 0x1c5a02 - public static Result.Base PortMmcInternalClockStableSoftwareTimeout => new Result.Base(ModuleFs, 3629); - /// Error code: 2002-3630; Inner value: 0x1c5c02 - public static Result.Base PortMmcSdHostStandardUnknownAutoCmdError => new Result.Base(ModuleFs, 3630); - /// Error code: 2002-3631; Inner value: 0x1c5e02 - public static Result.Base PortMmcSdHostStandardUnknownError => new Result.Base(ModuleFs, 3631); - /// Error code: 2002-3632; Inner value: 0x1c6002 - public static Result.Base PortMmcSdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleFs, 3632); - /// Error code: 2002-3633; Inner value: 0x1c6202 - public static Result.Base PortMmcSdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleFs, 3633); - /// Error code: 2002-3634; Inner value: 0x1c6402 - public static Result.Base PortMmcSdHostStandardFailSwitchTo18V => new Result.Base(ModuleFs, 3634); - - /// Error code: 2002-3660; Range: 3660-3690; Inner value: 0x1c9802 - public static Result.Base PortMmcInternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3660, 3690); } - /// Error code: 2002-3661; Inner value: 0x1c9a02 - public static Result.Base PortMmcNoWaitedInterrupt => new Result.Base(ModuleFs, 3661); - /// Error code: 2002-3662; Inner value: 0x1c9c02 - public static Result.Base PortMmcWaitInterruptSoftwareTimeout => new Result.Base(ModuleFs, 3662); - - /// Error code: 2002-3692; Inner value: 0x1cd802 - public static Result.Base PortMmcAbortCommandIssued => new Result.Base(ModuleFs, 3692); - /// Error code: 2002-3700; Inner value: 0x1ce802 - public static Result.Base PortMmcNotSupported => new Result.Base(ModuleFs, 3700); - /// Error code: 2002-3701; Inner value: 0x1cea02 - public static Result.Base PortMmcNotImplemented => new Result.Base(ModuleFs, 3701); - /// Error code: 2002-3998; Inner value: 0x1f3c02 - public static Result.Base Result3998 => new Result.Base(ModuleFs, 3998); - /// Error code: 2002-3999; Inner value: 0x1f3e02 - public static Result.Base PortMmcUnexpected => new Result.Base(ModuleFs, 3999); - - /// Error code: 2002-4000; Range: 4000-4999; Inner value: 0x1f4002 - public static Result.Base DataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4000, 4999); } - /// Error code: 2002-4001; Range: 4001-4299; Inner value: 0x1f4202 - public static Result.Base RomCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4001, 4299); } - /// Error code: 2002-4002; Inner value: 0x1f4402 - public static Result.Base UnsupportedRomVersion => new Result.Base(ModuleFs, 4002); - - /// Error code: 2002-4011; Range: 4011-4019; Inner value: 0x1f5602 - public static Result.Base AesCtrCounterExtendedStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4011, 4019); } - /// Error code: 2002-4012; Inner value: 0x1f5802 - public static Result.Base InvalidAesCtrCounterExtendedEntryOffset => new Result.Base(ModuleFs, 4012); - /// Error code: 2002-4013; Inner value: 0x1f5a02 - public static Result.Base InvalidAesCtrCounterExtendedTableSize => new Result.Base(ModuleFs, 4013); - /// Error code: 2002-4014; Inner value: 0x1f5c02 - public static Result.Base InvalidAesCtrCounterExtendedGeneration => new Result.Base(ModuleFs, 4014); - /// Error code: 2002-4015; Inner value: 0x1f5e02 - public static Result.Base InvalidAesCtrCounterExtendedOffset => new Result.Base(ModuleFs, 4015); - /// Error code: 2002-4016; Inner value: 0x1f6002 - public static Result.Base Result4016 => new Result.Base(ModuleFs, 4016); - - /// Error code: 2002-4021; Range: 4021-4029; Inner value: 0x1f6a02 - public static Result.Base IndirectStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4021, 4029); } - /// Error code: 2002-4022; Inner value: 0x1f6c02 - public static Result.Base InvalidIndirectEntryOffset => new Result.Base(ModuleFs, 4022); - /// Error code: 2002-4023; Inner value: 0x1f6e02 - public static Result.Base InvalidIndirectEntryStorageIndex => new Result.Base(ModuleFs, 4023); - /// Error code: 2002-4024; Inner value: 0x1f7002 - public static Result.Base InvalidIndirectStorageSize => new Result.Base(ModuleFs, 4024); - /// Error code: 2002-4025; Inner value: 0x1f7202 - public static Result.Base InvalidIndirectVirtualOffset => new Result.Base(ModuleFs, 4025); - /// Error code: 2002-4026; Inner value: 0x1f7402 - public static Result.Base InvalidIndirectPhysicalOffset => new Result.Base(ModuleFs, 4026); - /// Error code: 2002-4027; Inner value: 0x1f7602 - public static Result.Base InvalidIndirectStorageIndex => new Result.Base(ModuleFs, 4027); - /// Error code: 2002-4028; Inner value: 0x1f7802 - public static Result.Base InvalidIndirectStorageBucketTreeSize => new Result.Base(ModuleFs, 4028); - - /// Error code: 2002-4031; Range: 4031-4039; Inner value: 0x1f7e02 - public static Result.Base BucketTreeCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4031, 4039); } - /// Error code: 2002-4032; Inner value: 0x1f8002 - public static Result.Base InvalidBucketTreeSignature => new Result.Base(ModuleFs, 4032); - /// Error code: 2002-4033; Inner value: 0x1f8202 - public static Result.Base InvalidBucketTreeEntryCount => new Result.Base(ModuleFs, 4033); - /// Error code: 2002-4034; Inner value: 0x1f8402 - public static Result.Base InvalidBucketTreeNodeEntryCount => new Result.Base(ModuleFs, 4034); - /// Error code: 2002-4035; Inner value: 0x1f8602 - public static Result.Base InvalidBucketTreeNodeOffset => new Result.Base(ModuleFs, 4035); - /// Error code: 2002-4036; Inner value: 0x1f8802 - public static Result.Base InvalidBucketTreeEntryOffset => new Result.Base(ModuleFs, 4036); - /// Error code: 2002-4037; Inner value: 0x1f8a02 - public static Result.Base InvalidBucketTreeEntrySetOffset => new Result.Base(ModuleFs, 4037); - /// Error code: 2002-4038; Inner value: 0x1f8c02 - public static Result.Base InvalidBucketTreeNodeIndex => new Result.Base(ModuleFs, 4038); - /// Error code: 2002-4039; Inner value: 0x1f8e02 - public static Result.Base InvalidBucketTreeVirtualOffset => new Result.Base(ModuleFs, 4039); - - /// Error code: 2002-4041; Range: 4041-4139; Inner value: 0x1f9202 - public static Result.Base RomNcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4041, 4139); } - /// Error code: 2002-4051; Range: 4051-4069; Inner value: 0x1fa602 - public static Result.Base RomNcaFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4051, 4069); } - /// Error code: 2002-4052; Inner value: 0x1fa802 - public static Result.Base InvalidRomNcaFileSystemType => new Result.Base(ModuleFs, 4052); - /// Error code: 2002-4053; Inner value: 0x1faa02 - public static Result.Base InvalidRomAcidFileSize => new Result.Base(ModuleFs, 4053); - /// Error code: 2002-4054; Inner value: 0x1fac02 - public static Result.Base InvalidRomAcidSize => new Result.Base(ModuleFs, 4054); - /// Error code: 2002-4055; Inner value: 0x1fae02 - public static Result.Base InvalidRomAcid => new Result.Base(ModuleFs, 4055); - /// Error code: 2002-4056; Inner value: 0x1fb002 - public static Result.Base RomAcidVerificationFailed => new Result.Base(ModuleFs, 4056); - /// Error code: 2002-4057; Inner value: 0x1fb202 - public static Result.Base InvalidRomNcaSignature => new Result.Base(ModuleFs, 4057); - /// Error code: 2002-4058; Inner value: 0x1fb402 - public static Result.Base RomNcaHeaderSignature1VerificationFailed => new Result.Base(ModuleFs, 4058); - /// Error code: 2002-4059; Inner value: 0x1fb602 - public static Result.Base RomNcaHeaderSignature2VerificationFailed => new Result.Base(ModuleFs, 4059); - /// Error code: 2002-4060; Inner value: 0x1fb802 - public static Result.Base RomNcaFsHeaderHashVerificationFailed => new Result.Base(ModuleFs, 4060); - /// Error code: 2002-4061; Inner value: 0x1fba02 - public static Result.Base InvalidRomNcaKeyIndex => new Result.Base(ModuleFs, 4061); - /// Error code: 2002-4062; Inner value: 0x1fbc02 - public static Result.Base InvalidRomNcaFsHeaderHashType => new Result.Base(ModuleFs, 4062); - /// Error code: 2002-4063; Inner value: 0x1fbe02 - public static Result.Base InvalidRomNcaFsHeaderEncryptionType => new Result.Base(ModuleFs, 4063); - /// Error code: 2002-4064; Inner value: 0x1fc002 - public static Result.Base InvalidRomNcaPatchInfoIndirectSize => new Result.Base(ModuleFs, 4064); - /// Error code: 2002-4065; Inner value: 0x1fc202 - public static Result.Base InvalidRomNcaPatchInfoAesCtrExSize => new Result.Base(ModuleFs, 4065); - /// Error code: 2002-4066; Inner value: 0x1fc402 - public static Result.Base InvalidRomNcaPatchInfoAesCtrExOffset => new Result.Base(ModuleFs, 4066); - /// Error code: 2002-4067; Inner value: 0x1fc602 - public static Result.Base InvalidRomNcaId => new Result.Base(ModuleFs, 4067); - /// Error code: 2002-4068; Inner value: 0x1fc802 - public static Result.Base InvalidRomNcaHeader => new Result.Base(ModuleFs, 4068); - /// Error code: 2002-4069; Inner value: 0x1fca02 - public static Result.Base InvalidRomNcaFsHeader => new Result.Base(ModuleFs, 4069); - - /// Error code: 2002-4070; Inner value: 0x1fcc02 - public static Result.Base InvalidRomNcaPatchInfoIndirectOffset => new Result.Base(ModuleFs, 4070); - - /// Error code: 2002-4071; Range: 4071-4079; Inner value: 0x1fce02 - public static Result.Base RomNcaHierarchicalSha256StorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4071, 4079); } - /// Error code: 2002-4072; Inner value: 0x1fd002 - public static Result.Base InvalidRomHierarchicalSha256BlockSize => new Result.Base(ModuleFs, 4072); - /// Error code: 2002-4073; Inner value: 0x1fd202 - public static Result.Base InvalidRomHierarchicalSha256LayerCount => new Result.Base(ModuleFs, 4073); - /// Error code: 2002-4074; Inner value: 0x1fd402 - public static Result.Base RomHierarchicalSha256BaseStorageTooLarge => new Result.Base(ModuleFs, 4074); - /// Error code: 2002-4075; Inner value: 0x1fd602 - public static Result.Base RomHierarchicalSha256HashVerificationFailed => new Result.Base(ModuleFs, 4075); - - /// Error code: 2002-4081; Inner value: 0x1fe202 - public static Result.Base InvalidRomHierarchicalIntegrityVerificationLayerCount => new Result.Base(ModuleFs, 4081); - /// Error code: 2002-4082; Inner value: 0x1fe402 - public static Result.Base RomNcaIndirectStorageOutOfRange => new Result.Base(ModuleFs, 4082); - - /// Error code: 2002-4141; Range: 4141-4179; Inner value: 0x205a02 - public static Result.Base RomIntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4141, 4179); } - /// Error code: 2002-4142; Inner value: 0x205c02 - public static Result.Base IncorrectRomIntegrityVerificationMagic => new Result.Base(ModuleFs, 4142); - /// Error code: 2002-4143; Inner value: 0x205e02 - public static Result.Base InvalidRomZeroSignature => new Result.Base(ModuleFs, 4143); - /// Error code: 2002-4144; Inner value: 0x206002 - public static Result.Base RomNonRealDataVerificationFailed => new Result.Base(ModuleFs, 4144); - - /// Error code: 2002-4151; Range: 4151-4159; Inner value: 0x206e02 - public static Result.Base RomRealDataVerificationFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4151, 4159); } - /// Error code: 2002-4152; Inner value: 0x207002 - public static Result.Base ClearedRomRealDataVerificationFailed => new Result.Base(ModuleFs, 4152); - /// Error code: 2002-4153; Inner value: 0x207202 - public static Result.Base UnclearedRomRealDataVerificationFailed => new Result.Base(ModuleFs, 4153); - - /// Error code: 2002-4181; Range: 4181-4199; Inner value: 0x20aa02 - public static Result.Base RomPartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4181, 4199); } - /// Error code: 2002-4182; Inner value: 0x20ac02 - public static Result.Base InvalidRomSha256PartitionHashTarget => new Result.Base(ModuleFs, 4182); - /// Error code: 2002-4183; Inner value: 0x20ae02 - public static Result.Base RomSha256PartitionHashVerificationFailed => new Result.Base(ModuleFs, 4183); - /// Error code: 2002-4184; Inner value: 0x20b002 - public static Result.Base RomPartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4184); - /// Error code: 2002-4185; Inner value: 0x20b202 - public static Result.Base RomSha256PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4185); - /// Error code: 2002-4186; Inner value: 0x20b402 - public static Result.Base InvalidRomPartitionEntryOffset => new Result.Base(ModuleFs, 4186); - /// Error code: 2002-4187; Inner value: 0x20b602 - public static Result.Base InvalidRomSha256PartitionMetaDataSize => new Result.Base(ModuleFs, 4187); - - /// Error code: 2002-4201; Range: 4201-4219; Inner value: 0x20d202 - public static Result.Base RomBuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4201, 4219); } - /// Error code: 2002-4202; Inner value: 0x20d402 - public static Result.Base RomGptHeaderSignatureVerificationFailed => new Result.Base(ModuleFs, 4202); - - /// Error code: 2002-4241; Range: 4241-4259; Inner value: 0x212202 - public static Result.Base RomHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4241, 4259); } - /// Error code: 2002-4242; Inner value: 0x212402 - public static Result.Base RomHostEntryCorrupted => new Result.Base(ModuleFs, 4242); - /// Error code: 2002-4243; Inner value: 0x212602 - public static Result.Base RomHostFileDataCorrupted => new Result.Base(ModuleFs, 4243); - /// Error code: 2002-4244; Inner value: 0x212802 - public static Result.Base RomHostFileCorrupted => new Result.Base(ModuleFs, 4244); - /// Error code: 2002-4245; Inner value: 0x212a02 - public static Result.Base InvalidRomHostHandle => new Result.Base(ModuleFs, 4245); - - /// Error code: 2002-4261; Range: 4261-4279; Inner value: 0x214a02 - public static Result.Base RomDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4261, 4279); } - /// Error code: 2002-4262; Inner value: 0x214c02 - public static Result.Base InvalidRomAllocationTableBlock => new Result.Base(ModuleFs, 4262); - /// Error code: 2002-4263; Inner value: 0x214e02 - public static Result.Base InvalidRomKeyValueListElementIndex => new Result.Base(ModuleFs, 4263); - - /// Returned directly when the header says the total size of the RomFs metadata is 0 bytes.
Error code: 2002-4280; Range: 4280-4284; Inner value: 0x217002
- public static Result.Base RomStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4280, 4284); } - /// The RomFs metadata is located beyond the end of the provided storage.
Error code: 2002-4281; Inner value: 0x217202
- public static Result.Base InvalidRomStorageSize => new Result.Base(ModuleFs, 4281); - - /// Error code: 2002-4301; Range: 4301-4499; Inner value: 0x219a02 - public static Result.Base SaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4301, 4499); } - /// Error code: 2002-4302; Inner value: 0x219c02 - public static Result.Base UnsupportedSaveDataVersion => new Result.Base(ModuleFs, 4302); - /// Error code: 2002-4303; Inner value: 0x219e02 - public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303); - - /// Error code: 2002-4311; Range: 4311-4319; Inner value: 0x21ae02 - public static Result.Base SaveDataFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4311, 4319); } - /// Error code: 2002-4312; Inner value: 0x21b002 - public static Result.Base InvalidJournalIntegritySaveDataHashSize => new Result.Base(ModuleFs, 4312); - /// Error code: 2002-4313; Inner value: 0x21b202 - public static Result.Base InvalidJournalIntegritySaveDataCommitState => new Result.Base(ModuleFs, 4313); - /// Error code: 2002-4314; Inner value: 0x21b402 - public static Result.Base InvalidJournalIntegritySaveDataControlAreaSize => new Result.Base(ModuleFs, 4314); - /// Error code: 2002-4315; Inner value: 0x21b602 - public static Result.Base JournalIntegritySaveDataControlAreaVerificationFailed => new Result.Base(ModuleFs, 4315); - /// Error code: 2002-4316; Inner value: 0x21b802 - public static Result.Base JournalIntegritySaveDataMasterSignatureVerificationFailed => new Result.Base(ModuleFs, 4316); - /// Error code: 2002-4317; Inner value: 0x21ba02 - public static Result.Base IncorrectJournalIntegritySaveDataMagicCode => new Result.Base(ModuleFs, 4317); - - /// Error code: 2002-4321; Range: 4321-4329; Inner value: 0x21c202 - public static Result.Base SaveDataDuplexStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4321, 4329); } - /// Error code: 2002-4322; Inner value: 0x21c402 - public static Result.Base IncorrectDuplexMagicCode => new Result.Base(ModuleFs, 4322); - /// Error code: 2002-4323; Inner value: 0x21c602 - public static Result.Base DuplexStorageAccessOutOfRange => new Result.Base(ModuleFs, 4323); - - /// Error code: 2002-4331; Range: 4331-4339; Inner value: 0x21d602 - public static Result.Base SaveDataMapCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4331, 4339); } - /// Error code: 2002-4332; Inner value: 0x21d802 - public static Result.Base InvalidMapEntryCount => new Result.Base(ModuleFs, 4332); - /// Error code: 2002-4333; Inner value: 0x21da02 - public static Result.Base InvalidMapOffset => new Result.Base(ModuleFs, 4333); - /// Error code: 2002-4334; Inner value: 0x21dc02 - public static Result.Base InvalidMapSize => new Result.Base(ModuleFs, 4334); - /// Error code: 2002-4335; Inner value: 0x21de02 - public static Result.Base InvalidMapAlignment => new Result.Base(ModuleFs, 4335); - /// Error code: 2002-4336; Inner value: 0x21e002 - public static Result.Base InvalidMapStorageType => new Result.Base(ModuleFs, 4336); - /// The range of the new map entry overlaps with the range of an existing map entry.
Error code: 2002-4337; Inner value: 0x21e202
- public static Result.Base MapAddressAlreadyRegistered => new Result.Base(ModuleFs, 4337); - /// The storage for the map entry's storage type hasn't been registered.
Error code: 2002-4338; Inner value: 0x21e402
- public static Result.Base MapStorageNotFound => new Result.Base(ModuleFs, 4338); - /// The storage registered for the map entry's storage type is too short to contain the physical range specified in the map entry.
Error code: 2002-4339; Inner value: 0x21e602
- public static Result.Base InvalidMapStorageSize => new Result.Base(ModuleFs, 4339); - - /// Error code: 2002-4341; Range: 4341-4349; Inner value: 0x21ea02 - public static Result.Base SaveDataLogCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4341, 4349); } - /// Error code: 2002-4342; Inner value: 0x21ec02 - public static Result.Base InvalidLogBlockSize => new Result.Base(ModuleFs, 4342); - /// Error code: 2002-4343; Inner value: 0x21ee02 - public static Result.Base InvalidLogOffset => new Result.Base(ModuleFs, 4343); - /// Error code: 2002-4344; Inner value: 0x21f002 - public static Result.Base UnexpectedEndOfLog => new Result.Base(ModuleFs, 4344); - /// Error code: 2002-4345; Inner value: 0x21f202 - public static Result.Base LogNotFound => new Result.Base(ModuleFs, 4345); - - /// Error code: 2002-4352; Inner value: 0x220002 - public static Result.Base ThumbnailHashVerificationFailed => new Result.Base(ModuleFs, 4352); - /// Error code: 2002-4357; Inner value: 0x220a02 - public static Result.Base InvalidSaveDataInternalStorageIntegritySeedSize => new Result.Base(ModuleFs, 4357); - /// Error code: 2002-4358; Inner value: 0x220c02 - public static Result.Base Result4358 => new Result.Base(ModuleFs, 4358); - /// Error code: 2002-4359; Inner value: 0x220e02 - public static Result.Base Result4359 => new Result.Base(ModuleFs, 4359); - - /// Error code: 2002-4361; Range: 4361-4399; Inner value: 0x221202 - public static Result.Base SaveDataIntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4361, 4399); } - /// Error code: 2002-4362; Inner value: 0x221402 - public static Result.Base IncorrectSaveDataIntegrityVerificationMagic => new Result.Base(ModuleFs, 4362); - /// Error code: 2002-4363; Inner value: 0x221602 - public static Result.Base InvalidSaveDataZeroHash => new Result.Base(ModuleFs, 4363); - /// Error code: 2002-4364; Inner value: 0x221802 - public static Result.Base SaveDataNonRealDataVerificationFailed => new Result.Base(ModuleFs, 4364); - /// Error code: 2002-4372; Inner value: 0x222802 - public static Result.Base ClearedSaveDataRealDataVerificationFailed => new Result.Base(ModuleFs, 4372); - /// Error code: 2002-4373; Inner value: 0x222a02 - public static Result.Base UnclearedSaveDataRealDataVerificationFailed => new Result.Base(ModuleFs, 4373); - - /// Error code: 2002-4402; Inner value: 0x226402 - public static Result.Base SaveDataGptHeaderSignatureVerificationFailed => new Result.Base(ModuleFs, 4402); - - /// Error code: 2002-4411; Range: 4411-4419; Inner value: 0x227602 - public static Result.Base SaveDataCoreFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4411, 4419); } - /// Error code: 2002-4412; Inner value: 0x227802 - public static Result.Base IncorrectSaveDataControlAreaMagic => new Result.Base(ModuleFs, 4412); - /// Error code: 2002-4413; Inner value: 0x227a02 - public static Result.Base InvalidSaveDataFileReadOffset => new Result.Base(ModuleFs, 4413); - /// Error code: 2002-4414; Inner value: 0x227c02 - public static Result.Base InvalidSaveDataCoreDataStorageSize => new Result.Base(ModuleFs, 4414); - - /// Error code: 2002-4427; Inner value: 0x229602 - public static Result.Base IncompleteBlockInZeroBitmapHashStorageFileSaveData => new Result.Base(ModuleFs, 4427); - - /// Error code: 2002-4431; Range: 4431-4439; Inner value: 0x229e02 - public static Result.Base JournalStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4431, 4439); } - /// Error code: 2002-4432; Inner value: 0x22a002 - public static Result.Base JournalStorageAccessOutOfRange => new Result.Base(ModuleFs, 4432); - /// Error code: 2002-4433; Inner value: 0x22a202 - public static Result.Base InvalidJournalStorageDataStorageSize => new Result.Base(ModuleFs, 4433); - - /// Error code: 2002-4441; Range: 4441-4459; Inner value: 0x22b202 - public static Result.Base SaveDataHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4441, 4459); } - /// Error code: 2002-4442; Inner value: 0x22b402 - public static Result.Base SaveDataHostEntryCorrupted => new Result.Base(ModuleFs, 4442); - /// Error code: 2002-4443; Inner value: 0x22b602 - public static Result.Base SaveDataHostFileDataCorrupted => new Result.Base(ModuleFs, 4443); - /// Error code: 2002-4444; Inner value: 0x22b802 - public static Result.Base SaveDataHostFileCorrupted => new Result.Base(ModuleFs, 4444); - /// Error code: 2002-4445; Inner value: 0x22ba02 - public static Result.Base InvalidSaveDataHostHandle => new Result.Base(ModuleFs, 4445); - - /// Error code: 2002-4451; Range: 4451-4459; Inner value: 0x22c602 - public static Result.Base MappingTableCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4451, 4459); } - /// Error code: 2002-4452; Inner value: 0x22c802 - public static Result.Base InvalidMappingTableEntryCount => new Result.Base(ModuleFs, 4452); - /// Error code: 2002-4453; Inner value: 0x22ca02 - public static Result.Base InvalidMappingTablePhysicalIndex => new Result.Base(ModuleFs, 4453); - /// Error code: 2002-4454; Inner value: 0x22cc02 - public static Result.Base InvalidMappingTableVirtualIndex => new Result.Base(ModuleFs, 4454); - - /// Error code: 2002-4461; Range: 4461-4479; Inner value: 0x22da02 - public static Result.Base SaveDataDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4461, 4479); } - /// Error code: 2002-4462; Inner value: 0x22dc02 - public static Result.Base InvalidSaveDataAllocationTableBlock => new Result.Base(ModuleFs, 4462); - /// Error code: 2002-4463; Inner value: 0x22de02 - public static Result.Base InvalidSaveDataKeyValueListElementIndex => new Result.Base(ModuleFs, 4463); - /// Error code: 2002-4464; Inner value: 0x22e002 - public static Result.Base SaveDataAllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 4464); - /// Error code: 2002-4465; Inner value: 0x22e202 - public static Result.Base InvalidSaveDataAllocationTableOffset => new Result.Base(ModuleFs, 4465); - /// Error code: 2002-4466; Inner value: 0x22e402 - public static Result.Base InvalidSaveDataAllocationTableBlockCount => new Result.Base(ModuleFs, 4466); - /// Error code: 2002-4467; Inner value: 0x22e602 - public static Result.Base InvalidSaveDataKeyValueListEntryIndex => new Result.Base(ModuleFs, 4467); - /// Error code: 2002-4468; Inner value: 0x22e802 - public static Result.Base InvalidSaveDataBitmapIndex => new Result.Base(ModuleFs, 4468); - - /// Error code: 2002-4481; Range: 4481-4489; Inner value: 0x230202 - public static Result.Base SaveDataExtensionContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4481, 4489); } - /// Error code: 2002-4482; Inner value: 0x230402 - public static Result.Base IncorrectSaveDataExtensionContextMagicCode => new Result.Base(ModuleFs, 4482); - /// Error code: 2002-4483; Inner value: 0x230602 - public static Result.Base InvalidSaveDataExtensionContextState => new Result.Base(ModuleFs, 4483); - /// The extension context doesn't match the current save data.
Error code: 2002-4484; Inner value: 0x230802
- public static Result.Base DifferentSaveDataExtensionContextParameter => new Result.Base(ModuleFs, 4484); - /// Error code: 2002-4485; Inner value: 0x230a02 - public static Result.Base InvalidSaveDataExtensionContextParameter => new Result.Base(ModuleFs, 4485); - - /// Error code: 2002-4491; Range: 4491-4499; Inner value: 0x231602 - public static Result.Base IntegritySaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4491, 4499); } - /// Error code: 2002-4492; Inner value: 0x231802 - public static Result.Base InvalidIntegritySaveDataHashSize => new Result.Base(ModuleFs, 4492); - /// Error code: 2002-4494; Inner value: 0x231c02 - public static Result.Base InvalidIntegritySaveDataControlAreaSize => new Result.Base(ModuleFs, 4494); - /// Error code: 2002-4495; Inner value: 0x231e02 - public static Result.Base IntegritySaveDataControlAreaVerificationFailed => new Result.Base(ModuleFs, 4495); - /// Error code: 2002-4496; Inner value: 0x232002 - public static Result.Base IntegritySaveDataMasterSignatureVerificationFailed => new Result.Base(ModuleFs, 4496); - /// Error code: 2002-4497; Inner value: 0x232202 - public static Result.Base IncorrectIntegritySaveDataMagicCode => new Result.Base(ModuleFs, 4497); - - /// Error code: 2002-4501; Range: 4501-4599; Inner value: 0x232a02 - public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4501, 4599); } - /// Error code: 2002-4508; Inner value: 0x233802 - public static Result.Base NcaBaseStorageOutOfRangeA => new Result.Base(ModuleFs, 4508); - /// Error code: 2002-4509; Inner value: 0x233a02 - public static Result.Base NcaBaseStorageOutOfRangeB => new Result.Base(ModuleFs, 4509); - /// Error code: 2002-4510; Inner value: 0x233c02 - public static Result.Base NcaBaseStorageOutOfRangeC => new Result.Base(ModuleFs, 4510); - - /// Error code: 2002-4511; Range: 4511-4529; Inner value: 0x233e02 - public static Result.Base NcaFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4511, 4529); } - /// Error code: 2002-4512; Inner value: 0x234002 - public static Result.Base InvalidNcaFileSystemType => new Result.Base(ModuleFs, 4512); - /// Error code: 2002-4513; Inner value: 0x234202 - public static Result.Base InvalidAcidFileSize => new Result.Base(ModuleFs, 4513); - /// Error code: 2002-4514; Inner value: 0x234402 - public static Result.Base InvalidAcidSize => new Result.Base(ModuleFs, 4514); - /// Error code: 2002-4515; Inner value: 0x234602 - public static Result.Base InvalidAcid => new Result.Base(ModuleFs, 4515); - /// Error code: 2002-4516; Inner value: 0x234802 - public static Result.Base AcidVerificationFailed => new Result.Base(ModuleFs, 4516); - /// Error code: 2002-4517; Inner value: 0x234a02 - public static Result.Base InvalidNcaSignature => new Result.Base(ModuleFs, 4517); - /// Error code: 2002-4518; Inner value: 0x234c02 - public static Result.Base NcaHeaderSignature1VerificationFailed => new Result.Base(ModuleFs, 4518); - /// Error code: 2002-4519; Inner value: 0x234e02 - public static Result.Base NcaHeaderSignature2VerificationFailed => new Result.Base(ModuleFs, 4519); - /// Error code: 2002-4520; Inner value: 0x235002 - public static Result.Base NcaFsHeaderHashVerificationFailed => new Result.Base(ModuleFs, 4520); - /// Error code: 2002-4521; Inner value: 0x235202 - public static Result.Base InvalidNcaKeyIndex => new Result.Base(ModuleFs, 4521); - /// Error code: 2002-4522; Inner value: 0x235402 - public static Result.Base InvalidNcaFsHeaderHashType => new Result.Base(ModuleFs, 4522); - /// Error code: 2002-4523; Inner value: 0x235602 - public static Result.Base InvalidNcaFsHeaderEncryptionType => new Result.Base(ModuleFs, 4523); - /// Error code: 2002-4524; Inner value: 0x235802 - public static Result.Base InvalidNcaPatchInfoIndirectSize => new Result.Base(ModuleFs, 4524); - /// Error code: 2002-4525; Inner value: 0x235a02 - public static Result.Base InvalidNcaPatchInfoAesCtrExSize => new Result.Base(ModuleFs, 4525); - /// Error code: 2002-4526; Inner value: 0x235c02 - public static Result.Base InvalidNcaPatchInfoAesCtrExOffset => new Result.Base(ModuleFs, 4526); - /// Error code: 2002-4527; Inner value: 0x235e02 - public static Result.Base InvalidNcaId => new Result.Base(ModuleFs, 4527); - /// Error code: 2002-4528; Inner value: 0x236002 - public static Result.Base InvalidNcaHeader => new Result.Base(ModuleFs, 4528); - /// Error code: 2002-4529; Inner value: 0x236202 - public static Result.Base InvalidNcaFsHeader => new Result.Base(ModuleFs, 4529); - - /// Error code: 2002-4530; Inner value: 0x236402 - public static Result.Base InvalidNcaPatchInfoIndirectOffset => new Result.Base(ModuleFs, 4530); - - /// Error code: 2002-4531; Range: 4531-4539; Inner value: 0x236602 - public static Result.Base NcaHierarchicalSha256StorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4531, 4539); } - /// Error code: 2002-4532; Inner value: 0x236802 - public static Result.Base InvalidHierarchicalSha256BlockSize => new Result.Base(ModuleFs, 4532); - /// Error code: 2002-4533; Inner value: 0x236a02 - public static Result.Base InvalidHierarchicalSha256LayerCount => new Result.Base(ModuleFs, 4533); - /// Error code: 2002-4534; Inner value: 0x236c02 - public static Result.Base HierarchicalSha256BaseStorageTooLarge => new Result.Base(ModuleFs, 4534); - /// Error code: 2002-4535; Inner value: 0x236e02 - public static Result.Base HierarchicalSha256HashVerificationFailed => new Result.Base(ModuleFs, 4535); - - /// Error code: 2002-4541; Inner value: 0x237a02 - public static Result.Base InvalidHierarchicalIntegrityVerificationLayerCount => new Result.Base(ModuleFs, 4541); - /// Error code: 2002-4542; Inner value: 0x237c02 - public static Result.Base NcaIndirectStorageOutOfRange => new Result.Base(ModuleFs, 4542); - /// Error code: 2002-4543; Inner value: 0x237e02 - public static Result.Base InvalidNcaHeader1SignatureKeyGeneration => new Result.Base(ModuleFs, 4543); - /// Error code: 2002-4545; Inner value: 0x238202 - public static Result.Base InvalidNspdVerificationData => new Result.Base(ModuleFs, 4545); - /// Error code: 2002-4546; Inner value: 0x238402 - public static Result.Base MissingNspdVerificationData => new Result.Base(ModuleFs, 4546); - - /// Error code: 2002-4601; Range: 4601-4639; Inner value: 0x23f202 - public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4601, 4639); } - /// Error code: 2002-4602; Inner value: 0x23f402 - public static Result.Base IncorrectIntegrityVerificationMagic => new Result.Base(ModuleFs, 4602); - /// Error code: 2002-4603; Inner value: 0x23f602 - public static Result.Base InvalidZeroHash => new Result.Base(ModuleFs, 4603); - /// Error code: 2002-4604; Inner value: 0x23f802 - public static Result.Base NonRealDataVerificationFailed => new Result.Base(ModuleFs, 4604); - - /// Error code: 2002-4611; Range: 4611-4619; Inner value: 0x240602 - public static Result.Base RealDataVerificationFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4611, 4619); } - /// Error code: 2002-4612; Inner value: 0x240802 - public static Result.Base ClearedRealDataVerificationFailed => new Result.Base(ModuleFs, 4612); - /// Error code: 2002-4613; Inner value: 0x240a02 - public static Result.Base UnclearedRealDataVerificationFailed => new Result.Base(ModuleFs, 4613); - - /// Error code: 2002-4641; Range: 4641-4659; Inner value: 0x244202 - public static Result.Base PartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4641, 4659); } - /// Error code: 2002-4642; Inner value: 0x244402 - public static Result.Base InvalidSha256PartitionHashTarget => new Result.Base(ModuleFs, 4642); - /// Error code: 2002-4643; Inner value: 0x244602 - public static Result.Base Sha256PartitionHashVerificationFailed => new Result.Base(ModuleFs, 4643); - /// Error code: 2002-4644; Inner value: 0x244802 - public static Result.Base PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4644); - /// Error code: 2002-4645; Inner value: 0x244a02 - public static Result.Base Sha256PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4645); - /// Error code: 2002-4646; Inner value: 0x244c02 - public static Result.Base InvalidPartitionEntryOffset => new Result.Base(ModuleFs, 4646); - /// Error code: 2002-4647; Inner value: 0x244e02 - public static Result.Base InvalidSha256PartitionMetaDataSize => new Result.Base(ModuleFs, 4647); - - /// Error code: 2002-4661; Range: 4661-4679; Inner value: 0x246a02 - public static Result.Base BuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4661, 4679); } - /// Error code: 2002-4662; Inner value: 0x246c02 - public static Result.Base InvalidGptPartitionSignature => new Result.Base(ModuleFs, 4662); - /// Error code: 2002-4664; Inner value: 0x247002 - public static Result.Base InvalidGptPartitionStorageSize => new Result.Base(ModuleFs, 4664); - - /// Error code: 2002-4681; Range: 4681-4699; Inner value: 0x249202 - public static Result.Base FatFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4681, 4699); } - /// Error code: 2002-4683; Inner value: 0x249602 - public static Result.Base InvalidFatFormat => new Result.Base(ModuleFs, 4683); - /// Error code: 2002-4684; Inner value: 0x249802 - public static Result.Base InvalidFatFileNumber => new Result.Base(ModuleFs, 4684); - /// Error code: 2002-4685; Inner value: 0x249a02 - public static Result.Base ExFatUnavailable => new Result.Base(ModuleFs, 4685); - /// Error code: 2002-4686; Inner value: 0x249c02 - public static Result.Base InvalidFatFormatForBisUser => new Result.Base(ModuleFs, 4686); - /// Error code: 2002-4687; Inner value: 0x249e02 - public static Result.Base InvalidFatFormatForBisSystem => new Result.Base(ModuleFs, 4687); - /// Error code: 2002-4688; Inner value: 0x24a002 - public static Result.Base InvalidFatFormatForBisSafe => new Result.Base(ModuleFs, 4688); - /// Error code: 2002-4689; Inner value: 0x24a202 - public static Result.Base InvalidFatFormatForBisCalibration => new Result.Base(ModuleFs, 4689); - /// Error code: 2002-4690; Inner value: 0x24a402 - public static Result.Base InvalidFatFormatForSdCard => new Result.Base(ModuleFs, 4690); - - /// Error code: 2002-4701; Range: 4701-4719; Inner value: 0x24ba02 - public static Result.Base HostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4701, 4719); } - /// Error code: 2002-4702; Inner value: 0x24bc02 - public static Result.Base HostEntryCorrupted => new Result.Base(ModuleFs, 4702); - /// Error code: 2002-4703; Inner value: 0x24be02 - public static Result.Base HostFileDataCorrupted => new Result.Base(ModuleFs, 4703); - /// Error code: 2002-4704; Inner value: 0x24c002 - public static Result.Base HostFileCorrupted => new Result.Base(ModuleFs, 4704); - /// Error code: 2002-4705; Inner value: 0x24c202 - public static Result.Base InvalidHostHandle => new Result.Base(ModuleFs, 4705); - - /// Error code: 2002-4721; Range: 4721-4739; Inner value: 0x24e202 - public static Result.Base DatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4721, 4739); } - /// Error code: 2002-4722; Inner value: 0x24e402 - public static Result.Base InvalidAllocationTableBlock => new Result.Base(ModuleFs, 4722); - /// Error code: 2002-4723; Inner value: 0x24e602 - public static Result.Base InvalidKeyValueListElementIndex => new Result.Base(ModuleFs, 4723); - /// Error code: 2002-4724; Inner value: 0x24e802 - public static Result.Base AllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 4724); - /// Error code: 2002-4725; Inner value: 0x24ea02 - public static Result.Base InvalidAllocationTableOffset => new Result.Base(ModuleFs, 4725); - /// Error code: 2002-4726; Inner value: 0x24ec02 - public static Result.Base InvalidAllocationTableBlockCount => new Result.Base(ModuleFs, 4726); - /// Error code: 2002-4727; Inner value: 0x24ee02 - public static Result.Base InvalidKeyValueListEntryIndex => new Result.Base(ModuleFs, 4727); - /// Error code: 2002-4728; Inner value: 0x24f002 - public static Result.Base InvalidBitmapIndex => new Result.Base(ModuleFs, 4728); - - /// Error code: 2002-4741; Range: 4741-4759; Inner value: 0x250a02 - public static Result.Base AesXtsFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4741, 4759); } - /// Error code: 2002-4742; Inner value: 0x250c02 - public static Result.Base AesXtsFileHeaderTooShort => new Result.Base(ModuleFs, 4742); - /// Error code: 2002-4743; Inner value: 0x250e02 - public static Result.Base AesXtsFileHeaderInvalidKeys => new Result.Base(ModuleFs, 4743); - /// Error code: 2002-4744; Inner value: 0x251002 - public static Result.Base AesXtsFileHeaderInvalidMagic => new Result.Base(ModuleFs, 4744); - /// Error code: 2002-4745; Inner value: 0x251202 - public static Result.Base AesXtsFileTooShort => new Result.Base(ModuleFs, 4745); - /// Error code: 2002-4746; Inner value: 0x251402 - public static Result.Base AesXtsFileHeaderTooShortInSetSize => new Result.Base(ModuleFs, 4746); - /// Error code: 2002-4747; Inner value: 0x251602 - public static Result.Base AesXtsFileHeaderInvalidKeysInRenameFile => new Result.Base(ModuleFs, 4747); - /// Error code: 2002-4748; Inner value: 0x251802 - public static Result.Base AesXtsFileHeaderInvalidKeysInSetSize => new Result.Base(ModuleFs, 4748); - - /// Error code: 2002-4761; Range: 4761-4769; Inner value: 0x253202 - public static Result.Base SaveDataTransferDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4761, 4769); } - /// Error code: 2002-4762; Inner value: 0x253402 - public static Result.Base Result4762 => new Result.Base(ModuleFs, 4762); - /// Error code: 2002-4763; Inner value: 0x253602 - public static Result.Base Result4763 => new Result.Base(ModuleFs, 4763); - /// Error code: 2002-4764; Inner value: 0x253802 - public static Result.Base Result4764 => new Result.Base(ModuleFs, 4764); - /// Error code: 2002-4765; Inner value: 0x253a02 - public static Result.Base Result4765 => new Result.Base(ModuleFs, 4765); - /// Error code: 2002-4766; Inner value: 0x253c02 - public static Result.Base Result4766 => new Result.Base(ModuleFs, 4766); - /// Error code: 2002-4767; Inner value: 0x253e02 - public static Result.Base Result4767 => new Result.Base(ModuleFs, 4767); - - /// Error code: 2002-4771; Range: 4771-4779; Inner value: 0x254602 - public static Result.Base SignedSystemPartitionDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4771, 4779); } - /// Error code: 2002-4772; Inner value: 0x254802 - public static Result.Base Result4772 => new Result.Base(ModuleFs, 4772); - /// Error code: 2002-4773; Inner value: 0x254a02 - public static Result.Base Result4773 => new Result.Base(ModuleFs, 4773); - /// Error code: 2002-4774; Inner value: 0x254c02 - public static Result.Base Result4774 => new Result.Base(ModuleFs, 4774); - /// Error code: 2002-4775; Inner value: 0x254e02 - public static Result.Base Result4775 => new Result.Base(ModuleFs, 4775); - /// Error code: 2002-4776; Inner value: 0x255002 - public static Result.Base Result4776 => new Result.Base(ModuleFs, 4776); - - /// Error code: 2002-4781; Inner value: 0x255a02 - public static Result.Base GameCardLogoDataCorrupted => new Result.Base(ModuleFs, 4781); - /// Error code: 2002-4785; Inner value: 0x256202 - public static Result.Base SimulatedDeviceDataCorrupted => new Result.Base(ModuleFs, 4785); - - /// Error code: 2002-4790; Range: 4790-4799; Inner value: 0x256c02 - public static Result.Base MultiCommitContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4790, 4799); } - /// The version of the multi-commit context file is too high for the current MultiCommitManager implementation.
Error code: 2002-4791; Inner value: 0x256e02
- public static Result.Base InvalidMultiCommitContextVersion => new Result.Base(ModuleFs, 4791); - /// The multi-commit has not been provisionally committed.
Error code: 2002-4792; Inner value: 0x257002
- public static Result.Base InvalidMultiCommitContextState => new Result.Base(ModuleFs, 4792); - - /// Error code: 2002-4811; Range: 4811-4819; Inner value: 0x259602 - public static Result.Base ZeroBitmapFileCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4811, 4819); } - /// Error code: 2002-4812; Inner value: 0x259802 - public static Result.Base IncompleteBlockInZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 4812); - - /// Error code: 2002-5000; Range: 5000-5999; Inner value: 0x271002 - public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 5000, 5999); } - /// Error code: 2002-5002; Inner value: 0x271402 - public static Result.Base UnexpectedFatFileSystemError => new Result.Base(ModuleFs, 5002); - /// Error code: 2002-5003; Inner value: 0x271602 - public static Result.Base FatFileSystemDriverError => new Result.Base(ModuleFs, 5003); - /// Error code: 2002-5024; Inner value: 0x274002 - public static Result.Base FatSystemFileOpenCountLimit => new Result.Base(ModuleFs, 5024); - /// Error code: 2002-5025; Inner value: 0x274202 - public static Result.Base FatUserFileOpenCountLimit => new Result.Base(ModuleFs, 5025); - /// Error code: 2002-5026; Inner value: 0x274402 - public static Result.Base FatEntryIsDirectory => new Result.Base(ModuleFs, 5026); - /// Error code: 2002-5028; Inner value: 0x274802 - public static Result.Base FatFileSystemPermissionDenied => new Result.Base(ModuleFs, 5028); - /// Error code: 2002-5029; Inner value: 0x274a02 - public static Result.Base FatDriverInitializationFailed => new Result.Base(ModuleFs, 5029); - /// Error code: 2002-5055; Inner value: 0x277e02 - public static Result.Base UnexpectedFatSafeError => new Result.Base(ModuleFs, 5055); - /// Error code: 2002-5110; Inner value: 0x27ec02 - public static Result.Base Result5110 => new Result.Base(ModuleFs, 5110); - /// Error code: 2002-5121; Inner value: 0x280202 - public static Result.Base UnexpectedFatFileSystemSectorCount => new Result.Base(ModuleFs, 5121); - /// Error code: 2002-5122; Inner value: 0x280402 - public static Result.Base Result5122 => new Result.Base(ModuleFs, 5122); - /// Error code: 2002-5123; Inner value: 0x280602 - public static Result.Base NullptrArgumentForFatFormat => new Result.Base(ModuleFs, 5123); - /// Error code: 2002-5124; Inner value: 0x280802 - public static Result.Base InvalidFatFormatParamsA => new Result.Base(ModuleFs, 5124); - /// Error code: 2002-5125; Inner value: 0x280a02 - public static Result.Base InvalidFatFormatParamsB => new Result.Base(ModuleFs, 5125); - /// Error code: 2002-5126; Inner value: 0x280c02 - public static Result.Base InvalidFatFormatParamsC => new Result.Base(ModuleFs, 5126); - /// Error code: 2002-5127; Inner value: 0x280e02 - public static Result.Base InvalidFatFormatParamsD => new Result.Base(ModuleFs, 5127); - /// Error code: 2002-5131; Inner value: 0x281602 - public static Result.Base FatSectorWriteVerificationFailed => new Result.Base(ModuleFs, 5131); - /// Error code: 2002-5301; Inner value: 0x296a02 - public static Result.Base UnexpectedInMountTableA => new Result.Base(ModuleFs, 5301); - /// Error code: 2002-5302; Inner value: 0x296c02 - public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemA => new Result.Base(ModuleFs, 5302); - /// Error code: 2002-5303; Inner value: 0x296e02 - public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemB => new Result.Base(ModuleFs, 5303); - /// Tried to write to an IntegrityFilteredFile that is provisionally committed.
Error code: 2002-5304; Inner value: 0x297002
- public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemC => new Result.Base(ModuleFs, 5304); - /// Error code: 2002-5305; Inner value: 0x297202 - public static Result.Base UnexpectedInLocalFileSystemA => new Result.Base(ModuleFs, 5305); - /// Error code: 2002-5306; Inner value: 0x297402 - public static Result.Base UnexpectedInLocalFileSystemB => new Result.Base(ModuleFs, 5306); - /// Error code: 2002-5307; Inner value: 0x297602 - public static Result.Base UnexpectedInLocalFileSystemC => new Result.Base(ModuleFs, 5307); - /// Error code: 2002-5308; Inner value: 0x297802 - public static Result.Base UnexpectedInLocalFileSystemD => new Result.Base(ModuleFs, 5308); - /// Error code: 2002-5309; Inner value: 0x297a02 - public static Result.Base UnexpectedInLocalFileSystemE => new Result.Base(ModuleFs, 5309); - /// Error code: 2002-5310; Inner value: 0x297c02 - public static Result.Base UnexpectedInLocalFileSystemF => new Result.Base(ModuleFs, 5310); - /// Error code: 2002-5311; Inner value: 0x297e02 - public static Result.Base UnexpectedInPathToolA => new Result.Base(ModuleFs, 5311); - /// Error code: 2002-5312; Inner value: 0x298002 - public static Result.Base UnexpectedInPathOnExecutionDirectoryA => new Result.Base(ModuleFs, 5312); - /// Error code: 2002-5313; Inner value: 0x298202 - public static Result.Base UnexpectedInPathOnExecutionDirectoryB => new Result.Base(ModuleFs, 5313); - /// Error code: 2002-5314; Inner value: 0x298402 - public static Result.Base UnexpectedInPathOnExecutionDirectoryC => new Result.Base(ModuleFs, 5314); - /// Error code: 2002-5315; Inner value: 0x298602 - public static Result.Base UnexpectedInAesCtrStorageA => new Result.Base(ModuleFs, 5315); - /// Error code: 2002-5316; Inner value: 0x298802 - public static Result.Base UnexpectedInAesXtsStorageA => new Result.Base(ModuleFs, 5316); - /// Error code: 2002-5317; Inner value: 0x298a02 - public static Result.Base UnexpectedInSaveDataInternalStorageFileSystemA => new Result.Base(ModuleFs, 5317); - /// Error code: 2002-5318; Inner value: 0x298c02 - public static Result.Base UnexpectedInSaveDataInternalStorageFileSystemB => new Result.Base(ModuleFs, 5318); - /// Error code: 2002-5319; Inner value: 0x298e02 - public static Result.Base UnexpectedInMountUtilityA => new Result.Base(ModuleFs, 5319); - /// Ncas cannot be mounted from the given mount point.
Error code: 2002-5320; Inner value: 0x299002
- public static Result.Base UnexpectedInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 5320); - /// Error code: 2002-5321; Inner value: 0x299202 - public static Result.Base UnexpectedInRamDiskFileSystemA => new Result.Base(ModuleFs, 5321); - /// Error code: 2002-5322; Inner value: 0x299402 - public static Result.Base UnexpectedInBisWiperA => new Result.Base(ModuleFs, 5322); - /// Error code: 2002-5323; Inner value: 0x299602 - public static Result.Base UnexpectedInBisWiperB => new Result.Base(ModuleFs, 5323); - /// Error code: 2002-5324; Inner value: 0x299802 - public static Result.Base UnexpectedInCompressedStorageA => new Result.Base(ModuleFs, 5324); - /// Error code: 2002-5325; Inner value: 0x299a02 - public static Result.Base UnexpectedInCompressedStorageB => new Result.Base(ModuleFs, 5325); - /// Error code: 2002-5326; Inner value: 0x299c02 - public static Result.Base UnexpectedInCompressedStorageC => new Result.Base(ModuleFs, 5326); - /// Error code: 2002-5327; Inner value: 0x299e02 - public static Result.Base UnexpectedInCompressedStorageD => new Result.Base(ModuleFs, 5327); - /// Error code: 2002-5328; Inner value: 0x29a002 - public static Result.Base UnexpectedInPathA => new Result.Base(ModuleFs, 5328); - - /// Error code: 2002-6000; Range: 6000-6499; Inner value: 0x2ee002 - public static Result.Base PreconditionViolation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6000, 6499); } - /// Error code: 2002-6001; Range: 6001-6199; Inner value: 0x2ee202 - public static Result.Base InvalidArgument { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6001, 6199); } - /// Error code: 2002-6002; Range: 6002-6029; Inner value: 0x2ee402 - public static Result.Base InvalidPath { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6002, 6029); } - /// Error code: 2002-6003; Inner value: 0x2ee602 - public static Result.Base TooLongPath => new Result.Base(ModuleFs, 6003); - /// Error code: 2002-6004; Inner value: 0x2ee802 - public static Result.Base InvalidCharacter => new Result.Base(ModuleFs, 6004); - /// Error code: 2002-6005; Inner value: 0x2eea02 - public static Result.Base InvalidPathFormat => new Result.Base(ModuleFs, 6005); - /// Error code: 2002-6006; Inner value: 0x2eec02 - public static Result.Base DirectoryUnobtainable => new Result.Base(ModuleFs, 6006); - /// Error code: 2002-6007; Inner value: 0x2eee02 - public static Result.Base NotNormalized => new Result.Base(ModuleFs, 6007); - - /// Error code: 2002-6030; Range: 6030-6059; Inner value: 0x2f1c02 - public static Result.Base InvalidPathForOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6030, 6059); } - /// Error code: 2002-6031; Inner value: 0x2f1e02 - public static Result.Base DirectoryNotDeletable => new Result.Base(ModuleFs, 6031); - /// Error code: 2002-6032; Inner value: 0x2f2002 - public static Result.Base DirectoryNotRenamable => new Result.Base(ModuleFs, 6032); - /// Error code: 2002-6033; Inner value: 0x2f2202 - public static Result.Base IncompatiblePath => new Result.Base(ModuleFs, 6033); - /// Error code: 2002-6034; Inner value: 0x2f2402 - public static Result.Base RenameToOtherFileSystem => new Result.Base(ModuleFs, 6034); - - /// Error code: 2002-6061; Inner value: 0x2f5a02 - public static Result.Base InvalidOffset => new Result.Base(ModuleFs, 6061); - /// Error code: 2002-6062; Inner value: 0x2f5c02 - public static Result.Base InvalidSize => new Result.Base(ModuleFs, 6062); - /// Error code: 2002-6063; Inner value: 0x2f5e02 - public static Result.Base NullptrArgument => new Result.Base(ModuleFs, 6063); - /// Error code: 2002-6064; Inner value: 0x2f6002 - public static Result.Base InvalidAlignment => new Result.Base(ModuleFs, 6064); - /// Error code: 2002-6065; Inner value: 0x2f6202 - public static Result.Base InvalidMountName => new Result.Base(ModuleFs, 6065); - /// Error code: 2002-6066; Inner value: 0x2f6402 - public static Result.Base ExtensionSizeTooLarge => new Result.Base(ModuleFs, 6066); - /// Error code: 2002-6067; Inner value: 0x2f6602 - public static Result.Base ExtensionSizeInvalid => new Result.Base(ModuleFs, 6067); - /// Error code: 2002-6068; Inner value: 0x2f6802 - public static Result.Base InvalidHandle => new Result.Base(ModuleFs, 6068); - /// Error code: 2002-6069; Inner value: 0x2f6a02 - public static Result.Base CacheStorageSizeTooLarge => new Result.Base(ModuleFs, 6069); - /// Error code: 2002-6070; Inner value: 0x2f6c02 - public static Result.Base CacheStorageIndexTooLarge => new Result.Base(ModuleFs, 6070); - /// Up to 10 file systems can be committed at the same time.
Error code: 2002-6071; Inner value: 0x2f6e02
- public static Result.Base InvalidCommitNameCount => new Result.Base(ModuleFs, 6071); - /// Error code: 2002-6072; Inner value: 0x2f7002 - public static Result.Base InvalidOpenMode => new Result.Base(ModuleFs, 6072); - /// Error code: 2002-6073; Inner value: 0x2f7202 - public static Result.Base InvalidFileSize => new Result.Base(ModuleFs, 6073); - /// Error code: 2002-6074; Inner value: 0x2f7402 - public static Result.Base InvalidDirectoryOpenMode => new Result.Base(ModuleFs, 6074); - /// Error code: 2002-6075; Inner value: 0x2f7602 - public static Result.Base InvalidCommitOption => new Result.Base(ModuleFs, 6075); - - /// Error code: 2002-6080; Range: 6080-6099; Inner value: 0x2f8002 - public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6080, 6099); } - /// Error code: 2002-6081; Inner value: 0x2f8202 - public static Result.Base InvalidSaveDataState => new Result.Base(ModuleFs, 6081); - /// Error code: 2002-6082; Inner value: 0x2f8402 - public static Result.Base InvalidSaveDataSpaceId => new Result.Base(ModuleFs, 6082); - - /// Error code: 2002-6101; Inner value: 0x2faa02 - public static Result.Base GameCardLogoDataTooLarge => new Result.Base(ModuleFs, 6101); - /// Error code: 2002-6102; Inner value: 0x2fac02 - public static Result.Base FileDataCacheMemorySizeTooSmall => new Result.Base(ModuleFs, 6102); - - /// Error code: 2002-6200; Range: 6200-6299; Inner value: 0x307002 - public static Result.Base InvalidOperationForOpenMode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6200, 6299); } - /// Error code: 2002-6201; Inner value: 0x307202 - public static Result.Base FileExtensionWithoutOpenModeAllowAppend => new Result.Base(ModuleFs, 6201); - /// Error code: 2002-6202; Inner value: 0x307402 - public static Result.Base ReadUnpermitted => new Result.Base(ModuleFs, 6202); - /// Error code: 2002-6203; Inner value: 0x307602 - public static Result.Base WriteUnpermitted => new Result.Base(ModuleFs, 6203); - - /// Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802 - public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); } - /// Error code: 2002-6301; Inner value: 0x313a02 - public static Result.Base UnsupportedCommitTarget => new Result.Base(ModuleFs, 6301); - /// Attempted to resize a non-resizable SubStorage.
Error code: 2002-6302; Inner value: 0x313c02
- public static Result.Base UnsupportedSetSizeForNotResizableSubStorage => new Result.Base(ModuleFs, 6302); - /// Attempted to resize a SubStorage that wasn't located at the end of the base storage.
Error code: 2002-6303; Inner value: 0x313e02
- public static Result.Base UnsupportedSetSizeForResizableSubStorage => new Result.Base(ModuleFs, 6303); - /// Error code: 2002-6304; Inner value: 0x314002 - public static Result.Base UnsupportedSetSizeForMemoryStorage => new Result.Base(ModuleFs, 6304); - /// Error code: 2002-6305; Inner value: 0x314202 - public static Result.Base UnsupportedOperateRangeForMemoryStorage => new Result.Base(ModuleFs, 6305); - /// Error code: 2002-6306; Inner value: 0x314402 - public static Result.Base UnsupportedOperateRangeForFileStorage => new Result.Base(ModuleFs, 6306); - /// Error code: 2002-6307; Inner value: 0x314602 - public static Result.Base UnsupportedOperateRangeForFileHandleStorage => new Result.Base(ModuleFs, 6307); - /// Error code: 2002-6308; Inner value: 0x314802 - public static Result.Base UnsupportedOperateRangeForSwitchStorage => new Result.Base(ModuleFs, 6308); - /// Error code: 2002-6309; Inner value: 0x314a02 - public static Result.Base UnsupportedOperateRangeForStorageServiceObjectAdapter => new Result.Base(ModuleFs, 6309); - /// Error code: 2002-6310; Inner value: 0x314c02 - public static Result.Base UnsupportedWriteForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6310); - /// Error code: 2002-6311; Inner value: 0x314e02 - public static Result.Base UnsupportedSetSizeForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6311); - /// Error code: 2002-6312; Inner value: 0x315002 - public static Result.Base UnsupportedOperateRangeForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6312); - /// Error code: 2002-6313; Inner value: 0x315202 - public static Result.Base UnsupportedWriteForAesCtrStorageExternal => new Result.Base(ModuleFs, 6313); - /// Error code: 2002-6314; Inner value: 0x315402 - public static Result.Base UnsupportedSetSizeForAesCtrStorageExternal => new Result.Base(ModuleFs, 6314); - /// Error code: 2002-6315; Inner value: 0x315602 - public static Result.Base UnsupportedSetSizeForAesCtrStorage => new Result.Base(ModuleFs, 6315); - /// Error code: 2002-6316; Inner value: 0x315802 - public static Result.Base UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage => new Result.Base(ModuleFs, 6316); - /// Error code: 2002-6317; Inner value: 0x315a02 - public static Result.Base UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage => new Result.Base(ModuleFs, 6317); - /// Error code: 2002-6318; Inner value: 0x315c02 - public static Result.Base UnsupportedSetSizeForIntegrityVerificationStorage => new Result.Base(ModuleFs, 6318); - /// Error code: 2002-6319; Inner value: 0x315e02 - public static Result.Base UnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage => new Result.Base(ModuleFs, 6319); - /// Error code: 2002-6320; Inner value: 0x316002 - public static Result.Base UnsupportedOperateRangeForIntegrityVerificationStorage => new Result.Base(ModuleFs, 6320); - /// Error code: 2002-6321; Inner value: 0x316202 - public static Result.Base UnsupportedSetSizeForBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6321); - /// Error code: 2002-6322; Inner value: 0x316402 - public static Result.Base UnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6322); - /// Error code: 2002-6323; Inner value: 0x316602 - public static Result.Base UnsupportedOperateRangeForBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6323); - /// Error code: 2002-6324; Inner value: 0x316802 - public static Result.Base UnsupportedWriteForIndirectStorage => new Result.Base(ModuleFs, 6324); - /// Error code: 2002-6325; Inner value: 0x316a02 - public static Result.Base UnsupportedSetSizeForIndirectStorage => new Result.Base(ModuleFs, 6325); - /// Error code: 2002-6326; Inner value: 0x316c02 - public static Result.Base UnsupportedOperateRangeForIndirectStorage => new Result.Base(ModuleFs, 6326); - /// Error code: 2002-6327; Inner value: 0x316e02 - public static Result.Base UnsupportedWriteForZeroStorage => new Result.Base(ModuleFs, 6327); - /// Error code: 2002-6328; Inner value: 0x317002 - public static Result.Base UnsupportedSetSizeForZeroStorage => new Result.Base(ModuleFs, 6328); - /// Error code: 2002-6329; Inner value: 0x317202 - public static Result.Base UnsupportedSetSizeForHierarchicalSha256Storage => new Result.Base(ModuleFs, 6329); - /// Error code: 2002-6330; Inner value: 0x317402 - public static Result.Base UnsupportedWriteForReadOnlyBlockCacheStorage => new Result.Base(ModuleFs, 6330); - /// Error code: 2002-6331; Inner value: 0x317602 - public static Result.Base UnsupportedSetSizeForReadOnlyBlockCacheStorage => new Result.Base(ModuleFs, 6331); - /// Error code: 2002-6332; Inner value: 0x317802 - public static Result.Base UnsupportedSetSizeForIntegrityRomFsStorage => new Result.Base(ModuleFs, 6332); - /// Error code: 2002-6333; Inner value: 0x317a02 - public static Result.Base UnsupportedSetSizeForDuplexStorage => new Result.Base(ModuleFs, 6333); - /// Error code: 2002-6334; Inner value: 0x317c02 - public static Result.Base UnsupportedOperateRangeForDuplexStorage => new Result.Base(ModuleFs, 6334); - /// Error code: 2002-6335; Inner value: 0x317e02 - public static Result.Base UnsupportedSetSizeForHierarchicalDuplexStorage => new Result.Base(ModuleFs, 6335); - /// Error code: 2002-6336; Inner value: 0x318002 - public static Result.Base UnsupportedGetSizeForRemapStorage => new Result.Base(ModuleFs, 6336); - /// Error code: 2002-6337; Inner value: 0x318202 - public static Result.Base UnsupportedSetSizeForRemapStorage => new Result.Base(ModuleFs, 6337); - /// Error code: 2002-6338; Inner value: 0x318402 - public static Result.Base UnsupportedOperateRangeForRemapStorage => new Result.Base(ModuleFs, 6338); - /// Error code: 2002-6339; Inner value: 0x318602 - public static Result.Base UnsupportedSetSizeForIntegritySaveDataStorage => new Result.Base(ModuleFs, 6339); - /// Error code: 2002-6340; Inner value: 0x318802 - public static Result.Base UnsupportedOperateRangeForIntegritySaveDataStorage => new Result.Base(ModuleFs, 6340); - /// Error code: 2002-6341; Inner value: 0x318a02 - public static Result.Base UnsupportedSetSizeForJournalIntegritySaveDataStorage => new Result.Base(ModuleFs, 6341); - /// Error code: 2002-6342; Inner value: 0x318c02 - public static Result.Base UnsupportedOperateRangeForJournalIntegritySaveDataStorage => new Result.Base(ModuleFs, 6342); - /// Error code: 2002-6343; Inner value: 0x318e02 - public static Result.Base UnsupportedGetSizeForJournalStorage => new Result.Base(ModuleFs, 6343); - /// Error code: 2002-6344; Inner value: 0x319002 - public static Result.Base UnsupportedSetSizeForJournalStorage => new Result.Base(ModuleFs, 6344); - /// Error code: 2002-6345; Inner value: 0x319202 - public static Result.Base UnsupportedOperateRangeForJournalStorage => new Result.Base(ModuleFs, 6345); - /// Error code: 2002-6346; Inner value: 0x319402 - public static Result.Base UnsupportedSetSizeForUnionStorage => new Result.Base(ModuleFs, 6346); - /// Error code: 2002-6347; Inner value: 0x319602 - public static Result.Base UnsupportedSetSizeForAllocationTableStorage => new Result.Base(ModuleFs, 6347); - /// Error code: 2002-6348; Inner value: 0x319802 - public static Result.Base UnsupportedReadForWriteOnlyGameCardStorage => new Result.Base(ModuleFs, 6348); - /// Error code: 2002-6349; Inner value: 0x319a02 - public static Result.Base UnsupportedSetSizeForWriteOnlyGameCardStorage => new Result.Base(ModuleFs, 6349); - /// Error code: 2002-6350; Inner value: 0x319c02 - public static Result.Base UnsupportedWriteForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6350); - /// Error code: 2002-6351; Inner value: 0x319e02 - public static Result.Base UnsupportedSetSizeForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6351); - /// Error code: 2002-6352; Inner value: 0x31a002 - public static Result.Base UnsupportedOperateRangeForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6352); - /// Error code: 2002-6353; Inner value: 0x31a202 - public static Result.Base UnsupportedSetSizeForSdmmcStorage => new Result.Base(ModuleFs, 6353); - /// Error code: 2002-6354; Inner value: 0x31a402 - public static Result.Base UnsupportedOperateRangeForSdmmcStorage => new Result.Base(ModuleFs, 6354); - /// Error code: 2002-6355; Inner value: 0x31a602 - public static Result.Base UnsupportedOperateRangeForFatFile => new Result.Base(ModuleFs, 6355); - /// Error code: 2002-6356; Inner value: 0x31a802 - public static Result.Base UnsupportedOperateRangeForStorageFile => new Result.Base(ModuleFs, 6356); - /// Error code: 2002-6357; Inner value: 0x31aa02 - public static Result.Base UnsupportedSetSizeForInternalStorageConcatenationFile => new Result.Base(ModuleFs, 6357); - /// Error code: 2002-6358; Inner value: 0x31ac02 - public static Result.Base UnsupportedOperateRangeForInternalStorageConcatenationFile => new Result.Base(ModuleFs, 6358); - /// Error code: 2002-6359; Inner value: 0x31ae02 - public static Result.Base UnsupportedQueryEntryForConcatenationFileSystem => new Result.Base(ModuleFs, 6359); - /// Error code: 2002-6360; Inner value: 0x31b002 - public static Result.Base UnsupportedOperateRangeForConcatenationFile => new Result.Base(ModuleFs, 6360); - /// Error code: 2002-6361; Inner value: 0x31b202 - public static Result.Base UnsupportedSetSizeForZeroBitmapFile => new Result.Base(ModuleFs, 6361); - /// Called OperateRange with an invalid operation ID.
Error code: 2002-6362; Inner value: 0x31b402
- public static Result.Base UnsupportedOperateRangeForFileServiceObjectAdapter => new Result.Base(ModuleFs, 6362); - /// Error code: 2002-6363; Inner value: 0x31b602 - public static Result.Base UnsupportedOperateRangeForAesXtsFile => new Result.Base(ModuleFs, 6363); - /// Error code: 2002-6364; Inner value: 0x31b802 - public static Result.Base UnsupportedWriteForRomFsFileSystem => new Result.Base(ModuleFs, 6364); - /// Called RomFsFileSystem::CommitProvisionally.
Error code: 2002-6365; Inner value: 0x31ba02
- public static Result.Base UnsupportedCommitProvisionallyForRomFsFileSystem => new Result.Base(ModuleFs, 6365); - /// Error code: 2002-6366; Inner value: 0x31bc02 - public static Result.Base UnsupportedGetTotalSpaceSizeForRomFsFileSystem => new Result.Base(ModuleFs, 6366); - /// Error code: 2002-6367; Inner value: 0x31be02 - public static Result.Base UnsupportedWriteForRomFsFile => new Result.Base(ModuleFs, 6367); - /// Error code: 2002-6368; Inner value: 0x31c002 - public static Result.Base UnsupportedOperateRangeForRomFsFile => new Result.Base(ModuleFs, 6368); - /// Error code: 2002-6369; Inner value: 0x31c202 - public static Result.Base UnsupportedWriteForReadOnlyFileSystem => new Result.Base(ModuleFs, 6369); - /// Error code: 2002-6370; Inner value: 0x31c402 - public static Result.Base UnsupportedCommitProvisionallyForReadOnlyFileSystem => new Result.Base(ModuleFs, 6370); - /// Error code: 2002-6371; Inner value: 0x31c602 - public static Result.Base UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem => new Result.Base(ModuleFs, 6371); - /// Error code: 2002-6372; Inner value: 0x31c802 - public static Result.Base UnsupportedWriteForReadOnlyFile => new Result.Base(ModuleFs, 6372); - /// Error code: 2002-6373; Inner value: 0x31ca02 - public static Result.Base UnsupportedOperateRangeForReadOnlyFile => new Result.Base(ModuleFs, 6373); - /// Error code: 2002-6374; Inner value: 0x31cc02 - public static Result.Base UnsupportedWriteForPartitionFileSystem => new Result.Base(ModuleFs, 6374); - /// Called PartitionFileSystemCore::CommitProvisionally.
Error code: 2002-6375; Inner value: 0x31ce02
- public static Result.Base UnsupportedCommitProvisionallyForPartitionFileSystem => new Result.Base(ModuleFs, 6375); - /// Error code: 2002-6376; Inner value: 0x31d002 - public static Result.Base UnsupportedWriteForPartitionFile => new Result.Base(ModuleFs, 6376); - /// Error code: 2002-6377; Inner value: 0x31d202 - public static Result.Base UnsupportedOperateRangeForPartitionFile => new Result.Base(ModuleFs, 6377); - /// Error code: 2002-6378; Inner value: 0x31d402 - public static Result.Base UnsupportedOperateRangeForTmFileSystemFile => new Result.Base(ModuleFs, 6378); - /// Error code: 2002-6379; Inner value: 0x31d602 - public static Result.Base UnsupportedWriteForSaveDataInternalStorageFileSystem => new Result.Base(ModuleFs, 6379); - /// Error code: 2002-6382; Inner value: 0x31dc02 - public static Result.Base UnsupportedCommitProvisionallyForApplicationTemporaryFileSystem => new Result.Base(ModuleFs, 6382); - /// Error code: 2002-6383; Inner value: 0x31de02 - public static Result.Base UnsupportedCommitProvisionallyForSaveDataFileSystem => new Result.Base(ModuleFs, 6383); - /// Called DirectorySaveDataFileSystem::CommitProvisionally on a non-user savedata.
Error code: 2002-6384; Inner value: 0x31e002
- public static Result.Base UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6384); - /// Error code: 2002-6385; Inner value: 0x31e202 - public static Result.Base UnsupportedWriteForZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 6385); - /// Error code: 2002-6386; Inner value: 0x31e402 - public static Result.Base UnsupportedSetSizeForZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 6386); - - /// Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002 - public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); } - /// Returned when opening a host FS on a retail device.
Error code: 2002-6403; Inner value: 0x320602
- public static Result.Base PermissionDeniedForCreateHostFileSystem => new Result.Base(ModuleFs, 6403); - - /// Error code: 2002-6450; Inner value: 0x326402 - public static Result.Base PortAcceptableCountLimited => new Result.Base(ModuleFs, 6450); - /// Error code: 2002-6452; Inner value: 0x326802 - public static Result.Base ExternalKeyAlreadyRegistered => new Result.Base(ModuleFs, 6452); - /// Error code: 2002-6454; Inner value: 0x326c02 - public static Result.Base NeedFlush => new Result.Base(ModuleFs, 6454); - /// Error code: 2002-6455; Inner value: 0x326e02 - public static Result.Base FileNotClosed => new Result.Base(ModuleFs, 6455); - /// Error code: 2002-6456; Inner value: 0x327002 - public static Result.Base DirectoryNotClosed => new Result.Base(ModuleFs, 6456); - /// Error code: 2002-6457; Inner value: 0x327202 - public static Result.Base WriteModeFileNotClosed => new Result.Base(ModuleFs, 6457); - /// Error code: 2002-6458; Inner value: 0x327402 - public static Result.Base AllocatorAlreadyRegistered => new Result.Base(ModuleFs, 6458); - /// Error code: 2002-6459; Inner value: 0x327602 - public static Result.Base DefaultAllocatorUsed => new Result.Base(ModuleFs, 6459); - /// Error code: 2002-6460; Inner value: 0x327802 - public static Result.Base GameCardLogoDataNotReadable => new Result.Base(ModuleFs, 6460); - /// Error code: 2002-6461; Inner value: 0x327a02 - public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461); - /// Error code: 2002-6462; Inner value: 0x327c02 - public static Result.Base GlobalFileDataCacheAlreadyEnabled => new Result.Base(ModuleFs, 6462); - /// The provided file system has already been added to the multi-commit manager.
Error code: 2002-6463; Inner value: 0x327e02
- public static Result.Base MultiCommitFileSystemAlreadyAdded => new Result.Base(ModuleFs, 6463); - /// Error code: 2002-6464; Inner value: 0x328002 - public static Result.Base Result6464 => new Result.Base(ModuleFs, 6464); - /// Error code: 2002-6465; Inner value: 0x328202 - public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465); - /// Error code: 2002-6466; Inner value: 0x328402 - public static Result.Base DefaultGlobalFileDataCacheEnabled => new Result.Base(ModuleFs, 6466); - /// Error code: 2002-6467; Inner value: 0x328602 - public static Result.Base SaveDataRootPathUnavailable => new Result.Base(ModuleFs, 6467); - - /// Error code: 2002-6600; Range: 6600-6699; Inner value: 0x339002 - public static Result.Base NotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); } - /// Error code: 2002-6602; Inner value: 0x339402 - public static Result.Base FileNotFound => new Result.Base(ModuleFs, 6602); - /// Error code: 2002-6603; Inner value: 0x339602 - public static Result.Base DirectoryNotFound => new Result.Base(ModuleFs, 6603); - /// Error code: 2002-6604; Inner value: 0x339802 - public static Result.Base DatabaseKeyNotFound => new Result.Base(ModuleFs, 6604); - /// Specified program is not found in the program registry.
Error code: 2002-6605; Inner value: 0x339a02
- public static Result.Base ProgramInfoNotFound => new Result.Base(ModuleFs, 6605); - /// Specified program index is not found
Error code: 2002-6606; Inner value: 0x339c02
- public static Result.Base ProgramIndexNotFound => new Result.Base(ModuleFs, 6606); - - /// Error code: 2002-6700; Range: 6700-6799; Inner value: 0x345802 - public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6700, 6799); } - /// Error code: 2002-6705; Inner value: 0x346202 - public static Result.Base BufferAllocationFailed => new Result.Base(ModuleFs, 6705); - /// Error code: 2002-6706; Inner value: 0x346402 - public static Result.Base MappingTableFull => new Result.Base(ModuleFs, 6706); - /// Error code: 2002-6707; Inner value: 0x346602 - public static Result.Base AllocationTableFull => new Result.Base(ModuleFs, 6707); - /// Error code: 2002-6709; Inner value: 0x346a02 - public static Result.Base OpenCountLimit => new Result.Base(ModuleFs, 6709); - /// The maximum number of file systems have been added to the multi-commit manager.
Error code: 2002-6710; Inner value: 0x346c02
- public static Result.Base MultiCommitFileSystemLimit => new Result.Base(ModuleFs, 6710); - - /// Error code: 2002-6800; Range: 6800-6899; Inner value: 0x352002 - public static Result.Base MappingFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6800, 6899); } - /// Error code: 2002-6811; Inner value: 0x353602 - public static Result.Base MapFull => new Result.Base(ModuleFs, 6811); - - /// Error code: 2002-6900; Range: 6900-6999; Inner value: 0x35e802 - public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6900, 6999); } - /// Error code: 2002-6902; Inner value: 0x35ec02 - public static Result.Base NotInitialized => new Result.Base(ModuleFs, 6902); - /// Error code: 2002-6903; Inner value: 0x35ee02 - public static Result.Base BisProxyInvalidated => new Result.Base(ModuleFs, 6903); - /// Error code: 2002-6904; Inner value: 0x35f002 - public static Result.Base NcaDigestInconsistent => new Result.Base(ModuleFs, 6904); - /// Error code: 2002-6905; Inner value: 0x35f202 - public static Result.Base NotMounted => new Result.Base(ModuleFs, 6905); - /// Error code: 2002-6906; Inner value: 0x35f402 - public static Result.Base SaveDataExtending => new Result.Base(ModuleFs, 6906); - /// Error code: 2002-6907; Inner value: 0x35f602 - public static Result.Base SaveDataToExpandIsProvisionallyCommitted => new Result.Base(ModuleFs, 6907); - - /// Error code: 2002-7002; Inner value: 0x36b402 - public static Result.Base Result7002 => new Result.Base(ModuleFs, 7002); - /// Error code: 2002-7003; Inner value: 0x36b602 - public static Result.Base Result7003 => new Result.Base(ModuleFs, 7003); - /// Error code: 2002-7004; Inner value: 0x36b802 - public static Result.Base Result7004 => new Result.Base(ModuleFs, 7004); - /// Error code: 2002-7005; Inner value: 0x36ba02 - public static Result.Base Result7005 => new Result.Base(ModuleFs, 7005); - /// Error code: 2002-7006; Inner value: 0x36bc02 - public static Result.Base Result7006 => new Result.Base(ModuleFs, 7006); - /// Error code: 2002-7009; Inner value: 0x36c202 - public static Result.Base Result7009 => new Result.Base(ModuleFs, 7009); - /// Error code: 2002-7010; Inner value: 0x36c402 - public static Result.Base Result7010 => new Result.Base(ModuleFs, 7010); - /// Error code: 2002-7011; Inner value: 0x36c602 - public static Result.Base Result7011 => new Result.Base(ModuleFs, 7011); - /// Error code: 2002-7031; Inner value: 0x36ee02 - public static Result.Base SaveDataPorterInvalidated => new Result.Base(ModuleFs, 7031); - /// Error code: 2002-7032; Inner value: 0x36f002 - public static Result.Base Result7032 => new Result.Base(ModuleFs, 7032); - /// Error code: 2002-7033; Inner value: 0x36f202 - public static Result.Base Result7033 => new Result.Base(ModuleFs, 7033); - /// Error code: 2002-7034; Inner value: 0x36f402 - public static Result.Base Result7034 => new Result.Base(ModuleFs, 7034); - /// Error code: 2002-7035; Inner value: 0x36f602 - public static Result.Base Result7035 => new Result.Base(ModuleFs, 7035); - /// Error code: 2002-7036; Inner value: 0x36f802 - public static Result.Base Result7036 => new Result.Base(ModuleFs, 7036); - /// Error code: 2002-7037; Inner value: 0x36fa02 - public static Result.Base Result7037 => new Result.Base(ModuleFs, 7037); - /// Error code: 2002-7038; Inner value: 0x36fc02 - public static Result.Base Result7038 => new Result.Base(ModuleFs, 7038); - /// Error code: 2002-7062; Inner value: 0x372c02 - public static Result.Base InvalidKeyPackageMac => new Result.Base(ModuleFs, 7062); - /// Error code: 2002-7063; Inner value: 0x372e02 - public static Result.Base KeyPackageSignatureVerificationFailed => new Result.Base(ModuleFs, 7063); - /// Error code: 2002-7064; Inner value: 0x373002 - public static Result.Base InvalidKeyPackageChallengeData => new Result.Base(ModuleFs, 7064); - /// Error code: 2002-7065; Inner value: 0x373202 - public static Result.Base UnsupportedKeyPackageKeyGeneration => new Result.Base(ModuleFs, 7065); - /// Error code: 2002-7066; Inner value: 0x373402 - public static Result.Base InvalidSaveDataRepairInitialDataContentGcmMac => new Result.Base(ModuleFs, 7066); - /// Error code: 2002-7069; Inner value: 0x373a02 - public static Result.Base InvalidSaveDataRepairInitialDataCmac => new Result.Base(ModuleFs, 7069); - /// The before and after initial data have different AAD.
Error code: 2002-7070; Inner value: 0x373c02
- public static Result.Base SaveDataRepairInitialDataAadMismatch => new Result.Base(ModuleFs, 7070); - /// The before and after initial data refer to different saves.
Error code: 2002-7071; Inner value: 0x373e02
- public static Result.Base SaveDataRepairInitialDataSaveMismatch => new Result.Base(ModuleFs, 7071); - - /// Error code: 2002-7100; Range: 7100-7139; Inner value: 0x377802 - public static Result.Base RamDiskCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7100, 7139); } - /// Error code: 2002-7101; Inner value: 0x377a02 - public static Result.Base Result7101 => new Result.Base(ModuleFs, 7101); - - /// Error code: 2002-7111; Range: 7111-7119; Inner value: 0x378e02 - public static Result.Base RamDiskSaveDataCoreFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7111, 7119); } - /// Error code: 2002-7112; Inner value: 0x379002 - public static Result.Base IncorrectRamDiskSaveDataControlAreaMagic => new Result.Base(ModuleFs, 7112); - /// Error code: 2002-7113; Inner value: 0x379202 - public static Result.Base InvalidRamDiskSaveDataFileReadOffset => new Result.Base(ModuleFs, 7113); - /// Error code: 2002-7114; Inner value: 0x379402 - public static Result.Base InvalidRamDiskSaveDataCoreDataStorageSize => new Result.Base(ModuleFs, 7114); - - /// Error code: 2002-7121; Range: 7121-7139; Inner value: 0x37a202 - public static Result.Base RamDiskDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7121, 7139); } - /// Error code: 2002-7122; Inner value: 0x37a402 - public static Result.Base InvalidRamDiskAllocationTableBlock => new Result.Base(ModuleFs, 7122); - /// Error code: 2002-7123; Inner value: 0x37a602 - public static Result.Base InvalidRamDiskKeyValueListElementIndex => new Result.Base(ModuleFs, 7123); - /// Error code: 2002-7124; Inner value: 0x37a802 - public static Result.Base RamDiskAllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 7124); - /// Error code: 2002-7125; Inner value: 0x37aa02 - public static Result.Base InvalidRamDiskAllocationTableOffset => new Result.Base(ModuleFs, 7125); - /// Error code: 2002-7126; Inner value: 0x37ac02 - public static Result.Base InvalidRamDiskAllocationTableBlockCount => new Result.Base(ModuleFs, 7126); - /// Error code: 2002-7127; Inner value: 0x37ae02 - public static Result.Base InvalidRamDiskKeyValueListEntryIndex => new Result.Base(ModuleFs, 7127); - - /// Error code: 2002-7142; Inner value: 0x37cc02 - public static Result.Base Result7142 => new Result.Base(ModuleFs, 7142); - /// Error code: 2002-7900; Inner value: 0x3db802 - public static Result.Base Unknown => new Result.Base(ModuleFs, 7900); - - /// Error code: 2002-7901; Range: 7901-7904; Inner value: 0x3dba02 - public static Result.Base DbmNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7901, 7904); } - /// Error code: 2002-7902; Inner value: 0x3dbc02 - public static Result.Base DbmKeyNotFound => new Result.Base(ModuleFs, 7902); - /// Error code: 2002-7903; Inner value: 0x3dbe02 - public static Result.Base DbmFileNotFound => new Result.Base(ModuleFs, 7903); - /// Error code: 2002-7904; Inner value: 0x3dc002 - public static Result.Base DbmDirectoryNotFound => new Result.Base(ModuleFs, 7904); - - /// Error code: 2002-7906; Inner value: 0x3dc402 - public static Result.Base DbmAlreadyExists => new Result.Base(ModuleFs, 7906); - /// Error code: 2002-7907; Inner value: 0x3dc602 - public static Result.Base DbmKeyFull => new Result.Base(ModuleFs, 7907); - /// Error code: 2002-7908; Inner value: 0x3dc802 - public static Result.Base DbmDirectoryEntryFull => new Result.Base(ModuleFs, 7908); - /// Error code: 2002-7909; Inner value: 0x3dca02 - public static Result.Base DbmFileEntryFull => new Result.Base(ModuleFs, 7909); - - /// Error code: 2002-7910; Range: 7910-7912; Inner value: 0x3dcc02 - public static Result.Base DbmFindFinished { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7910, 7912); } - /// Error code: 2002-7911; Inner value: 0x3dce02 - public static Result.Base DbmFindKeyFinished => new Result.Base(ModuleFs, 7911); - /// Error code: 2002-7912; Inner value: 0x3dd002 - public static Result.Base DbmIterationFinished => new Result.Base(ModuleFs, 7912); - - /// Error code: 2002-7914; Inner value: 0x3dd402 - public static Result.Base DbmInvalidOperation => new Result.Base(ModuleFs, 7914); - /// Error code: 2002-7915; Inner value: 0x3dd602 - public static Result.Base DbmInvalidPathFormat => new Result.Base(ModuleFs, 7915); - /// Error code: 2002-7916; Inner value: 0x3dd802 - public static Result.Base DbmDirectoryNameTooLong => new Result.Base(ModuleFs, 7916); - /// Error code: 2002-7917; Inner value: 0x3dda02 - public static Result.Base DbmFileNameTooLong => new Result.Base(ModuleFs, 7917); - } + public const int ModuleFs = 2; + + /// Error code: 2002-0000; Range: 0-999; Inner value: 0x2 + public static Result.Base HandledByAllProcess => new Result.Base(ModuleFs, 0, 999); + /// Specified path does not exist
Error code: 2002-0001; Inner value: 0x202
+ public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1); + /// Specified path already exists
Error code: 2002-0002; Inner value: 0x402
+ public static Result.Base PathAlreadyExists => new Result.Base(ModuleFs, 2); + /// Resource already in use (file already opened, savedata filesystem already mounted)
Error code: 2002-0007; Inner value: 0xe02
+ public static Result.Base TargetLocked => new Result.Base(ModuleFs, 7); + /// Specified directory is not empty when trying to delete it
Error code: 2002-0008; Inner value: 0x1002
+ public static Result.Base DirectoryNotEmpty => new Result.Base(ModuleFs, 8); + /// Error code: 2002-0013; Inner value: 0x1a02 + public static Result.Base DirectoryStatusChanged => new Result.Base(ModuleFs, 13); + + /// Error code: 2002-0030; Range: 30-45; Inner value: 0x3c02 + public static Result.Base UsableSpaceNotEnough => new Result.Base(ModuleFs, 30, 45); + /// Error code: 2002-0031; Inner value: 0x3e02 + public static Result.Base UsableSpaceNotEnoughForSaveData => new Result.Base(ModuleFs, 31); + /// Error code: 2002-0032; Inner value: 0x4002 + public static Result.Base UsableSpaceNotEnoughAfterDataErase => new Result.Base(ModuleFs, 32); + /// Error code: 2002-0033; Inner value: 0x4202 + public static Result.Base UsableSpaceNotEnoughForCacheStorage => new Result.Base(ModuleFs, 33); + + /// Error code: 2002-0034; Range: 34-38; Inner value: 0x4402 + public static Result.Base UsableSpaceNotEnoughForBis => new Result.Base(ModuleFs, 34, 38); + /// Error code: 2002-0035; Inner value: 0x4602 + public static Result.Base UsableSpaceNotEnoughForBisCalibration => new Result.Base(ModuleFs, 35); + /// Error code: 2002-0036; Inner value: 0x4802 + public static Result.Base UsableSpaceNotEnoughForBisSafe => new Result.Base(ModuleFs, 36); + /// Error code: 2002-0037; Inner value: 0x4a02 + public static Result.Base UsableSpaceNotEnoughForBisUser => new Result.Base(ModuleFs, 37); + /// Error code: 2002-0038; Inner value: 0x4c02 + public static Result.Base UsableSpaceNotEnoughForBisSystem => new Result.Base(ModuleFs, 38); + + /// Error code: 2002-0039; Inner value: 0x4e02 + public static Result.Base UsableSpaceNotEnoughForSdCard => new Result.Base(ModuleFs, 39); + + /// Error code: 2002-0050; Inner value: 0x6402 + public static Result.Base UnsupportedSdkVersion => new Result.Base(ModuleFs, 50); + /// Error code: 2002-0060; Inner value: 0x7802 + public static Result.Base MountNameAlreadyExists => new Result.Base(ModuleFs, 60); + /// Error code: 2002-0070; Inner value: 0x8c02 + public static Result.Base IndividualFileDataCacheAlreadyEnabled => new Result.Base(ModuleFs, 70); + + /// Error code: 2002-1000; Range: 1000-2999; Inner value: 0x7d002 + public static Result.Base HandledBySystemProcess { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 1000, 2999); } + /// Error code: 2002-1001; Inner value: 0x7d202 + public static Result.Base PartitionNotFound => new Result.Base(ModuleFs, 1001); + /// Error code: 2002-1002; Inner value: 0x7d402 + public static Result.Base TargetNotFound => new Result.Base(ModuleFs, 1002); + /// Error code: 2002-1003; Inner value: 0x7d602 + public static Result.Base MmcPatrolDataNotInitialized => new Result.Base(ModuleFs, 1003); + /// The requested external key was not found
Error code: 2002-1004; Inner value: 0x7d802
+ public static Result.Base NcaExternalKeyUnavailable => new Result.Base(ModuleFs, 1004); + + /// Error code: 2002-2000; Range: 2000-2499; Inner value: 0xfa002 + public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } + /// Error code: 2002-2001; Inner value: 0xfa202 + public static Result.Base PortSdCardNoDevice => new Result.Base(ModuleFs, 2001); + /// Error code: 2002-2002; Inner value: 0xfa402 + public static Result.Base PortSdCardNotActivated => new Result.Base(ModuleFs, 2002); + /// Error code: 2002-2003; Inner value: 0xfa602 + public static Result.Base PortSdCardDeviceRemoved => new Result.Base(ModuleFs, 2003); + /// Error code: 2002-2004; Inner value: 0xfa802 + public static Result.Base PortSdCardNotAwakened => new Result.Base(ModuleFs, 2004); + + /// Error code: 2002-2032; Range: 2032-2126; Inner value: 0xfe002 + public static Result.Base PortSdCardCommunicationError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2032, 2126); } + /// Error code: 2002-2033; Range: 2033-2046; Inner value: 0xfe202 + public static Result.Base PortSdCardCommunicationNotAttained { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2033, 2046); } + /// Error code: 2002-2034; Inner value: 0xfe402 + public static Result.Base PortSdCardResponseIndexError => new Result.Base(ModuleFs, 2034); + /// Error code: 2002-2035; Inner value: 0xfe602 + public static Result.Base PortSdCardResponseEndBitError => new Result.Base(ModuleFs, 2035); + /// Error code: 2002-2036; Inner value: 0xfe802 + public static Result.Base PortSdCardResponseCrcError => new Result.Base(ModuleFs, 2036); + /// Error code: 2002-2037; Inner value: 0xfea02 + public static Result.Base PortSdCardResponseTimeoutError => new Result.Base(ModuleFs, 2037); + /// Error code: 2002-2038; Inner value: 0xfec02 + public static Result.Base PortSdCardDataEndBitError => new Result.Base(ModuleFs, 2038); + /// Error code: 2002-2039; Inner value: 0xfee02 + public static Result.Base PortSdCardDataCrcError => new Result.Base(ModuleFs, 2039); + /// Error code: 2002-2040; Inner value: 0xff002 + public static Result.Base PortSdCardDataTimeoutError => new Result.Base(ModuleFs, 2040); + /// Error code: 2002-2041; Inner value: 0xff202 + public static Result.Base PortSdCardAutoCommandResponseIndexError => new Result.Base(ModuleFs, 2041); + /// Error code: 2002-2042; Inner value: 0xff402 + public static Result.Base PortSdCardAutoCommandResponseEndBitError => new Result.Base(ModuleFs, 2042); + /// Error code: 2002-2043; Inner value: 0xff602 + public static Result.Base PortSdCardAutoCommandResponseCrcError => new Result.Base(ModuleFs, 2043); + /// Error code: 2002-2044; Inner value: 0xff802 + public static Result.Base PortSdCardAutoCommandResponseTimeoutError => new Result.Base(ModuleFs, 2044); + /// Error code: 2002-2045; Inner value: 0xffa02 + public static Result.Base PortSdCardCommandCompleteSoftwareTimeout => new Result.Base(ModuleFs, 2045); + /// Error code: 2002-2046; Inner value: 0xffc02 + public static Result.Base PortSdCardTransferCompleteSoftwareTimeout => new Result.Base(ModuleFs, 2046); + + /// Error code: 2002-2048; Range: 2048-2070; Inner value: 0x100002 + public static Result.Base PortSdCardDeviceStatusHasError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2048, 2070); } + /// Error code: 2002-2049; Inner value: 0x100202 + public static Result.Base PortSdCardDeviceStatusAddressOutOfRange => new Result.Base(ModuleFs, 2049); + /// Error code: 2002-2050; Inner value: 0x100402 + public static Result.Base PortSdCardDeviceStatusAddressMisaligned => new Result.Base(ModuleFs, 2050); + /// Error code: 2002-2051; Inner value: 0x100602 + public static Result.Base PortSdCardDeviceStatusBlockLenError => new Result.Base(ModuleFs, 2051); + /// Error code: 2002-2052; Inner value: 0x100802 + public static Result.Base PortSdCardDeviceStatusEraseSeqError => new Result.Base(ModuleFs, 2052); + /// Error code: 2002-2053; Inner value: 0x100a02 + public static Result.Base PortSdCardDeviceStatusEraseParam => new Result.Base(ModuleFs, 2053); + /// Error code: 2002-2054; Inner value: 0x100c02 + public static Result.Base PortSdCardDeviceStatusWpViolation => new Result.Base(ModuleFs, 2054); + /// Error code: 2002-2055; Inner value: 0x100e02 + public static Result.Base PortSdCardDeviceStatusLockUnlockFailed => new Result.Base(ModuleFs, 2055); + /// Error code: 2002-2056; Inner value: 0x101002 + public static Result.Base PortSdCardDeviceStatusComCrcError => new Result.Base(ModuleFs, 2056); + /// Error code: 2002-2057; Inner value: 0x101202 + public static Result.Base PortSdCardDeviceStatusIllegalCommand => new Result.Base(ModuleFs, 2057); + /// Error code: 2002-2058; Inner value: 0x101402 + public static Result.Base PortSdCardDeviceStatusDeviceEccFailed => new Result.Base(ModuleFs, 2058); + /// Error code: 2002-2059; Inner value: 0x101602 + public static Result.Base PortSdCardDeviceStatusCcError => new Result.Base(ModuleFs, 2059); + /// Error code: 2002-2060; Inner value: 0x101802 + public static Result.Base PortSdCardDeviceStatusError => new Result.Base(ModuleFs, 2060); + /// Error code: 2002-2061; Inner value: 0x101a02 + public static Result.Base PortSdCardDeviceStatusCidCsdOverwrite => new Result.Base(ModuleFs, 2061); + /// Error code: 2002-2062; Inner value: 0x101c02 + public static Result.Base PortSdCardDeviceStatusWpEraseSkip => new Result.Base(ModuleFs, 2062); + /// Error code: 2002-2063; Inner value: 0x101e02 + public static Result.Base PortSdCardDeviceStatusEraseReset => new Result.Base(ModuleFs, 2063); + /// Error code: 2002-2064; Inner value: 0x102002 + public static Result.Base PortSdCardDeviceStatusSwitchError => new Result.Base(ModuleFs, 2064); + + /// Error code: 2002-2072; Inner value: 0x103002 + public static Result.Base PortSdCardUnexpectedDeviceState => new Result.Base(ModuleFs, 2072); + /// Error code: 2002-2073; Inner value: 0x103202 + public static Result.Base PortSdCardUnexpectedDeviceCsdValue => new Result.Base(ModuleFs, 2073); + /// Error code: 2002-2074; Inner value: 0x103402 + public static Result.Base PortSdCardAbortTransactionSoftwareTimeout => new Result.Base(ModuleFs, 2074); + /// Error code: 2002-2075; Inner value: 0x103602 + public static Result.Base PortSdCardCommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleFs, 2075); + /// Error code: 2002-2076; Inner value: 0x103802 + public static Result.Base PortSdCardCommandInhibitDatSoftwareTimeout => new Result.Base(ModuleFs, 2076); + /// Error code: 2002-2077; Inner value: 0x103a02 + public static Result.Base PortSdCardBusySoftwareTimeout => new Result.Base(ModuleFs, 2077); + /// Error code: 2002-2078; Inner value: 0x103c02 + public static Result.Base PortSdCardIssueTuningCommandSoftwareTimeout => new Result.Base(ModuleFs, 2078); + /// Error code: 2002-2079; Inner value: 0x103e02 + public static Result.Base PortSdCardTuningFailed => new Result.Base(ModuleFs, 2079); + /// Error code: 2002-2080; Inner value: 0x104002 + public static Result.Base PortSdCardMmcInitializationSoftwareTimeout => new Result.Base(ModuleFs, 2080); + /// Error code: 2002-2081; Inner value: 0x104202 + public static Result.Base PortSdCardMmcNotSupportExtendedCsd => new Result.Base(ModuleFs, 2081); + /// Error code: 2002-2082; Inner value: 0x104402 + public static Result.Base PortSdCardUnexpectedMmcExtendedCsdValue => new Result.Base(ModuleFs, 2082); + /// Error code: 2002-2083; Inner value: 0x104602 + public static Result.Base PortSdCardMmcEraseSoftwareTimeout => new Result.Base(ModuleFs, 2083); + /// Error code: 2002-2084; Inner value: 0x104802 + public static Result.Base PortSdCardSdCardValidationError => new Result.Base(ModuleFs, 2084); + /// Error code: 2002-2085; Inner value: 0x104a02 + public static Result.Base PortSdCardSdCardInitializationSoftwareTimeout => new Result.Base(ModuleFs, 2085); + /// Error code: 2002-2086; Inner value: 0x104c02 + public static Result.Base PortSdCardSdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleFs, 2086); + /// Error code: 2002-2087; Inner value: 0x104e02 + public static Result.Base PortSdCardUnexpectedSdCardAcmdDisabled => new Result.Base(ModuleFs, 2087); + /// Error code: 2002-2088; Inner value: 0x105002 + public static Result.Base PortSdCardSdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleFs, 2088); + /// Error code: 2002-2089; Inner value: 0x105202 + public static Result.Base PortSdCardUnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleFs, 2089); + /// Error code: 2002-2090; Inner value: 0x105402 + public static Result.Base PortSdCardSdCardNotSupportAccessMode => new Result.Base(ModuleFs, 2090); + /// Error code: 2002-2091; Inner value: 0x105602 + public static Result.Base PortSdCardSdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleFs, 2091); + /// Error code: 2002-2092; Inner value: 0x105802 + public static Result.Base PortSdCardSdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleFs, 2092); + /// Error code: 2002-2093; Inner value: 0x105a02 + public static Result.Base PortSdCardSdCardCannotSwitchAccessMode => new Result.Base(ModuleFs, 2093); + /// Error code: 2002-2094; Inner value: 0x105c02 + public static Result.Base PortSdCardSdCardFailedSwitchAccessMode => new Result.Base(ModuleFs, 2094); + /// Error code: 2002-2095; Inner value: 0x105e02 + public static Result.Base PortSdCardSdCardUnacceptableCurrentConsumption => new Result.Base(ModuleFs, 2095); + /// Error code: 2002-2096; Inner value: 0x106002 + public static Result.Base PortSdCardSdCardNotReadyToVoltageSwitch => new Result.Base(ModuleFs, 2096); + /// Error code: 2002-2097; Inner value: 0x106202 + public static Result.Base PortSdCardSdCardNotCompleteVoltageSwitch => new Result.Base(ModuleFs, 2097); + + /// Error code: 2002-2128; Range: 2128-2158; Inner value: 0x10a002 + public static Result.Base PortSdCardHostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2128, 2158); } + /// Error code: 2002-2129; Inner value: 0x10a202 + public static Result.Base PortSdCardInternalClockStableSoftwareTimeout => new Result.Base(ModuleFs, 2129); + /// Error code: 2002-2130; Inner value: 0x10a402 + public static Result.Base PortSdCardSdHostStandardUnknownAutoCmdError => new Result.Base(ModuleFs, 2130); + /// Error code: 2002-2131; Inner value: 0x10a602 + public static Result.Base PortSdCardSdHostStandardUnknownError => new Result.Base(ModuleFs, 2131); + /// Error code: 2002-2132; Inner value: 0x10a802 + public static Result.Base PortSdCardSdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleFs, 2132); + /// Error code: 2002-2133; Inner value: 0x10aa02 + public static Result.Base PortSdCardSdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleFs, 2133); + /// Error code: 2002-2134; Inner value: 0x10ac02 + public static Result.Base PortSdCardSdHostStandardFailSwitchTo18V => new Result.Base(ModuleFs, 2134); + + /// Error code: 2002-2160; Range: 2160-2190; Inner value: 0x10e002 + public static Result.Base PortSdCardInternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2160, 2190); } + /// Error code: 2002-2161; Inner value: 0x10e202 + public static Result.Base PortSdCardNoWaitedInterrupt => new Result.Base(ModuleFs, 2161); + /// Error code: 2002-2162; Inner value: 0x10e402 + public static Result.Base PortSdCardWaitInterruptSoftwareTimeout => new Result.Base(ModuleFs, 2162); + + /// Error code: 2002-2192; Inner value: 0x112002 + public static Result.Base PortSdCardAbortCommandIssued => new Result.Base(ModuleFs, 2192); + /// Error code: 2002-2200; Inner value: 0x113002 + public static Result.Base PortSdCardNotSupported => new Result.Base(ModuleFs, 2200); + /// Error code: 2002-2201; Inner value: 0x113202 + public static Result.Base PortSdCardNotImplemented => new Result.Base(ModuleFs, 2201); + /// Error code: 2002-2496; Inner value: 0x138002 + public static Result.Base SdCardStorageDeviceInvalidated => new Result.Base(ModuleFs, 2496); + /// Error code: 2002-2497; Inner value: 0x138202 + public static Result.Base SdCardFormatWriteVerificationFailed => new Result.Base(ModuleFs, 2497); + /// Error code: 2002-2498; Inner value: 0x138402 + public static Result.Base SdCardFileSystemInvalidatedByRemoved => new Result.Base(ModuleFs, 2498); + /// Error code: 2002-2499; Inner value: 0x138602 + public static Result.Base SdCardDeviceUnknownError => new Result.Base(ModuleFs, 2499); + + /// Error code: 2002-2500; Range: 2500-2999; Inner value: 0x138802 + public static Result.Base GameCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2500, 2999); } + /// Error code: 2002-2503; Inner value: 0x138e02 + public static Result.Base GameCardPreconditionViolation => new Result.Base(ModuleFs, 2503); + /// Error code: 2002-2504; Inner value: 0x139002 + public static Result.Base GameCardNotImplemented => new Result.Base(ModuleFs, 2504); + /// Error code: 2002-2510; Inner value: 0x139c02 + public static Result.Base GameCardNoAvailableLockers => new Result.Base(ModuleFs, 2510); + /// Error code: 2002-2511; Inner value: 0x139e02 + public static Result.Base GameCardLockerIndexOutOfRange => new Result.Base(ModuleFs, 2511); + /// Error code: 2002-2520; Inner value: 0x13b002 + public static Result.Base GameCardNotInserted => new Result.Base(ModuleFs, 2520); + /// Error code: 2002-2521; Inner value: 0x13b202 + public static Result.Base InvalidGameCardIdInSpecificData => new Result.Base(ModuleFs, 2521); + /// Error code: 2002-2522; Inner value: 0x13b402 + public static Result.Base GameCardCardNotActivated => new Result.Base(ModuleFs, 2522); + /// Error code: 2002-2523; Inner value: 0x13b602 + public static Result.Base InvalidCommandForDeactivatedGameCardAsic => new Result.Base(ModuleFs, 2523); + /// Error code: 2002-2531; Inner value: 0x13c602 + public static Result.Base GameCardCardAccessTimeout => new Result.Base(ModuleFs, 2531); + /// Error code: 2002-2532; Inner value: 0x13c802 + public static Result.Base GameCardStatusFatalError => new Result.Base(ModuleFs, 2532); + /// Error code: 2002-2533; Inner value: 0x13ca02 + public static Result.Base GameCardReadFailure => new Result.Base(ModuleFs, 2533); + /// Error code: 2002-2536; Inner value: 0x13d002 + public static Result.Base GameCardRetryLimitHit => new Result.Base(ModuleFs, 2536); + /// Error code: 2002-2537; Inner value: 0x13d202 + public static Result.Base GameCardStatusRefreshRequested => new Result.Base(ModuleFs, 2537); + /// Error code: 2002-2538; Inner value: 0x13d402 + public static Result.Base GameCardStatusCrcErrorAndRefreshRequested => new Result.Base(ModuleFs, 2538); + /// Error code: 2002-2540; Inner value: 0x13d802 + public static Result.Base InvalidSecureAccess => new Result.Base(ModuleFs, 2540); + /// Error code: 2002-2541; Inner value: 0x13da02 + public static Result.Base InvalidNormalAccess => new Result.Base(ModuleFs, 2541); + /// Error code: 2002-2542; Inner value: 0x13dc02 + public static Result.Base InvalidAccessAcrossMode => new Result.Base(ModuleFs, 2542); + + /// Error code: 2002-2543; Range: 2543-2546; Inner value: 0x13de02 + public static Result.Base GameCardWrongCard { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2543, 2546); } + /// Error code: 2002-2544; Inner value: 0x13e002 + public static Result.Base InvalidGameCardInitialDataHash => new Result.Base(ModuleFs, 2544); + /// Error code: 2002-2545; Inner value: 0x13e202 + public static Result.Base InvalidGameCardInitialDataReservedArea => new Result.Base(ModuleFs, 2545); + /// Error code: 2002-2546; Inner value: 0x13e402 + public static Result.Base InvalidGameCardCertificateKekIndex => new Result.Base(ModuleFs, 2546); + + /// Error code: 2002-2548; Inner value: 0x13e802 + public static Result.Base InvalidGameCardModeForGetCardDeviceCertificate => new Result.Base(ModuleFs, 2548); + /// Error code: 2002-2549; Inner value: 0x13ea02 + public static Result.Base NotSupportedForGameCardSecurityMode => new Result.Base(ModuleFs, 2549); + /// Error code: 2002-2550; Inner value: 0x13ec02 + public static Result.Base Result2550 => new Result.Base(ModuleFs, 2550); + /// Error code: 2002-2551; Inner value: 0x13ee02 + public static Result.Base GameCardReadHeaderTryTimeoutForActivation => new Result.Base(ModuleFs, 2551); + /// Error code: 2002-2552; Inner value: 0x13f002 + public static Result.Base Result2552 => new Result.Base(ModuleFs, 2552); + /// Error code: 2002-2553; Inner value: 0x13f202 + public static Result.Base InvalidGameCardModeForGetChallengeCardExistence => new Result.Base(ModuleFs, 2553); + /// Error code: 2002-2554; Inner value: 0x13f402 + public static Result.Base InvalidGameCardHeader => new Result.Base(ModuleFs, 2554); + /// Error code: 2002-2555; Inner value: 0x13f602 + public static Result.Base InvalidGameCardCertificate => new Result.Base(ModuleFs, 2555); + /// Error code: 2002-2557; Inner value: 0x13fa02 + public static Result.Base Result2557 => new Result.Base(ModuleFs, 2557); + /// Error code: 2002-2558; Inner value: 0x13fc02 + public static Result.Base Result2558 => new Result.Base(ModuleFs, 2558); + + /// Error code: 2002-2565; Range: 2565-2595; Inner value: 0x140a02 + public static Result.Base GameCardCommunicationFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2565, 2595); } + + /// Error code: 2002-2599; Inner value: 0x144e02 + public static Result.Base GameCardInvalidStateTransition => new Result.Base(ModuleFs, 2599); + /// Error code: 2002-2600; Inner value: 0x145002 + public static Result.Base GameCardAsicInvalidTransitionToNormalMode => new Result.Base(ModuleFs, 2600); + /// Error code: 2002-2601; Inner value: 0x145202 + public static Result.Base GameCardAsicInvalidTransitionToSecureMode => new Result.Base(ModuleFs, 2601); + /// Error code: 2002-2602; Inner value: 0x145402 + public static Result.Base GameCardAsicInvalidTransitionToWriteMode => new Result.Base(ModuleFs, 2602); + /// Error code: 2002-2629; Inner value: 0x148a02 + public static Result.Base GameCardAsicInitializationFailureForWriterFirmware => new Result.Base(ModuleFs, 2629); + + /// Error code: 2002-2630; Range: 2630-2669; Inner value: 0x148c02 + public static Result.Base GameCardAsicInitializationFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2630, 2669); } + /// Error code: 2002-2631; Inner value: 0x148e02 + public static Result.Base GameCardAsicGetDeviceStatusFailure => new Result.Base(ModuleFs, 2631); + /// Error code: 2002-2632; Inner value: 0x149002 + public static Result.Base GameCardAsicActivationFailure => new Result.Base(ModuleFs, 2632); + /// Error code: 2002-2634; Inner value: 0x149402 + public static Result.Base GameCardAsicSetUserAsicFirmwareFailure => new Result.Base(ModuleFs, 2634); + /// Error code: 2002-2637; Inner value: 0x149a02 + public static Result.Base GameCardAsicGetAsicCertFailure => new Result.Base(ModuleFs, 2637); + /// Error code: 2002-2638; Inner value: 0x149c02 + public static Result.Base GameCardParseCertificateFailure => new Result.Base(ModuleFs, 2638); + /// Error code: 2002-2639; Inner value: 0x149e02 + public static Result.Base InvalidGameCardAsicCertificate => new Result.Base(ModuleFs, 2639); + /// Error code: 2002-2640; Inner value: 0x14a002 + public static Result.Base GameCardAsicSetEmmcEmbeddedSocCertificateFailure => new Result.Base(ModuleFs, 2640); + /// Error code: 2002-2645; Inner value: 0x14aa02 + public static Result.Base GameCardAsicGetAsicEncryptedMessageFailure => new Result.Base(ModuleFs, 2645); + /// Error code: 2002-2646; Inner value: 0x14ac02 + public static Result.Base GameCardAsicSetLibraryEncryptedMessageFailure => new Result.Base(ModuleFs, 2646); + /// Error code: 2002-2651; Inner value: 0x14b602 + public static Result.Base GameCardAsicGetAsicAuthenticationDataFailure => new Result.Base(ModuleFs, 2651); + /// Error code: 2002-2652; Inner value: 0x14b802 + public static Result.Base GameCardAsicSetAsicAuthenticationDataHashFailure => new Result.Base(ModuleFs, 2652); + /// Error code: 2002-2653; Inner value: 0x14ba02 + public static Result.Base GameCardAsicSetLibraryAuthenticationDataFailure => new Result.Base(ModuleFs, 2653); + /// Error code: 2002-2654; Inner value: 0x14bc02 + public static Result.Base GameCardAsicGetLibraryAuthenticationDataHashFailure => new Result.Base(ModuleFs, 2654); + /// Error code: 2002-2655; Inner value: 0x14be02 + public static Result.Base GameCardInvalidLibraryAuthenticationDataHash => new Result.Base(ModuleFs, 2655); + /// Error code: 2002-2658; Inner value: 0x14c402 + public static Result.Base GameCardAsicEnterSecureAsicModeFailure => new Result.Base(ModuleFs, 2658); + /// Error code: 2002-2659; Inner value: 0x14c602 + public static Result.Base GameCardAsicExchangeRandomValuesInSecureModeFailure => new Result.Base(ModuleFs, 2659); + /// Error code: 2002-2660; Inner value: 0x14c802 + public static Result.Base GameCardAsicChallengeCardExistenceFailure => new Result.Base(ModuleFs, 2660); + /// Error code: 2002-2663; Inner value: 0x14ce02 + public static Result.Base GameCardAsicActivationTimeout => new Result.Base(ModuleFs, 2663); + + /// Error code: 2002-2665; Range: 2665-2669; Inner value: 0x14d202 + public static Result.Base GameCardSplFailure { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2665, 2669); } + /// Error code: 2002-2666; Inner value: 0x14d402 + public static Result.Base GameCardSplDecryptAesKeyFailure => new Result.Base(ModuleFs, 2666); + /// Error code: 2002-2667; Inner value: 0x14d602 + public static Result.Base GameCardSplDecryptAndStoreGcKeyFailure => new Result.Base(ModuleFs, 2667); + /// Error code: 2002-2668; Inner value: 0x14d802 + public static Result.Base GameCardSplGenerateRandomBytesFailure => new Result.Base(ModuleFs, 2668); + /// Error code: 2002-2669; Inner value: 0x14da02 + public static Result.Base GameCardSplDecryptGcMessageFailure => new Result.Base(ModuleFs, 2669); + + /// Error code: 2002-2671; Inner value: 0x14de02 + public static Result.Base GameCardAsicReadAsicRegisterFailure => new Result.Base(ModuleFs, 2671); + /// Error code: 2002-2672; Inner value: 0x14e002 + public static Result.Base GameCardAsicWriteAsicRegisterFailure => new Result.Base(ModuleFs, 2672); + /// Error code: 2002-2673; Inner value: 0x14e202 + public static Result.Base GameCardAsicEnableCardBusFailure => new Result.Base(ModuleFs, 2673); + /// Error code: 2002-2674; Inner value: 0x14e402 + public static Result.Base GameCardAsicGetCardHeaderFailure => new Result.Base(ModuleFs, 2674); + /// Error code: 2002-2675; Inner value: 0x14e602 + public static Result.Base GameCardAsicStatusError => new Result.Base(ModuleFs, 2675); + /// Error code: 2002-2676; Inner value: 0x14e802 + public static Result.Base GameCardAsicGetCardKeyAreaFailure => new Result.Base(ModuleFs, 2676); + /// Error code: 2002-2677; Inner value: 0x14ea02 + public static Result.Base GameCardAsicChangeDebugModeFailure => new Result.Base(ModuleFs, 2677); + /// Error code: 2002-2678; Inner value: 0x14ec02 + public static Result.Base GameCardAsicGetRmaInformationFailure => new Result.Base(ModuleFs, 2678); + /// Error code: 2002-2680; Inner value: 0x14f002 + public static Result.Base GameCardAsicStatusBit22Set => new Result.Base(ModuleFs, 2680); + /// Error code: 2002-2681; Inner value: 0x14f202 + public static Result.Base GameCardSecureValuesNotInitialized => new Result.Base(ModuleFs, 2681); + /// Error code: 2002-2692; Inner value: 0x150802 + public static Result.Base InvalidSecureGameCardCommand => new Result.Base(ModuleFs, 2692); + /// Error code: 2002-2693; Inner value: 0x150a02 + public static Result.Base InvalidWriteGameCardCommand => new Result.Base(ModuleFs, 2693); + /// Error code: 2002-2703; Inner value: 0x151e02 + public static Result.Base GameCardSetVoltageFailure => new Result.Base(ModuleFs, 2703); + /// Error code: 2002-2731; Inner value: 0x155602 + public static Result.Base GameCardCommandReadId1Failure => new Result.Base(ModuleFs, 2731); + /// Error code: 2002-2732; Inner value: 0x155802 + public static Result.Base GameCardCommandReadId2Failure => new Result.Base(ModuleFs, 2732); + /// Error code: 2002-2733; Inner value: 0x155a02 + public static Result.Base GameCardCommandReadId3Failure => new Result.Base(ModuleFs, 2733); + /// Error code: 2002-2735; Inner value: 0x155e02 + public static Result.Base GameCardCommandReadPageFailure => new Result.Base(ModuleFs, 2735); + /// Error code: 2002-2736; Inner value: 0x156002 + public static Result.Base GameCardCommandReadPageUnalignedFailure => new Result.Base(ModuleFs, 2736); + /// Error code: 2002-2737; Inner value: 0x156202 + public static Result.Base GameCardCommandWritePageFailure => new Result.Base(ModuleFs, 2737); + /// Error code: 2002-2738; Inner value: 0x156402 + public static Result.Base GameCardCommandRefreshFailure => new Result.Base(ModuleFs, 2738); + /// Error code: 2002-2739; Inner value: 0x156602 + public static Result.Base GameCardCommandUpdateKeyFailure => new Result.Base(ModuleFs, 2739); + /// Error code: 2002-2742; Inner value: 0x156c02 + public static Result.Base GameCardCommandReadCrcFailure => new Result.Base(ModuleFs, 2742); + /// Error code: 2002-2743; Inner value: 0x156e02 + public static Result.Base GameCardCommandEraseFailure => new Result.Base(ModuleFs, 2743); + /// Error code: 2002-2744; Inner value: 0x157002 + public static Result.Base GameCardCommandReadDevParamFailure => new Result.Base(ModuleFs, 2744); + /// Error code: 2002-2745; Inner value: 0x157202 + public static Result.Base GameCardCommandWriteDevParamFailure => new Result.Base(ModuleFs, 2745); + /// Error code: 2002-2901; Inner value: 0x16aa02 + public static Result.Base GameCardParameterError => new Result.Base(ModuleFs, 2901); + /// Error code: 2002-2902; Inner value: 0x16ac02 + public static Result.Base Result2902 => new Result.Base(ModuleFs, 2902); + /// Error code: 2002-2903; Inner value: 0x16ae02 + public static Result.Base Result2903 => new Result.Base(ModuleFs, 2903); + /// Error code: 2002-2904; Inner value: 0x16b002 + public static Result.Base Result2904 => new Result.Base(ModuleFs, 2904); + /// Error code: 2002-2905; Inner value: 0x16b202 + public static Result.Base Result2905 => new Result.Base(ModuleFs, 2905); + /// Error code: 2002-2906; Inner value: 0x16b402 + public static Result.Base Result2906 => new Result.Base(ModuleFs, 2906); + /// Error code: 2002-2950; Inner value: 0x170c02 + public static Result.Base InvalidGameCardStorageAttribute => new Result.Base(ModuleFs, 2950); + /// Error code: 2002-2951; Inner value: 0x170e02 + public static Result.Base GameCardNotInsertedOnGetHandle => new Result.Base(ModuleFs, 2951); + /// Error code: 2002-2952; Inner value: 0x171002 + public static Result.Base InvalidGameCardHandleOnRead => new Result.Base(ModuleFs, 2952); + /// Error code: 2002-2954; Inner value: 0x171402 + public static Result.Base InvalidGameCardHandleOnGetCardInfo => new Result.Base(ModuleFs, 2954); + /// Error code: 2002-2955; Inner value: 0x171602 + public static Result.Base InvalidGameCardHandleOnGetGameCardDeviceCertificate => new Result.Base(ModuleFs, 2955); + /// Error code: 2002-2956; Inner value: 0x171802 + public static Result.Base InvalidGameCardHandleOnGetGameCardImageHash => new Result.Base(ModuleFs, 2956); + /// Error code: 2002-2957; Inner value: 0x171a02 + public static Result.Base InvalidGameCardHandleOnChallengeCardExistence => new Result.Base(ModuleFs, 2957); + /// Error code: 2002-2958; Inner value: 0x171c02 + public static Result.Base InvalidGameCardHandleOnOnAcquireLock => new Result.Base(ModuleFs, 2958); + /// Error code: 2002-2959; Inner value: 0x171e02 + public static Result.Base InvalidGameCardModeOnAcquireSecureLock => new Result.Base(ModuleFs, 2959); + /// Error code: 2002-2960; Inner value: 0x172002 + public static Result.Base InvalidGameCardHandleOnOpenNormalPartition => new Result.Base(ModuleFs, 2960); + /// Error code: 2002-2961; Inner value: 0x172202 + public static Result.Base InvalidGameCardHandleOnOpenSecurePartition => new Result.Base(ModuleFs, 2961); + /// Error code: 2002-2962; Inner value: 0x172402 + public static Result.Base InvalidGameCardCompatibilityType => new Result.Base(ModuleFs, 2962); + /// Error code: 2002-2963; Inner value: 0x172602 + public static Result.Base GameCardsNotSupportedOnDeviceModel => new Result.Base(ModuleFs, 2963); + + /// Error code: 2002-3000; Range: 3000-7999; Inner value: 0x177002 + public static Result.Base Internal { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3000, 7999); } + /// Error code: 2002-3001; Inner value: 0x177202 + public static Result.Base NotImplemented => new Result.Base(ModuleFs, 3001); + /// Error code: 2002-3002; Inner value: 0x177402 + public static Result.Base UnsupportedVersion => new Result.Base(ModuleFs, 3002); + /// Error code: 2002-3003; Inner value: 0x177602 + public static Result.Base AlreadyExists => new Result.Base(ModuleFs, 3003); + /// Error code: 2002-3005; Inner value: 0x177a02 + public static Result.Base OutOfRange => new Result.Base(ModuleFs, 3005); + /// Error code: 2002-3099; Inner value: 0x183602 + public static Result.Base Result3099 => new Result.Base(ModuleFs, 3099); + /// Error code: 2002-3100; Inner value: 0x183802 + public static Result.Base SystemPartitionNotReady => new Result.Base(ModuleFs, 3100); + /// Error code: 2002-3101; Inner value: 0x183a02 + public static Result.Base StorageDeviceNotReady => new Result.Base(ModuleFs, 3101); + + /// Error code: 2002-3200; Range: 3200-3499; Inner value: 0x190002 + public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); } + /// Error code: 2002-3201; Inner value: 0x190202 + public static Result.Base AllocationMemoryFailedInPrFile2 => new Result.Base(ModuleFs, 3201); + /// Error code: 2002-3203; Inner value: 0x190602 + public static Result.Base AllocationMemoryFailedInFatFileSystemA => new Result.Base(ModuleFs, 3203); + /// Error code: 2002-3204; Inner value: 0x190802 + public static Result.Base AllocationMemoryFailedInFatFileSystemB => new Result.Base(ModuleFs, 3204); + /// Error code: 2002-3205; Inner value: 0x190a02 + public static Result.Base AllocationMemoryFailedInFatFileSystemC => new Result.Base(ModuleFs, 3205); + /// Error code: 2002-3206; Inner value: 0x190c02 + public static Result.Base AllocationMemoryFailedInFatFileSystemD => new Result.Base(ModuleFs, 3206); + /// Error code: 2002-3208; Inner value: 0x191002 + public static Result.Base AllocationMemoryFailedInFatFileSystemE => new Result.Base(ModuleFs, 3208); + /// Error code: 2002-3211; Inner value: 0x191602 + public static Result.Base AllocationMemoryFailedInFileSystemAccessorA => new Result.Base(ModuleFs, 3211); + /// Error code: 2002-3212; Inner value: 0x191802 + public static Result.Base AllocationMemoryFailedInFileSystemAccessorB => new Result.Base(ModuleFs, 3212); + /// Error code: 2002-3213; Inner value: 0x191a02 + public static Result.Base AllocationMemoryFailedInApplicationA => new Result.Base(ModuleFs, 3213); + /// Error code: 2002-3214; Inner value: 0x191c02 + public static Result.Base AllocationMemoryFailedInBcatSaveDataA => new Result.Base(ModuleFs, 3214); + /// Error code: 2002-3215; Inner value: 0x191e02 + public static Result.Base AllocationMemoryFailedInBisA => new Result.Base(ModuleFs, 3215); + /// Error code: 2002-3216; Inner value: 0x192002 + public static Result.Base AllocationMemoryFailedInBisB => new Result.Base(ModuleFs, 3216); + /// Error code: 2002-3217; Inner value: 0x192202 + public static Result.Base AllocationMemoryFailedInBisC => new Result.Base(ModuleFs, 3217); + /// Error code: 2002-3218; Inner value: 0x192402 + public static Result.Base AllocationMemoryFailedInCodeA => new Result.Base(ModuleFs, 3218); + /// Error code: 2002-3219; Inner value: 0x192602 + public static Result.Base AllocationMemoryFailedInContentA => new Result.Base(ModuleFs, 3219); + /// Error code: 2002-3220; Inner value: 0x192802 + public static Result.Base AllocationMemoryFailedInContentStorageA => new Result.Base(ModuleFs, 3220); + /// Error code: 2002-3221; Inner value: 0x192a02 + public static Result.Base AllocationMemoryFailedInContentStorageB => new Result.Base(ModuleFs, 3221); + /// Error code: 2002-3222; Inner value: 0x192c02 + public static Result.Base AllocationMemoryFailedInDataA => new Result.Base(ModuleFs, 3222); + /// Error code: 2002-3223; Inner value: 0x192e02 + public static Result.Base AllocationMemoryFailedInDataB => new Result.Base(ModuleFs, 3223); + /// Error code: 2002-3224; Inner value: 0x193002 + public static Result.Base AllocationMemoryFailedInDeviceSaveDataA => new Result.Base(ModuleFs, 3224); + /// Error code: 2002-3225; Inner value: 0x193202 + public static Result.Base AllocationMemoryFailedInGameCardA => new Result.Base(ModuleFs, 3225); + /// Error code: 2002-3226; Inner value: 0x193402 + public static Result.Base AllocationMemoryFailedInGameCardB => new Result.Base(ModuleFs, 3226); + /// Error code: 2002-3227; Inner value: 0x193602 + public static Result.Base AllocationMemoryFailedInGameCardC => new Result.Base(ModuleFs, 3227); + /// Error code: 2002-3228; Inner value: 0x193802 + public static Result.Base AllocationMemoryFailedInGameCardD => new Result.Base(ModuleFs, 3228); + /// Error code: 2002-3229; Inner value: 0x193a02 + public static Result.Base AllocationMemoryFailedInHostA => new Result.Base(ModuleFs, 3229); + /// Error code: 2002-3230; Inner value: 0x193c02 + public static Result.Base AllocationMemoryFailedInHostB => new Result.Base(ModuleFs, 3230); + /// Error code: 2002-3231; Inner value: 0x193e02 + public static Result.Base AllocationMemoryFailedInHostC => new Result.Base(ModuleFs, 3231); + /// Error code: 2002-3232; Inner value: 0x194002 + public static Result.Base AllocationMemoryFailedInImageDirectoryA => new Result.Base(ModuleFs, 3232); + /// Error code: 2002-3233; Inner value: 0x194202 + public static Result.Base AllocationMemoryFailedInLogoA => new Result.Base(ModuleFs, 3233); + /// Error code: 2002-3234; Inner value: 0x194402 + public static Result.Base AllocationMemoryFailedInRomA => new Result.Base(ModuleFs, 3234); + /// Error code: 2002-3235; Inner value: 0x194602 + public static Result.Base AllocationMemoryFailedInRomB => new Result.Base(ModuleFs, 3235); + /// Error code: 2002-3236; Inner value: 0x194802 + public static Result.Base AllocationMemoryFailedInRomC => new Result.Base(ModuleFs, 3236); + /// Error code: 2002-3237; Inner value: 0x194a02 + public static Result.Base AllocationMemoryFailedInRomD => new Result.Base(ModuleFs, 3237); + /// Error code: 2002-3238; Inner value: 0x194c02 + public static Result.Base AllocationMemoryFailedInRomE => new Result.Base(ModuleFs, 3238); + /// Error code: 2002-3239; Inner value: 0x194e02 + public static Result.Base AllocationMemoryFailedInRomF => new Result.Base(ModuleFs, 3239); + /// Error code: 2002-3242; Inner value: 0x195402 + public static Result.Base AllocationMemoryFailedInSaveDataManagementA => new Result.Base(ModuleFs, 3242); + /// Error code: 2002-3243; Inner value: 0x195602 + public static Result.Base AllocationMemoryFailedInSaveDataThumbnailA => new Result.Base(ModuleFs, 3243); + /// Error code: 2002-3244; Inner value: 0x195802 + public static Result.Base AllocationMemoryFailedInSdCardA => new Result.Base(ModuleFs, 3244); + /// Error code: 2002-3245; Inner value: 0x195a02 + public static Result.Base AllocationMemoryFailedInSdCardB => new Result.Base(ModuleFs, 3245); + /// Error code: 2002-3246; Inner value: 0x195c02 + public static Result.Base AllocationMemoryFailedInSystemSaveDataA => new Result.Base(ModuleFs, 3246); + /// Error code: 2002-3247; Inner value: 0x195e02 + public static Result.Base AllocationMemoryFailedInRomFsFileSystemA => new Result.Base(ModuleFs, 3247); + /// Error code: 2002-3248; Inner value: 0x196002 + public static Result.Base AllocationMemoryFailedInRomFsFileSystemB => new Result.Base(ModuleFs, 3248); + /// Error code: 2002-3249; Inner value: 0x196202 + public static Result.Base AllocationMemoryFailedInRomFsFileSystemC => new Result.Base(ModuleFs, 3249); + /// Error code: 2002-3251; Inner value: 0x196602 + public static Result.Base AllocationMemoryFailedInGuidPartitionTableA => new Result.Base(ModuleFs, 3251); + /// Error code: 2002-3252; Inner value: 0x196802 + public static Result.Base AllocationMemoryFailedInDeviceDetectionEventManagerA => new Result.Base(ModuleFs, 3252); + /// Error code: 2002-3253; Inner value: 0x196a02 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemServiceImplA => new Result.Base(ModuleFs, 3253); + /// Error code: 2002-3254; Inner value: 0x196c02 + public static Result.Base AllocationMemoryFailedInFileSystemProxyCoreImplB => new Result.Base(ModuleFs, 3254); + /// Error code: 2002-3255; Inner value: 0x196e02 + public static Result.Base AllocationMemoryFailedInSdCardProxyFileSystemCreatorA => new Result.Base(ModuleFs, 3255); + /// In ParseNsp allocating FileStorageBasedFileSystem
Error code: 2002-3256; Inner value: 0x197002
+ public static Result.Base AllocationMemoryFailedInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 3256); + /// In ParseNca allocating FileStorageBasedFileSystem
Error code: 2002-3257; Inner value: 0x197202
+ public static Result.Base AllocationMemoryFailedInNcaFileSystemServiceImplB => new Result.Base(ModuleFs, 3257); + /// In RegisterProgram allocating ProgramInfoNode
Error code: 2002-3258; Inner value: 0x197402
+ public static Result.Base AllocationMemoryFailedInProgramRegistryManagerA => new Result.Base(ModuleFs, 3258); + /// Error code: 2002-3259; Inner value: 0x197602 + public static Result.Base AllocationMemoryFailedInSdmmcStorageServiceA => new Result.Base(ModuleFs, 3259); + /// Error code: 2002-3260; Inner value: 0x197802 + public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorA => new Result.Base(ModuleFs, 3260); + /// Error code: 2002-3261; Inner value: 0x197a02 + public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorB => new Result.Base(ModuleFs, 3261); + /// Error code: 2002-3262; Inner value: 0x197c02 + public static Result.Base AllocationMemoryFailedInBuiltInStorageCreatorC => new Result.Base(ModuleFs, 3262); + /// In Initialize allocating ProgramInfoNode
Error code: 2002-3264; Inner value: 0x198002
+ public static Result.Base AllocationMemoryFailedFatFileSystemWithBufferA => new Result.Base(ModuleFs, 3264); + /// Error code: 2002-3265; Inner value: 0x198202 + public static Result.Base AllocationMemoryFailedInFatFileSystemCreatorA => new Result.Base(ModuleFs, 3265); + /// Error code: 2002-3266; Inner value: 0x198402 + public static Result.Base AllocationMemoryFailedInFatFileSystemCreatorB => new Result.Base(ModuleFs, 3266); + /// Error code: 2002-3267; Inner value: 0x198602 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorA => new Result.Base(ModuleFs, 3267); + /// Error code: 2002-3268; Inner value: 0x198802 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorB => new Result.Base(ModuleFs, 3268); + /// Error code: 2002-3269; Inner value: 0x198a02 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorC => new Result.Base(ModuleFs, 3269); + /// Error code: 2002-3270; Inner value: 0x198c02 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorD => new Result.Base(ModuleFs, 3270); + /// Error code: 2002-3271; Inner value: 0x198e02 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorE => new Result.Base(ModuleFs, 3271); + /// Error code: 2002-3272; Inner value: 0x199002 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorF => new Result.Base(ModuleFs, 3272); + /// Error code: 2002-3273; Inner value: 0x199202 + public static Result.Base AllocationMemoryFailedInGameCardManagerA => new Result.Base(ModuleFs, 3273); + /// Error code: 2002-3274; Inner value: 0x199402 + public static Result.Base AllocationMemoryFailedInGameCardManagerB => new Result.Base(ModuleFs, 3274); + /// Error code: 2002-3275; Inner value: 0x199602 + public static Result.Base AllocationMemoryFailedInGameCardManagerC => new Result.Base(ModuleFs, 3275); + /// Error code: 2002-3276; Inner value: 0x199802 + public static Result.Base AllocationMemoryFailedInGameCardManagerD => new Result.Base(ModuleFs, 3276); + /// Error code: 2002-3277; Inner value: 0x199a02 + public static Result.Base AllocationMemoryFailedInGameCardManagerE => new Result.Base(ModuleFs, 3277); + /// Error code: 2002-3278; Inner value: 0x199c02 + public static Result.Base AllocationMemoryFailedInGameCardManagerF => new Result.Base(ModuleFs, 3278); + /// Error code: 2002-3279; Inner value: 0x199e02 + public static Result.Base AllocationMemoryFailedInLocalFileSystemCreatorA => new Result.Base(ModuleFs, 3279); + /// In Create allocating PartitionFileSystemCore
Error code: 2002-3280; Inner value: 0x19a002
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemCreatorA => new Result.Base(ModuleFs, 3280); + /// Error code: 2002-3281; Inner value: 0x19a202 + public static Result.Base AllocationMemoryFailedInRomFileSystemCreatorA => new Result.Base(ModuleFs, 3281); + /// Error code: 2002-3282; Inner value: 0x19a402 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorA => new Result.Base(ModuleFs, 3282); + /// Error code: 2002-3283; Inner value: 0x19a602 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorB => new Result.Base(ModuleFs, 3283); + /// Error code: 2002-3284; Inner value: 0x19a802 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorC => new Result.Base(ModuleFs, 3284); + /// Error code: 2002-3285; Inner value: 0x19aa02 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorD => new Result.Base(ModuleFs, 3285); + /// Error code: 2002-3286; Inner value: 0x19ac02 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCreatorE => new Result.Base(ModuleFs, 3286); + /// Error code: 2002-3288; Inner value: 0x19b002 + public static Result.Base AllocationMemoryFailedInStorageOnNcaCreatorA => new Result.Base(ModuleFs, 3288); + /// Error code: 2002-3289; Inner value: 0x19b202 + public static Result.Base AllocationMemoryFailedInStorageOnNcaCreatorB => new Result.Base(ModuleFs, 3289); + /// Error code: 2002-3290; Inner value: 0x19b402 + public static Result.Base AllocationMemoryFailedInSubDirectoryFileSystemCreatorA => new Result.Base(ModuleFs, 3290); + /// Error code: 2002-3291; Inner value: 0x19b602 + public static Result.Base AllocationMemoryFailedInTargetManagerFileSystemCreatorA => new Result.Base(ModuleFs, 3291); + /// Error code: 2002-3292; Inner value: 0x19b802 + public static Result.Base AllocationMemoryFailedInSaveDataIndexerA => new Result.Base(ModuleFs, 3292); + /// Error code: 2002-3293; Inner value: 0x19ba02 + public static Result.Base AllocationMemoryFailedInSaveDataIndexerB => new Result.Base(ModuleFs, 3293); + /// Error code: 2002-3294; Inner value: 0x19bc02 + public static Result.Base AllocationMemoryFailedInFileSystemBuddyHeapA => new Result.Base(ModuleFs, 3294); + /// Error code: 2002-3295; Inner value: 0x19be02 + public static Result.Base AllocationMemoryFailedInFileSystemBufferManagerA => new Result.Base(ModuleFs, 3295); + /// Error code: 2002-3296; Inner value: 0x19c002 + public static Result.Base AllocationMemoryFailedInBlockCacheBufferedStorageA => new Result.Base(ModuleFs, 3296); + /// Error code: 2002-3297; Inner value: 0x19c202 + public static Result.Base AllocationMemoryFailedInBlockCacheBufferedStorageB => new Result.Base(ModuleFs, 3297); + /// Error code: 2002-3298; Inner value: 0x19c402 + public static Result.Base AllocationMemoryFailedInDuplexStorageA => new Result.Base(ModuleFs, 3298); + /// Error code: 2002-3304; Inner value: 0x19d002 + public static Result.Base AllocationMemoryFailedInIntegrityVerificationStorageA => new Result.Base(ModuleFs, 3304); + /// Error code: 2002-3305; Inner value: 0x19d202 + public static Result.Base AllocationMemoryFailedInIntegrityVerificationStorageB => new Result.Base(ModuleFs, 3305); + /// Error code: 2002-3306; Inner value: 0x19d402 + public static Result.Base AllocationMemoryFailedInJournalStorageA => new Result.Base(ModuleFs, 3306); + /// Error code: 2002-3307; Inner value: 0x19d602 + public static Result.Base AllocationMemoryFailedInJournalStorageB => new Result.Base(ModuleFs, 3307); + /// Error code: 2002-3310; Inner value: 0x19dc02 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCoreA => new Result.Base(ModuleFs, 3310); + /// Error code: 2002-3311; Inner value: 0x19de02 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemCoreB => new Result.Base(ModuleFs, 3311); + /// In Initialize allocating FileStorage
Error code: 2002-3312; Inner value: 0x19e002
+ public static Result.Base AllocationMemoryFailedInAesXtsFileA => new Result.Base(ModuleFs, 3312); + /// In Initialize allocating AesXtsStorage
Error code: 2002-3313; Inner value: 0x19e202
+ public static Result.Base AllocationMemoryFailedInAesXtsFileB => new Result.Base(ModuleFs, 3313); + /// In Initialize allocating AlignmentMatchingStoragePooledBuffer
Error code: 2002-3314; Inner value: 0x19e402
+ public static Result.Base AllocationMemoryFailedInAesXtsFileC => new Result.Base(ModuleFs, 3314); + /// In Initialize allocating StorageFile
Error code: 2002-3315; Inner value: 0x19e602
+ public static Result.Base AllocationMemoryFailedInAesXtsFileD => new Result.Base(ModuleFs, 3315); + /// Error code: 2002-3316; Inner value: 0x19e802 + public static Result.Base AllocationMemoryFailedInAesXtsFileSystemA => new Result.Base(ModuleFs, 3316); + /// Error code: 2002-3319; Inner value: 0x19ee02 + public static Result.Base AllocationMemoryFailedInConcatenationFileSystemA => new Result.Base(ModuleFs, 3319); + /// Error code: 2002-3320; Inner value: 0x19f002 + public static Result.Base AllocationMemoryFailedInConcatenationFileSystemB => new Result.Base(ModuleFs, 3320); + /// Error code: 2002-3321; Inner value: 0x19f202 + public static Result.Base AllocationMemoryFailedInDirectorySaveDataFileSystemA => new Result.Base(ModuleFs, 3321); + /// Error code: 2002-3322; Inner value: 0x19f402 + public static Result.Base AllocationMemoryFailedInLocalFileSystemA => new Result.Base(ModuleFs, 3322); + /// Error code: 2002-3323; Inner value: 0x19f602 + public static Result.Base AllocationMemoryFailedInLocalFileSystemB => new Result.Base(ModuleFs, 3323); + /// Error code: 2002-3341; Inner value: 0x1a1a02 + public static Result.Base AllocationMemoryFailedInNcaFileSystemDriverI => new Result.Base(ModuleFs, 3341); + /// In Initialize allocating PartitionFileSystemMetaCore
Error code: 2002-3347; Inner value: 0x1a2602
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemA => new Result.Base(ModuleFs, 3347); + /// In DoOpenFile allocating PartitionFile
Error code: 2002-3348; Inner value: 0x1a2802
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemB => new Result.Base(ModuleFs, 3348); + /// In DoOpenDirectory allocating PartitionDirectory
Error code: 2002-3349; Inner value: 0x1a2a02
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemC => new Result.Base(ModuleFs, 3349); + /// In Initialize allocating metadata buffer
Error code: 2002-3350; Inner value: 0x1a2c02
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemMetaA => new Result.Base(ModuleFs, 3350); + /// In Sha256 Initialize allocating metadata buffer
Error code: 2002-3351; Inner value: 0x1a2e02
+ public static Result.Base AllocationMemoryFailedInPartitionFileSystemMetaB => new Result.Base(ModuleFs, 3351); + /// Error code: 2002-3352; Inner value: 0x1a3002 + public static Result.Base AllocationMemoryFailedInRomFsFileSystemD => new Result.Base(ModuleFs, 3352); + /// In Initialize allocating RootPathBuffer
Error code: 2002-3355; Inner value: 0x1a3602
+ public static Result.Base AllocationMemoryFailedInSubdirectoryFileSystemA => new Result.Base(ModuleFs, 3355); + /// Error code: 2002-3356; Inner value: 0x1a3802 + public static Result.Base AllocationMemoryFailedInTmFileSystemA => new Result.Base(ModuleFs, 3356); + /// Error code: 2002-3357; Inner value: 0x1a3a02 + public static Result.Base AllocationMemoryFailedInTmFileSystemB => new Result.Base(ModuleFs, 3357); + /// Error code: 2002-3359; Inner value: 0x1a3e02 + public static Result.Base AllocationMemoryFailedInProxyFileSystemA => new Result.Base(ModuleFs, 3359); + /// Error code: 2002-3360; Inner value: 0x1a4002 + public static Result.Base AllocationMemoryFailedInProxyFileSystemB => new Result.Base(ModuleFs, 3360); + /// Error code: 2002-3362; Inner value: 0x1a4402 + public static Result.Base AllocationMemoryFailedInSaveDataExtraDataAccessorCacheManagerA => new Result.Base(ModuleFs, 3362); + /// Error code: 2002-3363; Inner value: 0x1a4602 + public static Result.Base AllocationMemoryFailedInNcaReaderA => new Result.Base(ModuleFs, 3363); + /// Error code: 2002-3365; Inner value: 0x1a4a02 + public static Result.Base AllocationMemoryFailedInRegisterA => new Result.Base(ModuleFs, 3365); + /// Error code: 2002-3366; Inner value: 0x1a4c02 + public static Result.Base AllocationMemoryFailedInRegisterB => new Result.Base(ModuleFs, 3366); + /// Error code: 2002-3367; Inner value: 0x1a4e02 + public static Result.Base AllocationMemoryFailedInPathNormalizer => new Result.Base(ModuleFs, 3367); + /// Error code: 2002-3375; Inner value: 0x1a5e02 + public static Result.Base AllocationMemoryFailedInDbmRomKeyValueStorage => new Result.Base(ModuleFs, 3375); + /// Error code: 2002-3376; Inner value: 0x1a6002 + public static Result.Base AllocationMemoryFailedInDbmHierarchicalRomFileTable => new Result.Base(ModuleFs, 3376); + /// Error code: 2002-3377; Inner value: 0x1a6202 + public static Result.Base AllocationMemoryFailedInRomFsFileSystemE => new Result.Base(ModuleFs, 3377); + /// Error code: 2002-3378; Inner value: 0x1a6402 + public static Result.Base AllocationMemoryFailedInISaveFileSystemA => new Result.Base(ModuleFs, 3378); + /// Error code: 2002-3379; Inner value: 0x1a6602 + public static Result.Base AllocationMemoryFailedInISaveFileSystemB => new Result.Base(ModuleFs, 3379); + /// Error code: 2002-3380; Inner value: 0x1a6802 + public static Result.Base AllocationMemoryFailedInRomOnFileA => new Result.Base(ModuleFs, 3380); + /// Error code: 2002-3381; Inner value: 0x1a6a02 + public static Result.Base AllocationMemoryFailedInRomOnFileB => new Result.Base(ModuleFs, 3381); + /// Error code: 2002-3382; Inner value: 0x1a6c02 + public static Result.Base AllocationMemoryFailedInRomOnFileC => new Result.Base(ModuleFs, 3382); + /// In Initialize
Error code: 2002-3383; Inner value: 0x1a6e02
+ public static Result.Base AllocationMemoryFailedInAesXtsFileE => new Result.Base(ModuleFs, 3383); + /// Error code: 2002-3384; Inner value: 0x1a7002 + public static Result.Base AllocationMemoryFailedInAesXtsFileSystemB => new Result.Base(ModuleFs, 3384); + /// Error code: 2002-3385; Inner value: 0x1a7202 + public static Result.Base AllocationMemoryFailedInAesXtsFileSystemC => new Result.Base(ModuleFs, 3385); + /// Error code: 2002-3386; Inner value: 0x1a7402 + public static Result.Base AllocationMemoryFailedInReadOnlyFileSystemA => new Result.Base(ModuleFs, 3386); + /// In Create allocating AesXtsFileSystem
Error code: 2002-3394; Inner value: 0x1a8402
+ public static Result.Base AllocationMemoryFailedInEncryptedFileSystemCreatorA => new Result.Base(ModuleFs, 3394); + /// Error code: 2002-3399; Inner value: 0x1a8e02 + public static Result.Base AllocationMemoryFailedInAesCtrCounterExtendedStorageA => new Result.Base(ModuleFs, 3399); + /// Error code: 2002-3400; Inner value: 0x1a9002 + public static Result.Base AllocationMemoryFailedInAesCtrCounterExtendedStorageB => new Result.Base(ModuleFs, 3400); + /// Error code: 2002-3406; Inner value: 0x1a9c02 + public static Result.Base AllocationMemoryFailedInSdmmcStorageServiceB => new Result.Base(ModuleFs, 3406); + /// In OpenFile or OpenDirectory
Error code: 2002-3407; Inner value: 0x1a9e02
+ public static Result.Base AllocationMemoryFailedInFileSystemInterfaceAdapter => new Result.Base(ModuleFs, 3407); + /// Error code: 2002-3408; Inner value: 0x1aa002 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorG => new Result.Base(ModuleFs, 3408); + /// Error code: 2002-3409; Inner value: 0x1aa202 + public static Result.Base AllocationMemoryFailedInGameCardFileSystemCreatorH => new Result.Base(ModuleFs, 3409); + /// Error code: 2002-3411; Inner value: 0x1aa602 + public static Result.Base AllocationMemoryFailedInBufferedStorageA => new Result.Base(ModuleFs, 3411); + /// Error code: 2002-3412; Inner value: 0x1aa802 + public static Result.Base AllocationMemoryFailedInIntegrityRomFsStorageA => new Result.Base(ModuleFs, 3412); + /// Error code: 2002-3416; Inner value: 0x1ab002 + public static Result.Base AllocationMemoryFailedInSaveDataFileSystemServiceImplB => new Result.Base(ModuleFs, 3416); + /// Error code: 2002-3420; Inner value: 0x1ab802 + public static Result.Base AllocationMemoryFailedNew => new Result.Base(ModuleFs, 3420); + /// Error code: 2002-3421; Inner value: 0x1aba02 + public static Result.Base AllocationMemoryFailedCreateShared => new Result.Base(ModuleFs, 3421); + /// Error code: 2002-3422; Inner value: 0x1abc02 + public static Result.Base AllocationMemoryFailedMakeUnique => new Result.Base(ModuleFs, 3422); + /// Error code: 2002-3423; Inner value: 0x1abe02 + public static Result.Base AllocationMemoryFailedAllocateShared => new Result.Base(ModuleFs, 3423); + /// Error code: 2002-3424; Inner value: 0x1ac002 + public static Result.Base AllocationPooledBufferNotEnoughSize => new Result.Base(ModuleFs, 3424); + /// Error code: 2002-3428; Inner value: 0x1ac802 + public static Result.Base AllocationMemoryFailedInWriteThroughCacheStorageA => new Result.Base(ModuleFs, 3428); + /// Error code: 2002-3429; Inner value: 0x1aca02 + public static Result.Base AllocationMemoryFailedInSaveDataTransferManagerA => new Result.Base(ModuleFs, 3429); + /// Error code: 2002-3430; Inner value: 0x1acc02 + public static Result.Base AllocationMemoryFailedInSaveDataTransferManagerB => new Result.Base(ModuleFs, 3430); + /// Error code: 2002-3431; Inner value: 0x1ace02 + public static Result.Base AllocationMemoryFailedInHtcFileSystemA => new Result.Base(ModuleFs, 3431); + /// Error code: 2002-3432; Inner value: 0x1ad002 + public static Result.Base AllocationMemoryFailedInHtcFileSystemB => new Result.Base(ModuleFs, 3432); + /// Error code: 2002-3433; Inner value: 0x1ad202 + public static Result.Base AllocationMemoryFailedInGameCardManagerG => new Result.Base(ModuleFs, 3433); + + /// Error code: 2002-3500; Range: 3500-3999; Inner value: 0x1b5802 + public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3500, 3999); } + /// Error code: 2002-3501; Inner value: 0x1b5a02 + public static Result.Base PortMmcNoDevice => new Result.Base(ModuleFs, 3501); + /// Error code: 2002-3502; Inner value: 0x1b5c02 + public static Result.Base PortMmcNotActivated => new Result.Base(ModuleFs, 3502); + /// Error code: 2002-3503; Inner value: 0x1b5e02 + public static Result.Base PortMmcDeviceRemoved => new Result.Base(ModuleFs, 3503); + /// Error code: 2002-3504; Inner value: 0x1b6002 + public static Result.Base PortMmcNotAwakened => new Result.Base(ModuleFs, 3504); + + /// Error code: 2002-3532; Range: 3532-3626; Inner value: 0x1b9802 + public static Result.Base PortMmcCommunicationError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3532, 3626); } + /// Error code: 2002-3533; Range: 3533-3546; Inner value: 0x1b9a02 + public static Result.Base PortMmcCommunicationNotAttained { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3533, 3546); } + /// Error code: 2002-3534; Inner value: 0x1b9c02 + public static Result.Base PortMmcResponseIndexError => new Result.Base(ModuleFs, 3534); + /// Error code: 2002-3535; Inner value: 0x1b9e02 + public static Result.Base PortMmcResponseEndBitError => new Result.Base(ModuleFs, 3535); + /// Error code: 2002-3536; Inner value: 0x1ba002 + public static Result.Base PortMmcResponseCrcError => new Result.Base(ModuleFs, 3536); + /// Error code: 2002-3537; Inner value: 0x1ba202 + public static Result.Base PortMmcResponseTimeoutError => new Result.Base(ModuleFs, 3537); + /// Error code: 2002-3538; Inner value: 0x1ba402 + public static Result.Base PortMmcDataEndBitError => new Result.Base(ModuleFs, 3538); + /// Error code: 2002-3539; Inner value: 0x1ba602 + public static Result.Base PortMmcDataCrcError => new Result.Base(ModuleFs, 3539); + /// Error code: 2002-3540; Inner value: 0x1ba802 + public static Result.Base PortMmcDataTimeoutError => new Result.Base(ModuleFs, 3540); + /// Error code: 2002-3541; Inner value: 0x1baa02 + public static Result.Base PortMmcAutoCommandResponseIndexError => new Result.Base(ModuleFs, 3541); + /// Error code: 2002-3542; Inner value: 0x1bac02 + public static Result.Base PortMmcAutoCommandResponseEndBitError => new Result.Base(ModuleFs, 3542); + /// Error code: 2002-3543; Inner value: 0x1bae02 + public static Result.Base PortMmcAutoCommandResponseCrcError => new Result.Base(ModuleFs, 3543); + /// Error code: 2002-3544; Inner value: 0x1bb002 + public static Result.Base PortMmcAutoCommandResponseTimeoutError => new Result.Base(ModuleFs, 3544); + /// Error code: 2002-3545; Inner value: 0x1bb202 + public static Result.Base PortMmcCommandCompleteSoftwareTimeout => new Result.Base(ModuleFs, 3545); + /// Error code: 2002-3546; Inner value: 0x1bb402 + public static Result.Base PortMmcTransferCompleteSoftwareTimeout => new Result.Base(ModuleFs, 3546); + + /// Error code: 2002-3548; Range: 3548-3570; Inner value: 0x1bb802 + public static Result.Base PortMmcDeviceStatusHasError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3548, 3570); } + /// Error code: 2002-3549; Inner value: 0x1bba02 + public static Result.Base PortMmcDeviceStatusAddressOutOfRange => new Result.Base(ModuleFs, 3549); + /// Error code: 2002-3550; Inner value: 0x1bbc02 + public static Result.Base PortMmcDeviceStatusAddressMisaligned => new Result.Base(ModuleFs, 3550); + /// Error code: 2002-3551; Inner value: 0x1bbe02 + public static Result.Base PortMmcDeviceStatusBlockLenError => new Result.Base(ModuleFs, 3551); + /// Error code: 2002-3552; Inner value: 0x1bc002 + public static Result.Base PortMmcDeviceStatusEraseSeqError => new Result.Base(ModuleFs, 3552); + /// Error code: 2002-3553; Inner value: 0x1bc202 + public static Result.Base PortMmcDeviceStatusEraseParam => new Result.Base(ModuleFs, 3553); + /// Error code: 2002-3554; Inner value: 0x1bc402 + public static Result.Base PortMmcDeviceStatusWpViolation => new Result.Base(ModuleFs, 3554); + /// Error code: 2002-3555; Inner value: 0x1bc602 + public static Result.Base PortMmcDeviceStatusLockUnlockFailed => new Result.Base(ModuleFs, 3555); + /// Error code: 2002-3556; Inner value: 0x1bc802 + public static Result.Base PortMmcDeviceStatusComCrcError => new Result.Base(ModuleFs, 3556); + /// Error code: 2002-3557; Inner value: 0x1bca02 + public static Result.Base PortMmcDeviceStatusIllegalCommand => new Result.Base(ModuleFs, 3557); + /// Error code: 2002-3558; Inner value: 0x1bcc02 + public static Result.Base PortMmcDeviceStatusDeviceEccFailed => new Result.Base(ModuleFs, 3558); + /// Error code: 2002-3559; Inner value: 0x1bce02 + public static Result.Base PortMmcDeviceStatusCcError => new Result.Base(ModuleFs, 3559); + /// Error code: 2002-3560; Inner value: 0x1bd002 + public static Result.Base PortMmcDeviceStatusError => new Result.Base(ModuleFs, 3560); + /// Error code: 2002-3561; Inner value: 0x1bd202 + public static Result.Base PortMmcDeviceStatusCidCsdOverwrite => new Result.Base(ModuleFs, 3561); + /// Error code: 2002-3562; Inner value: 0x1bd402 + public static Result.Base PortMmcDeviceStatusWpEraseSkip => new Result.Base(ModuleFs, 3562); + /// Error code: 2002-3563; Inner value: 0x1bd602 + public static Result.Base PortMmcDeviceStatusEraseReset => new Result.Base(ModuleFs, 3563); + /// Error code: 2002-3564; Inner value: 0x1bd802 + public static Result.Base PortMmcDeviceStatusSwitchError => new Result.Base(ModuleFs, 3564); + + /// Error code: 2002-3572; Inner value: 0x1be802 + public static Result.Base PortMmcUnexpectedDeviceState => new Result.Base(ModuleFs, 3572); + /// Error code: 2002-3573; Inner value: 0x1bea02 + public static Result.Base PortMmcUnexpectedDeviceCsdValue => new Result.Base(ModuleFs, 3573); + /// Error code: 2002-3574; Inner value: 0x1bec02 + public static Result.Base PortMmcAbortTransactionSoftwareTimeout => new Result.Base(ModuleFs, 3574); + /// Error code: 2002-3575; Inner value: 0x1bee02 + public static Result.Base PortMmcCommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleFs, 3575); + /// Error code: 2002-3576; Inner value: 0x1bf002 + public static Result.Base PortMmcCommandInhibitDatSoftwareTimeout => new Result.Base(ModuleFs, 3576); + /// Error code: 2002-3577; Inner value: 0x1bf202 + public static Result.Base PortMmcBusySoftwareTimeout => new Result.Base(ModuleFs, 3577); + /// Error code: 2002-3578; Inner value: 0x1bf402 + public static Result.Base PortMmcIssueTuningCommandSoftwareTimeout => new Result.Base(ModuleFs, 3578); + /// Error code: 2002-3579; Inner value: 0x1bf602 + public static Result.Base PortMmcTuningFailed => new Result.Base(ModuleFs, 3579); + /// Error code: 2002-3580; Inner value: 0x1bf802 + public static Result.Base PortMmcMmcInitializationSoftwareTimeout => new Result.Base(ModuleFs, 3580); + /// Error code: 2002-3581; Inner value: 0x1bfa02 + public static Result.Base PortMmcMmcNotSupportExtendedCsd => new Result.Base(ModuleFs, 3581); + /// Error code: 2002-3582; Inner value: 0x1bfc02 + public static Result.Base PortMmcUnexpectedMmcExtendedCsdValue => new Result.Base(ModuleFs, 3582); + /// Error code: 2002-3583; Inner value: 0x1bfe02 + public static Result.Base PortMmcMmcEraseSoftwareTimeout => new Result.Base(ModuleFs, 3583); + /// Error code: 2002-3584; Inner value: 0x1c0002 + public static Result.Base PortMmcSdCardValidationError => new Result.Base(ModuleFs, 3584); + /// Error code: 2002-3585; Inner value: 0x1c0202 + public static Result.Base PortMmcSdCardInitializationSoftwareTimeout => new Result.Base(ModuleFs, 3585); + /// Error code: 2002-3586; Inner value: 0x1c0402 + public static Result.Base PortMmcSdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleFs, 3586); + /// Error code: 2002-3587; Inner value: 0x1c0602 + public static Result.Base PortMmcUnexpectedSdCardAcmdDisabled => new Result.Base(ModuleFs, 3587); + /// Error code: 2002-3588; Inner value: 0x1c0802 + public static Result.Base PortMmcSdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleFs, 3588); + /// Error code: 2002-3589; Inner value: 0x1c0a02 + public static Result.Base PortMmcUnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleFs, 3589); + /// Error code: 2002-3590; Inner value: 0x1c0c02 + public static Result.Base PortMmcSdCardNotSupportAccessMode => new Result.Base(ModuleFs, 3590); + /// Error code: 2002-3591; Inner value: 0x1c0e02 + public static Result.Base PortMmcSdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleFs, 3591); + /// Error code: 2002-3592; Inner value: 0x1c1002 + public static Result.Base PortMmcSdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleFs, 3592); + /// Error code: 2002-3593; Inner value: 0x1c1202 + public static Result.Base PortMmcSdCardCannotSwitchAccessMode => new Result.Base(ModuleFs, 3593); + /// Error code: 2002-3594; Inner value: 0x1c1402 + public static Result.Base PortMmcSdCardFailedSwitchAccessMode => new Result.Base(ModuleFs, 3594); + /// Error code: 2002-3595; Inner value: 0x1c1602 + public static Result.Base PortMmcSdCardUnacceptableCurrentConsumption => new Result.Base(ModuleFs, 3595); + /// Error code: 2002-3596; Inner value: 0x1c1802 + public static Result.Base PortMmcSdCardNotReadyToVoltageSwitch => new Result.Base(ModuleFs, 3596); + /// Error code: 2002-3597; Inner value: 0x1c1a02 + public static Result.Base PortMmcSdCardNotCompleteVoltageSwitch => new Result.Base(ModuleFs, 3597); + + /// Error code: 2002-3628; Range: 3628-3658; Inner value: 0x1c5802 + public static Result.Base PortMmcHostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3628, 3658); } + /// Error code: 2002-3629; Inner value: 0x1c5a02 + public static Result.Base PortMmcInternalClockStableSoftwareTimeout => new Result.Base(ModuleFs, 3629); + /// Error code: 2002-3630; Inner value: 0x1c5c02 + public static Result.Base PortMmcSdHostStandardUnknownAutoCmdError => new Result.Base(ModuleFs, 3630); + /// Error code: 2002-3631; Inner value: 0x1c5e02 + public static Result.Base PortMmcSdHostStandardUnknownError => new Result.Base(ModuleFs, 3631); + /// Error code: 2002-3632; Inner value: 0x1c6002 + public static Result.Base PortMmcSdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleFs, 3632); + /// Error code: 2002-3633; Inner value: 0x1c6202 + public static Result.Base PortMmcSdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleFs, 3633); + /// Error code: 2002-3634; Inner value: 0x1c6402 + public static Result.Base PortMmcSdHostStandardFailSwitchTo18V => new Result.Base(ModuleFs, 3634); + + /// Error code: 2002-3660; Range: 3660-3690; Inner value: 0x1c9802 + public static Result.Base PortMmcInternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3660, 3690); } + /// Error code: 2002-3661; Inner value: 0x1c9a02 + public static Result.Base PortMmcNoWaitedInterrupt => new Result.Base(ModuleFs, 3661); + /// Error code: 2002-3662; Inner value: 0x1c9c02 + public static Result.Base PortMmcWaitInterruptSoftwareTimeout => new Result.Base(ModuleFs, 3662); + + /// Error code: 2002-3692; Inner value: 0x1cd802 + public static Result.Base PortMmcAbortCommandIssued => new Result.Base(ModuleFs, 3692); + /// Error code: 2002-3700; Inner value: 0x1ce802 + public static Result.Base PortMmcNotSupported => new Result.Base(ModuleFs, 3700); + /// Error code: 2002-3701; Inner value: 0x1cea02 + public static Result.Base PortMmcNotImplemented => new Result.Base(ModuleFs, 3701); + /// Error code: 2002-3998; Inner value: 0x1f3c02 + public static Result.Base Result3998 => new Result.Base(ModuleFs, 3998); + /// Error code: 2002-3999; Inner value: 0x1f3e02 + public static Result.Base PortMmcUnexpected => new Result.Base(ModuleFs, 3999); + + /// Error code: 2002-4000; Range: 4000-4999; Inner value: 0x1f4002 + public static Result.Base DataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4000, 4999); } + /// Error code: 2002-4001; Range: 4001-4299; Inner value: 0x1f4202 + public static Result.Base RomCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4001, 4299); } + /// Error code: 2002-4002; Inner value: 0x1f4402 + public static Result.Base UnsupportedRomVersion => new Result.Base(ModuleFs, 4002); + + /// Error code: 2002-4011; Range: 4011-4019; Inner value: 0x1f5602 + public static Result.Base AesCtrCounterExtendedStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4011, 4019); } + /// Error code: 2002-4012; Inner value: 0x1f5802 + public static Result.Base InvalidAesCtrCounterExtendedEntryOffset => new Result.Base(ModuleFs, 4012); + /// Error code: 2002-4013; Inner value: 0x1f5a02 + public static Result.Base InvalidAesCtrCounterExtendedTableSize => new Result.Base(ModuleFs, 4013); + /// Error code: 2002-4014; Inner value: 0x1f5c02 + public static Result.Base InvalidAesCtrCounterExtendedGeneration => new Result.Base(ModuleFs, 4014); + /// Error code: 2002-4015; Inner value: 0x1f5e02 + public static Result.Base InvalidAesCtrCounterExtendedOffset => new Result.Base(ModuleFs, 4015); + /// Error code: 2002-4016; Inner value: 0x1f6002 + public static Result.Base Result4016 => new Result.Base(ModuleFs, 4016); + + /// Error code: 2002-4021; Range: 4021-4029; Inner value: 0x1f6a02 + public static Result.Base IndirectStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4021, 4029); } + /// Error code: 2002-4022; Inner value: 0x1f6c02 + public static Result.Base InvalidIndirectEntryOffset => new Result.Base(ModuleFs, 4022); + /// Error code: 2002-4023; Inner value: 0x1f6e02 + public static Result.Base InvalidIndirectEntryStorageIndex => new Result.Base(ModuleFs, 4023); + /// Error code: 2002-4024; Inner value: 0x1f7002 + public static Result.Base InvalidIndirectStorageSize => new Result.Base(ModuleFs, 4024); + /// Error code: 2002-4025; Inner value: 0x1f7202 + public static Result.Base InvalidIndirectVirtualOffset => new Result.Base(ModuleFs, 4025); + /// Error code: 2002-4026; Inner value: 0x1f7402 + public static Result.Base InvalidIndirectPhysicalOffset => new Result.Base(ModuleFs, 4026); + /// Error code: 2002-4027; Inner value: 0x1f7602 + public static Result.Base InvalidIndirectStorageIndex => new Result.Base(ModuleFs, 4027); + /// Error code: 2002-4028; Inner value: 0x1f7802 + public static Result.Base InvalidIndirectStorageBucketTreeSize => new Result.Base(ModuleFs, 4028); + + /// Error code: 2002-4031; Range: 4031-4039; Inner value: 0x1f7e02 + public static Result.Base BucketTreeCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4031, 4039); } + /// Error code: 2002-4032; Inner value: 0x1f8002 + public static Result.Base InvalidBucketTreeSignature => new Result.Base(ModuleFs, 4032); + /// Error code: 2002-4033; Inner value: 0x1f8202 + public static Result.Base InvalidBucketTreeEntryCount => new Result.Base(ModuleFs, 4033); + /// Error code: 2002-4034; Inner value: 0x1f8402 + public static Result.Base InvalidBucketTreeNodeEntryCount => new Result.Base(ModuleFs, 4034); + /// Error code: 2002-4035; Inner value: 0x1f8602 + public static Result.Base InvalidBucketTreeNodeOffset => new Result.Base(ModuleFs, 4035); + /// Error code: 2002-4036; Inner value: 0x1f8802 + public static Result.Base InvalidBucketTreeEntryOffset => new Result.Base(ModuleFs, 4036); + /// Error code: 2002-4037; Inner value: 0x1f8a02 + public static Result.Base InvalidBucketTreeEntrySetOffset => new Result.Base(ModuleFs, 4037); + /// Error code: 2002-4038; Inner value: 0x1f8c02 + public static Result.Base InvalidBucketTreeNodeIndex => new Result.Base(ModuleFs, 4038); + /// Error code: 2002-4039; Inner value: 0x1f8e02 + public static Result.Base InvalidBucketTreeVirtualOffset => new Result.Base(ModuleFs, 4039); + + /// Error code: 2002-4041; Range: 4041-4139; Inner value: 0x1f9202 + public static Result.Base RomNcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4041, 4139); } + /// Error code: 2002-4051; Range: 4051-4069; Inner value: 0x1fa602 + public static Result.Base RomNcaFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4051, 4069); } + /// Error code: 2002-4052; Inner value: 0x1fa802 + public static Result.Base InvalidRomNcaFileSystemType => new Result.Base(ModuleFs, 4052); + /// Error code: 2002-4053; Inner value: 0x1faa02 + public static Result.Base InvalidRomAcidFileSize => new Result.Base(ModuleFs, 4053); + /// Error code: 2002-4054; Inner value: 0x1fac02 + public static Result.Base InvalidRomAcidSize => new Result.Base(ModuleFs, 4054); + /// Error code: 2002-4055; Inner value: 0x1fae02 + public static Result.Base InvalidRomAcid => new Result.Base(ModuleFs, 4055); + /// Error code: 2002-4056; Inner value: 0x1fb002 + public static Result.Base RomAcidVerificationFailed => new Result.Base(ModuleFs, 4056); + /// Error code: 2002-4057; Inner value: 0x1fb202 + public static Result.Base InvalidRomNcaSignature => new Result.Base(ModuleFs, 4057); + /// Error code: 2002-4058; Inner value: 0x1fb402 + public static Result.Base RomNcaHeaderSignature1VerificationFailed => new Result.Base(ModuleFs, 4058); + /// Error code: 2002-4059; Inner value: 0x1fb602 + public static Result.Base RomNcaHeaderSignature2VerificationFailed => new Result.Base(ModuleFs, 4059); + /// Error code: 2002-4060; Inner value: 0x1fb802 + public static Result.Base RomNcaFsHeaderHashVerificationFailed => new Result.Base(ModuleFs, 4060); + /// Error code: 2002-4061; Inner value: 0x1fba02 + public static Result.Base InvalidRomNcaKeyIndex => new Result.Base(ModuleFs, 4061); + /// Error code: 2002-4062; Inner value: 0x1fbc02 + public static Result.Base InvalidRomNcaFsHeaderHashType => new Result.Base(ModuleFs, 4062); + /// Error code: 2002-4063; Inner value: 0x1fbe02 + public static Result.Base InvalidRomNcaFsHeaderEncryptionType => new Result.Base(ModuleFs, 4063); + /// Error code: 2002-4064; Inner value: 0x1fc002 + public static Result.Base InvalidRomNcaPatchInfoIndirectSize => new Result.Base(ModuleFs, 4064); + /// Error code: 2002-4065; Inner value: 0x1fc202 + public static Result.Base InvalidRomNcaPatchInfoAesCtrExSize => new Result.Base(ModuleFs, 4065); + /// Error code: 2002-4066; Inner value: 0x1fc402 + public static Result.Base InvalidRomNcaPatchInfoAesCtrExOffset => new Result.Base(ModuleFs, 4066); + /// Error code: 2002-4067; Inner value: 0x1fc602 + public static Result.Base InvalidRomNcaId => new Result.Base(ModuleFs, 4067); + /// Error code: 2002-4068; Inner value: 0x1fc802 + public static Result.Base InvalidRomNcaHeader => new Result.Base(ModuleFs, 4068); + /// Error code: 2002-4069; Inner value: 0x1fca02 + public static Result.Base InvalidRomNcaFsHeader => new Result.Base(ModuleFs, 4069); + + /// Error code: 2002-4070; Inner value: 0x1fcc02 + public static Result.Base InvalidRomNcaPatchInfoIndirectOffset => new Result.Base(ModuleFs, 4070); + + /// Error code: 2002-4071; Range: 4071-4079; Inner value: 0x1fce02 + public static Result.Base RomNcaHierarchicalSha256StorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4071, 4079); } + /// Error code: 2002-4072; Inner value: 0x1fd002 + public static Result.Base InvalidRomHierarchicalSha256BlockSize => new Result.Base(ModuleFs, 4072); + /// Error code: 2002-4073; Inner value: 0x1fd202 + public static Result.Base InvalidRomHierarchicalSha256LayerCount => new Result.Base(ModuleFs, 4073); + /// Error code: 2002-4074; Inner value: 0x1fd402 + public static Result.Base RomHierarchicalSha256BaseStorageTooLarge => new Result.Base(ModuleFs, 4074); + /// Error code: 2002-4075; Inner value: 0x1fd602 + public static Result.Base RomHierarchicalSha256HashVerificationFailed => new Result.Base(ModuleFs, 4075); + + /// Error code: 2002-4081; Inner value: 0x1fe202 + public static Result.Base InvalidRomHierarchicalIntegrityVerificationLayerCount => new Result.Base(ModuleFs, 4081); + /// Error code: 2002-4082; Inner value: 0x1fe402 + public static Result.Base RomNcaIndirectStorageOutOfRange => new Result.Base(ModuleFs, 4082); + + /// Error code: 2002-4141; Range: 4141-4179; Inner value: 0x205a02 + public static Result.Base RomIntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4141, 4179); } + /// Error code: 2002-4142; Inner value: 0x205c02 + public static Result.Base IncorrectRomIntegrityVerificationMagic => new Result.Base(ModuleFs, 4142); + /// Error code: 2002-4143; Inner value: 0x205e02 + public static Result.Base InvalidRomZeroSignature => new Result.Base(ModuleFs, 4143); + /// Error code: 2002-4144; Inner value: 0x206002 + public static Result.Base RomNonRealDataVerificationFailed => new Result.Base(ModuleFs, 4144); + + /// Error code: 2002-4151; Range: 4151-4159; Inner value: 0x206e02 + public static Result.Base RomRealDataVerificationFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4151, 4159); } + /// Error code: 2002-4152; Inner value: 0x207002 + public static Result.Base ClearedRomRealDataVerificationFailed => new Result.Base(ModuleFs, 4152); + /// Error code: 2002-4153; Inner value: 0x207202 + public static Result.Base UnclearedRomRealDataVerificationFailed => new Result.Base(ModuleFs, 4153); + + /// Error code: 2002-4181; Range: 4181-4199; Inner value: 0x20aa02 + public static Result.Base RomPartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4181, 4199); } + /// Error code: 2002-4182; Inner value: 0x20ac02 + public static Result.Base InvalidRomSha256PartitionHashTarget => new Result.Base(ModuleFs, 4182); + /// Error code: 2002-4183; Inner value: 0x20ae02 + public static Result.Base RomSha256PartitionHashVerificationFailed => new Result.Base(ModuleFs, 4183); + /// Error code: 2002-4184; Inner value: 0x20b002 + public static Result.Base RomPartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4184); + /// Error code: 2002-4185; Inner value: 0x20b202 + public static Result.Base RomSha256PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4185); + /// Error code: 2002-4186; Inner value: 0x20b402 + public static Result.Base InvalidRomPartitionEntryOffset => new Result.Base(ModuleFs, 4186); + /// Error code: 2002-4187; Inner value: 0x20b602 + public static Result.Base InvalidRomSha256PartitionMetaDataSize => new Result.Base(ModuleFs, 4187); + + /// Error code: 2002-4201; Range: 4201-4219; Inner value: 0x20d202 + public static Result.Base RomBuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4201, 4219); } + /// Error code: 2002-4202; Inner value: 0x20d402 + public static Result.Base RomGptHeaderSignatureVerificationFailed => new Result.Base(ModuleFs, 4202); + + /// Error code: 2002-4241; Range: 4241-4259; Inner value: 0x212202 + public static Result.Base RomHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4241, 4259); } + /// Error code: 2002-4242; Inner value: 0x212402 + public static Result.Base RomHostEntryCorrupted => new Result.Base(ModuleFs, 4242); + /// Error code: 2002-4243; Inner value: 0x212602 + public static Result.Base RomHostFileDataCorrupted => new Result.Base(ModuleFs, 4243); + /// Error code: 2002-4244; Inner value: 0x212802 + public static Result.Base RomHostFileCorrupted => new Result.Base(ModuleFs, 4244); + /// Error code: 2002-4245; Inner value: 0x212a02 + public static Result.Base InvalidRomHostHandle => new Result.Base(ModuleFs, 4245); + + /// Error code: 2002-4261; Range: 4261-4279; Inner value: 0x214a02 + public static Result.Base RomDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4261, 4279); } + /// Error code: 2002-4262; Inner value: 0x214c02 + public static Result.Base InvalidRomAllocationTableBlock => new Result.Base(ModuleFs, 4262); + /// Error code: 2002-4263; Inner value: 0x214e02 + public static Result.Base InvalidRomKeyValueListElementIndex => new Result.Base(ModuleFs, 4263); + + /// Returned directly when the header says the total size of the RomFs metadata is 0 bytes.
Error code: 2002-4280; Range: 4280-4284; Inner value: 0x217002
+ public static Result.Base RomStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4280, 4284); } + /// The RomFs metadata is located beyond the end of the provided storage.
Error code: 2002-4281; Inner value: 0x217202
+ public static Result.Base InvalidRomStorageSize => new Result.Base(ModuleFs, 4281); + + /// Error code: 2002-4301; Range: 4301-4499; Inner value: 0x219a02 + public static Result.Base SaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4301, 4499); } + /// Error code: 2002-4302; Inner value: 0x219c02 + public static Result.Base UnsupportedSaveDataVersion => new Result.Base(ModuleFs, 4302); + /// Error code: 2002-4303; Inner value: 0x219e02 + public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303); + + /// Error code: 2002-4311; Range: 4311-4319; Inner value: 0x21ae02 + public static Result.Base SaveDataFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4311, 4319); } + /// Error code: 2002-4312; Inner value: 0x21b002 + public static Result.Base InvalidJournalIntegritySaveDataHashSize => new Result.Base(ModuleFs, 4312); + /// Error code: 2002-4313; Inner value: 0x21b202 + public static Result.Base InvalidJournalIntegritySaveDataCommitState => new Result.Base(ModuleFs, 4313); + /// Error code: 2002-4314; Inner value: 0x21b402 + public static Result.Base InvalidJournalIntegritySaveDataControlAreaSize => new Result.Base(ModuleFs, 4314); + /// Error code: 2002-4315; Inner value: 0x21b602 + public static Result.Base JournalIntegritySaveDataControlAreaVerificationFailed => new Result.Base(ModuleFs, 4315); + /// Error code: 2002-4316; Inner value: 0x21b802 + public static Result.Base JournalIntegritySaveDataMasterSignatureVerificationFailed => new Result.Base(ModuleFs, 4316); + /// Error code: 2002-4317; Inner value: 0x21ba02 + public static Result.Base IncorrectJournalIntegritySaveDataMagicCode => new Result.Base(ModuleFs, 4317); + + /// Error code: 2002-4321; Range: 4321-4329; Inner value: 0x21c202 + public static Result.Base SaveDataDuplexStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4321, 4329); } + /// Error code: 2002-4322; Inner value: 0x21c402 + public static Result.Base IncorrectDuplexMagicCode => new Result.Base(ModuleFs, 4322); + /// Error code: 2002-4323; Inner value: 0x21c602 + public static Result.Base DuplexStorageAccessOutOfRange => new Result.Base(ModuleFs, 4323); + + /// Error code: 2002-4331; Range: 4331-4339; Inner value: 0x21d602 + public static Result.Base SaveDataMapCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4331, 4339); } + /// Error code: 2002-4332; Inner value: 0x21d802 + public static Result.Base InvalidMapEntryCount => new Result.Base(ModuleFs, 4332); + /// Error code: 2002-4333; Inner value: 0x21da02 + public static Result.Base InvalidMapOffset => new Result.Base(ModuleFs, 4333); + /// Error code: 2002-4334; Inner value: 0x21dc02 + public static Result.Base InvalidMapSize => new Result.Base(ModuleFs, 4334); + /// Error code: 2002-4335; Inner value: 0x21de02 + public static Result.Base InvalidMapAlignment => new Result.Base(ModuleFs, 4335); + /// Error code: 2002-4336; Inner value: 0x21e002 + public static Result.Base InvalidMapStorageType => new Result.Base(ModuleFs, 4336); + /// The range of the new map entry overlaps with the range of an existing map entry.
Error code: 2002-4337; Inner value: 0x21e202
+ public static Result.Base MapAddressAlreadyRegistered => new Result.Base(ModuleFs, 4337); + /// The storage for the map entry's storage type hasn't been registered.
Error code: 2002-4338; Inner value: 0x21e402
+ public static Result.Base MapStorageNotFound => new Result.Base(ModuleFs, 4338); + /// The storage registered for the map entry's storage type is too short to contain the physical range specified in the map entry.
Error code: 2002-4339; Inner value: 0x21e602
+ public static Result.Base InvalidMapStorageSize => new Result.Base(ModuleFs, 4339); + + /// Error code: 2002-4341; Range: 4341-4349; Inner value: 0x21ea02 + public static Result.Base SaveDataLogCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4341, 4349); } + /// Error code: 2002-4342; Inner value: 0x21ec02 + public static Result.Base InvalidLogBlockSize => new Result.Base(ModuleFs, 4342); + /// Error code: 2002-4343; Inner value: 0x21ee02 + public static Result.Base InvalidLogOffset => new Result.Base(ModuleFs, 4343); + /// Error code: 2002-4344; Inner value: 0x21f002 + public static Result.Base UnexpectedEndOfLog => new Result.Base(ModuleFs, 4344); + /// Error code: 2002-4345; Inner value: 0x21f202 + public static Result.Base LogNotFound => new Result.Base(ModuleFs, 4345); + + /// Error code: 2002-4352; Inner value: 0x220002 + public static Result.Base ThumbnailHashVerificationFailed => new Result.Base(ModuleFs, 4352); + /// Error code: 2002-4357; Inner value: 0x220a02 + public static Result.Base InvalidSaveDataInternalStorageIntegritySeedSize => new Result.Base(ModuleFs, 4357); + /// Error code: 2002-4358; Inner value: 0x220c02 + public static Result.Base Result4358 => new Result.Base(ModuleFs, 4358); + /// Error code: 2002-4359; Inner value: 0x220e02 + public static Result.Base Result4359 => new Result.Base(ModuleFs, 4359); + + /// Error code: 2002-4361; Range: 4361-4399; Inner value: 0x221202 + public static Result.Base SaveDataIntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4361, 4399); } + /// Error code: 2002-4362; Inner value: 0x221402 + public static Result.Base IncorrectSaveDataIntegrityVerificationMagic => new Result.Base(ModuleFs, 4362); + /// Error code: 2002-4363; Inner value: 0x221602 + public static Result.Base InvalidSaveDataZeroHash => new Result.Base(ModuleFs, 4363); + /// Error code: 2002-4364; Inner value: 0x221802 + public static Result.Base SaveDataNonRealDataVerificationFailed => new Result.Base(ModuleFs, 4364); + /// Error code: 2002-4372; Inner value: 0x222802 + public static Result.Base ClearedSaveDataRealDataVerificationFailed => new Result.Base(ModuleFs, 4372); + /// Error code: 2002-4373; Inner value: 0x222a02 + public static Result.Base UnclearedSaveDataRealDataVerificationFailed => new Result.Base(ModuleFs, 4373); + + /// Error code: 2002-4402; Inner value: 0x226402 + public static Result.Base SaveDataGptHeaderSignatureVerificationFailed => new Result.Base(ModuleFs, 4402); + + /// Error code: 2002-4411; Range: 4411-4419; Inner value: 0x227602 + public static Result.Base SaveDataCoreFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4411, 4419); } + /// Error code: 2002-4412; Inner value: 0x227802 + public static Result.Base IncorrectSaveDataControlAreaMagic => new Result.Base(ModuleFs, 4412); + /// Error code: 2002-4413; Inner value: 0x227a02 + public static Result.Base InvalidSaveDataFileReadOffset => new Result.Base(ModuleFs, 4413); + /// Error code: 2002-4414; Inner value: 0x227c02 + public static Result.Base InvalidSaveDataCoreDataStorageSize => new Result.Base(ModuleFs, 4414); + + /// Error code: 2002-4427; Inner value: 0x229602 + public static Result.Base IncompleteBlockInZeroBitmapHashStorageFileSaveData => new Result.Base(ModuleFs, 4427); + + /// Error code: 2002-4431; Range: 4431-4439; Inner value: 0x229e02 + public static Result.Base JournalStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4431, 4439); } + /// Error code: 2002-4432; Inner value: 0x22a002 + public static Result.Base JournalStorageAccessOutOfRange => new Result.Base(ModuleFs, 4432); + /// Error code: 2002-4433; Inner value: 0x22a202 + public static Result.Base InvalidJournalStorageDataStorageSize => new Result.Base(ModuleFs, 4433); + + /// Error code: 2002-4441; Range: 4441-4459; Inner value: 0x22b202 + public static Result.Base SaveDataHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4441, 4459); } + /// Error code: 2002-4442; Inner value: 0x22b402 + public static Result.Base SaveDataHostEntryCorrupted => new Result.Base(ModuleFs, 4442); + /// Error code: 2002-4443; Inner value: 0x22b602 + public static Result.Base SaveDataHostFileDataCorrupted => new Result.Base(ModuleFs, 4443); + /// Error code: 2002-4444; Inner value: 0x22b802 + public static Result.Base SaveDataHostFileCorrupted => new Result.Base(ModuleFs, 4444); + /// Error code: 2002-4445; Inner value: 0x22ba02 + public static Result.Base InvalidSaveDataHostHandle => new Result.Base(ModuleFs, 4445); + + /// Error code: 2002-4451; Range: 4451-4459; Inner value: 0x22c602 + public static Result.Base MappingTableCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4451, 4459); } + /// Error code: 2002-4452; Inner value: 0x22c802 + public static Result.Base InvalidMappingTableEntryCount => new Result.Base(ModuleFs, 4452); + /// Error code: 2002-4453; Inner value: 0x22ca02 + public static Result.Base InvalidMappingTablePhysicalIndex => new Result.Base(ModuleFs, 4453); + /// Error code: 2002-4454; Inner value: 0x22cc02 + public static Result.Base InvalidMappingTableVirtualIndex => new Result.Base(ModuleFs, 4454); + + /// Error code: 2002-4461; Range: 4461-4479; Inner value: 0x22da02 + public static Result.Base SaveDataDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4461, 4479); } + /// Error code: 2002-4462; Inner value: 0x22dc02 + public static Result.Base InvalidSaveDataAllocationTableBlock => new Result.Base(ModuleFs, 4462); + /// Error code: 2002-4463; Inner value: 0x22de02 + public static Result.Base InvalidSaveDataKeyValueListElementIndex => new Result.Base(ModuleFs, 4463); + /// Error code: 2002-4464; Inner value: 0x22e002 + public static Result.Base SaveDataAllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 4464); + /// Error code: 2002-4465; Inner value: 0x22e202 + public static Result.Base InvalidSaveDataAllocationTableOffset => new Result.Base(ModuleFs, 4465); + /// Error code: 2002-4466; Inner value: 0x22e402 + public static Result.Base InvalidSaveDataAllocationTableBlockCount => new Result.Base(ModuleFs, 4466); + /// Error code: 2002-4467; Inner value: 0x22e602 + public static Result.Base InvalidSaveDataKeyValueListEntryIndex => new Result.Base(ModuleFs, 4467); + /// Error code: 2002-4468; Inner value: 0x22e802 + public static Result.Base InvalidSaveDataBitmapIndex => new Result.Base(ModuleFs, 4468); + + /// Error code: 2002-4481; Range: 4481-4489; Inner value: 0x230202 + public static Result.Base SaveDataExtensionContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4481, 4489); } + /// Error code: 2002-4482; Inner value: 0x230402 + public static Result.Base IncorrectSaveDataExtensionContextMagicCode => new Result.Base(ModuleFs, 4482); + /// Error code: 2002-4483; Inner value: 0x230602 + public static Result.Base InvalidSaveDataExtensionContextState => new Result.Base(ModuleFs, 4483); + /// The extension context doesn't match the current save data.
Error code: 2002-4484; Inner value: 0x230802
+ public static Result.Base DifferentSaveDataExtensionContextParameter => new Result.Base(ModuleFs, 4484); + /// Error code: 2002-4485; Inner value: 0x230a02 + public static Result.Base InvalidSaveDataExtensionContextParameter => new Result.Base(ModuleFs, 4485); + + /// Error code: 2002-4491; Range: 4491-4499; Inner value: 0x231602 + public static Result.Base IntegritySaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4491, 4499); } + /// Error code: 2002-4492; Inner value: 0x231802 + public static Result.Base InvalidIntegritySaveDataHashSize => new Result.Base(ModuleFs, 4492); + /// Error code: 2002-4494; Inner value: 0x231c02 + public static Result.Base InvalidIntegritySaveDataControlAreaSize => new Result.Base(ModuleFs, 4494); + /// Error code: 2002-4495; Inner value: 0x231e02 + public static Result.Base IntegritySaveDataControlAreaVerificationFailed => new Result.Base(ModuleFs, 4495); + /// Error code: 2002-4496; Inner value: 0x232002 + public static Result.Base IntegritySaveDataMasterSignatureVerificationFailed => new Result.Base(ModuleFs, 4496); + /// Error code: 2002-4497; Inner value: 0x232202 + public static Result.Base IncorrectIntegritySaveDataMagicCode => new Result.Base(ModuleFs, 4497); + + /// Error code: 2002-4501; Range: 4501-4599; Inner value: 0x232a02 + public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4501, 4599); } + /// Error code: 2002-4508; Inner value: 0x233802 + public static Result.Base NcaBaseStorageOutOfRangeA => new Result.Base(ModuleFs, 4508); + /// Error code: 2002-4509; Inner value: 0x233a02 + public static Result.Base NcaBaseStorageOutOfRangeB => new Result.Base(ModuleFs, 4509); + /// Error code: 2002-4510; Inner value: 0x233c02 + public static Result.Base NcaBaseStorageOutOfRangeC => new Result.Base(ModuleFs, 4510); + + /// Error code: 2002-4511; Range: 4511-4529; Inner value: 0x233e02 + public static Result.Base NcaFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4511, 4529); } + /// Error code: 2002-4512; Inner value: 0x234002 + public static Result.Base InvalidNcaFileSystemType => new Result.Base(ModuleFs, 4512); + /// Error code: 2002-4513; Inner value: 0x234202 + public static Result.Base InvalidAcidFileSize => new Result.Base(ModuleFs, 4513); + /// Error code: 2002-4514; Inner value: 0x234402 + public static Result.Base InvalidAcidSize => new Result.Base(ModuleFs, 4514); + /// Error code: 2002-4515; Inner value: 0x234602 + public static Result.Base InvalidAcid => new Result.Base(ModuleFs, 4515); + /// Error code: 2002-4516; Inner value: 0x234802 + public static Result.Base AcidVerificationFailed => new Result.Base(ModuleFs, 4516); + /// Error code: 2002-4517; Inner value: 0x234a02 + public static Result.Base InvalidNcaSignature => new Result.Base(ModuleFs, 4517); + /// Error code: 2002-4518; Inner value: 0x234c02 + public static Result.Base NcaHeaderSignature1VerificationFailed => new Result.Base(ModuleFs, 4518); + /// Error code: 2002-4519; Inner value: 0x234e02 + public static Result.Base NcaHeaderSignature2VerificationFailed => new Result.Base(ModuleFs, 4519); + /// Error code: 2002-4520; Inner value: 0x235002 + public static Result.Base NcaFsHeaderHashVerificationFailed => new Result.Base(ModuleFs, 4520); + /// Error code: 2002-4521; Inner value: 0x235202 + public static Result.Base InvalidNcaKeyIndex => new Result.Base(ModuleFs, 4521); + /// Error code: 2002-4522; Inner value: 0x235402 + public static Result.Base InvalidNcaFsHeaderHashType => new Result.Base(ModuleFs, 4522); + /// Error code: 2002-4523; Inner value: 0x235602 + public static Result.Base InvalidNcaFsHeaderEncryptionType => new Result.Base(ModuleFs, 4523); + /// Error code: 2002-4524; Inner value: 0x235802 + public static Result.Base InvalidNcaPatchInfoIndirectSize => new Result.Base(ModuleFs, 4524); + /// Error code: 2002-4525; Inner value: 0x235a02 + public static Result.Base InvalidNcaPatchInfoAesCtrExSize => new Result.Base(ModuleFs, 4525); + /// Error code: 2002-4526; Inner value: 0x235c02 + public static Result.Base InvalidNcaPatchInfoAesCtrExOffset => new Result.Base(ModuleFs, 4526); + /// Error code: 2002-4527; Inner value: 0x235e02 + public static Result.Base InvalidNcaId => new Result.Base(ModuleFs, 4527); + /// Error code: 2002-4528; Inner value: 0x236002 + public static Result.Base InvalidNcaHeader => new Result.Base(ModuleFs, 4528); + /// Error code: 2002-4529; Inner value: 0x236202 + public static Result.Base InvalidNcaFsHeader => new Result.Base(ModuleFs, 4529); + + /// Error code: 2002-4530; Inner value: 0x236402 + public static Result.Base InvalidNcaPatchInfoIndirectOffset => new Result.Base(ModuleFs, 4530); + + /// Error code: 2002-4531; Range: 4531-4539; Inner value: 0x236602 + public static Result.Base NcaHierarchicalSha256StorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4531, 4539); } + /// Error code: 2002-4532; Inner value: 0x236802 + public static Result.Base InvalidHierarchicalSha256BlockSize => new Result.Base(ModuleFs, 4532); + /// Error code: 2002-4533; Inner value: 0x236a02 + public static Result.Base InvalidHierarchicalSha256LayerCount => new Result.Base(ModuleFs, 4533); + /// Error code: 2002-4534; Inner value: 0x236c02 + public static Result.Base HierarchicalSha256BaseStorageTooLarge => new Result.Base(ModuleFs, 4534); + /// Error code: 2002-4535; Inner value: 0x236e02 + public static Result.Base HierarchicalSha256HashVerificationFailed => new Result.Base(ModuleFs, 4535); + + /// Error code: 2002-4541; Inner value: 0x237a02 + public static Result.Base InvalidHierarchicalIntegrityVerificationLayerCount => new Result.Base(ModuleFs, 4541); + /// Error code: 2002-4542; Inner value: 0x237c02 + public static Result.Base NcaIndirectStorageOutOfRange => new Result.Base(ModuleFs, 4542); + /// Error code: 2002-4543; Inner value: 0x237e02 + public static Result.Base InvalidNcaHeader1SignatureKeyGeneration => new Result.Base(ModuleFs, 4543); + /// Error code: 2002-4545; Inner value: 0x238202 + public static Result.Base InvalidNspdVerificationData => new Result.Base(ModuleFs, 4545); + /// Error code: 2002-4546; Inner value: 0x238402 + public static Result.Base MissingNspdVerificationData => new Result.Base(ModuleFs, 4546); + + /// Error code: 2002-4601; Range: 4601-4639; Inner value: 0x23f202 + public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4601, 4639); } + /// Error code: 2002-4602; Inner value: 0x23f402 + public static Result.Base IncorrectIntegrityVerificationMagic => new Result.Base(ModuleFs, 4602); + /// Error code: 2002-4603; Inner value: 0x23f602 + public static Result.Base InvalidZeroHash => new Result.Base(ModuleFs, 4603); + /// Error code: 2002-4604; Inner value: 0x23f802 + public static Result.Base NonRealDataVerificationFailed => new Result.Base(ModuleFs, 4604); + + /// Error code: 2002-4611; Range: 4611-4619; Inner value: 0x240602 + public static Result.Base RealDataVerificationFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4611, 4619); } + /// Error code: 2002-4612; Inner value: 0x240802 + public static Result.Base ClearedRealDataVerificationFailed => new Result.Base(ModuleFs, 4612); + /// Error code: 2002-4613; Inner value: 0x240a02 + public static Result.Base UnclearedRealDataVerificationFailed => new Result.Base(ModuleFs, 4613); + + /// Error code: 2002-4641; Range: 4641-4659; Inner value: 0x244202 + public static Result.Base PartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4641, 4659); } + /// Error code: 2002-4642; Inner value: 0x244402 + public static Result.Base InvalidSha256PartitionHashTarget => new Result.Base(ModuleFs, 4642); + /// Error code: 2002-4643; Inner value: 0x244602 + public static Result.Base Sha256PartitionHashVerificationFailed => new Result.Base(ModuleFs, 4643); + /// Error code: 2002-4644; Inner value: 0x244802 + public static Result.Base PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4644); + /// Error code: 2002-4645; Inner value: 0x244a02 + public static Result.Base Sha256PartitionSignatureVerificationFailed => new Result.Base(ModuleFs, 4645); + /// Error code: 2002-4646; Inner value: 0x244c02 + public static Result.Base InvalidPartitionEntryOffset => new Result.Base(ModuleFs, 4646); + /// Error code: 2002-4647; Inner value: 0x244e02 + public static Result.Base InvalidSha256PartitionMetaDataSize => new Result.Base(ModuleFs, 4647); + + /// Error code: 2002-4661; Range: 4661-4679; Inner value: 0x246a02 + public static Result.Base BuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4661, 4679); } + /// Error code: 2002-4662; Inner value: 0x246c02 + public static Result.Base InvalidGptPartitionSignature => new Result.Base(ModuleFs, 4662); + /// Error code: 2002-4664; Inner value: 0x247002 + public static Result.Base InvalidGptPartitionStorageSize => new Result.Base(ModuleFs, 4664); + + /// Error code: 2002-4681; Range: 4681-4699; Inner value: 0x249202 + public static Result.Base FatFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4681, 4699); } + /// Error code: 2002-4683; Inner value: 0x249602 + public static Result.Base InvalidFatFormat => new Result.Base(ModuleFs, 4683); + /// Error code: 2002-4684; Inner value: 0x249802 + public static Result.Base InvalidFatFileNumber => new Result.Base(ModuleFs, 4684); + /// Error code: 2002-4685; Inner value: 0x249a02 + public static Result.Base ExFatUnavailable => new Result.Base(ModuleFs, 4685); + /// Error code: 2002-4686; Inner value: 0x249c02 + public static Result.Base InvalidFatFormatForBisUser => new Result.Base(ModuleFs, 4686); + /// Error code: 2002-4687; Inner value: 0x249e02 + public static Result.Base InvalidFatFormatForBisSystem => new Result.Base(ModuleFs, 4687); + /// Error code: 2002-4688; Inner value: 0x24a002 + public static Result.Base InvalidFatFormatForBisSafe => new Result.Base(ModuleFs, 4688); + /// Error code: 2002-4689; Inner value: 0x24a202 + public static Result.Base InvalidFatFormatForBisCalibration => new Result.Base(ModuleFs, 4689); + /// Error code: 2002-4690; Inner value: 0x24a402 + public static Result.Base InvalidFatFormatForSdCard => new Result.Base(ModuleFs, 4690); + + /// Error code: 2002-4701; Range: 4701-4719; Inner value: 0x24ba02 + public static Result.Base HostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4701, 4719); } + /// Error code: 2002-4702; Inner value: 0x24bc02 + public static Result.Base HostEntryCorrupted => new Result.Base(ModuleFs, 4702); + /// Error code: 2002-4703; Inner value: 0x24be02 + public static Result.Base HostFileDataCorrupted => new Result.Base(ModuleFs, 4703); + /// Error code: 2002-4704; Inner value: 0x24c002 + public static Result.Base HostFileCorrupted => new Result.Base(ModuleFs, 4704); + /// Error code: 2002-4705; Inner value: 0x24c202 + public static Result.Base InvalidHostHandle => new Result.Base(ModuleFs, 4705); + + /// Error code: 2002-4721; Range: 4721-4739; Inner value: 0x24e202 + public static Result.Base DatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4721, 4739); } + /// Error code: 2002-4722; Inner value: 0x24e402 + public static Result.Base InvalidAllocationTableBlock => new Result.Base(ModuleFs, 4722); + /// Error code: 2002-4723; Inner value: 0x24e602 + public static Result.Base InvalidKeyValueListElementIndex => new Result.Base(ModuleFs, 4723); + /// Error code: 2002-4724; Inner value: 0x24e802 + public static Result.Base AllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 4724); + /// Error code: 2002-4725; Inner value: 0x24ea02 + public static Result.Base InvalidAllocationTableOffset => new Result.Base(ModuleFs, 4725); + /// Error code: 2002-4726; Inner value: 0x24ec02 + public static Result.Base InvalidAllocationTableBlockCount => new Result.Base(ModuleFs, 4726); + /// Error code: 2002-4727; Inner value: 0x24ee02 + public static Result.Base InvalidKeyValueListEntryIndex => new Result.Base(ModuleFs, 4727); + /// Error code: 2002-4728; Inner value: 0x24f002 + public static Result.Base InvalidBitmapIndex => new Result.Base(ModuleFs, 4728); + + /// Error code: 2002-4741; Range: 4741-4759; Inner value: 0x250a02 + public static Result.Base AesXtsFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4741, 4759); } + /// Error code: 2002-4742; Inner value: 0x250c02 + public static Result.Base AesXtsFileHeaderTooShort => new Result.Base(ModuleFs, 4742); + /// Error code: 2002-4743; Inner value: 0x250e02 + public static Result.Base AesXtsFileHeaderInvalidKeys => new Result.Base(ModuleFs, 4743); + /// Error code: 2002-4744; Inner value: 0x251002 + public static Result.Base AesXtsFileHeaderInvalidMagic => new Result.Base(ModuleFs, 4744); + /// Error code: 2002-4745; Inner value: 0x251202 + public static Result.Base AesXtsFileTooShort => new Result.Base(ModuleFs, 4745); + /// Error code: 2002-4746; Inner value: 0x251402 + public static Result.Base AesXtsFileHeaderTooShortInSetSize => new Result.Base(ModuleFs, 4746); + /// Error code: 2002-4747; Inner value: 0x251602 + public static Result.Base AesXtsFileHeaderInvalidKeysInRenameFile => new Result.Base(ModuleFs, 4747); + /// Error code: 2002-4748; Inner value: 0x251802 + public static Result.Base AesXtsFileHeaderInvalidKeysInSetSize => new Result.Base(ModuleFs, 4748); + + /// Error code: 2002-4761; Range: 4761-4769; Inner value: 0x253202 + public static Result.Base SaveDataTransferDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4761, 4769); } + /// Error code: 2002-4762; Inner value: 0x253402 + public static Result.Base Result4762 => new Result.Base(ModuleFs, 4762); + /// Error code: 2002-4763; Inner value: 0x253602 + public static Result.Base Result4763 => new Result.Base(ModuleFs, 4763); + /// Error code: 2002-4764; Inner value: 0x253802 + public static Result.Base Result4764 => new Result.Base(ModuleFs, 4764); + /// Error code: 2002-4765; Inner value: 0x253a02 + public static Result.Base Result4765 => new Result.Base(ModuleFs, 4765); + /// Error code: 2002-4766; Inner value: 0x253c02 + public static Result.Base Result4766 => new Result.Base(ModuleFs, 4766); + /// Error code: 2002-4767; Inner value: 0x253e02 + public static Result.Base Result4767 => new Result.Base(ModuleFs, 4767); + + /// Error code: 2002-4771; Range: 4771-4779; Inner value: 0x254602 + public static Result.Base SignedSystemPartitionDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4771, 4779); } + /// Error code: 2002-4772; Inner value: 0x254802 + public static Result.Base Result4772 => new Result.Base(ModuleFs, 4772); + /// Error code: 2002-4773; Inner value: 0x254a02 + public static Result.Base Result4773 => new Result.Base(ModuleFs, 4773); + /// Error code: 2002-4774; Inner value: 0x254c02 + public static Result.Base Result4774 => new Result.Base(ModuleFs, 4774); + /// Error code: 2002-4775; Inner value: 0x254e02 + public static Result.Base Result4775 => new Result.Base(ModuleFs, 4775); + /// Error code: 2002-4776; Inner value: 0x255002 + public static Result.Base Result4776 => new Result.Base(ModuleFs, 4776); + + /// Error code: 2002-4781; Inner value: 0x255a02 + public static Result.Base GameCardLogoDataCorrupted => new Result.Base(ModuleFs, 4781); + /// Error code: 2002-4785; Inner value: 0x256202 + public static Result.Base SimulatedDeviceDataCorrupted => new Result.Base(ModuleFs, 4785); + + /// Error code: 2002-4790; Range: 4790-4799; Inner value: 0x256c02 + public static Result.Base MultiCommitContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4790, 4799); } + /// The version of the multi-commit context file is too high for the current MultiCommitManager implementation.
Error code: 2002-4791; Inner value: 0x256e02
+ public static Result.Base InvalidMultiCommitContextVersion => new Result.Base(ModuleFs, 4791); + /// The multi-commit has not been provisionally committed.
Error code: 2002-4792; Inner value: 0x257002
+ public static Result.Base InvalidMultiCommitContextState => new Result.Base(ModuleFs, 4792); + + /// Error code: 2002-4811; Range: 4811-4819; Inner value: 0x259602 + public static Result.Base ZeroBitmapFileCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4811, 4819); } + /// Error code: 2002-4812; Inner value: 0x259802 + public static Result.Base IncompleteBlockInZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 4812); + + /// Error code: 2002-5000; Range: 5000-5999; Inner value: 0x271002 + public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 5000, 5999); } + /// Error code: 2002-5002; Inner value: 0x271402 + public static Result.Base UnexpectedFatFileSystemError => new Result.Base(ModuleFs, 5002); + /// Error code: 2002-5003; Inner value: 0x271602 + public static Result.Base FatFileSystemDriverError => new Result.Base(ModuleFs, 5003); + /// Error code: 2002-5024; Inner value: 0x274002 + public static Result.Base FatSystemFileOpenCountLimit => new Result.Base(ModuleFs, 5024); + /// Error code: 2002-5025; Inner value: 0x274202 + public static Result.Base FatUserFileOpenCountLimit => new Result.Base(ModuleFs, 5025); + /// Error code: 2002-5026; Inner value: 0x274402 + public static Result.Base FatEntryIsDirectory => new Result.Base(ModuleFs, 5026); + /// Error code: 2002-5028; Inner value: 0x274802 + public static Result.Base FatFileSystemPermissionDenied => new Result.Base(ModuleFs, 5028); + /// Error code: 2002-5029; Inner value: 0x274a02 + public static Result.Base FatDriverInitializationFailed => new Result.Base(ModuleFs, 5029); + /// Error code: 2002-5055; Inner value: 0x277e02 + public static Result.Base UnexpectedFatSafeError => new Result.Base(ModuleFs, 5055); + /// Error code: 2002-5110; Inner value: 0x27ec02 + public static Result.Base Result5110 => new Result.Base(ModuleFs, 5110); + /// Error code: 2002-5121; Inner value: 0x280202 + public static Result.Base UnexpectedFatFileSystemSectorCount => new Result.Base(ModuleFs, 5121); + /// Error code: 2002-5122; Inner value: 0x280402 + public static Result.Base Result5122 => new Result.Base(ModuleFs, 5122); + /// Error code: 2002-5123; Inner value: 0x280602 + public static Result.Base NullptrArgumentForFatFormat => new Result.Base(ModuleFs, 5123); + /// Error code: 2002-5124; Inner value: 0x280802 + public static Result.Base InvalidFatFormatParamsA => new Result.Base(ModuleFs, 5124); + /// Error code: 2002-5125; Inner value: 0x280a02 + public static Result.Base InvalidFatFormatParamsB => new Result.Base(ModuleFs, 5125); + /// Error code: 2002-5126; Inner value: 0x280c02 + public static Result.Base InvalidFatFormatParamsC => new Result.Base(ModuleFs, 5126); + /// Error code: 2002-5127; Inner value: 0x280e02 + public static Result.Base InvalidFatFormatParamsD => new Result.Base(ModuleFs, 5127); + /// Error code: 2002-5131; Inner value: 0x281602 + public static Result.Base FatSectorWriteVerificationFailed => new Result.Base(ModuleFs, 5131); + /// Error code: 2002-5301; Inner value: 0x296a02 + public static Result.Base UnexpectedInMountTableA => new Result.Base(ModuleFs, 5301); + /// Error code: 2002-5302; Inner value: 0x296c02 + public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemA => new Result.Base(ModuleFs, 5302); + /// Error code: 2002-5303; Inner value: 0x296e02 + public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemB => new Result.Base(ModuleFs, 5303); + /// Tried to write to an IntegrityFilteredFile that is provisionally committed.
Error code: 2002-5304; Inner value: 0x297002
+ public static Result.Base UnexpectedInJournalIntegritySaveDataFileSystemC => new Result.Base(ModuleFs, 5304); + /// Error code: 2002-5305; Inner value: 0x297202 + public static Result.Base UnexpectedInLocalFileSystemA => new Result.Base(ModuleFs, 5305); + /// Error code: 2002-5306; Inner value: 0x297402 + public static Result.Base UnexpectedInLocalFileSystemB => new Result.Base(ModuleFs, 5306); + /// Error code: 2002-5307; Inner value: 0x297602 + public static Result.Base UnexpectedInLocalFileSystemC => new Result.Base(ModuleFs, 5307); + /// Error code: 2002-5308; Inner value: 0x297802 + public static Result.Base UnexpectedInLocalFileSystemD => new Result.Base(ModuleFs, 5308); + /// Error code: 2002-5309; Inner value: 0x297a02 + public static Result.Base UnexpectedInLocalFileSystemE => new Result.Base(ModuleFs, 5309); + /// Error code: 2002-5310; Inner value: 0x297c02 + public static Result.Base UnexpectedInLocalFileSystemF => new Result.Base(ModuleFs, 5310); + /// Error code: 2002-5311; Inner value: 0x297e02 + public static Result.Base UnexpectedInPathToolA => new Result.Base(ModuleFs, 5311); + /// Error code: 2002-5312; Inner value: 0x298002 + public static Result.Base UnexpectedInPathOnExecutionDirectoryA => new Result.Base(ModuleFs, 5312); + /// Error code: 2002-5313; Inner value: 0x298202 + public static Result.Base UnexpectedInPathOnExecutionDirectoryB => new Result.Base(ModuleFs, 5313); + /// Error code: 2002-5314; Inner value: 0x298402 + public static Result.Base UnexpectedInPathOnExecutionDirectoryC => new Result.Base(ModuleFs, 5314); + /// Error code: 2002-5315; Inner value: 0x298602 + public static Result.Base UnexpectedInAesCtrStorageA => new Result.Base(ModuleFs, 5315); + /// Error code: 2002-5316; Inner value: 0x298802 + public static Result.Base UnexpectedInAesXtsStorageA => new Result.Base(ModuleFs, 5316); + /// Error code: 2002-5317; Inner value: 0x298a02 + public static Result.Base UnexpectedInSaveDataInternalStorageFileSystemA => new Result.Base(ModuleFs, 5317); + /// Error code: 2002-5318; Inner value: 0x298c02 + public static Result.Base UnexpectedInSaveDataInternalStorageFileSystemB => new Result.Base(ModuleFs, 5318); + /// Error code: 2002-5319; Inner value: 0x298e02 + public static Result.Base UnexpectedInMountUtilityA => new Result.Base(ModuleFs, 5319); + /// Ncas cannot be mounted from the given mount point.
Error code: 2002-5320; Inner value: 0x299002
+ public static Result.Base UnexpectedInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 5320); + /// Error code: 2002-5321; Inner value: 0x299202 + public static Result.Base UnexpectedInRamDiskFileSystemA => new Result.Base(ModuleFs, 5321); + /// Error code: 2002-5322; Inner value: 0x299402 + public static Result.Base UnexpectedInBisWiperA => new Result.Base(ModuleFs, 5322); + /// Error code: 2002-5323; Inner value: 0x299602 + public static Result.Base UnexpectedInBisWiperB => new Result.Base(ModuleFs, 5323); + /// Error code: 2002-5324; Inner value: 0x299802 + public static Result.Base UnexpectedInCompressedStorageA => new Result.Base(ModuleFs, 5324); + /// Error code: 2002-5325; Inner value: 0x299a02 + public static Result.Base UnexpectedInCompressedStorageB => new Result.Base(ModuleFs, 5325); + /// Error code: 2002-5326; Inner value: 0x299c02 + public static Result.Base UnexpectedInCompressedStorageC => new Result.Base(ModuleFs, 5326); + /// Error code: 2002-5327; Inner value: 0x299e02 + public static Result.Base UnexpectedInCompressedStorageD => new Result.Base(ModuleFs, 5327); + /// Error code: 2002-5328; Inner value: 0x29a002 + public static Result.Base UnexpectedInPathA => new Result.Base(ModuleFs, 5328); + + /// Error code: 2002-6000; Range: 6000-6499; Inner value: 0x2ee002 + public static Result.Base PreconditionViolation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6000, 6499); } + /// Error code: 2002-6001; Range: 6001-6199; Inner value: 0x2ee202 + public static Result.Base InvalidArgument { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6001, 6199); } + /// Error code: 2002-6002; Range: 6002-6029; Inner value: 0x2ee402 + public static Result.Base InvalidPath { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6002, 6029); } + /// Error code: 2002-6003; Inner value: 0x2ee602 + public static Result.Base TooLongPath => new Result.Base(ModuleFs, 6003); + /// Error code: 2002-6004; Inner value: 0x2ee802 + public static Result.Base InvalidCharacter => new Result.Base(ModuleFs, 6004); + /// Error code: 2002-6005; Inner value: 0x2eea02 + public static Result.Base InvalidPathFormat => new Result.Base(ModuleFs, 6005); + /// Error code: 2002-6006; Inner value: 0x2eec02 + public static Result.Base DirectoryUnobtainable => new Result.Base(ModuleFs, 6006); + /// Error code: 2002-6007; Inner value: 0x2eee02 + public static Result.Base NotNormalized => new Result.Base(ModuleFs, 6007); + + /// Error code: 2002-6030; Range: 6030-6059; Inner value: 0x2f1c02 + public static Result.Base InvalidPathForOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6030, 6059); } + /// Error code: 2002-6031; Inner value: 0x2f1e02 + public static Result.Base DirectoryNotDeletable => new Result.Base(ModuleFs, 6031); + /// Error code: 2002-6032; Inner value: 0x2f2002 + public static Result.Base DirectoryNotRenamable => new Result.Base(ModuleFs, 6032); + /// Error code: 2002-6033; Inner value: 0x2f2202 + public static Result.Base IncompatiblePath => new Result.Base(ModuleFs, 6033); + /// Error code: 2002-6034; Inner value: 0x2f2402 + public static Result.Base RenameToOtherFileSystem => new Result.Base(ModuleFs, 6034); + + /// Error code: 2002-6061; Inner value: 0x2f5a02 + public static Result.Base InvalidOffset => new Result.Base(ModuleFs, 6061); + /// Error code: 2002-6062; Inner value: 0x2f5c02 + public static Result.Base InvalidSize => new Result.Base(ModuleFs, 6062); + /// Error code: 2002-6063; Inner value: 0x2f5e02 + public static Result.Base NullptrArgument => new Result.Base(ModuleFs, 6063); + /// Error code: 2002-6064; Inner value: 0x2f6002 + public static Result.Base InvalidAlignment => new Result.Base(ModuleFs, 6064); + /// Error code: 2002-6065; Inner value: 0x2f6202 + public static Result.Base InvalidMountName => new Result.Base(ModuleFs, 6065); + /// Error code: 2002-6066; Inner value: 0x2f6402 + public static Result.Base ExtensionSizeTooLarge => new Result.Base(ModuleFs, 6066); + /// Error code: 2002-6067; Inner value: 0x2f6602 + public static Result.Base ExtensionSizeInvalid => new Result.Base(ModuleFs, 6067); + /// Error code: 2002-6068; Inner value: 0x2f6802 + public static Result.Base InvalidHandle => new Result.Base(ModuleFs, 6068); + /// Error code: 2002-6069; Inner value: 0x2f6a02 + public static Result.Base CacheStorageSizeTooLarge => new Result.Base(ModuleFs, 6069); + /// Error code: 2002-6070; Inner value: 0x2f6c02 + public static Result.Base CacheStorageIndexTooLarge => new Result.Base(ModuleFs, 6070); + /// Up to 10 file systems can be committed at the same time.
Error code: 2002-6071; Inner value: 0x2f6e02
+ public static Result.Base InvalidCommitNameCount => new Result.Base(ModuleFs, 6071); + /// Error code: 2002-6072; Inner value: 0x2f7002 + public static Result.Base InvalidOpenMode => new Result.Base(ModuleFs, 6072); + /// Error code: 2002-6073; Inner value: 0x2f7202 + public static Result.Base InvalidFileSize => new Result.Base(ModuleFs, 6073); + /// Error code: 2002-6074; Inner value: 0x2f7402 + public static Result.Base InvalidDirectoryOpenMode => new Result.Base(ModuleFs, 6074); + /// Error code: 2002-6075; Inner value: 0x2f7602 + public static Result.Base InvalidCommitOption => new Result.Base(ModuleFs, 6075); + + /// Error code: 2002-6080; Range: 6080-6099; Inner value: 0x2f8002 + public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6080, 6099); } + /// Error code: 2002-6081; Inner value: 0x2f8202 + public static Result.Base InvalidSaveDataState => new Result.Base(ModuleFs, 6081); + /// Error code: 2002-6082; Inner value: 0x2f8402 + public static Result.Base InvalidSaveDataSpaceId => new Result.Base(ModuleFs, 6082); + + /// Error code: 2002-6101; Inner value: 0x2faa02 + public static Result.Base GameCardLogoDataTooLarge => new Result.Base(ModuleFs, 6101); + /// Error code: 2002-6102; Inner value: 0x2fac02 + public static Result.Base FileDataCacheMemorySizeTooSmall => new Result.Base(ModuleFs, 6102); + + /// Error code: 2002-6200; Range: 6200-6299; Inner value: 0x307002 + public static Result.Base InvalidOperationForOpenMode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6200, 6299); } + /// Error code: 2002-6201; Inner value: 0x307202 + public static Result.Base FileExtensionWithoutOpenModeAllowAppend => new Result.Base(ModuleFs, 6201); + /// Error code: 2002-6202; Inner value: 0x307402 + public static Result.Base ReadUnpermitted => new Result.Base(ModuleFs, 6202); + /// Error code: 2002-6203; Inner value: 0x307602 + public static Result.Base WriteUnpermitted => new Result.Base(ModuleFs, 6203); + + /// Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802 + public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); } + /// Error code: 2002-6301; Inner value: 0x313a02 + public static Result.Base UnsupportedCommitTarget => new Result.Base(ModuleFs, 6301); + /// Attempted to resize a non-resizable SubStorage.
Error code: 2002-6302; Inner value: 0x313c02
+ public static Result.Base UnsupportedSetSizeForNotResizableSubStorage => new Result.Base(ModuleFs, 6302); + /// Attempted to resize a SubStorage that wasn't located at the end of the base storage.
Error code: 2002-6303; Inner value: 0x313e02
+ public static Result.Base UnsupportedSetSizeForResizableSubStorage => new Result.Base(ModuleFs, 6303); + /// Error code: 2002-6304; Inner value: 0x314002 + public static Result.Base UnsupportedSetSizeForMemoryStorage => new Result.Base(ModuleFs, 6304); + /// Error code: 2002-6305; Inner value: 0x314202 + public static Result.Base UnsupportedOperateRangeForMemoryStorage => new Result.Base(ModuleFs, 6305); + /// Error code: 2002-6306; Inner value: 0x314402 + public static Result.Base UnsupportedOperateRangeForFileStorage => new Result.Base(ModuleFs, 6306); + /// Error code: 2002-6307; Inner value: 0x314602 + public static Result.Base UnsupportedOperateRangeForFileHandleStorage => new Result.Base(ModuleFs, 6307); + /// Error code: 2002-6308; Inner value: 0x314802 + public static Result.Base UnsupportedOperateRangeForSwitchStorage => new Result.Base(ModuleFs, 6308); + /// Error code: 2002-6309; Inner value: 0x314a02 + public static Result.Base UnsupportedOperateRangeForStorageServiceObjectAdapter => new Result.Base(ModuleFs, 6309); + /// Error code: 2002-6310; Inner value: 0x314c02 + public static Result.Base UnsupportedWriteForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6310); + /// Error code: 2002-6311; Inner value: 0x314e02 + public static Result.Base UnsupportedSetSizeForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6311); + /// Error code: 2002-6312; Inner value: 0x315002 + public static Result.Base UnsupportedOperateRangeForAesCtrCounterExtendedStorage => new Result.Base(ModuleFs, 6312); + /// Error code: 2002-6313; Inner value: 0x315202 + public static Result.Base UnsupportedWriteForAesCtrStorageExternal => new Result.Base(ModuleFs, 6313); + /// Error code: 2002-6314; Inner value: 0x315402 + public static Result.Base UnsupportedSetSizeForAesCtrStorageExternal => new Result.Base(ModuleFs, 6314); + /// Error code: 2002-6315; Inner value: 0x315602 + public static Result.Base UnsupportedSetSizeForAesCtrStorage => new Result.Base(ModuleFs, 6315); + /// Error code: 2002-6316; Inner value: 0x315802 + public static Result.Base UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage => new Result.Base(ModuleFs, 6316); + /// Error code: 2002-6317; Inner value: 0x315a02 + public static Result.Base UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage => new Result.Base(ModuleFs, 6317); + /// Error code: 2002-6318; Inner value: 0x315c02 + public static Result.Base UnsupportedSetSizeForIntegrityVerificationStorage => new Result.Base(ModuleFs, 6318); + /// Error code: 2002-6319; Inner value: 0x315e02 + public static Result.Base UnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage => new Result.Base(ModuleFs, 6319); + /// Error code: 2002-6320; Inner value: 0x316002 + public static Result.Base UnsupportedOperateRangeForIntegrityVerificationStorage => new Result.Base(ModuleFs, 6320); + /// Error code: 2002-6321; Inner value: 0x316202 + public static Result.Base UnsupportedSetSizeForBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6321); + /// Error code: 2002-6322; Inner value: 0x316402 + public static Result.Base UnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6322); + /// Error code: 2002-6323; Inner value: 0x316602 + public static Result.Base UnsupportedOperateRangeForBlockCacheBufferedStorage => new Result.Base(ModuleFs, 6323); + /// Error code: 2002-6324; Inner value: 0x316802 + public static Result.Base UnsupportedWriteForIndirectStorage => new Result.Base(ModuleFs, 6324); + /// Error code: 2002-6325; Inner value: 0x316a02 + public static Result.Base UnsupportedSetSizeForIndirectStorage => new Result.Base(ModuleFs, 6325); + /// Error code: 2002-6326; Inner value: 0x316c02 + public static Result.Base UnsupportedOperateRangeForIndirectStorage => new Result.Base(ModuleFs, 6326); + /// Error code: 2002-6327; Inner value: 0x316e02 + public static Result.Base UnsupportedWriteForZeroStorage => new Result.Base(ModuleFs, 6327); + /// Error code: 2002-6328; Inner value: 0x317002 + public static Result.Base UnsupportedSetSizeForZeroStorage => new Result.Base(ModuleFs, 6328); + /// Error code: 2002-6329; Inner value: 0x317202 + public static Result.Base UnsupportedSetSizeForHierarchicalSha256Storage => new Result.Base(ModuleFs, 6329); + /// Error code: 2002-6330; Inner value: 0x317402 + public static Result.Base UnsupportedWriteForReadOnlyBlockCacheStorage => new Result.Base(ModuleFs, 6330); + /// Error code: 2002-6331; Inner value: 0x317602 + public static Result.Base UnsupportedSetSizeForReadOnlyBlockCacheStorage => new Result.Base(ModuleFs, 6331); + /// Error code: 2002-6332; Inner value: 0x317802 + public static Result.Base UnsupportedSetSizeForIntegrityRomFsStorage => new Result.Base(ModuleFs, 6332); + /// Error code: 2002-6333; Inner value: 0x317a02 + public static Result.Base UnsupportedSetSizeForDuplexStorage => new Result.Base(ModuleFs, 6333); + /// Error code: 2002-6334; Inner value: 0x317c02 + public static Result.Base UnsupportedOperateRangeForDuplexStorage => new Result.Base(ModuleFs, 6334); + /// Error code: 2002-6335; Inner value: 0x317e02 + public static Result.Base UnsupportedSetSizeForHierarchicalDuplexStorage => new Result.Base(ModuleFs, 6335); + /// Error code: 2002-6336; Inner value: 0x318002 + public static Result.Base UnsupportedGetSizeForRemapStorage => new Result.Base(ModuleFs, 6336); + /// Error code: 2002-6337; Inner value: 0x318202 + public static Result.Base UnsupportedSetSizeForRemapStorage => new Result.Base(ModuleFs, 6337); + /// Error code: 2002-6338; Inner value: 0x318402 + public static Result.Base UnsupportedOperateRangeForRemapStorage => new Result.Base(ModuleFs, 6338); + /// Error code: 2002-6339; Inner value: 0x318602 + public static Result.Base UnsupportedSetSizeForIntegritySaveDataStorage => new Result.Base(ModuleFs, 6339); + /// Error code: 2002-6340; Inner value: 0x318802 + public static Result.Base UnsupportedOperateRangeForIntegritySaveDataStorage => new Result.Base(ModuleFs, 6340); + /// Error code: 2002-6341; Inner value: 0x318a02 + public static Result.Base UnsupportedSetSizeForJournalIntegritySaveDataStorage => new Result.Base(ModuleFs, 6341); + /// Error code: 2002-6342; Inner value: 0x318c02 + public static Result.Base UnsupportedOperateRangeForJournalIntegritySaveDataStorage => new Result.Base(ModuleFs, 6342); + /// Error code: 2002-6343; Inner value: 0x318e02 + public static Result.Base UnsupportedGetSizeForJournalStorage => new Result.Base(ModuleFs, 6343); + /// Error code: 2002-6344; Inner value: 0x319002 + public static Result.Base UnsupportedSetSizeForJournalStorage => new Result.Base(ModuleFs, 6344); + /// Error code: 2002-6345; Inner value: 0x319202 + public static Result.Base UnsupportedOperateRangeForJournalStorage => new Result.Base(ModuleFs, 6345); + /// Error code: 2002-6346; Inner value: 0x319402 + public static Result.Base UnsupportedSetSizeForUnionStorage => new Result.Base(ModuleFs, 6346); + /// Error code: 2002-6347; Inner value: 0x319602 + public static Result.Base UnsupportedSetSizeForAllocationTableStorage => new Result.Base(ModuleFs, 6347); + /// Error code: 2002-6348; Inner value: 0x319802 + public static Result.Base UnsupportedReadForWriteOnlyGameCardStorage => new Result.Base(ModuleFs, 6348); + /// Error code: 2002-6349; Inner value: 0x319a02 + public static Result.Base UnsupportedSetSizeForWriteOnlyGameCardStorage => new Result.Base(ModuleFs, 6349); + /// Error code: 2002-6350; Inner value: 0x319c02 + public static Result.Base UnsupportedWriteForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6350); + /// Error code: 2002-6351; Inner value: 0x319e02 + public static Result.Base UnsupportedSetSizeForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6351); + /// Error code: 2002-6352; Inner value: 0x31a002 + public static Result.Base UnsupportedOperateRangeForReadOnlyGameCardStorage => new Result.Base(ModuleFs, 6352); + /// Error code: 2002-6353; Inner value: 0x31a202 + public static Result.Base UnsupportedSetSizeForSdmmcStorage => new Result.Base(ModuleFs, 6353); + /// Error code: 2002-6354; Inner value: 0x31a402 + public static Result.Base UnsupportedOperateRangeForSdmmcStorage => new Result.Base(ModuleFs, 6354); + /// Error code: 2002-6355; Inner value: 0x31a602 + public static Result.Base UnsupportedOperateRangeForFatFile => new Result.Base(ModuleFs, 6355); + /// Error code: 2002-6356; Inner value: 0x31a802 + public static Result.Base UnsupportedOperateRangeForStorageFile => new Result.Base(ModuleFs, 6356); + /// Error code: 2002-6357; Inner value: 0x31aa02 + public static Result.Base UnsupportedSetSizeForInternalStorageConcatenationFile => new Result.Base(ModuleFs, 6357); + /// Error code: 2002-6358; Inner value: 0x31ac02 + public static Result.Base UnsupportedOperateRangeForInternalStorageConcatenationFile => new Result.Base(ModuleFs, 6358); + /// Error code: 2002-6359; Inner value: 0x31ae02 + public static Result.Base UnsupportedQueryEntryForConcatenationFileSystem => new Result.Base(ModuleFs, 6359); + /// Error code: 2002-6360; Inner value: 0x31b002 + public static Result.Base UnsupportedOperateRangeForConcatenationFile => new Result.Base(ModuleFs, 6360); + /// Error code: 2002-6361; Inner value: 0x31b202 + public static Result.Base UnsupportedSetSizeForZeroBitmapFile => new Result.Base(ModuleFs, 6361); + /// Called OperateRange with an invalid operation ID.
Error code: 2002-6362; Inner value: 0x31b402
+ public static Result.Base UnsupportedOperateRangeForFileServiceObjectAdapter => new Result.Base(ModuleFs, 6362); + /// Error code: 2002-6363; Inner value: 0x31b602 + public static Result.Base UnsupportedOperateRangeForAesXtsFile => new Result.Base(ModuleFs, 6363); + /// Error code: 2002-6364; Inner value: 0x31b802 + public static Result.Base UnsupportedWriteForRomFsFileSystem => new Result.Base(ModuleFs, 6364); + /// Called RomFsFileSystem::CommitProvisionally.
Error code: 2002-6365; Inner value: 0x31ba02
+ public static Result.Base UnsupportedCommitProvisionallyForRomFsFileSystem => new Result.Base(ModuleFs, 6365); + /// Error code: 2002-6366; Inner value: 0x31bc02 + public static Result.Base UnsupportedGetTotalSpaceSizeForRomFsFileSystem => new Result.Base(ModuleFs, 6366); + /// Error code: 2002-6367; Inner value: 0x31be02 + public static Result.Base UnsupportedWriteForRomFsFile => new Result.Base(ModuleFs, 6367); + /// Error code: 2002-6368; Inner value: 0x31c002 + public static Result.Base UnsupportedOperateRangeForRomFsFile => new Result.Base(ModuleFs, 6368); + /// Error code: 2002-6369; Inner value: 0x31c202 + public static Result.Base UnsupportedWriteForReadOnlyFileSystem => new Result.Base(ModuleFs, 6369); + /// Error code: 2002-6370; Inner value: 0x31c402 + public static Result.Base UnsupportedCommitProvisionallyForReadOnlyFileSystem => new Result.Base(ModuleFs, 6370); + /// Error code: 2002-6371; Inner value: 0x31c602 + public static Result.Base UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem => new Result.Base(ModuleFs, 6371); + /// Error code: 2002-6372; Inner value: 0x31c802 + public static Result.Base UnsupportedWriteForReadOnlyFile => new Result.Base(ModuleFs, 6372); + /// Error code: 2002-6373; Inner value: 0x31ca02 + public static Result.Base UnsupportedOperateRangeForReadOnlyFile => new Result.Base(ModuleFs, 6373); + /// Error code: 2002-6374; Inner value: 0x31cc02 + public static Result.Base UnsupportedWriteForPartitionFileSystem => new Result.Base(ModuleFs, 6374); + /// Called PartitionFileSystemCore::CommitProvisionally.
Error code: 2002-6375; Inner value: 0x31ce02
+ public static Result.Base UnsupportedCommitProvisionallyForPartitionFileSystem => new Result.Base(ModuleFs, 6375); + /// Error code: 2002-6376; Inner value: 0x31d002 + public static Result.Base UnsupportedWriteForPartitionFile => new Result.Base(ModuleFs, 6376); + /// Error code: 2002-6377; Inner value: 0x31d202 + public static Result.Base UnsupportedOperateRangeForPartitionFile => new Result.Base(ModuleFs, 6377); + /// Error code: 2002-6378; Inner value: 0x31d402 + public static Result.Base UnsupportedOperateRangeForTmFileSystemFile => new Result.Base(ModuleFs, 6378); + /// Error code: 2002-6379; Inner value: 0x31d602 + public static Result.Base UnsupportedWriteForSaveDataInternalStorageFileSystem => new Result.Base(ModuleFs, 6379); + /// Error code: 2002-6382; Inner value: 0x31dc02 + public static Result.Base UnsupportedCommitProvisionallyForApplicationTemporaryFileSystem => new Result.Base(ModuleFs, 6382); + /// Error code: 2002-6383; Inner value: 0x31de02 + public static Result.Base UnsupportedCommitProvisionallyForSaveDataFileSystem => new Result.Base(ModuleFs, 6383); + /// Called DirectorySaveDataFileSystem::CommitProvisionally on a non-user savedata.
Error code: 2002-6384; Inner value: 0x31e002
+ public static Result.Base UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6384); + /// Error code: 2002-6385; Inner value: 0x31e202 + public static Result.Base UnsupportedWriteForZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 6385); + /// Error code: 2002-6386; Inner value: 0x31e402 + public static Result.Base UnsupportedSetSizeForZeroBitmapHashStorageFile => new Result.Base(ModuleFs, 6386); + + /// Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002 + public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); } + /// Returned when opening a host FS on a retail device.
Error code: 2002-6403; Inner value: 0x320602
+ public static Result.Base PermissionDeniedForCreateHostFileSystem => new Result.Base(ModuleFs, 6403); + + /// Error code: 2002-6450; Inner value: 0x326402 + public static Result.Base PortAcceptableCountLimited => new Result.Base(ModuleFs, 6450); + /// Error code: 2002-6452; Inner value: 0x326802 + public static Result.Base ExternalKeyAlreadyRegistered => new Result.Base(ModuleFs, 6452); + /// Error code: 2002-6454; Inner value: 0x326c02 + public static Result.Base NeedFlush => new Result.Base(ModuleFs, 6454); + /// Error code: 2002-6455; Inner value: 0x326e02 + public static Result.Base FileNotClosed => new Result.Base(ModuleFs, 6455); + /// Error code: 2002-6456; Inner value: 0x327002 + public static Result.Base DirectoryNotClosed => new Result.Base(ModuleFs, 6456); + /// Error code: 2002-6457; Inner value: 0x327202 + public static Result.Base WriteModeFileNotClosed => new Result.Base(ModuleFs, 6457); + /// Error code: 2002-6458; Inner value: 0x327402 + public static Result.Base AllocatorAlreadyRegistered => new Result.Base(ModuleFs, 6458); + /// Error code: 2002-6459; Inner value: 0x327602 + public static Result.Base DefaultAllocatorUsed => new Result.Base(ModuleFs, 6459); + /// Error code: 2002-6460; Inner value: 0x327802 + public static Result.Base GameCardLogoDataNotReadable => new Result.Base(ModuleFs, 6460); + /// Error code: 2002-6461; Inner value: 0x327a02 + public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461); + /// Error code: 2002-6462; Inner value: 0x327c02 + public static Result.Base GlobalFileDataCacheAlreadyEnabled => new Result.Base(ModuleFs, 6462); + /// The provided file system has already been added to the multi-commit manager.
Error code: 2002-6463; Inner value: 0x327e02
+ public static Result.Base MultiCommitFileSystemAlreadyAdded => new Result.Base(ModuleFs, 6463); + /// Error code: 2002-6464; Inner value: 0x328002 + public static Result.Base Result6464 => new Result.Base(ModuleFs, 6464); + /// Error code: 2002-6465; Inner value: 0x328202 + public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465); + /// Error code: 2002-6466; Inner value: 0x328402 + public static Result.Base DefaultGlobalFileDataCacheEnabled => new Result.Base(ModuleFs, 6466); + /// Error code: 2002-6467; Inner value: 0x328602 + public static Result.Base SaveDataRootPathUnavailable => new Result.Base(ModuleFs, 6467); + + /// Error code: 2002-6600; Range: 6600-6699; Inner value: 0x339002 + public static Result.Base NotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); } + /// Error code: 2002-6602; Inner value: 0x339402 + public static Result.Base FileNotFound => new Result.Base(ModuleFs, 6602); + /// Error code: 2002-6603; Inner value: 0x339602 + public static Result.Base DirectoryNotFound => new Result.Base(ModuleFs, 6603); + /// Error code: 2002-6604; Inner value: 0x339802 + public static Result.Base DatabaseKeyNotFound => new Result.Base(ModuleFs, 6604); + /// Specified program is not found in the program registry.
Error code: 2002-6605; Inner value: 0x339a02
+ public static Result.Base ProgramInfoNotFound => new Result.Base(ModuleFs, 6605); + /// Specified program index is not found
Error code: 2002-6606; Inner value: 0x339c02
+ public static Result.Base ProgramIndexNotFound => new Result.Base(ModuleFs, 6606); + + /// Error code: 2002-6700; Range: 6700-6799; Inner value: 0x345802 + public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6700, 6799); } + /// Error code: 2002-6705; Inner value: 0x346202 + public static Result.Base BufferAllocationFailed => new Result.Base(ModuleFs, 6705); + /// Error code: 2002-6706; Inner value: 0x346402 + public static Result.Base MappingTableFull => new Result.Base(ModuleFs, 6706); + /// Error code: 2002-6707; Inner value: 0x346602 + public static Result.Base AllocationTableFull => new Result.Base(ModuleFs, 6707); + /// Error code: 2002-6709; Inner value: 0x346a02 + public static Result.Base OpenCountLimit => new Result.Base(ModuleFs, 6709); + /// The maximum number of file systems have been added to the multi-commit manager.
Error code: 2002-6710; Inner value: 0x346c02
+ public static Result.Base MultiCommitFileSystemLimit => new Result.Base(ModuleFs, 6710); + + /// Error code: 2002-6800; Range: 6800-6899; Inner value: 0x352002 + public static Result.Base MappingFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6800, 6899); } + /// Error code: 2002-6811; Inner value: 0x353602 + public static Result.Base MapFull => new Result.Base(ModuleFs, 6811); + + /// Error code: 2002-6900; Range: 6900-6999; Inner value: 0x35e802 + public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6900, 6999); } + /// Error code: 2002-6902; Inner value: 0x35ec02 + public static Result.Base NotInitialized => new Result.Base(ModuleFs, 6902); + /// Error code: 2002-6903; Inner value: 0x35ee02 + public static Result.Base BisProxyInvalidated => new Result.Base(ModuleFs, 6903); + /// Error code: 2002-6904; Inner value: 0x35f002 + public static Result.Base NcaDigestInconsistent => new Result.Base(ModuleFs, 6904); + /// Error code: 2002-6905; Inner value: 0x35f202 + public static Result.Base NotMounted => new Result.Base(ModuleFs, 6905); + /// Error code: 2002-6906; Inner value: 0x35f402 + public static Result.Base SaveDataExtending => new Result.Base(ModuleFs, 6906); + /// Error code: 2002-6907; Inner value: 0x35f602 + public static Result.Base SaveDataToExpandIsProvisionallyCommitted => new Result.Base(ModuleFs, 6907); + + /// Error code: 2002-7002; Inner value: 0x36b402 + public static Result.Base Result7002 => new Result.Base(ModuleFs, 7002); + /// Error code: 2002-7003; Inner value: 0x36b602 + public static Result.Base Result7003 => new Result.Base(ModuleFs, 7003); + /// Error code: 2002-7004; Inner value: 0x36b802 + public static Result.Base Result7004 => new Result.Base(ModuleFs, 7004); + /// Error code: 2002-7005; Inner value: 0x36ba02 + public static Result.Base Result7005 => new Result.Base(ModuleFs, 7005); + /// Error code: 2002-7006; Inner value: 0x36bc02 + public static Result.Base Result7006 => new Result.Base(ModuleFs, 7006); + /// Error code: 2002-7009; Inner value: 0x36c202 + public static Result.Base Result7009 => new Result.Base(ModuleFs, 7009); + /// Error code: 2002-7010; Inner value: 0x36c402 + public static Result.Base Result7010 => new Result.Base(ModuleFs, 7010); + /// Error code: 2002-7011; Inner value: 0x36c602 + public static Result.Base Result7011 => new Result.Base(ModuleFs, 7011); + /// Error code: 2002-7031; Inner value: 0x36ee02 + public static Result.Base SaveDataPorterInvalidated => new Result.Base(ModuleFs, 7031); + /// Error code: 2002-7032; Inner value: 0x36f002 + public static Result.Base Result7032 => new Result.Base(ModuleFs, 7032); + /// Error code: 2002-7033; Inner value: 0x36f202 + public static Result.Base Result7033 => new Result.Base(ModuleFs, 7033); + /// Error code: 2002-7034; Inner value: 0x36f402 + public static Result.Base Result7034 => new Result.Base(ModuleFs, 7034); + /// Error code: 2002-7035; Inner value: 0x36f602 + public static Result.Base Result7035 => new Result.Base(ModuleFs, 7035); + /// Error code: 2002-7036; Inner value: 0x36f802 + public static Result.Base Result7036 => new Result.Base(ModuleFs, 7036); + /// Error code: 2002-7037; Inner value: 0x36fa02 + public static Result.Base Result7037 => new Result.Base(ModuleFs, 7037); + /// Error code: 2002-7038; Inner value: 0x36fc02 + public static Result.Base Result7038 => new Result.Base(ModuleFs, 7038); + /// Error code: 2002-7062; Inner value: 0x372c02 + public static Result.Base InvalidKeyPackageMac => new Result.Base(ModuleFs, 7062); + /// Error code: 2002-7063; Inner value: 0x372e02 + public static Result.Base KeyPackageSignatureVerificationFailed => new Result.Base(ModuleFs, 7063); + /// Error code: 2002-7064; Inner value: 0x373002 + public static Result.Base InvalidKeyPackageChallengeData => new Result.Base(ModuleFs, 7064); + /// Error code: 2002-7065; Inner value: 0x373202 + public static Result.Base UnsupportedKeyPackageKeyGeneration => new Result.Base(ModuleFs, 7065); + /// Error code: 2002-7066; Inner value: 0x373402 + public static Result.Base InvalidSaveDataRepairInitialDataContentGcmMac => new Result.Base(ModuleFs, 7066); + /// Error code: 2002-7069; Inner value: 0x373a02 + public static Result.Base InvalidSaveDataRepairInitialDataCmac => new Result.Base(ModuleFs, 7069); + /// The before and after initial data have different AAD.
Error code: 2002-7070; Inner value: 0x373c02
+ public static Result.Base SaveDataRepairInitialDataAadMismatch => new Result.Base(ModuleFs, 7070); + /// The before and after initial data refer to different saves.
Error code: 2002-7071; Inner value: 0x373e02
+ public static Result.Base SaveDataRepairInitialDataSaveMismatch => new Result.Base(ModuleFs, 7071); + + /// Error code: 2002-7100; Range: 7100-7139; Inner value: 0x377802 + public static Result.Base RamDiskCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7100, 7139); } + /// Error code: 2002-7101; Inner value: 0x377a02 + public static Result.Base Result7101 => new Result.Base(ModuleFs, 7101); + + /// Error code: 2002-7111; Range: 7111-7119; Inner value: 0x378e02 + public static Result.Base RamDiskSaveDataCoreFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7111, 7119); } + /// Error code: 2002-7112; Inner value: 0x379002 + public static Result.Base IncorrectRamDiskSaveDataControlAreaMagic => new Result.Base(ModuleFs, 7112); + /// Error code: 2002-7113; Inner value: 0x379202 + public static Result.Base InvalidRamDiskSaveDataFileReadOffset => new Result.Base(ModuleFs, 7113); + /// Error code: 2002-7114; Inner value: 0x379402 + public static Result.Base InvalidRamDiskSaveDataCoreDataStorageSize => new Result.Base(ModuleFs, 7114); + + /// Error code: 2002-7121; Range: 7121-7139; Inner value: 0x37a202 + public static Result.Base RamDiskDatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7121, 7139); } + /// Error code: 2002-7122; Inner value: 0x37a402 + public static Result.Base InvalidRamDiskAllocationTableBlock => new Result.Base(ModuleFs, 7122); + /// Error code: 2002-7123; Inner value: 0x37a602 + public static Result.Base InvalidRamDiskKeyValueListElementIndex => new Result.Base(ModuleFs, 7123); + /// Error code: 2002-7124; Inner value: 0x37a802 + public static Result.Base RamDiskAllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 7124); + /// Error code: 2002-7125; Inner value: 0x37aa02 + public static Result.Base InvalidRamDiskAllocationTableOffset => new Result.Base(ModuleFs, 7125); + /// Error code: 2002-7126; Inner value: 0x37ac02 + public static Result.Base InvalidRamDiskAllocationTableBlockCount => new Result.Base(ModuleFs, 7126); + /// Error code: 2002-7127; Inner value: 0x37ae02 + public static Result.Base InvalidRamDiskKeyValueListEntryIndex => new Result.Base(ModuleFs, 7127); + + /// Error code: 2002-7142; Inner value: 0x37cc02 + public static Result.Base Result7142 => new Result.Base(ModuleFs, 7142); + /// Error code: 2002-7900; Inner value: 0x3db802 + public static Result.Base Unknown => new Result.Base(ModuleFs, 7900); + + /// Error code: 2002-7901; Range: 7901-7904; Inner value: 0x3dba02 + public static Result.Base DbmNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7901, 7904); } + /// Error code: 2002-7902; Inner value: 0x3dbc02 + public static Result.Base DbmKeyNotFound => new Result.Base(ModuleFs, 7902); + /// Error code: 2002-7903; Inner value: 0x3dbe02 + public static Result.Base DbmFileNotFound => new Result.Base(ModuleFs, 7903); + /// Error code: 2002-7904; Inner value: 0x3dc002 + public static Result.Base DbmDirectoryNotFound => new Result.Base(ModuleFs, 7904); + + /// Error code: 2002-7906; Inner value: 0x3dc402 + public static Result.Base DbmAlreadyExists => new Result.Base(ModuleFs, 7906); + /// Error code: 2002-7907; Inner value: 0x3dc602 + public static Result.Base DbmKeyFull => new Result.Base(ModuleFs, 7907); + /// Error code: 2002-7908; Inner value: 0x3dc802 + public static Result.Base DbmDirectoryEntryFull => new Result.Base(ModuleFs, 7908); + /// Error code: 2002-7909; Inner value: 0x3dca02 + public static Result.Base DbmFileEntryFull => new Result.Base(ModuleFs, 7909); + + /// Error code: 2002-7910; Range: 7910-7912; Inner value: 0x3dcc02 + public static Result.Base DbmFindFinished { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 7910, 7912); } + /// Error code: 2002-7911; Inner value: 0x3dce02 + public static Result.Base DbmFindKeyFinished => new Result.Base(ModuleFs, 7911); + /// Error code: 2002-7912; Inner value: 0x3dd002 + public static Result.Base DbmIterationFinished => new Result.Base(ModuleFs, 7912); + + /// Error code: 2002-7914; Inner value: 0x3dd402 + public static Result.Base DbmInvalidOperation => new Result.Base(ModuleFs, 7914); + /// Error code: 2002-7915; Inner value: 0x3dd602 + public static Result.Base DbmInvalidPathFormat => new Result.Base(ModuleFs, 7915); + /// Error code: 2002-7916; Inner value: 0x3dd802 + public static Result.Base DbmDirectoryNameTooLong => new Result.Base(ModuleFs, 7916); + /// Error code: 2002-7917; Inner value: 0x3dda02 + public static Result.Base DbmFileNameTooLong => new Result.Base(ModuleFs, 7917); } diff --git a/src/LibHac/Fs/ResultHandlingUtility.cs b/src/LibHac/Fs/ResultHandlingUtility.cs index 753cf9b8..d70a80ba 100644 --- a/src/LibHac/Fs/ResultHandlingUtility.cs +++ b/src/LibHac/Fs/ResultHandlingUtility.cs @@ -1,79 +1,78 @@ using System.Runtime.CompilerServices; using LibHac.Diag; -namespace LibHac.Fs +namespace LibHac.Fs; + +internal struct ResultHandlingUtilityGlobals { - internal struct ResultHandlingUtilityGlobals + public bool IsResultHandledByApplication; +} + +public static class ResultHandlingUtility +{ + public static void SetResultHandledByApplication(this FileSystemClient fs, bool isHandledByApplication) { - public bool IsResultHandledByApplication; + fs.Globals.ResultHandlingUtility.IsResultHandledByApplication = isHandledByApplication; } - public static class ResultHandlingUtility + public static bool IsAbortNeeded(this FileSystemClientImpl fs, Result result) { - public static void SetResultHandledByApplication(this FileSystemClient fs, bool isHandledByApplication) - { - fs.Globals.ResultHandlingUtility.IsResultHandledByApplication = isHandledByApplication; - } + if (result.IsSuccess()) + return false; - public static bool IsAbortNeeded(this FileSystemClientImpl fs, Result result) + switch (fs.Fs.GetCurrentThreadFsContext().HandleResult(result)) { - if (result.IsSuccess()) + case AbortSpecifier.Default: + if (fs.Globals.ResultHandlingUtility.IsResultHandledByApplication) + { + return ResultFs.HandledByAllProcess.Includes(result); + } + else + { + return !(ResultFs.HandledByAllProcess.Includes(result) || + ResultFs.HandledBySystemProcess.Includes(result)); + } + case AbortSpecifier.Abort: + return true; + case AbortSpecifier.Return: return false; - - switch (fs.Fs.GetCurrentThreadFsContext().HandleResult(result)) - { - case AbortSpecifier.Default: - if (fs.Globals.ResultHandlingUtility.IsResultHandledByApplication) - { - return ResultFs.HandledByAllProcess.Includes(result); - } - else - { - return !(ResultFs.HandledByAllProcess.Includes(result) || - ResultFs.HandledBySystemProcess.Includes(result)); - } - case AbortSpecifier.Abort: - return true; - case AbortSpecifier.Return: - return false; - default: - Abort.UnexpectedDefault(); - return default; - } + default: + Abort.UnexpectedDefault(); + return default; } + } - public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string message) - { - // Todo - } + public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string message) + { + // Todo + } - public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format, object arg0) - { - // Todo - } + public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format, object arg0) + { + // Todo + } - public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format, - params object[] args) - { - // Todo - } + public static void LogErrorMessage(this FileSystemClientImpl fs, Result result, string format, + params object[] args) + { + // Todo + } - public static void LogResultErrorMessage(this FileSystemClientImpl fs, Result result, - [CallerMemberName] string functionName = "") - { - // Todo - } + public static void LogResultErrorMessage(this FileSystemClientImpl fs, Result result, + [CallerMemberName] string functionName = "") + { + // Todo + } - internal static void AbortIfNeeded(this FileSystemClientImpl fs, Result result, - [CallerMemberName] string functionName = "") - { - if (!IsAbortNeeded(fs, result)) - return; + internal static void AbortIfNeeded(this FileSystemClientImpl fs, Result result, + [CallerMemberName] string functionName = "") + { + if (!IsAbortNeeded(fs, result)) + return; - fs.LogResultErrorMessage(result, functionName); + fs.LogResultErrorMessage(result, functionName); - if (!result.IsSuccess()) - Abort.DoAbort(result); - } + if (!result.IsSuccess()) + Abort.DoAbort(result); } } diff --git a/src/LibHac/Fs/RightsId.cs b/src/LibHac/Fs/RightsId.cs index ba5216af..1653c1c0 100644 --- a/src/LibHac/Fs/RightsId.cs +++ b/src/LibHac/Fs/RightsId.cs @@ -4,60 +4,59 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Fs +namespace LibHac.Fs; + +[DebuggerDisplay("{DebugDisplay(),nq}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct RightsId : IEquatable, IComparable, IComparable { - [DebuggerDisplay("{DebugDisplay(),nq}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct RightsId : IEquatable, IComparable, IComparable + public readonly Id128 Id; + + public RightsId(ulong high, ulong low) { - public readonly Id128 Id; - - public RightsId(ulong high, ulong low) - { - Id = new Id128(high, low); - } - - public RightsId(ReadOnlySpan uid) - { - Id = new Id128(uid); - } - - public override string ToString() => Id.ToString(); - - public string DebugDisplay() - { - ReadOnlySpan highBytes = AsBytes().Slice(0, 8); - ReadOnlySpan lowBytes = AsBytes().Slice(8, 8); - - return $"{highBytes.ToHexString()} {lowBytes.ToHexString()}"; - } - - public bool Equals(RightsId other) => Id == other.Id; - public override bool Equals(object obj) => obj is RightsId other && Equals(other); - - public override int GetHashCode() => Id.GetHashCode(); - - public int CompareTo(RightsId other) => Id.CompareTo(other.Id); - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is RightsId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(RightsId)}"); - } - - public void ToBytes(Span output) => Id.ToBytes(output); - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(RightsId left, RightsId right) => left.Equals(right); - public static bool operator !=(RightsId left, RightsId right) => !left.Equals(right); - - public static bool operator <(RightsId left, RightsId right) => left.CompareTo(right) < 0; - public static bool operator >(RightsId left, RightsId right) => left.CompareTo(right) > 0; - public static bool operator <=(RightsId left, RightsId right) => left.CompareTo(right) <= 0; - public static bool operator >=(RightsId left, RightsId right) => left.CompareTo(right) >= 0; + Id = new Id128(high, low); } -} \ No newline at end of file + + public RightsId(ReadOnlySpan uid) + { + Id = new Id128(uid); + } + + public override string ToString() => Id.ToString(); + + public string DebugDisplay() + { + ReadOnlySpan highBytes = AsBytes().Slice(0, 8); + ReadOnlySpan lowBytes = AsBytes().Slice(8, 8); + + return $"{highBytes.ToHexString()} {lowBytes.ToHexString()}"; + } + + public bool Equals(RightsId other) => Id == other.Id; + public override bool Equals(object obj) => obj is RightsId other && Equals(other); + + public override int GetHashCode() => Id.GetHashCode(); + + public int CompareTo(RightsId other) => Id.CompareTo(other.Id); + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is RightsId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(RightsId)}"); + } + + public void ToBytes(Span output) => Id.ToBytes(output); + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(RightsId left, RightsId right) => left.Equals(right); + public static bool operator !=(RightsId left, RightsId right) => !left.Equals(right); + + public static bool operator <(RightsId left, RightsId right) => left.CompareTo(right) < 0; + public static bool operator >(RightsId left, RightsId right) => left.CompareTo(right) > 0; + public static bool operator <=(RightsId left, RightsId right) => left.CompareTo(right) <= 0; + public static bool operator >=(RightsId left, RightsId right) => left.CompareTo(right) >= 0; +} diff --git a/src/LibHac/Fs/SaveData.cs b/src/LibHac/Fs/SaveData.cs index be85e166..4f533c4b 100644 --- a/src/LibHac/Fs/SaveData.cs +++ b/src/LibHac/Fs/SaveData.cs @@ -1,13 +1,12 @@ using LibHac.Ncm; -namespace LibHac.Fs -{ - public static class SaveData - { - public const ulong SaveIndexerId = 0x8000000000000000; - public static ProgramId InvalidProgramId => ProgramId.InvalidId; - public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1); - public static UserId InvalidUserId => UserId.InvalidId; +namespace LibHac.Fs; - } -} \ No newline at end of file +public static class SaveData +{ + public const ulong SaveIndexerId = 0x8000000000000000; + public static ProgramId InvalidProgramId => ProgramId.InvalidId; + public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1); + public static UserId InvalidUserId => UserId.InvalidId; + +} diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index 3ef58da8..27412a93 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -5,339 +5,338 @@ using LibHac.FsSrv.Impl; using LibHac.Ncm; using LibHac.Util; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Explicit, Size = 0x40)] +public struct SaveDataAttribute : IEquatable, IComparable { - [StructLayout(LayoutKind.Explicit, Size = 0x40)] - public struct SaveDataAttribute : IEquatable, IComparable + [FieldOffset(0x00)] public ProgramId ProgramId; + [FieldOffset(0x08)] public UserId UserId; + [FieldOffset(0x18)] public ulong StaticSaveDataId; + [FieldOffset(0x20)] public SaveDataType Type; + [FieldOffset(0x21)] public SaveDataRank Rank; + [FieldOffset(0x22)] public ushort Index; + + public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId) : this( + programId, type, userId, saveDataId, 0, SaveDataRank.Primary) + { } + + public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, + ushort index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary) + { } + + public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, ushort index, + SaveDataRank rank) { - [FieldOffset(0x00)] public ProgramId ProgramId; - [FieldOffset(0x08)] public UserId UserId; - [FieldOffset(0x18)] public ulong StaticSaveDataId; - [FieldOffset(0x20)] public SaveDataType Type; - [FieldOffset(0x21)] public SaveDataRank Rank; - [FieldOffset(0x22)] public ushort Index; - - public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId) : this( - programId, type, userId, saveDataId, 0, SaveDataRank.Primary) - { } - - public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, - ushort index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary) - { } - - public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, ushort index, - SaveDataRank rank) - { - ProgramId = programId; - Type = type; - UserId = userId; - StaticSaveDataId = saveDataId; - Index = index; - Rank = rank; - } - - public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, - UserId userId, ulong staticSaveDataId) - { - return Make(out attribute, programId, type, userId, staticSaveDataId, 0, SaveDataRank.Primary); - } - - public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, - UserId userId, ulong staticSaveDataId, ushort index) - { - return Make(out attribute, programId, type, userId, staticSaveDataId, index, SaveDataRank.Primary); - } - - public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, - UserId userId, ulong staticSaveDataId, ushort index, SaveDataRank rank) - { - UnsafeHelpers.SkipParamInit(out attribute); - SaveDataAttribute tempAttribute = default; - - tempAttribute.ProgramId = programId; - tempAttribute.Type = type; - tempAttribute.UserId = userId; - tempAttribute.StaticSaveDataId = staticSaveDataId; - tempAttribute.Index = index; - tempAttribute.Rank = rank; - - if (!SaveDataTypesValidity.IsValid(in tempAttribute)) - return ResultFs.InvalidArgument.Log(); - - attribute = tempAttribute; - return Result.Success; - } - - public override readonly bool Equals(object obj) - { - return obj is SaveDataAttribute attribute && Equals(attribute); - } - - public readonly bool Equals(SaveDataAttribute other) - { - return ProgramId == other.ProgramId && - Type == other.Type && - UserId.Equals(other.UserId) && - StaticSaveDataId == other.StaticSaveDataId && - Rank == other.Rank && - Index == other.Index; - } - - public static bool operator ==(SaveDataAttribute left, SaveDataAttribute right) => left.Equals(right); - public static bool operator !=(SaveDataAttribute left, SaveDataAttribute right) => !(left == right); - - public override readonly int GetHashCode() - { - // ReSharper disable NonReadonlyMemberInGetHashCode - return HashCode.Combine(ProgramId, Type, UserId, StaticSaveDataId, Rank, Index); - // ReSharper restore NonReadonlyMemberInGetHashCode - } - - public readonly int CompareTo(SaveDataAttribute other) - { - int titleIdComparison = ProgramId.CompareTo(other.ProgramId); - if (titleIdComparison != 0) return titleIdComparison; - int typeComparison = ((int)Type).CompareTo((int)other.Type); - if (typeComparison != 0) return typeComparison; - int userIdComparison = UserId.CompareTo(other.UserId); - if (userIdComparison != 0) return userIdComparison; - int saveDataIdComparison = StaticSaveDataId.CompareTo(other.StaticSaveDataId); - if (saveDataIdComparison != 0) return saveDataIdComparison; - int rankComparison = ((int)Rank).CompareTo((int)other.Rank); - if (rankComparison != 0) return rankComparison; - return Index.CompareTo(other.Index); - } + ProgramId = programId; + Type = type; + UserId = userId; + StaticSaveDataId = saveDataId; + Index = index; + Rank = rank; } - [StructLayout(LayoutKind.Explicit, Size = 0x40)] - public struct SaveDataCreationInfo + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId) { - [FieldOffset(0x00)] public long Size; - [FieldOffset(0x08)] public long JournalSize; - [FieldOffset(0x10)] public long BlockSize; - [FieldOffset(0x18)] public ulong OwnerId; - [FieldOffset(0x20)] public SaveDataFlags Flags; - [FieldOffset(0x24)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x25)] public bool Field25; - - public static Result Make(out SaveDataCreationInfo creationInfo, long size, long journalSize, ulong ownerId, - SaveDataFlags flags, SaveDataSpaceId spaceId) - { - UnsafeHelpers.SkipParamInit(out creationInfo); - SaveDataCreationInfo tempCreationInfo = default; - - tempCreationInfo.Size = size; - tempCreationInfo.JournalSize = journalSize; - tempCreationInfo.BlockSize = SaveDataProperties.DefaultSaveDataBlockSize; - tempCreationInfo.OwnerId = ownerId; - tempCreationInfo.Flags = flags; - tempCreationInfo.SpaceId = spaceId; - tempCreationInfo.Field25 = false; - - if (!SaveDataTypesValidity.IsValid(in tempCreationInfo)) - return ResultFs.InvalidArgument.Log(); - - creationInfo = tempCreationInfo; - return Result.Success; - } + return Make(out attribute, programId, type, userId, staticSaveDataId, 0, SaveDataRank.Primary); } - [StructLayout(LayoutKind.Explicit, Size = 0x48)] - public struct SaveDataFilter + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId, ushort index) { - [FieldOffset(0x00)] public bool FilterByProgramId; - [FieldOffset(0x01)] public bool FilterBySaveDataType; - [FieldOffset(0x02)] public bool FilterByUserId; - [FieldOffset(0x03)] public bool FilterBySaveDataId; - [FieldOffset(0x04)] public bool FilterByIndex; - [FieldOffset(0x05)] public SaveDataRank Rank; - - [FieldOffset(0x08)] public SaveDataAttribute Attribute; - - public void SetProgramId(ProgramId value) - { - FilterByProgramId = true; - Attribute.ProgramId = value; - } - - public void SetSaveDataType(SaveDataType value) - { - FilterBySaveDataType = true; - Attribute.Type = value; - } - - public void SetUserId(UserId value) - { - FilterByUserId = true; - Attribute.UserId = value; - } - - public void SetSaveDataId(ulong value) - { - FilterBySaveDataId = true; - Attribute.StaticSaveDataId = value; - } - - public void SetIndex(ushort value) - { - FilterByIndex = true; - Attribute.Index = value; - } - - public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, - Optional userId, Optional saveDataId, Optional index) - { - return Make(out filter, programId, saveType, userId, saveDataId, index, SaveDataRank.Primary); - } - - public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, - Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) - { - UnsafeHelpers.SkipParamInit(out filter); - - SaveDataFilter tempFilter = Make(programId, saveType, userId, saveDataId, index, rank); - - if (!SaveDataTypesValidity.IsValid(in tempFilter)) - return ResultFs.InvalidArgument.Log(); - - filter = tempFilter; - return Result.Success; - } - - public static SaveDataFilter Make(Optional programId, Optional saveType, - Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) - { - var filter = new SaveDataFilter(); - - if (programId.HasValue) - { - filter.FilterByProgramId = true; - filter.Attribute.ProgramId = new ProgramId(programId.Value); - } - - if (saveType.HasValue) - { - filter.FilterBySaveDataType = true; - filter.Attribute.Type = saveType.Value; - } - - if (userId.HasValue) - { - filter.FilterByUserId = true; - filter.Attribute.UserId = userId.Value; - } - - if (saveDataId.HasValue) - { - filter.FilterBySaveDataId = true; - filter.Attribute.StaticSaveDataId = saveDataId.Value; - } - - if (index.HasValue) - { - filter.FilterByIndex = true; - filter.Attribute.Index = index.Value; - } - - filter.Rank = rank; - - return filter; - } + return Make(out attribute, programId, type, userId, staticSaveDataId, index, SaveDataRank.Primary); } - [StructLayout(LayoutKind.Explicit, Size = HashLength)] - public struct HashSalt + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId, ushort index, SaveDataRank rank) { - private const int HashLength = 0x20; + UnsafeHelpers.SkipParamInit(out attribute); + SaveDataAttribute tempAttribute = default; - [FieldOffset(0x00)] private byte _hashStart; + tempAttribute.ProgramId = programId; + tempAttribute.Type = type; + tempAttribute.UserId = userId; + tempAttribute.StaticSaveDataId = staticSaveDataId; + tempAttribute.Index = index; + tempAttribute.Rank = rank; - public Span Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength); - public ReadOnlySpan HashRo => SpanHelpers.CreateReadOnlySpan(in _hashStart, HashLength); + if (!SaveDataTypesValidity.IsValid(in tempAttribute)) + return ResultFs.InvalidArgument.Log(); + + attribute = tempAttribute; + return Result.Success; } - [StructLayout(LayoutKind.Explicit, Size = 0x10)] - public struct SaveDataMetaInfo + public override readonly bool Equals(object obj) { - [FieldOffset(0)] public int Size; - [FieldOffset(4)] public SaveDataMetaType Type; + return obj is SaveDataAttribute attribute && Equals(attribute); } - [StructLayout(LayoutKind.Explicit, Size = 0x60)] - public struct SaveDataInfo + public readonly bool Equals(SaveDataAttribute other) { - [FieldOffset(0x00)] public ulong SaveDataId; - [FieldOffset(0x08)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x09)] public SaveDataType Type; - [FieldOffset(0x10)] public UserId UserId; - [FieldOffset(0x20)] public ulong StaticSaveDataId; - [FieldOffset(0x28)] public ProgramId ProgramId; - [FieldOffset(0x30)] public long Size; - [FieldOffset(0x38)] public ushort Index; - [FieldOffset(0x3A)] public SaveDataRank Rank; - [FieldOffset(0x3B)] public SaveDataState State; + return ProgramId == other.ProgramId && + Type == other.Type && + UserId.Equals(other.UserId) && + StaticSaveDataId == other.StaticSaveDataId && + Rank == other.Rank && + Index == other.Index; } - [StructLayout(LayoutKind.Explicit, Size = 0x200)] - public struct SaveDataExtraData + public static bool operator ==(SaveDataAttribute left, SaveDataAttribute right) => left.Equals(right); + public static bool operator !=(SaveDataAttribute left, SaveDataAttribute right) => !(left == right); + + public override readonly int GetHashCode() { - [FieldOffset(0x00)] public SaveDataAttribute Attribute; - [FieldOffset(0x40)] public ulong OwnerId; - [FieldOffset(0x48)] public long TimeStamp; - [FieldOffset(0x50)] public SaveDataFlags Flags; - [FieldOffset(0x58)] public long DataSize; - [FieldOffset(0x60)] public long JournalSize; - [FieldOffset(0x68)] public long CommitId; + // ReSharper disable NonReadonlyMemberInGetHashCode + return HashCode.Combine(ProgramId, Type, UserId, StaticSaveDataId, Rank, Index); + // ReSharper restore NonReadonlyMemberInGetHashCode } - public struct CommitOption + public readonly int CompareTo(SaveDataAttribute other) { - public CommitOptionFlag Flags; - } - - internal static class SaveDataTypesValidity - { - public static bool IsValid(in SaveDataAttribute attribute) - { - return IsValid(in attribute.Type) && IsValid(in attribute.Rank); - } - - public static bool IsValid(in SaveDataCreationInfo creationInfo) - { - return creationInfo.Size >= 0 && creationInfo.JournalSize >= 0 && creationInfo.BlockSize >= 0 && - IsValid(in creationInfo.SpaceId); - } - - public static bool IsValid(in SaveDataMetaInfo metaInfo) - { - return IsValid(in metaInfo.Type); - } - - public static bool IsValid(in SaveDataFilter filter) - { - return IsValid(in filter.Attribute); - } - - public static bool IsValid(in SaveDataType type) - { - // SaveDataType.SystemBcat is excluded in this check - return (uint)type <= (uint)SaveDataType.Cache; - } - - public static bool IsValid(in SaveDataRank rank) - { - return (uint)rank <= (uint)SaveDataRank.Secondary; - } - - public static bool IsValid(in SaveDataSpaceId spaceId) - { - return (uint)spaceId <= (uint)SaveDataSpaceId.SdCache || spaceId == SaveDataSpaceId.ProperSystem || - spaceId == SaveDataSpaceId.SafeMode; - } - - public static bool IsValid(in SaveDataMetaType metaType) - { - return (uint)metaType <= (uint)SaveDataMetaType.ExtensionContext; - } + int titleIdComparison = ProgramId.CompareTo(other.ProgramId); + if (titleIdComparison != 0) return titleIdComparison; + int typeComparison = ((int)Type).CompareTo((int)other.Type); + if (typeComparison != 0) return typeComparison; + int userIdComparison = UserId.CompareTo(other.UserId); + if (userIdComparison != 0) return userIdComparison; + int saveDataIdComparison = StaticSaveDataId.CompareTo(other.StaticSaveDataId); + if (saveDataIdComparison != 0) return saveDataIdComparison; + int rankComparison = ((int)Rank).CompareTo((int)other.Rank); + if (rankComparison != 0) return rankComparison; + return Index.CompareTo(other.Index); + } +} + +[StructLayout(LayoutKind.Explicit, Size = 0x40)] +public struct SaveDataCreationInfo +{ + [FieldOffset(0x00)] public long Size; + [FieldOffset(0x08)] public long JournalSize; + [FieldOffset(0x10)] public long BlockSize; + [FieldOffset(0x18)] public ulong OwnerId; + [FieldOffset(0x20)] public SaveDataFlags Flags; + [FieldOffset(0x24)] public SaveDataSpaceId SpaceId; + [FieldOffset(0x25)] public bool Field25; + + public static Result Make(out SaveDataCreationInfo creationInfo, long size, long journalSize, ulong ownerId, + SaveDataFlags flags, SaveDataSpaceId spaceId) + { + UnsafeHelpers.SkipParamInit(out creationInfo); + SaveDataCreationInfo tempCreationInfo = default; + + tempCreationInfo.Size = size; + tempCreationInfo.JournalSize = journalSize; + tempCreationInfo.BlockSize = SaveDataProperties.DefaultSaveDataBlockSize; + tempCreationInfo.OwnerId = ownerId; + tempCreationInfo.Flags = flags; + tempCreationInfo.SpaceId = spaceId; + tempCreationInfo.Field25 = false; + + if (!SaveDataTypesValidity.IsValid(in tempCreationInfo)) + return ResultFs.InvalidArgument.Log(); + + creationInfo = tempCreationInfo; + return Result.Success; + } +} + +[StructLayout(LayoutKind.Explicit, Size = 0x48)] +public struct SaveDataFilter +{ + [FieldOffset(0x00)] public bool FilterByProgramId; + [FieldOffset(0x01)] public bool FilterBySaveDataType; + [FieldOffset(0x02)] public bool FilterByUserId; + [FieldOffset(0x03)] public bool FilterBySaveDataId; + [FieldOffset(0x04)] public bool FilterByIndex; + [FieldOffset(0x05)] public SaveDataRank Rank; + + [FieldOffset(0x08)] public SaveDataAttribute Attribute; + + public void SetProgramId(ProgramId value) + { + FilterByProgramId = true; + Attribute.ProgramId = value; + } + + public void SetSaveDataType(SaveDataType value) + { + FilterBySaveDataType = true; + Attribute.Type = value; + } + + public void SetUserId(UserId value) + { + FilterByUserId = true; + Attribute.UserId = value; + } + + public void SetSaveDataId(ulong value) + { + FilterBySaveDataId = true; + Attribute.StaticSaveDataId = value; + } + + public void SetIndex(ushort value) + { + FilterByIndex = true; + Attribute.Index = value; + } + + public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index) + { + return Make(out filter, programId, saveType, userId, saveDataId, index, SaveDataRank.Primary); + } + + public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) + { + UnsafeHelpers.SkipParamInit(out filter); + + SaveDataFilter tempFilter = Make(programId, saveType, userId, saveDataId, index, rank); + + if (!SaveDataTypesValidity.IsValid(in tempFilter)) + return ResultFs.InvalidArgument.Log(); + + filter = tempFilter; + return Result.Success; + } + + public static SaveDataFilter Make(Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) + { + var filter = new SaveDataFilter(); + + if (programId.HasValue) + { + filter.FilterByProgramId = true; + filter.Attribute.ProgramId = new ProgramId(programId.Value); + } + + if (saveType.HasValue) + { + filter.FilterBySaveDataType = true; + filter.Attribute.Type = saveType.Value; + } + + if (userId.HasValue) + { + filter.FilterByUserId = true; + filter.Attribute.UserId = userId.Value; + } + + if (saveDataId.HasValue) + { + filter.FilterBySaveDataId = true; + filter.Attribute.StaticSaveDataId = saveDataId.Value; + } + + if (index.HasValue) + { + filter.FilterByIndex = true; + filter.Attribute.Index = index.Value; + } + + filter.Rank = rank; + + return filter; + } +} + +[StructLayout(LayoutKind.Explicit, Size = HashLength)] +public struct HashSalt +{ + private const int HashLength = 0x20; + + [FieldOffset(0x00)] private byte _hashStart; + + public Span Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength); + public ReadOnlySpan HashRo => SpanHelpers.CreateReadOnlySpan(in _hashStart, HashLength); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x10)] +public struct SaveDataMetaInfo +{ + [FieldOffset(0)] public int Size; + [FieldOffset(4)] public SaveDataMetaType Type; +} + +[StructLayout(LayoutKind.Explicit, Size = 0x60)] +public struct SaveDataInfo +{ + [FieldOffset(0x00)] public ulong SaveDataId; + [FieldOffset(0x08)] public SaveDataSpaceId SpaceId; + [FieldOffset(0x09)] public SaveDataType Type; + [FieldOffset(0x10)] public UserId UserId; + [FieldOffset(0x20)] public ulong StaticSaveDataId; + [FieldOffset(0x28)] public ProgramId ProgramId; + [FieldOffset(0x30)] public long Size; + [FieldOffset(0x38)] public ushort Index; + [FieldOffset(0x3A)] public SaveDataRank Rank; + [FieldOffset(0x3B)] public SaveDataState State; +} + +[StructLayout(LayoutKind.Explicit, Size = 0x200)] +public struct SaveDataExtraData +{ + [FieldOffset(0x00)] public SaveDataAttribute Attribute; + [FieldOffset(0x40)] public ulong OwnerId; + [FieldOffset(0x48)] public long TimeStamp; + [FieldOffset(0x50)] public SaveDataFlags Flags; + [FieldOffset(0x58)] public long DataSize; + [FieldOffset(0x60)] public long JournalSize; + [FieldOffset(0x68)] public long CommitId; +} + +public struct CommitOption +{ + public CommitOptionFlag Flags; +} + +internal static class SaveDataTypesValidity +{ + public static bool IsValid(in SaveDataAttribute attribute) + { + return IsValid(in attribute.Type) && IsValid(in attribute.Rank); + } + + public static bool IsValid(in SaveDataCreationInfo creationInfo) + { + return creationInfo.Size >= 0 && creationInfo.JournalSize >= 0 && creationInfo.BlockSize >= 0 && + IsValid(in creationInfo.SpaceId); + } + + public static bool IsValid(in SaveDataMetaInfo metaInfo) + { + return IsValid(in metaInfo.Type); + } + + public static bool IsValid(in SaveDataFilter filter) + { + return IsValid(in filter.Attribute); + } + + public static bool IsValid(in SaveDataType type) + { + // SaveDataType.SystemBcat is excluded in this check + return (uint)type <= (uint)SaveDataType.Cache; + } + + public static bool IsValid(in SaveDataRank rank) + { + return (uint)rank <= (uint)SaveDataRank.Secondary; + } + + public static bool IsValid(in SaveDataSpaceId spaceId) + { + return (uint)spaceId <= (uint)SaveDataSpaceId.SdCache || spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.SafeMode; + } + + public static bool IsValid(in SaveDataMetaType metaType) + { + return (uint)metaType <= (uint)SaveDataMetaType.ExtensionContext; } } diff --git a/src/LibHac/Fs/SaveDataTransferTypes.cs b/src/LibHac/Fs/SaveDataTransferTypes.cs index e443d9b9..18871a89 100644 --- a/src/LibHac/Fs/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/SaveDataTransferTypes.cs @@ -2,32 +2,31 @@ using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Sequential, Size = 0x100)] +public struct RsaEncryptedKey { - [StructLayout(LayoutKind.Sequential, Size = 0x100)] - public struct RsaEncryptedKey + public byte this[int i] { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - + readonly get => BytesRo[i]; + set => Bytes[i] = value; } - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct AesKey - { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - } +} + +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct AesKey +{ + public byte this[int i] + { + readonly get => BytesRo[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); } diff --git a/src/LibHac/Fs/ScopedSetter.cs b/src/LibHac/Fs/ScopedSetter.cs index 68e04354..a6873872 100644 --- a/src/LibHac/Fs/ScopedSetter.cs +++ b/src/LibHac/Fs/ScopedSetter.cs @@ -1,29 +1,28 @@ using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +public ref struct ScopedSetter { - public ref struct ScopedSetter + private Ref _ref; + private T _value; + + public ScopedSetter(ref T reference, T value) { - private Ref _ref; - private T _value; + _ref = new Ref(ref reference); + _value = value; + } - public ScopedSetter(ref T reference, T value) - { - _ref = new Ref(ref reference); - _value = value; - } + public void Dispose() + { + if (!_ref.IsNull) + _ref.Value = _value; + } - public void Dispose() - { - if (!_ref.IsNull) - _ref.Value = _value; - } + public void Set(T value) => _value = value; - public void Set(T value) => _value = value; - - public static ScopedSetter MakeScopedSetter(ref T reference, T value) - { - return new ScopedSetter(ref reference, value); - } + public static ScopedSetter MakeScopedSetter(ref T reference, T value) + { + return new ScopedSetter(ref reference, value); } } diff --git a/src/LibHac/Fs/Shim/Application.cs b/src/LibHac/Fs/Shim/Application.cs index 400b402e..7f41e596 100644 --- a/src/LibHac/Fs/Shim/Application.cs +++ b/src/LibHac/Fs/Shim/Application.cs @@ -9,72 +9,71 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting application packages. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class Application { - /// - /// Contains functions for mounting application packages. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class Application + public static Result MountApplicationPackage(this FileSystemClient fs, U8Span mountName, U8Span path) { - public static Result MountApplicationPackage(this FileSystemClient fs, U8Span mountName, U8Span path) + Result rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - Result rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, path); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, path); - Tick end = fs.Hos.Os.GetSystemTick(); + Span logBuffer = stackalloc byte[0x300]; + var sb = new U8StringBuilder(logBuffer, true); - Span logBuffer = stackalloc byte[0x300]; - var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote); + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, path); + } - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, path); - } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - fs.Impl.AbortIfNeeded(rc); + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, U8Span path) + { + Result rc = fs.Impl.CheckMountName(mountName); if (rc.IsFailure()) return rc; - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenFileSystemWithId(ref fileSystem.Ref(), in sfPath, + Ncm.ProgramId.InvalidId.Value, FileSystemProxyType.Package); + if (rc.IsFailure()) return rc; + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInApplicationA.Log(); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); + if (rc.IsFailure()) return rc.Miss(); return Result.Success; - - static Result Mount(FileSystemClient fs, U8Span mountName, U8Span path) - { - Result rc = fs.Impl.CheckMountName(mountName); - if (rc.IsFailure()) return rc; - - rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenFileSystemWithId(ref fileSystem.Ref(), in sfPath, - Ncm.ProgramId.InvalidId.Value, FileSystemProxyType.Package); - if (rc.IsFailure()) return rc; - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInApplicationA.Log(); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } } } } diff --git a/src/LibHac/Fs/Shim/BcatSaveData.cs b/src/LibHac/Fs/Shim/BcatSaveData.cs index fb457e71..70cf4a6a 100644 --- a/src/LibHac/Fs/Shim/BcatSaveData.cs +++ b/src/LibHac/Fs/Shim/BcatSaveData.cs @@ -9,74 +9,73 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting BCAT save data. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class BcatSaveData { - /// - /// Contains functions for mounting BCAT save data. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class BcatSaveData + public static Result MountBcatSaveData(this FileSystemClient fs, U8Span mountName, + Ncm.ApplicationId applicationId) { - public static Result MountBcatSaveData(this FileSystemClient fs, U8Span mountName, - Ncm.ApplicationId applicationId) + Result rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - Result rc; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, applicationId); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, applicationId); - Tick end = fs.Hos.Os.GetSystemTick(); + Span logBuffer = stackalloc byte[0x50]; + var sb = new U8StringBuilder(logBuffer, true); - Span logBuffer = stackalloc byte[0x50]; - var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, applicationId); + } - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, applicationId); - } + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - fs.Impl.AbortIfNeeded(rc); + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId) + { + Result rc = fs.Impl.CheckMountName(mountName); if (rc.IsFailure()) return rc; - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat, + Fs.SaveData.InvalidUserId, 0); + if (rc.IsFailure()) return rc; + + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), SaveDataSpaceId.User, in attribute); + if (rc.IsFailure()) return rc; + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInBcatSaveDataA.Log(); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); + if (rc.IsFailure()) return rc.Miss(); return Result.Success; - - static Result Mount(FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId) - { - Result rc = fs.Impl.CheckMountName(mountName); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat, - Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; - - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), SaveDataSpaceId.User, in attribute); - if (rc.IsFailure()) return rc; - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInBcatSaveDataA.Log(); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } } } } diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index b5a5fe8f..43fff17a 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -14,185 +14,184 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting built-in-storage partition file systems +/// and opening the raw partitions as s. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class Bis { - /// - /// Contains functions for mounting built-in-storage partition file systems - /// and opening the raw partitions as s. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class Bis + private class BisCommonMountNameGenerator : ICommonMountNameGenerator { - private class BisCommonMountNameGenerator : ICommonMountNameGenerator + private BisPartitionId _partitionId; + + public BisCommonMountNameGenerator(BisPartitionId partitionId) { - private BisPartitionId _partitionId; - - public BisCommonMountNameGenerator(BisPartitionId partitionId) - { - _partitionId = partitionId; - } - - public void Dispose() { } - - public Result GenerateCommonMountName(Span nameBuffer) - { - ReadOnlySpan mountName = GetBisMountName(_partitionId); - - // Add 2 for the mount name separator and null terminator - int requiredNameBufferSize = StringUtils.GetLength(mountName, PathTools.MountNameLengthMax) + 2; - - Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - - var sb = new U8StringBuilder(nameBuffer); - sb.Append(mountName).Append(StringTraits.DriveSeparator); - - Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - - return Result.Success; - } + _partitionId = partitionId; } - private static Result MountBis(this FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId, - U8Span rootPath) + public void Dispose() { } + + public Result GenerateCommonMountName(Span nameBuffer) { - Result rc; + ReadOnlySpan mountName = GetBisMountName(_partitionId); - if (fs.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, partitionId); - Tick end = fs.Hos.Os.GetSystemTick(); + // Add 2 for the mount name separator and null terminator + int requiredNameBufferSize = StringUtils.GetLength(mountName, PathTools.MountNameLengthMax) + 2; - Span logBuffer = stackalloc byte[0x300]; - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); + Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogBisPartitionId).Append(idString.ToString(partitionId)) - .Append(LogPath).Append(rootPath).Append(LogQuote); + var sb = new U8StringBuilder(nameBuffer); + sb.Append(mountName).Append(StringTraits.DriveSeparator); - fs.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, partitionId); - } - - fs.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.IsEnabledAccessLog(AccessLogTarget.System)) - fs.EnableFileSystemAccessorAccessLog(mountName); + Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); return Result.Success; + } + } - static Result Mount(FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId) - { - Result rc = fs.CheckMountNameAcceptingReservedMountName(mountName); - if (rc.IsFailure()) return rc.Miss(); + private static Result MountBis(this FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId, + U8Span rootPath) + { + Result rc; - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + if (fs.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, partitionId); + Tick end = fs.Hos.Os.GetSystemTick(); - // Nintendo doesn't use the provided rootPath - FspPath.CreateEmpty(out FspPath sfPath); + Span logBuffer = stackalloc byte[0x300]; + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); - using var fileSystem = new SharedRef(); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogBisPartitionId).Append(idString.ToString(partitionId)) + .Append(LogPath).Append(rootPath).Append(LogQuote); - rc = fileSystemProxy.Get.OpenBisFileSystem(ref fileSystem.Ref(), in sfPath, partitionId); - if (rc.IsFailure()) return rc.Miss(); - - using var mountNameGenerator = - new UniqueRef(new BisCommonMountNameGenerator(partitionId)); - - if (!mountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInBisA.Log(); - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInBisB.Log(); - - rc = fs.Fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } + fs.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, partitionId); } - public static Result MountBis(this FileSystemClient fs, U8Span mountName, BisPartitionId partitionId) + fs.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.IsEnabledAccessLog(AccessLogTarget.System)) + fs.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId) { - return MountBis(fs.Impl, mountName, partitionId, default); - } + Result rc = fs.CheckMountNameAcceptingReservedMountName(mountName); + if (rc.IsFailure()) return rc.Miss(); - public static Result MountBis(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath) - { - return MountBis(fs.Impl, new U8Span(GetBisMountName(partitionId)), partitionId, rootPath); - } + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - public static ReadOnlySpan GetBisMountName(BisPartitionId partitionId) - { - switch (partitionId) - { - case BisPartitionId.BootPartition1Root: - case BisPartitionId.BootPartition2Root: - case BisPartitionId.UserDataRoot: - case BisPartitionId.BootConfigAndPackage2Part1: - case BisPartitionId.BootConfigAndPackage2Part2: - case BisPartitionId.BootConfigAndPackage2Part3: - case BisPartitionId.BootConfigAndPackage2Part4: - case BisPartitionId.BootConfigAndPackage2Part5: - case BisPartitionId.BootConfigAndPackage2Part6: - case BisPartitionId.CalibrationBinary: - Abort.DoAbort("The partition specified is not mountable."); - break; + // Nintendo doesn't use the provided rootPath + FspPath.CreateEmpty(out FspPath sfPath); - case BisPartitionId.CalibrationFile: - return BisCalibrationFilePartitionMountName; - case BisPartitionId.SafeMode: - return BisSafeModePartitionMountName; - case BisPartitionId.User: - return BisUserPartitionMountName; - case BisPartitionId.System: - return BisSystemPartitionMountName; + using var fileSystem = new SharedRef(); - default: - Abort.UnexpectedDefault(); - break; - } + rc = fileSystemProxy.Get.OpenBisFileSystem(ref fileSystem.Ref(), in sfPath, partitionId); + if (rc.IsFailure()) return rc.Miss(); - return ReadOnlySpan.Empty; - } + using var mountNameGenerator = + new UniqueRef(new BisCommonMountNameGenerator(partitionId)); - public static Result OpenBisPartition(this FileSystemClient fs, ref UniqueRef outPartitionStorage, - BisPartitionId partitionId) - { - using var storage = new SharedRef(); - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + if (!mountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInBisA.Log(); - Result rc = fileSystemProxy.Get.OpenBisStorage(ref storage.Ref(), partitionId); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - using var storageAdapter = new UniqueRef(new StorageServiceObjectAdapter(ref storage.Ref())); + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInBisB.Log(); - if (!storageAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInBisC.Log(); - - outPartitionStorage.Set(ref storageAdapter.Ref()); - return Result.Success; - } - - public static Result InvalidateBisCache(this FileSystemClient fs) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.InvalidateBisCache(); - fs.Impl.AbortIfNeeded(rc); + rc = fs.Fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); if (rc.IsFailure()) return rc.Miss(); return Result.Success; } } + + public static Result MountBis(this FileSystemClient fs, U8Span mountName, BisPartitionId partitionId) + { + return MountBis(fs.Impl, mountName, partitionId, default); + } + + public static Result MountBis(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath) + { + return MountBis(fs.Impl, new U8Span(GetBisMountName(partitionId)), partitionId, rootPath); + } + + public static ReadOnlySpan GetBisMountName(BisPartitionId partitionId) + { + switch (partitionId) + { + case BisPartitionId.BootPartition1Root: + case BisPartitionId.BootPartition2Root: + case BisPartitionId.UserDataRoot: + case BisPartitionId.BootConfigAndPackage2Part1: + case BisPartitionId.BootConfigAndPackage2Part2: + case BisPartitionId.BootConfigAndPackage2Part3: + case BisPartitionId.BootConfigAndPackage2Part4: + case BisPartitionId.BootConfigAndPackage2Part5: + case BisPartitionId.BootConfigAndPackage2Part6: + case BisPartitionId.CalibrationBinary: + Abort.DoAbort("The partition specified is not mountable."); + break; + + case BisPartitionId.CalibrationFile: + return BisCalibrationFilePartitionMountName; + case BisPartitionId.SafeMode: + return BisSafeModePartitionMountName; + case BisPartitionId.User: + return BisUserPartitionMountName; + case BisPartitionId.System: + return BisSystemPartitionMountName; + + default: + Abort.UnexpectedDefault(); + break; + } + + return ReadOnlySpan.Empty; + } + + public static Result OpenBisPartition(this FileSystemClient fs, ref UniqueRef outPartitionStorage, + BisPartitionId partitionId) + { + using var storage = new SharedRef(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.OpenBisStorage(ref storage.Ref(), partitionId); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var storageAdapter = new UniqueRef(new StorageServiceObjectAdapter(ref storage.Ref())); + + if (!storageAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInBisC.Log(); + + outPartitionStorage.Set(ref storageAdapter.Ref()); + return Result.Success; + } + + public static Result InvalidateBisCache(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.InvalidateBisCache(); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } } diff --git a/src/LibHac/Fs/Shim/Code.cs b/src/LibHac/Fs/Shim/Code.cs index 014d9252..12b34d63 100644 --- a/src/LibHac/Fs/Shim/Code.cs +++ b/src/LibHac/Fs/Shim/Code.cs @@ -10,83 +10,82 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting code file systems. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class Code { - /// - /// Contains functions for mounting code file systems. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class Code + public static Result MountCode(this FileSystemClient fs, out CodeVerificationData verificationData, + U8Span mountName, U8Span path, ProgramId programId) { - public static Result MountCode(this FileSystemClient fs, out CodeVerificationData verificationData, + Result rc; + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, out verificationData, mountName, path, programId); + Tick end = fs.Hos.Os.GetSystemTick(); + + Span logBuffer = stackalloc byte[0x300]; + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote) + .Append(LogProgramId).AppendFormat(programId.Value, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, out verificationData, mountName, path, programId); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, out CodeVerificationData verificationData, U8Span mountName, U8Span path, ProgramId programId) { - Result rc; - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, out verificationData, mountName, path, programId); - Tick end = fs.Hos.Os.GetSystemTick(); + UnsafeHelpers.SkipParamInit(out verificationData); - Span logBuffer = stackalloc byte[0x300]; - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote) - .Append(LogProgramId).AppendFormat(programId.Value, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, out verificationData, mountName, path, programId); - } - - fs.Impl.AbortIfNeeded(rc); + Result rc = fs.Impl.CheckMountName(mountName); if (rc.IsFailure()) return rc; - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + rc = PathUtility.ConvertToFspPath(out FspPath fsPath, path); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = + fs.Impl.GetFileSystemProxyForLoaderServiceObject(); + + // SetCurrentProcess + + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenCodeFileSystem(ref fileSystem.Ref(), out verificationData, in fsPath, + programId); + if (rc.IsFailure()) return rc; + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInCodeA.Log(); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); + if (rc.IsFailure()) return rc.Miss(); return Result.Success; - - static Result Mount(FileSystemClient fs, out CodeVerificationData verificationData, - U8Span mountName, U8Span path, ProgramId programId) - { - UnsafeHelpers.SkipParamInit(out verificationData); - - Result rc = fs.Impl.CheckMountName(mountName); - if (rc.IsFailure()) return rc; - - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - rc = PathUtility.ConvertToFspPath(out FspPath fsPath, path); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = - fs.Impl.GetFileSystemProxyForLoaderServiceObject(); - - // SetCurrentProcess - - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenCodeFileSystem(ref fileSystem.Ref(), out verificationData, in fsPath, - programId); - if (rc.IsFailure()) return rc; - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInCodeA.Log(); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } } } } diff --git a/src/LibHac/Fs/Shim/Content.cs b/src/LibHac/Fs/Shim/Content.cs index b1926854..5f1c3643 100644 --- a/src/LibHac/Fs/Shim/Content.cs +++ b/src/LibHac/Fs/Shim/Content.cs @@ -11,47 +11,249 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting content file systems. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class Content { - /// - /// Contains functions for mounting content file systems. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class Content + private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type) { - private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type) + switch (type) { - switch (type) - { - case ContentType.Meta: return FileSystemProxyType.Meta; - case ContentType.Control: return FileSystemProxyType.Control; - case ContentType.Manual: return FileSystemProxyType.Manual; - case ContentType.Data: return FileSystemProxyType.Data; - default: - Abort.UnexpectedDefault(); - return default; - } + case ContentType.Meta: return FileSystemProxyType.Meta; + case ContentType.Control: return FileSystemProxyType.Control; + case ContentType.Manual: return FileSystemProxyType.Manual; + case ContentType.Data: return FileSystemProxyType.Data; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id, + ContentType contentType) + { + Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); + if (rc.IsFailure()) return rc; + + FileSystemProxyType fsType = ConvertToFileSystemProxyType(contentType); + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + rc = PathUtility.ConvertToFspPath(out FspPath fsPath, path); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenFileSystemWithId(ref fileSystem.Ref(), in fsPath, id, fsType); + if (rc.IsFailure()) return rc; + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInContentA.Log(); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, + ContentType contentType) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PreMount(contentType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote) + .Append(LogContentType).Append(idString.ToString(contentType)); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = PreMount(contentType); } - private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id, - ContentType contentType) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + const ulong programId = 0; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountContentImpl(fs, mountName, path, programId, contentType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote) + .Append(LogProgramId).AppendFormat(programId, 'X') + .Append(LogContentType).Append(idString.ToString(contentType)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountContentImpl(fs, mountName, path, programId, contentType); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result PreMount(ContentType contentType) + { + if (contentType == ContentType.Meta) + return ResultFs.InvalidArgument.Log(); + + return Result.Success; + } + } + + public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId, + ContentType contentType) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountContentImpl(fs, mountName, path, programId.Value, contentType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote) + .Append(LogProgramId).AppendFormat(programId.Value, 'X') + .Append(LogContentType).Append(idString.ToString(contentType)); + + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = MountContentImpl(fs, mountName, path, programId.Value, contentType); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + } + + public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, DataId dataId, + ContentType contentType) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogPath).Append(path).Append(LogQuote) + .Append(LogDataId).AppendFormat(dataId.Value, 'X') + .Append(LogContentType).Append(idString.ToString(contentType)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + } + + public static Result MountContent(this FileSystemClient fs, U8Span mountName, ProgramId programId, + ContentType contentType) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, programId, contentType); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogProgramId).AppendFormat(programId.Value, 'X') + .Append(LogContentType).Append(idString.ToString(contentType)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, programId, contentType); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, ProgramId programId, ContentType contentType) { Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); if (rc.IsFailure()) return rc; FileSystemProxyType fsType = ConvertToFileSystemProxyType(contentType); - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - rc = PathUtility.ConvertToFspPath(out FspPath fsPath, path); - if (rc.IsFailure()) return rc; - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); using var fileSystem = new SharedRef(); - rc = fileSystemProxy.Get.OpenFileSystemWithId(ref fileSystem.Ref(), in fsPath, id, fsType); + rc = fileSystemProxy.Get.OpenFileSystemWithPatch(ref fileSystem.Ref(), programId, fsType); if (rc.IsFailure()) return rc; using var fileSystemAdapter = @@ -65,208 +267,5 @@ namespace LibHac.Fs.Shim return Result.Success; } - - public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, - ContentType contentType) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PreMount(contentType); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote) - .Append(LogContentType).Append(idString.ToString(contentType)); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = PreMount(contentType); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - const ulong programId = 0; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountContentImpl(fs, mountName, path, programId, contentType); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote) - .Append(LogProgramId).AppendFormat(programId, 'X') - .Append(LogContentType).Append(idString.ToString(contentType)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountContentImpl(fs, mountName, path, programId, contentType); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - - static Result PreMount(ContentType contentType) - { - if (contentType == ContentType.Meta) - return ResultFs.InvalidArgument.Log(); - - return Result.Success; - } - } - - public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId, - ContentType contentType) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountContentImpl(fs, mountName, path, programId.Value, contentType); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote) - .Append(LogProgramId).AppendFormat(programId.Value, 'X') - .Append(LogContentType).Append(idString.ToString(contentType)); - - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = MountContentImpl(fs, mountName, path, programId.Value, contentType); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - } - - public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, DataId dataId, - ContentType contentType) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogPath).Append(path).Append(LogQuote) - .Append(LogDataId).AppendFormat(dataId.Value, 'X') - .Append(LogContentType).Append(idString.ToString(contentType)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - } - - public static Result MountContent(this FileSystemClient fs, U8Span mountName, ProgramId programId, - ContentType contentType) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, programId, contentType); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogProgramId).AppendFormat(programId.Value, 'X') - .Append(LogContentType).Append(idString.ToString(contentType)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, programId, contentType); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - - static Result Mount(FileSystemClient fs, U8Span mountName, ProgramId programId, ContentType contentType) - { - Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); - if (rc.IsFailure()) return rc; - - FileSystemProxyType fsType = ConvertToFileSystemProxyType(contentType); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenFileSystemWithPatch(ref fileSystem.Ref(), programId, fsType); - if (rc.IsFailure()) return rc; - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInContentA.Log(); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - } } } diff --git a/src/LibHac/Fs/Shim/ContentStorage.cs b/src/LibHac/Fs/Shim/ContentStorage.cs index 2aaac004..ab9fc358 100644 --- a/src/LibHac/Fs/Shim/ContentStorage.cs +++ b/src/LibHac/Fs/Shim/ContentStorage.cs @@ -11,143 +11,142 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting the directories where content is stored. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class ContentStorage { - /// - /// Contains functions for mounting the directories where content is stored. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class ContentStorage + private class ContentStorageCommonMountNameGenerator : ICommonMountNameGenerator { - private class ContentStorageCommonMountNameGenerator : ICommonMountNameGenerator + private ContentStorageId _storageId; + + public ContentStorageCommonMountNameGenerator(ContentStorageId storageId) { - private ContentStorageId _storageId; - - public ContentStorageCommonMountNameGenerator(ContentStorageId storageId) - { - _storageId = storageId; - } - - public void Dispose() { } - - public Result GenerateCommonMountName(Span nameBuffer) - { - ReadOnlySpan mountName = GetContentStorageMountName(_storageId); - - // Add 2 for the mount name separator and null terminator - int requiredNameBufferSize = StringUtils.GetLength(mountName, PathTool.MountNameLengthMax) + 2; - - Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - - var sb = new U8StringBuilder(nameBuffer); - sb.Append(mountName).Append(StringTraits.DriveSeparator); - - Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - - return Result.Success; - } + _storageId = storageId; } - public static Result MountContentStorage(this FileSystemClient fs, ContentStorageId storageId) + public void Dispose() { } + + public Result GenerateCommonMountName(Span nameBuffer) { - return MountContentStorage(fs, new U8Span(GetContentStorageMountName(storageId)), storageId); - } + ReadOnlySpan mountName = GetContentStorageMountName(_storageId); - public static Result MountContentStorage(this FileSystemClient fs, U8Span mountName, ContentStorageId storageId) - { - Result rc; - Span logBuffer = stackalloc byte[0x40]; + // Add 2 for the mount name separator and null terminator + int requiredNameBufferSize = StringUtils.GetLength(mountName, PathTool.MountNameLengthMax) + 2; - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, storageId); - Tick end = fs.Hos.Os.GetSystemTick(); + Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); + var sb = new U8StringBuilder(nameBuffer); + sb.Append(mountName).Append(StringTraits.DriveSeparator); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogContentStorageId).Append(idString.ToString(storageId)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, storageId); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); return Result.Success; + } + } - static Result Mount(FileSystemClient fs, U8Span mountName, ContentStorageId storageId) - { - // It can take some time for the system partition to be ready (if it's on the SD card). - // Thus, we will retry up to 10 times, waiting one second each time. - const int maxRetries = 10; - const int retryInterval = 1000; + public static Result MountContentStorage(this FileSystemClient fs, ContentStorageId storageId) + { + return MountContentStorage(fs, new U8Span(GetContentStorageMountName(storageId)), storageId); + } - Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); - if (rc.IsFailure()) return rc; + public static Result MountContentStorage(this FileSystemClient fs, U8Span mountName, ContentStorageId storageId) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, storageId); + Tick end = fs.Hos.Os.GetSystemTick(); - for (int i = 0; i < maxRetries; i++) - { - rc = fileSystemProxy.Get.OpenContentStorageFileSystem(ref fileSystem.Ref(), storageId); + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); - if (rc.IsSuccess()) - break; + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogContentStorageId).Append(idString.ToString(storageId)); - if (!ResultFs.SystemPartitionNotReady.Includes(rc)) - return rc; - - if (i == maxRetries - 1) - return rc; - - fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); - } - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInContentStorageA.Log(); - - using var mountNameGenerator = - new UniqueRef(new ContentStorageCommonMountNameGenerator(storageId)); - - if (!mountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInContentStorageB.Log(); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, storageId); } - public static ReadOnlySpan GetContentStorageMountName(ContentStorageId storageId) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, ContentStorageId storageId) { - switch (storageId) + // It can take some time for the system partition to be ready (if it's on the SD card). + // Thus, we will retry up to 10 times, waiting one second each time. + const int maxRetries = 10; + const int retryInterval = 1000; + + Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); + + for (int i = 0; i < maxRetries; i++) { - case ContentStorageId.System: - return CommonMountNames.ContentStorageSystemMountName; - case ContentStorageId.User: - return CommonMountNames.ContentStorageUserMountName; - case ContentStorageId.SdCard: - return CommonMountNames.ContentStorageSdCardMountName; - default: - Abort.UnexpectedDefault(); - return default; + rc = fileSystemProxy.Get.OpenContentStorageFileSystem(ref fileSystem.Ref(), storageId); + + if (rc.IsSuccess()) + break; + + if (!ResultFs.SystemPartitionNotReady.Includes(rc)) + return rc; + + if (i == maxRetries - 1) + return rc; + + fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); } + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInContentStorageA.Log(); + + using var mountNameGenerator = + new UniqueRef(new ContentStorageCommonMountNameGenerator(storageId)); + + if (!mountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInContentStorageB.Log(); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + public static ReadOnlySpan GetContentStorageMountName(ContentStorageId storageId) + { + switch (storageId) + { + case ContentStorageId.System: + return CommonMountNames.ContentStorageSystemMountName; + case ContentStorageId.User: + return CommonMountNames.ContentStorageUserMountName; + case ContentStorageId.SdCard: + return CommonMountNames.ContentStorageSdCardMountName; + default: + Abort.UnexpectedDefault(); + return default; } } } diff --git a/src/LibHac/Fs/Shim/CustomStorage.cs b/src/LibHac/Fs/Shim/CustomStorage.cs index 39c777a7..4ddd5f97 100644 --- a/src/LibHac/Fs/Shim/CustomStorage.cs +++ b/src/LibHac/Fs/Shim/CustomStorage.cs @@ -8,63 +8,62 @@ using LibHac.FsSrv.Sf; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting custom storage file systems. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +[SkipLocalsInit] +public static class CustomStorage { - /// - /// Contains functions for mounting custom storage file systems. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - [SkipLocalsInit] - public static class CustomStorage + public static ReadOnlySpan GetCustomStorageDirectoryName(CustomStorageId storageId) { - public static ReadOnlySpan GetCustomStorageDirectoryName(CustomStorageId storageId) + switch (storageId) { - switch (storageId) - { - case CustomStorageId.System: - case CustomStorageId.SdCard: - return CustomStorageDirectoryName; - default: - Abort.UnexpectedDefault(); - return default; - } + case CustomStorageId.System: + case CustomStorageId.SdCard: + return CustomStorageDirectoryName; + default: + Abort.UnexpectedDefault(); + return default; } + } - public static Result MountCustomStorage(this FileSystemClient fs, U8Span mountName, CustomStorageId storageId) + public static Result MountCustomStorage(this FileSystemClient fs, U8Span mountName, CustomStorageId storageId) + { + Result rc = Mount(fs, mountName, storageId); + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, CustomStorageId storageId) { - Result rc = Mount(fs, mountName, storageId); + Result rc = fs.Impl.CheckMountName(mountName); + if (rc.IsFailure()) return rc.Miss(); - fs.Impl.AbortIfNeeded(rc); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenCustomStorageFileSystem(ref fileSystem.Ref(), storageId); + if (rc.IsFailure()) return rc.Miss(); + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); if (rc.IsFailure()) return rc.Miss(); return Result.Success; - - static Result Mount(FileSystemClient fs, U8Span mountName, CustomStorageId storageId) - { - Result rc = fs.Impl.CheckMountName(mountName); - if (rc.IsFailure()) return rc.Miss(); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenCustomStorageFileSystem(ref fileSystem.Ref(), storageId); - if (rc.IsFailure()) return rc.Miss(); - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - rc = fs.Register(mountName, ref fileSystemAdapter.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } } - - private static ReadOnlySpan CustomStorageDirectoryName => // "CustomStorage0" - new[] - { - (byte)'C', (byte)'u', (byte)'s', (byte)'t', (byte)'o', (byte)'m', (byte)'S', (byte)'t', - (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', (byte)'0' - }; } + + private static ReadOnlySpan CustomStorageDirectoryName => // "CustomStorage0" + new[] + { + (byte)'C', (byte)'u', (byte)'s', (byte)'t', (byte)'o', (byte)'m', (byte)'S', (byte)'t', + (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', (byte)'0' + }; } diff --git a/src/LibHac/Fs/Shim/FileSystemProxyServiceObject.cs b/src/LibHac/Fs/Shim/FileSystemProxyServiceObject.cs index 59a8c14a..6f8c019b 100644 --- a/src/LibHac/Fs/Shim/FileSystemProxyServiceObject.cs +++ b/src/LibHac/Fs/Shim/FileSystemProxyServiceObject.cs @@ -2,147 +2,146 @@ using LibHac.Common; using LibHac.FsSrv.Sf; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +[NonCopyable] +internal struct FileSystemProxyServiceObjectGlobals : IDisposable { - [NonCopyable] - internal struct FileSystemProxyServiceObjectGlobals : IDisposable + public nint FileSystemProxyServiceObjectInitGuard; + public SharedRef FileSystemProxyServiceObject; + + public nint FileSystemProxyForLoaderServiceObjectInitGuard; + public SharedRef FileSystemProxyForLoaderServiceObject; + + public nint ProgramRegistryServiceObjectInitGuard; + public SharedRef ProgramRegistryServiceObject; + + public SharedRef DfcFileSystemProxyServiceObject; + + public void Dispose() { - public nint FileSystemProxyServiceObjectInitGuard; - public SharedRef FileSystemProxyServiceObject; + FileSystemProxyServiceObject.Destroy(); + FileSystemProxyServiceObjectInitGuard = default; - public nint FileSystemProxyForLoaderServiceObjectInitGuard; - public SharedRef FileSystemProxyForLoaderServiceObject; + FileSystemProxyForLoaderServiceObject.Destroy(); + FileSystemProxyForLoaderServiceObjectInitGuard = default; - public nint ProgramRegistryServiceObjectInitGuard; - public SharedRef ProgramRegistryServiceObject; + ProgramRegistryServiceObject.Destroy(); + ProgramRegistryServiceObjectInitGuard = default; - public SharedRef DfcFileSystemProxyServiceObject; - - public void Dispose() - { - FileSystemProxyServiceObject.Destroy(); - FileSystemProxyServiceObjectInitGuard = default; - - FileSystemProxyForLoaderServiceObject.Destroy(); - FileSystemProxyForLoaderServiceObjectInitGuard = default; - - ProgramRegistryServiceObject.Destroy(); - ProgramRegistryServiceObjectInitGuard = default; - - DfcFileSystemProxyServiceObject.Destroy(); - } - } - - public static class FileSystemProxyServiceObject - { - public static SharedRef GetFileSystemProxyServiceObject(this FileSystemClientImpl fs) - { - ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; - using var guard = new InitializationGuard(ref g.FileSystemProxyServiceObjectInitGuard, - fs.Globals.InitMutex); - - if (!guard.IsInitialized) - { - using SharedRef createdObject = GetFileSystemProxyServiceObjectImpl(fs); - g.FileSystemProxyServiceObject.SetByMove(ref createdObject.Ref()); - } - - return SharedRef.CreateCopy(in g.FileSystemProxyServiceObject); - } - - private static SharedRef GetFileSystemProxyServiceObjectImpl(FileSystemClientImpl fs) - { - ref SharedRef dfcServiceObject = - ref fs.Globals.FileSystemProxyServiceObject.DfcFileSystemProxyServiceObject; - - if (dfcServiceObject.HasValue) - { - return SharedRef.CreateCopy(in dfcServiceObject); - } - - using var fileSystemProxy = new SharedRef(); - Result rc = fs.Hos.Sm.GetService(ref fileSystemProxy.Ref(), "fsp-srv"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to get file system proxy service object."); - } - - fileSystemProxy.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); - return SharedRef.CreateMove(ref fileSystemProxy.Ref()); - } - - public static SharedRef GetFileSystemProxyForLoaderServiceObject( - this FileSystemClientImpl fs) - { - ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; - using var guard = new InitializationGuard(ref g.FileSystemProxyForLoaderServiceObjectInitGuard, - fs.Globals.InitMutex); - - if (!guard.IsInitialized) - { - using SharedRef createdObject = GetFileSystemProxyForLoaderServiceObjectImpl(fs); - g.FileSystemProxyForLoaderServiceObject.SetByMove(ref createdObject.Ref()); - } - - return SharedRef.CreateCopy(in g.FileSystemProxyForLoaderServiceObject); - } - - private static SharedRef GetFileSystemProxyForLoaderServiceObjectImpl( - FileSystemClientImpl fs) - { - using var fileSystemProxy = new SharedRef(); - Result rc = fs.Hos.Sm.GetService(ref fileSystemProxy.Ref(), "fsp-ldr"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to get file system proxy service object."); - } - - fileSystemProxy.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); - return SharedRef.CreateMove(ref fileSystemProxy.Ref()); - } - - public static SharedRef GetProgramRegistryServiceObject(this FileSystemClientImpl fs) - { - ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; - using var guard = new InitializationGuard(ref g.ProgramRegistryServiceObjectInitGuard, - fs.Globals.InitMutex); - - if (!guard.IsInitialized) - { - using SharedRef createdObject = GetProgramRegistryServiceObjectImpl(fs); - g.ProgramRegistryServiceObject.SetByMove(ref createdObject.Ref()); - } - - return SharedRef.CreateCopy(in g.ProgramRegistryServiceObject); - } - - private static SharedRef GetProgramRegistryServiceObjectImpl(FileSystemClientImpl fs) - { - using var registry = new SharedRef(); - Result rc = fs.Hos.Sm.GetService(ref registry.Ref(), "fsp-pr"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to get registry service object."); - } - - registry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); - return SharedRef.CreateMove(ref registry.Ref()); - } - - /// - /// Sets an service object to use for direct function calls - /// instead of going over IPC. If using a DFC service object, this function should be - /// called before calling . - /// - /// The to use. - /// The service object this will use. - public static void InitializeDfcFileSystemProxyServiceObject(this FileSystemClientImpl fs, - ref SharedRef serviceObject) - { - fs.Globals.FileSystemProxyServiceObject.DfcFileSystemProxyServiceObject.SetByMove(ref serviceObject); - } + DfcFileSystemProxyServiceObject.Destroy(); + } +} + +public static class FileSystemProxyServiceObject +{ + public static SharedRef GetFileSystemProxyServiceObject(this FileSystemClientImpl fs) + { + ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; + using var guard = new InitializationGuard(ref g.FileSystemProxyServiceObjectInitGuard, + fs.Globals.InitMutex); + + if (!guard.IsInitialized) + { + using SharedRef createdObject = GetFileSystemProxyServiceObjectImpl(fs); + g.FileSystemProxyServiceObject.SetByMove(ref createdObject.Ref()); + } + + return SharedRef.CreateCopy(in g.FileSystemProxyServiceObject); + } + + private static SharedRef GetFileSystemProxyServiceObjectImpl(FileSystemClientImpl fs) + { + ref SharedRef dfcServiceObject = + ref fs.Globals.FileSystemProxyServiceObject.DfcFileSystemProxyServiceObject; + + if (dfcServiceObject.HasValue) + { + return SharedRef.CreateCopy(in dfcServiceObject); + } + + using var fileSystemProxy = new SharedRef(); + Result rc = fs.Hos.Sm.GetService(ref fileSystemProxy.Ref(), "fsp-srv"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to get file system proxy service object."); + } + + fileSystemProxy.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); + return SharedRef.CreateMove(ref fileSystemProxy.Ref()); + } + + public static SharedRef GetFileSystemProxyForLoaderServiceObject( + this FileSystemClientImpl fs) + { + ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; + using var guard = new InitializationGuard(ref g.FileSystemProxyForLoaderServiceObjectInitGuard, + fs.Globals.InitMutex); + + if (!guard.IsInitialized) + { + using SharedRef createdObject = GetFileSystemProxyForLoaderServiceObjectImpl(fs); + g.FileSystemProxyForLoaderServiceObject.SetByMove(ref createdObject.Ref()); + } + + return SharedRef.CreateCopy(in g.FileSystemProxyForLoaderServiceObject); + } + + private static SharedRef GetFileSystemProxyForLoaderServiceObjectImpl( + FileSystemClientImpl fs) + { + using var fileSystemProxy = new SharedRef(); + Result rc = fs.Hos.Sm.GetService(ref fileSystemProxy.Ref(), "fsp-ldr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to get file system proxy service object."); + } + + fileSystemProxy.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); + return SharedRef.CreateMove(ref fileSystemProxy.Ref()); + } + + public static SharedRef GetProgramRegistryServiceObject(this FileSystemClientImpl fs) + { + ref FileSystemProxyServiceObjectGlobals g = ref fs.Globals.FileSystemProxyServiceObject; + using var guard = new InitializationGuard(ref g.ProgramRegistryServiceObjectInitGuard, + fs.Globals.InitMutex); + + if (!guard.IsInitialized) + { + using SharedRef createdObject = GetProgramRegistryServiceObjectImpl(fs); + g.ProgramRegistryServiceObject.SetByMove(ref createdObject.Ref()); + } + + return SharedRef.CreateCopy(in g.ProgramRegistryServiceObject); + } + + private static SharedRef GetProgramRegistryServiceObjectImpl(FileSystemClientImpl fs) + { + using var registry = new SharedRef(); + Result rc = fs.Hos.Sm.GetService(ref registry.Ref(), "fsp-pr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to get registry service object."); + } + + registry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); + return SharedRef.CreateMove(ref registry.Ref()); + } + + /// + /// Sets an service object to use for direct function calls + /// instead of going over IPC. If using a DFC service object, this function should be + /// called before calling . + /// + /// The to use. + /// The service object this will use. + public static void InitializeDfcFileSystemProxyServiceObject(this FileSystemClientImpl fs, + ref SharedRef serviceObject) + { + fs.Globals.FileSystemProxyServiceObject.DfcFileSystemProxyServiceObject.SetByMove(ref serviceObject); } } diff --git a/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs index cf69d200..f343795e 100644 --- a/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs +++ b/src/LibHac/Fs/Shim/FileSystemServiceObjectAdapter.cs @@ -15,302 +15,301 @@ using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using PathSf = LibHac.FsSrv.Sf.Path; // ReSharper disable CheckNamespace -namespace LibHac.Fs.Impl +namespace LibHac.Fs.Impl; + +/// +/// An adapter for using an service object as an . Used +/// when receiving a Horizon IPC file object so it can be used as an locally. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class FileServiceObjectAdapter : IFile { - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC file object so it can be used as an locally. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class FileServiceObjectAdapter : IFile + private SharedRef _baseFile; + + public FileServiceObjectAdapter(ref SharedRef baseFile) { - private SharedRef _baseFile; - - public FileServiceObjectAdapter(ref SharedRef baseFile) - { - _baseFile = SharedRef.CreateMove(ref baseFile); - } - - public override void Dispose() - { - _baseFile.Destroy(); - base.Dispose(); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) - { - return _baseFile.Get.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return _baseFile.Get.Write(offset, new InBuffer(source), source.Length, option); - } - - protected override Result DoFlush() - { - return _baseFile.Get.Flush(); - } - - protected override Result DoSetSize(long size) - { - return _baseFile.Get.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - return _baseFile.Get.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - switch (operationId) - { - case OperationId.InvalidateCache: - return _baseFile.Get.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); - case OperationId.QueryRange: - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); - - ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); - - return _baseFile.Get.OperateRange(out info, (int)OperationId.QueryRange, offset, size); - default: - return _baseFile.Get.OperateRangeWithBuffer(new OutBuffer(outBuffer), new InBuffer(inBuffer), - (int)operationId, offset, size); - } - } + _baseFile = SharedRef.CreateMove(ref baseFile); } - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC directory object so it can be used as an locally. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class DirectoryServiceObjectAdapter : IDirectory + public override void Dispose() { - private SharedRef _baseDirectory; - - public DirectoryServiceObjectAdapter(ref SharedRef baseDirectory) - { - _baseDirectory = SharedRef.CreateMove(ref baseDirectory); - } - - public override void Dispose() - { - _baseDirectory.Destroy(); - base.Dispose(); - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - Span buffer = MemoryMarshal.Cast(entryBuffer); - return _baseDirectory.Get.Read(out entriesRead, new OutBuffer(buffer)); - } - - protected override Result DoGetEntryCount(out long entryCount) - { - return _baseDirectory.Get.GetEntryCount(out entryCount); - } + _baseFile.Destroy(); + base.Dispose(); } - /// - /// An adapter for using an service object as an . Used - /// when receiving a Horizon IPC file system object so it can be used as an locally. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) { - private SharedRef _baseFs; + return _baseFile.Get.Read(out bytesRead, offset, new OutBuffer(destination), destination.Length, option); + } - private static Result GetPathForServiceObject(out PathSf sfPath, in Path path) + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return _baseFile.Get.Write(offset, new InBuffer(source), source.Length, option); + } + + protected override Result DoFlush() + { + return _baseFile.Get.Flush(); + } + + protected override Result DoSetSize(long size) + { + return _baseFile.Get.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + return _baseFile.Get.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) { - UnsafeHelpers.SkipParamInit(out sfPath); + case OperationId.InvalidateCache: + return _baseFile.Get.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); - int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref sfPath), path.GetString(), - PathTool.EntryNameLengthMax + 1); + ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); - if (length > PathTool.EntryNameLengthMax) - return ResultFs.TooLongPath.Log(); - - return Result.Success; - } - - public FileSystemServiceObjectAdapter(ref SharedRef baseFileSystem) - { - _baseFs = SharedRef.CreateMove(ref baseFileSystem); - } - - public override void Dispose() - { - _baseFs.Destroy(); - base.Dispose(); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.CreateFile(in sfPath, size, (int)option); - } - - protected override Result DoDeleteFile(in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.DeleteFile(in sfPath); - } - - protected override Result DoCreateDirectory(in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.CreateDirectory(in sfPath); - } - - protected override Result DoDeleteDirectory(in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.DeleteDirectory(in sfPath); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.DeleteDirectoryRecursively(in sfPath); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.CleanDirectoryRecursively(in sfPath); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - Result rc = GetPathForServiceObject(out PathSf currentSfPath, in currentPath); - if (rc.IsFailure()) return rc; - - rc = GetPathForServiceObject(out PathSf newSfPath, in newPath); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.RenameFile(in currentSfPath, in newSfPath); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - Result rc = GetPathForServiceObject(out PathSf currentSfPath, in currentPath); - if (rc.IsFailure()) return rc; - - rc = GetPathForServiceObject(out PathSf newSfPath, in newPath); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.RenameDirectory(in currentSfPath, in newSfPath); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - ref uint sfEntryType = ref Unsafe.As(ref entryType); - - return _baseFs.Get.GetEntryType(out sfEntryType, in sfPath); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); - - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.GetFreeSpaceSize(out freeSpace, in sfPath); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.GetTotalSpaceSize(out totalSpace, in sfPath); - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - using var fileServiceObject = new SharedRef(); - - rc = _baseFs.Get.OpenFile(ref fileServiceObject.Ref(), in sfPath, (uint)mode); - if (rc.IsFailure()) return rc; - - outFile.Reset(new FileServiceObjectAdapter(ref fileServiceObject.Ref())); - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - using var directoryServiceObject = new SharedRef(); - - rc = _baseFs.Get.OpenDirectory(ref directoryServiceObject.Ref(), in sfPath, (uint)mode); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new DirectoryServiceObjectAdapter(ref directoryServiceObject.Ref())); - return Result.Success; - } - - protected override Result DoCommit() - { - return _baseFs.Get.Commit(); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.GetFileTimeStampRaw(out timeStamp, in sfPath); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - Result rc = GetPathForServiceObject(out PathSf sfPath, in path); - if (rc.IsFailure()) return rc; - - return _baseFs.Get.QueryEntry(new OutBuffer(outBuffer), new InBuffer(inBuffer), (int)queryId, in sfPath); - } - - public SharedRef GetFileSystem() - { - return SharedRef.CreateCopy(in _baseFs); - } - - public SharedRef GetMultiCommitTarget() - { - return GetFileSystem(); + return _baseFile.Get.OperateRange(out info, (int)OperationId.QueryRange, offset, size); + default: + return _baseFile.Get.OperateRangeWithBuffer(new OutBuffer(outBuffer), new InBuffer(inBuffer), + (int)operationId, offset, size); } } } + +/// +/// An adapter for using an service object as an . Used +/// when receiving a Horizon IPC directory object so it can be used as an locally. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class DirectoryServiceObjectAdapter : IDirectory +{ + private SharedRef _baseDirectory; + + public DirectoryServiceObjectAdapter(ref SharedRef baseDirectory) + { + _baseDirectory = SharedRef.CreateMove(ref baseDirectory); + } + + public override void Dispose() + { + _baseDirectory.Destroy(); + base.Dispose(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + Span buffer = MemoryMarshal.Cast(entryBuffer); + return _baseDirectory.Get.Read(out entriesRead, new OutBuffer(buffer)); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + return _baseDirectory.Get.GetEntryCount(out entryCount); + } +} + +/// +/// An adapter for using an service object as an . Used +/// when receiving a Horizon IPC file system object so it can be used as an locally. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class FileSystemServiceObjectAdapter : IFileSystem, IMultiCommitTarget +{ + private SharedRef _baseFs; + + private static Result GetPathForServiceObject(out PathSf sfPath, in Path path) + { + UnsafeHelpers.SkipParamInit(out sfPath); + + int length = StringUtils.Copy(SpanHelpers.AsByteSpan(ref sfPath), path.GetString(), + PathTool.EntryNameLengthMax + 1); + + if (length > PathTool.EntryNameLengthMax) + return ResultFs.TooLongPath.Log(); + + return Result.Success; + } + + public FileSystemServiceObjectAdapter(ref SharedRef baseFileSystem) + { + _baseFs = SharedRef.CreateMove(ref baseFileSystem); + } + + public override void Dispose() + { + _baseFs.Destroy(); + base.Dispose(); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.CreateFile(in sfPath, size, (int)option); + } + + protected override Result DoDeleteFile(in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.DeleteFile(in sfPath); + } + + protected override Result DoCreateDirectory(in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.CreateDirectory(in sfPath); + } + + protected override Result DoDeleteDirectory(in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.DeleteDirectory(in sfPath); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.DeleteDirectoryRecursively(in sfPath); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.CleanDirectoryRecursively(in sfPath); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + Result rc = GetPathForServiceObject(out PathSf currentSfPath, in currentPath); + if (rc.IsFailure()) return rc; + + rc = GetPathForServiceObject(out PathSf newSfPath, in newPath); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.RenameFile(in currentSfPath, in newSfPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + Result rc = GetPathForServiceObject(out PathSf currentSfPath, in currentPath); + if (rc.IsFailure()) return rc; + + rc = GetPathForServiceObject(out PathSf newSfPath, in newPath); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.RenameDirectory(in currentSfPath, in newSfPath); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + ref uint sfEntryType = ref Unsafe.As(ref entryType); + + return _baseFs.Get.GetEntryType(out sfEntryType, in sfPath); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.GetFreeSpaceSize(out freeSpace, in sfPath); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.GetTotalSpaceSize(out totalSpace, in sfPath); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + using var fileServiceObject = new SharedRef(); + + rc = _baseFs.Get.OpenFile(ref fileServiceObject.Ref(), in sfPath, (uint)mode); + if (rc.IsFailure()) return rc; + + outFile.Reset(new FileServiceObjectAdapter(ref fileServiceObject.Ref())); + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + using var directoryServiceObject = new SharedRef(); + + rc = _baseFs.Get.OpenDirectory(ref directoryServiceObject.Ref(), in sfPath, (uint)mode); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(new DirectoryServiceObjectAdapter(ref directoryServiceObject.Ref())); + return Result.Success; + } + + protected override Result DoCommit() + { + return _baseFs.Get.Commit(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.GetFileTimeStampRaw(out timeStamp, in sfPath); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + Result rc = GetPathForServiceObject(out PathSf sfPath, in path); + if (rc.IsFailure()) return rc; + + return _baseFs.Get.QueryEntry(new OutBuffer(outBuffer), new InBuffer(inBuffer), (int)queryId, in sfPath); + } + + public SharedRef GetFileSystem() + { + return SharedRef.CreateCopy(in _baseFs); + } + + public SharedRef GetMultiCommitTarget() + { + return GetFileSystem(); + } +} diff --git a/src/LibHac/Fs/Shim/GameCard.cs b/src/LibHac/Fs/Shim/GameCard.cs index 4705989a..0e7947df 100644 --- a/src/LibHac/Fs/Shim/GameCard.cs +++ b/src/LibHac/Fs/Shim/GameCard.cs @@ -13,173 +13,172 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +[SkipLocalsInit] +public static class GameCard { - [SkipLocalsInit] - public static class GameCard + private static ReadOnlySpan GetGameCardMountNameSuffix(GameCardPartition partition) { - private static ReadOnlySpan GetGameCardMountNameSuffix(GameCardPartition partition) + switch (partition) { - switch (partition) - { - case GameCardPartition.Update: return CommonMountNames.GameCardFileSystemMountNameUpdateSuffix; - case GameCardPartition.Normal: return CommonMountNames.GameCardFileSystemMountNameNormalSuffix; - case GameCardPartition.Secure: return CommonMountNames.GameCardFileSystemMountNameSecureSuffix; - default: - Abort.UnexpectedDefault(); - return default; - } + case GameCardPartition.Update: return CommonMountNames.GameCardFileSystemMountNameUpdateSuffix; + case GameCardPartition.Normal: return CommonMountNames.GameCardFileSystemMountNameNormalSuffix; + case GameCardPartition.Secure: return CommonMountNames.GameCardFileSystemMountNameSecureSuffix; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator + { + private GameCardHandle Handle { get; } + private GameCardPartition PartitionId { get; } + + public GameCardCommonMountNameGenerator(GameCardHandle handle, GameCardPartition partitionId) + { + Handle = handle; + PartitionId = partitionId; } - private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator + public void Dispose() { } + + public Result GenerateCommonMountName(Span nameBuffer) { - private GameCardHandle Handle { get; } - private GameCardPartition PartitionId { get; } + int handleDigitCount = Unsafe.SizeOf() * 2; - public GameCardCommonMountNameGenerator(GameCardHandle handle, GameCardPartition partitionId) - { - Handle = handle; - PartitionId = partitionId; - } + // Determine how much space we need. + int requiredNameBufferSize = + StringUtils.GetLength(CommonMountNames.GameCardFileSystemMountName, PathTool.MountNameLengthMax) + + StringUtils.GetLength(GetGameCardMountNameSuffix(PartitionId), PathTool.MountNameLengthMax) + + handleDigitCount + 2; - public void Dispose() { } + Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - public Result GenerateCommonMountName(Span nameBuffer) - { - int handleDigitCount = Unsafe.SizeOf() * 2; + // Generate the name. + var sb = new U8StringBuilder(nameBuffer); + sb.Append(CommonMountNames.GameCardFileSystemMountName) + .Append(GetGameCardMountNameSuffix(PartitionId)) + .AppendFormat(Handle.Value, 'x', (byte)handleDigitCount) + .Append(StringTraits.DriveSeparator); - // Determine how much space we need. - int requiredNameBufferSize = - StringUtils.GetLength(CommonMountNames.GameCardFileSystemMountName, PathTool.MountNameLengthMax) + - StringUtils.GetLength(GetGameCardMountNameSuffix(PartitionId), PathTool.MountNameLengthMax) + - handleDigitCount + 2; + Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - - // Generate the name. - var sb = new U8StringBuilder(nameBuffer); - sb.Append(CommonMountNames.GameCardFileSystemMountName) - .Append(GetGameCardMountNameSuffix(PartitionId)) - .AppendFormat(Handle.Value, 'x', (byte)handleDigitCount) - .Append(StringTraits.DriveSeparator); - - Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - - return Result.Success; - } - } - - public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle) - { - UnsafeHelpers.SkipParamInit(out handle); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var deviceOperator = new SharedRef(); - - Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - rc = deviceOperator.Get.GetGameCardHandle(out handle); - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle, - GameCardPartition partitionId) - { - Result rc; - Span logBuffer = stackalloc byte[0x60]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, handle, partitionId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogGameCardHandle).AppendFormat(handle.Value) - .Append(LogGameCardPartition).Append(idString.ToString(partitionId)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Mount(fs, mountName, handle, partitionId); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - - static Result Mount(FileSystemClient fs, U8Span mountName, GameCardHandle handle, - GameCardPartition partitionId) - { - Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenGameCardFileSystem(ref fileSystem.Ref(), handle, partitionId); - if (rc.IsFailure()) return rc; - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInGameCardC.Log(); - - using var mountNameGenerator = - new UniqueRef(new GameCardCommonMountNameGenerator(handle, partitionId)); - - if (!mountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInGameCardD.Log(); - - return fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); - } - } - - public static bool IsGameCardInserted(this FileSystemClient fs) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var deviceOperator = new SharedRef(); - - Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - - rc = deviceOperator.Get.IsGameCardInserted(out bool isInserted); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - - return isInserted; - } - - public static Result OpenGameCardPartition(this FileSystemClient fs, ref UniqueRef outStorage, - GameCardHandle handle, GameCardPartitionRaw partitionType) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var storage = new SharedRef(); - - Result rc = fileSystemProxy.Get.OpenGameCardStorage(ref storage.Ref(), handle, partitionType); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - using var storageAdapter = new UniqueRef(new StorageServiceObjectAdapter(ref storage.Ref())); - - if (!storageAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInGameCardB.Log(); - - outStorage.Set(ref storageAdapter.Ref()); return Result.Success; } } + + public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle) + { + UnsafeHelpers.SkipParamInit(out handle); + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var deviceOperator = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + rc = deviceOperator.Get.GetGameCardHandle(out handle); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle, + GameCardPartition partitionId) + { + Result rc; + Span logBuffer = stackalloc byte[0x60]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, handle, partitionId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogGameCardHandle).AppendFormat(handle.Value) + .Append(LogGameCardPartition).Append(idString.ToString(partitionId)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, handle, partitionId); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result Mount(FileSystemClient fs, U8Span mountName, GameCardHandle handle, + GameCardPartition partitionId) + { + Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenGameCardFileSystem(ref fileSystem.Ref(), handle, partitionId); + if (rc.IsFailure()) return rc; + + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInGameCardC.Log(); + + using var mountNameGenerator = + new UniqueRef(new GameCardCommonMountNameGenerator(handle, partitionId)); + + if (!mountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInGameCardD.Log(); + + return fs.Register(mountName, ref fileSystemAdapter.Ref(), ref mountNameGenerator.Ref()); + } + } + + public static bool IsGameCardInserted(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var deviceOperator = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + rc = deviceOperator.Get.IsGameCardInserted(out bool isInserted); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return isInserted; + } + + public static Result OpenGameCardPartition(this FileSystemClient fs, ref UniqueRef outStorage, + GameCardHandle handle, GameCardPartitionRaw partitionType) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var storage = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenGameCardStorage(ref storage.Ref(), handle, partitionType); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var storageAdapter = new UniqueRef(new StorageServiceObjectAdapter(ref storage.Ref())); + + if (!storageAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInGameCardB.Log(); + + outStorage.Set(ref storageAdapter.Ref()); + return Result.Success; + } } diff --git a/src/LibHac/Fs/Shim/Host.cs b/src/LibHac/Fs/Shim/Host.cs index 8392463f..fabebedd 100644 --- a/src/LibHac/Fs/Shim/Host.cs +++ b/src/LibHac/Fs/Shim/Host.cs @@ -14,524 +14,523 @@ using static LibHac.Fs.Impl.CommonMountNames; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +/// +/// Contains functions for mounting file systems from a host computer. +/// +/// Based on nnSdk 11.4.0 +[SkipLocalsInit] +public static class Host { + private static ReadOnlySpan HostRootFileSystemPath => // "@Host:/" + new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)':', (byte)'/' }; + + private const int HostRootFileSystemPathLength = 7; + /// - /// Contains functions for mounting file systems from a host computer. + /// Opens a host file system via . /// - /// Based on nnSdk 11.4.0 - [SkipLocalsInit] - public static class Host + /// The to use. + /// If successful, the opened host file system. + /// The path on the host computer to open. e.g. /C:\Windows\System32/ + /// Options for opening the host file system. + /// The of the operation. + private static Result OpenHostFileSystemImpl(FileSystemClient fs, ref UniqueRef outFileSystem, + in FspPath path, MountHostOption option) { - private static ReadOnlySpan HostRootFileSystemPath => // "@Host:/" - new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)':', (byte)'/' }; + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var fileSystem = new SharedRef(); - private const int HostRootFileSystemPathLength = 7; - - /// - /// Opens a host file system via . - /// - /// The to use. - /// If successful, the opened host file system. - /// The path on the host computer to open. e.g. /C:\Windows\System32/ - /// Options for opening the host file system. - /// The of the operation. - private static Result OpenHostFileSystemImpl(FileSystemClient fs, ref UniqueRef outFileSystem, - in FspPath path, MountHostOption option) + if (option.Flags != MountHostOptionFlag.None) { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var fileSystem = new SharedRef(); - - if (option.Flags != MountHostOptionFlag.None) - { - Result rc = fileSystemProxy.Get.OpenHostFileSystemWithOption(ref fileSystem.Ref(), in path, option); - if (rc.IsFailure()) return rc; - } - else - { - Result rc = fileSystemProxy.Get.OpenHostFileSystem(ref fileSystem.Ref(), in path); - if (rc.IsFailure()) return rc; - } - - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInHostA.Log(); - - outFileSystem.Set(ref fileSystemAdapter.Ref()); - return Result.Success; - } - - private class HostCommonMountNameGenerator : ICommonMountNameGenerator - { - private FsPath _path; - - public HostCommonMountNameGenerator(U8Span path) - { - StringUtils.Strlcpy(_path.Str, path, FsPath.MaxLength + 1); - } - - public void Dispose() { } - - public Result GenerateCommonMountName(Span nameBuffer) - { - int requiredNameBufferSize = - StringUtils.GetLength(_path.Str, FsPath.MaxLength + 1) + HostRootFileSystemPathLength; - - if (nameBuffer.Length < requiredNameBufferSize) - return ResultFs.TooLongPath.Log(); - - var sb = new U8StringBuilder(nameBuffer); - sb.Append(HostRootFileSystemPath).Append(_path.Str); - - Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - - return Result.Success; - } - } - - private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator - { - public void Dispose() { } - - public Result GenerateCommonMountName(Span nameBuffer) - { - const int requiredNameBufferSize = HostRootFileSystemPathLength; - - Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); - - var sb = new U8StringBuilder(nameBuffer); - sb.Append(HostRootFileSystemPath); - - Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); - - return Result.Success; - } - } - - /// - /// Verifies parameters and opens a host file system. - /// - /// The to use. - /// If successful, the opened host file system. - /// The mount name to be verified. - /// The path on the host computer to open. e.g. C:\Windows\System32 - /// Options for opening the host file system. - /// The of the operation. - private static Result OpenHostFileSystem(FileSystemClient fs, ref UniqueRef outFileSystem, - U8Span mountName, U8Span path, MountHostOption option) - { - if (mountName.IsNull()) - return ResultFs.NullptrArgument.Log(); - - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); - - if (WindowsPath.IsWindowsDrive(mountName)) - return ResultFs.InvalidMountName.Log(); - - if (fs.Impl.IsUsedReservedMountName(mountName)) - return ResultFs.InvalidMountName.Log(); - - Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); - if (rc.IsFailure()) return rc.Miss(); - - if (sfPath.Str[0] == NullTerminator) - { - SpanHelpers.AsByteSpan(ref sfPath)[0] = Dot; - SpanHelpers.AsByteSpan(ref sfPath)[1] = NullTerminator; - } - - using var fileSystem = new UniqueRef(); - - rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); - if (rc.IsFailure()) return rc.Miss(); - - outFileSystem.Set(ref fileSystem.Ref()); - return Result.Success; - } - - /// - /// Creates a based on the and - /// , and verifies the . - /// - /// The to use. - /// If successful, the created . - /// The mount name at which the file system will be mounted. - /// The path that will be opened on the host computer. e.g. C:\Windows\System32 - /// The of the operation. - private static Result PreMountHost(FileSystemClient fs, - ref UniqueRef outMountNameGenerator, U8Span mountName, U8Span path) - { - Result rc = fs.Impl.CheckMountName(mountName); + Result rc = fileSystemProxy.Get.OpenHostFileSystemWithOption(ref fileSystem.Ref(), in path, option); if (rc.IsFailure()) return rc; + } + else + { + Result rc = fileSystemProxy.Get.OpenHostFileSystem(ref fileSystem.Ref(), in path); + if (rc.IsFailure()) return rc; + } - if (path.IsNull()) - return ResultFs.NullptrArgument.Log(); + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem.Ref())); - outMountNameGenerator.Reset(new HostCommonMountNameGenerator(path)); + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInHostA.Log(); - if (!outMountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInHostB.Log(); + outFileSystem.Set(ref fileSystemAdapter.Ref()); + return Result.Success; + } + + private class HostCommonMountNameGenerator : ICommonMountNameGenerator + { + private FsPath _path; + + public HostCommonMountNameGenerator(U8Span path) + { + StringUtils.Strlcpy(_path.Str, path, FsPath.MaxLength + 1); + } + + public void Dispose() { } + + public Result GenerateCommonMountName(Span nameBuffer) + { + int requiredNameBufferSize = + StringUtils.GetLength(_path.Str, FsPath.MaxLength + 1) + HostRootFileSystemPathLength; + + if (nameBuffer.Length < requiredNameBufferSize) + return ResultFs.TooLongPath.Log(); + + var sb = new U8StringBuilder(nameBuffer); + sb.Append(HostRootFileSystemPath).Append(_path.Str); + + Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); return Result.Success; } - - /// - /// Mounts a directory on a host Windows computer at the specified mount point. - /// - /// The to use. - /// The mount name at which the file system will be mounted. - /// The path on the host computer to mount. e.g. C:\Windows\System32 - /// The of the operation. - public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - using var mountNameGenerator = new UniqueRef(); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogRootPath).Append(path).Append(LogQuote); - - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - using var fileSystem = new UniqueRef(); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, MountHostOption.None); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, MountHostOption.None); - } - - // No AbortIfNeeded here - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - - static Result PostMount(FileSystemClient fs, U8Span mountName, ref UniqueRef fileSystem, - ref UniqueRef mountNameGenerator) - { - using UniqueRef baseMountNameGenerator = - UniqueRef.Create(ref mountNameGenerator); - - Result rc = fs.Register(mountName, ref fileSystem, ref baseMountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - } - - /// - /// Mounts a directory on a host Windows computer at the specified mount point. - /// - /// The to use. - /// The mount name at which the file system will be mounted. - /// The path on the host computer to mount. e.g. C:\Windows\System32 - /// Options for mounting the host file system. - /// The of the operation. - public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option) - { - Result rc; - Span logBuffer = stackalloc byte[0x300]; - - using var mountNameGenerator = new UniqueRef(); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogRootPath).Append(path).Append(LogQuote) - .Append(LogMountHostOption).Append(idString.ToString(option)); - - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - using var fileSystem = new UniqueRef(); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, option); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, option); - } - - // No AbortIfNeeded here - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return Result.Success; - - static Result PostMount(FileSystemClient fs, U8Span mountName, ref UniqueRef fileSystem, - ref UniqueRef mountNameGenerator) - { - using UniqueRef baseMountNameGenerator = - UniqueRef.Create(ref mountNameGenerator); - - Result rc = fs.Register(mountName, ref fileSystem, ref baseMountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - } - - /// - /// Mounts the C:\ drive of a host Windows computer at @Host:/ - /// - /// The to use. - /// The of the operation. - public static Result MountHostRoot(this FileSystemClient fs) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - using var fileSystem = new UniqueRef(); - FspPath.CreateEmpty(out FspPath sfPath); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, MountHostOption.None); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, MountHostOption.None); - } - - // No AbortIfNeeded here - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PostMount(fs, ref fileSystem.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PostMount(fs, ref fileSystem.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName)); - - return Result.Success; - - static Result PostMount(FileSystemClient fs, ref UniqueRef fileSystem) - { - using var mountNameGenerator = - new UniqueRef(new HostRootCommonMountNameGenerator()); - - if (!mountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInHostC.Log(); - - Result rc = fs.Register(new U8Span(HostRootFileSystemMountName), ref fileSystem, - ref mountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - } - - /// - /// Mounts the C:\ drive of a host Windows computer at @Host:/ - /// - /// The to use. - /// Options for mounting the host file system. - /// The of the operation. - public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option) - { - Result rc; - Span logBuffer = stackalloc byte[0x60]; - - using var fileSystem = new UniqueRef(); - FspPath.CreateEmpty(out FspPath sfPath); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote) - .Append(LogMountHostOption).Append(idString.ToString(option)); - - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); - } - - // No AbortIfNeeded here - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = PostMount(fs, ref fileSystem.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = PostMount(fs, ref fileSystem.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName)); - - return Result.Success; - - static Result PostMount(FileSystemClient fs, ref UniqueRef fileSystem) - { - using var mountNameGenerator = - new UniqueRef(new HostRootCommonMountNameGenerator()); - - if (!mountNameGenerator.HasValue) - return ResultFs.AllocationMemoryFailedInHostC.Log(); - - Result rc = fs.Register(new U8Span(HostRootFileSystemMountName), ref fileSystem, - ref mountNameGenerator.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - } - - /// - /// Unmounts the file system at @Host:/ - /// - /// The to use. - public static void UnmountHostRoot(this FileSystemClient fs) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - var mountName = new U8Span(HostRootFileSystemMountName); - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.Unmount(mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append((byte)'"'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.Unmount(mountName); - } - - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - } + } + + private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator + { + public void Dispose() { } + + public Result GenerateCommonMountName(Span nameBuffer) + { + const int requiredNameBufferSize = HostRootFileSystemPathLength; + + Assert.SdkRequiresGreaterEqual(nameBuffer.Length, requiredNameBufferSize); + + var sb = new U8StringBuilder(nameBuffer); + sb.Append(HostRootFileSystemPath); + + Assert.SdkEqual(sb.Length, requiredNameBufferSize - 1); + + return Result.Success; + } + } + + /// + /// Verifies parameters and opens a host file system. + /// + /// The to use. + /// If successful, the opened host file system. + /// The mount name to be verified. + /// The path on the host computer to open. e.g. C:\Windows\System32 + /// Options for opening the host file system. + /// The of the operation. + private static Result OpenHostFileSystem(FileSystemClient fs, ref UniqueRef outFileSystem, + U8Span mountName, U8Span path, MountHostOption option) + { + if (mountName.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (WindowsPath.IsWindowsDrive(mountName)) + return ResultFs.InvalidMountName.Log(); + + if (fs.Impl.IsUsedReservedMountName(mountName)) + return ResultFs.InvalidMountName.Log(); + + Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); + if (rc.IsFailure()) return rc.Miss(); + + if (sfPath.Str[0] == NullTerminator) + { + SpanHelpers.AsByteSpan(ref sfPath)[0] = Dot; + SpanHelpers.AsByteSpan(ref sfPath)[1] = NullTerminator; + } + + using var fileSystem = new UniqueRef(); + + rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); + if (rc.IsFailure()) return rc.Miss(); + + outFileSystem.Set(ref fileSystem.Ref()); + return Result.Success; + } + + /// + /// Creates a based on the and + /// , and verifies the . + /// + /// The to use. + /// If successful, the created . + /// The mount name at which the file system will be mounted. + /// The path that will be opened on the host computer. e.g. C:\Windows\System32 + /// The of the operation. + private static Result PreMountHost(FileSystemClient fs, + ref UniqueRef outMountNameGenerator, U8Span mountName, U8Span path) + { + Result rc = fs.Impl.CheckMountName(mountName); + if (rc.IsFailure()) return rc; + + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + outMountNameGenerator.Reset(new HostCommonMountNameGenerator(path)); + + if (!outMountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInHostB.Log(); + + return Result.Success; + } + + /// + /// Mounts a directory on a host Windows computer at the specified mount point. + /// + /// The to use. + /// The mount name at which the file system will be mounted. + /// The path on the host computer to mount. e.g. C:\Windows\System32 + /// The of the operation. + public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + using var mountNameGenerator = new UniqueRef(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogRootPath).Append(path).Append(LogQuote); + + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var fileSystem = new UniqueRef(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, MountHostOption.None); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, MountHostOption.None); + } + + // No AbortIfNeeded here + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result PostMount(FileSystemClient fs, U8Span mountName, ref UniqueRef fileSystem, + ref UniqueRef mountNameGenerator) + { + using UniqueRef baseMountNameGenerator = + UniqueRef.Create(ref mountNameGenerator); + + Result rc = fs.Register(mountName, ref fileSystem, ref baseMountNameGenerator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + /// + /// Mounts a directory on a host Windows computer at the specified mount point. + /// + /// The to use. + /// The mount name at which the file system will be mounted. + /// The path on the host computer to mount. e.g. C:\Windows\System32 + /// Options for mounting the host file system. + /// The of the operation. + public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option) + { + Result rc; + Span logBuffer = stackalloc byte[0x300]; + + using var mountNameGenerator = new UniqueRef(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogRootPath).Append(path).Append(LogQuote) + .Append(LogMountHostOption).Append(idString.ToString(option)); + + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PreMountHost(fs, ref mountNameGenerator.Ref(), mountName, path); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + using var fileSystem = new UniqueRef(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, option); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = OpenHostFileSystem(fs, ref fileSystem.Ref(), mountName, path, option); + } + + // No AbortIfNeeded here + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PostMount(fs, mountName, ref fileSystem.Ref(), ref mountNameGenerator.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + + static Result PostMount(FileSystemClient fs, U8Span mountName, ref UniqueRef fileSystem, + ref UniqueRef mountNameGenerator) + { + using UniqueRef baseMountNameGenerator = + UniqueRef.Create(ref mountNameGenerator); + + Result rc = fs.Register(mountName, ref fileSystem, ref baseMountNameGenerator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + /// + /// Mounts the C:\ drive of a host Windows computer at @Host:/ + /// + /// The to use. + /// The of the operation. + public static Result MountHostRoot(this FileSystemClient fs) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + using var fileSystem = new UniqueRef(); + FspPath.CreateEmpty(out FspPath sfPath); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, MountHostOption.None); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote); + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, MountHostOption.None); + } + + // No AbortIfNeeded here + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PostMount(fs, ref fileSystem.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PostMount(fs, ref fileSystem.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName)); + + return Result.Success; + + static Result PostMount(FileSystemClient fs, ref UniqueRef fileSystem) + { + using var mountNameGenerator = + new UniqueRef(new HostRootCommonMountNameGenerator()); + + if (!mountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInHostC.Log(); + + Result rc = fs.Register(new U8Span(HostRootFileSystemMountName), ref fileSystem, + ref mountNameGenerator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + /// + /// Mounts the C:\ drive of a host Windows computer at @Host:/ + /// + /// The to use. + /// Options for mounting the host file system. + /// The of the operation. + public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option) + { + Result rc; + Span logBuffer = stackalloc byte[0x60]; + + using var fileSystem = new UniqueRef(); + FspPath.CreateEmpty(out FspPath sfPath); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote) + .Append(LogMountHostOption).Append(idString.ToString(option)); + + logBuffer = sb.Buffer; + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = OpenHostFileSystemImpl(fs, ref fileSystem.Ref(), in sfPath, option); + } + + // No AbortIfNeeded here + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = PostMount(fs, ref fileSystem.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = PostMount(fs, ref fileSystem.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName)); + + return Result.Success; + + static Result PostMount(FileSystemClient fs, ref UniqueRef fileSystem) + { + using var mountNameGenerator = + new UniqueRef(new HostRootCommonMountNameGenerator()); + + if (!mountNameGenerator.HasValue) + return ResultFs.AllocationMemoryFailedInHostC.Log(); + + Result rc = fs.Register(new U8Span(HostRootFileSystemMountName), ref fileSystem, + ref mountNameGenerator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + } + + /// + /// Unmounts the file system at @Host:/ + /// + /// The to use. + public static void UnmountHostRoot(this FileSystemClient fs) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + var mountName = new U8Span(HostRootFileSystemMountName); + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Unmount(mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append((byte)'"'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.Unmount(mountName); + } + + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); } } diff --git a/src/LibHac/Fs/Shim/LoaderApi.cs b/src/LibHac/Fs/Shim/LoaderApi.cs index a92d2fba..47e5976f 100644 --- a/src/LibHac/Fs/Shim/LoaderApi.cs +++ b/src/LibHac/Fs/Shim/LoaderApi.cs @@ -2,18 +2,17 @@ using LibHac.FsSrv.Sf; using LibHac.Os; -namespace LibHac.Fs.Shim -{ - public static class LoaderApi - { - public static Result IsArchivedProgram(this FileSystemClient fs, out bool isArchived, ProcessId processId) - { - using SharedRef fileSystemProxy = - fs.Impl.GetFileSystemProxyForLoaderServiceObject(); +namespace LibHac.Fs.Shim; - Result rc = fileSystemProxy.Get.IsArchivedProgram(out isArchived, processId.Value); - fs.Impl.AbortIfNeeded(rc); - return rc; - } +public static class LoaderApi +{ + public static Result IsArchivedProgram(this FileSystemClient fs, out bool isArchived, ProcessId processId) + { + using SharedRef fileSystemProxy = + fs.Impl.GetFileSystemProxyForLoaderServiceObject(); + + Result rc = fileSystemProxy.Get.IsArchivedProgram(out isArchived, processId.Value); + fs.Impl.AbortIfNeeded(rc); + return rc; } } diff --git a/src/LibHac/Fs/Shim/PosixTime.cs b/src/LibHac/Fs/Shim/PosixTime.cs index 4271023d..dbb60a8c 100644 --- a/src/LibHac/Fs/Shim/PosixTime.cs +++ b/src/LibHac/Fs/Shim/PosixTime.cs @@ -1,19 +1,18 @@ using LibHac.Common; using LibHac.FsSrv.Sf; -namespace LibHac.Fs.Shim -{ - public static class PosixTimeShim - { - public static Result SetCurrentPosixTime(this FileSystemClient fs, Time.PosixTime currentPosixTime, - int timeDifferenceSeconds) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); +namespace LibHac.Fs.Shim; - Result rc = fileSystemProxy.Get.SetCurrentPosixTimeWithTimeDifference(currentPosixTime.Value, - timeDifferenceSeconds); - fs.Impl.AbortIfNeeded(rc); - return rc; - } +public static class PosixTimeShim +{ + public static Result SetCurrentPosixTime(this FileSystemClient fs, Time.PosixTime currentPosixTime, + int timeDifferenceSeconds) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.SetCurrentPosixTimeWithTimeDifference(currentPosixTime.Value, + timeDifferenceSeconds); + fs.Impl.AbortIfNeeded(rc); + return rc; } } diff --git a/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs b/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs index cb043894..976e31fe 100644 --- a/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs +++ b/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs @@ -4,30 +4,29 @@ using LibHac.Common; using LibHac.FsSrv.Sf; using LibHac.Sf; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +public static class ProgramIndexMapInfoShim { - public static class ProgramIndexMapInfoShim + /// + /// Unregisters any previously registered program index map info and registers the provided map info. + /// + /// The to use. + /// The program index map info entries to register. + /// : The operation was successful.
+ /// : Insufficient permissions.
+ public static Result RegisterProgramIndexMapInfo(this FileSystemClient fs, + ReadOnlySpan mapInfo) { - /// - /// Unregisters any previously registered program index map info and registers the provided map info. - /// - /// The to use. - /// The program index map info entries to register. - /// : The operation was successful.
- /// : Insufficient permissions.
- public static Result RegisterProgramIndexMapInfo(this FileSystemClient fs, - ReadOnlySpan mapInfo) - { - if (mapInfo.IsEmpty) - return Result.Success; + if (mapInfo.IsEmpty) + return Result.Success; - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - var mapInfoBuffer = new InBuffer(MemoryMarshal.Cast(mapInfo)); + var mapInfoBuffer = new InBuffer(MemoryMarshal.Cast(mapInfo)); - Result rc = fileSystemProxy.Get.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length); - fs.Impl.AbortIfNeeded(rc); - return rc; - } + Result rc = fileSystemProxy.Get.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length); + fs.Impl.AbortIfNeeded(rc); + return rc; } } diff --git a/src/LibHac/Fs/Shim/ProgramRegistry.cs b/src/LibHac/Fs/Shim/ProgramRegistry.cs index 304ca83b..79b61fd7 100644 --- a/src/LibHac/Fs/Shim/ProgramRegistry.cs +++ b/src/LibHac/Fs/Shim/ProgramRegistry.cs @@ -5,41 +5,40 @@ using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +public static class ProgramRegistry { - public static class ProgramRegistry + /// + public static Result RegisterProgram(this FileSystemClient fs, ulong processId, ProgramId programId, + StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) { - /// - public static Result RegisterProgram(this FileSystemClient fs, ulong processId, ProgramId programId, - StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) - { - using SharedRef programRegistry = fs.Impl.GetProgramRegistryServiceObject(); + using SharedRef programRegistry = fs.Impl.GetProgramRegistryServiceObject(); - Result rc = programRegistry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = programRegistry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - rc = programRegistry.Get.RegisterProgram(processId, programId, storageId, new InBuffer(accessControlData), - new InBuffer(accessControlDescriptor)); + rc = programRegistry.Get.RegisterProgram(processId, programId, storageId, new InBuffer(accessControlData), + new InBuffer(accessControlDescriptor)); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - /// - public static Result UnregisterProgram(this FileSystemClient fs, ulong processId) - { - using SharedRef programRegistry = fs.Impl.GetProgramRegistryServiceObject(); + /// + public static Result UnregisterProgram(this FileSystemClient fs, ulong processId) + { + using SharedRef programRegistry = fs.Impl.GetProgramRegistryServiceObject(); - Result rc = programRegistry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value); - if (rc.IsFailure()) return rc.Miss(); + Result rc = programRegistry.Get.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value); + if (rc.IsFailure()) return rc.Miss(); - rc = programRegistry.Get.UnregisterProgram(processId); - if (rc.IsFailure()) return rc.Miss(); + rc = programRegistry.Get.UnregisterProgram(processId); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/Fs/Shim/RightsId.cs b/src/LibHac/Fs/Shim/RightsId.cs index 939c3cf9..8796aa64 100644 --- a/src/LibHac/Fs/Shim/RightsId.cs +++ b/src/LibHac/Fs/Shim/RightsId.cs @@ -3,87 +3,86 @@ using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Spl; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +public static class RightsIdShim { - public static class RightsIdShim + public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, ProgramId programId, + StorageId storageId) { - public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, ProgramId programId, - StorageId storageId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - Result rc = fileSystemProxy.Get.GetRightsId(out rightsId, programId, storageId); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = fileSystemProxy.Get.GetRightsId(out rightsId, programId, storageId); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, U8Span path) - { - UnsafeHelpers.SkipParamInit(out rightsId); + public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, U8Span path) + { + UnsafeHelpers.SkipParamInit(out rightsId); - Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - rc = fileSystemProxy.Get.GetRightsIdByPath(out rightsId, in sfPath); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + rc = fileSystemProxy.Get.GetRightsIdByPath(out rightsId, in sfPath); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, out byte keyGeneration, U8Span path) - { - UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration); + public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, out byte keyGeneration, U8Span path) + { + UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration); - Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = PathUtility.ConvertToFspPath(out FspPath sfPath, path); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - rc = fileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in sfPath); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + rc = fileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in sfPath); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result RegisterExternalKey(this FileSystemClient fs, in RightsId rightsId, in AccessKey key) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + public static Result RegisterExternalKey(this FileSystemClient fs, in RightsId rightsId, in AccessKey key) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - Result rc = fileSystemProxy.Get.RegisterExternalKey(in rightsId, in key); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = fileSystemProxy.Get.RegisterExternalKey(in rightsId, in key); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result UnregisterExternalKey(this FileSystemClient fs, ref RightsId rightsId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + public static Result UnregisterExternalKey(this FileSystemClient fs, ref RightsId rightsId) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - Result rc = fileSystemProxy.Get.UnregisterExternalKey(in rightsId); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = fileSystemProxy.Get.UnregisterExternalKey(in rightsId); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; + } - public static Result UnregisterAllExternalKey(this FileSystemClient fs) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + public static Result UnregisterAllExternalKey(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - Result rc = fileSystemProxy.Get.UnregisterAllExternalKey(); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + Result rc = fileSystemProxy.Get.UnregisterAllExternalKey(); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index 2951c884..704f58d6 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -10,286 +10,285 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +[SkipLocalsInit] +public static class SaveData { - [SkipLocalsInit] - public static class SaveData + private static Result MountSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName, SaveDataSpaceId spaceId, + ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index) { - private static Result MountSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName, SaveDataSpaceId spaceId, - ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index) + Result rc = fs.CheckMountName(mountName); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, programId, type, userId, 0, index); + if (rc.IsFailure()) return rc; + + using var fileSystem = new SharedRef(); + + if (openReadOnly) { - Result rc = fs.CheckMountName(mountName); + rc = fileSystemProxy.Get.OpenReadOnlySaveDataFileSystem(ref fileSystem.Ref(), spaceId, in attribute); if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, programId, type, userId, 0, index); + } + else + { + rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), spaceId, in attribute); if (rc.IsFailure()) return rc; - - using var fileSystem = new SharedRef(); - - if (openReadOnly) - { - rc = fileSystemProxy.Get.OpenReadOnlySaveDataFileSystem(ref fileSystem.Ref(), spaceId, in attribute); - if (rc.IsFailure()) return rc; - } - else - { - rc = fileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref(), spaceId, in attribute); - if (rc.IsFailure()) return rc; - } - - // Note: Nintendo does pass in the same object both as a unique_ptr and as a raw pointer. - // Both of these are tied to the lifetime of the created FileSystemServiceObjectAdapter so it shouldn't be an issue. - var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref()); - using var fileSystemAdapter = new UniqueRef(fileSystemAdapterRaw); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedNew.Log(); - - using var mountNameGenerator = new UniqueRef(); - - rc = fs.Fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), - ref mountNameGenerator.Ref(), false, true); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; } - public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, - UserId userId) + // Note: Nintendo does pass in the same object both as a unique_ptr and as a raw pointer. + // Both of these are tied to the lifetime of the created FileSystemServiceObjectAdapter so it shouldn't be an issue. + var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref()); + using var fileSystemAdapter = new UniqueRef(fileSystemAdapterRaw); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedNew.Log(); + + using var mountNameGenerator = new UniqueRef(); + + rc = fs.Fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), + ref mountNameGenerator.Ref(), false, true); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, + UserId userId) + { + Result rc; + Span logBuffer = stackalloc byte[0x90]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { - Result rc; - Span logBuffer = stackalloc byte[0x90]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, + SaveDataType.Account, openReadOnly: false, index: 0); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, - SaveDataType.Account, openReadOnly: false, index: 0); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, - SaveDataType.Account, openReadOnly: false, index: 0); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, + SaveDataType.Account, openReadOnly: false, index: 0); } - public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, - Ncm.ApplicationId applicationId, UserId userId) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, + Ncm.ApplicationId applicationId, UserId userId) + { + Result rc; + Span logBuffer = stackalloc byte[0x90]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { - Result rc; - Span logBuffer = stackalloc byte[0x90]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, + SaveDataType.Account, openReadOnly: true, index: 0); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, - SaveDataType.Account, openReadOnly: true, index: 0); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, - SaveDataType.Account, openReadOnly: true, index: 0); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId, + SaveDataType.Account, openReadOnly: true, index: 0); } - public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { - Result rc; - Span logBuffer = stackalloc byte[0x30]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0); } - public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { - Result rc; - Span logBuffer = stackalloc byte[0x30]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); } - public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, int index) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, int index) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) { - Result rc; - Span logBuffer = stackalloc byte[0x40]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogIndex).AppendFormat(index); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogIndex).AppendFormat(index); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); } - public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, - Ncm.ApplicationId applicationId) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, + Ncm.ApplicationId applicationId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - Result rc; - Span logBuffer = stackalloc byte[0x50]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0); } - public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, - Ncm.ApplicationId applicationId, int index) + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; + } + + public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, + Ncm.ApplicationId applicationId, int index) + { + Result rc; + Span logBuffer = stackalloc byte[0x60]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - Result rc; - Span logBuffer = stackalloc byte[0x60]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); - Tick end = fs.Hos.Os.GetSystemTick(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogIndex).AppendFormat(index); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogIndex).AppendFormat(index); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, - Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); - - return rc; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); } + else + { + rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, + Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return rc; } } diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index fe60e5ee..c785a48e 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -12,233 +12,679 @@ using LibHac.Sf; using LibHac.Time; using static LibHac.Fs.Impl.AccessLogStrings; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +public class SaveDataIterator : IDisposable { - public class SaveDataIterator : IDisposable + private readonly FileSystemClient _fsClient; + private SharedRef _reader; + + internal SaveDataIterator(FileSystemClient fsClient, ref SharedRef reader) { - private readonly FileSystemClient _fsClient; - private SharedRef _reader; + _reader = SharedRef.CreateMove(ref reader); + _fsClient = fsClient; + } - internal SaveDataIterator(FileSystemClient fsClient, ref SharedRef reader) + public void Dispose() + { + _reader.Destroy(); + } + + private Result ReadSaveDataInfoImpl(out long readCount, Span buffer) + { + Result rc = _reader.Get.Read(out readCount, OutBuffer.FromSpan(buffer)); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result ReadSaveDataInfo(out long readCount, Span buffer) + { + Result rc; + FileSystemClient fs = _fsClient; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - _reader = SharedRef.CreateMove(ref reader); - _fsClient = fsClient; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = ReadSaveDataInfoImpl(out readCount, buffer); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSize).AppendFormat(buffer.Length, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = ReadSaveDataInfoImpl(out readCount, buffer); } - public void Dispose() + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } +} + +[SkipLocalsInit] +public static class SaveDataManagement +{ + public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraData(OutBuffer.FromStruct(ref extraData), saveDataId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId( + OutBuffer.FromStruct(ref extraData), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataAttribute( + OutBuffer.FromStruct(ref extraData), spaceId, in attribute); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, + out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, + in SaveDataExtraData extraDataMask) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute( + OutBuffer.FromStruct(ref extraData), spaceId, in attribute, InBuffer.FromStruct(in extraDataMask)); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + /// + /// Writes the of the provided + /// to the save data in the specified with the provided save data ID. + /// + /// The to use. + /// The containing the save data to be written to. + /// The save data ID of the save data to be written to. + /// The containing the data to write. + /// : The operation was successful.
+ /// : The save data was not found.
+ /// : Insufficient permissions.
+ public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, + InBuffer.FromStruct(in extraData)); + fs.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + /// + /// Writes the provided to the save data in the specified + /// with the provided save data ID. + /// + /// The to use. + /// The containing the save data to be written to. + /// The save data ID of the save data to be written to. + /// The to write to the save data. + /// A mask specifying which bits of + /// to write to the save's extra data. 0 = don't write, 1 = write. + /// : The operation was successful.
+ /// : The save data was not found.
+ /// : Insufficient permissions.
+ /// + /// Calling programs may have permission to write to all, some or none of the save data's extra data. + /// If any bits are set in that the caller does not have the permissions + /// to write to, nothing will be written and will be returned. + /// + public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, + InBuffer.FromStruct(in extraData), InBuffer.FromStruct(in extraDataMask)); + fs.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + /// + /// Writes the provided to the save data in the specified + /// that matches the provided key. + /// The mask specifies which parts of the extra data will be written to the save data. + /// + /// The to use. + /// The containing the save data to be written to. + /// The key for the save data. + /// The to write to the save data. + /// A mask specifying which bits of + /// to write to the save's extra data. 0 = don't write, 1 = write. + /// : The operation was successful.
+ /// : The save data was not found.
+ /// : Insufficient permissions.
+ /// + /// If is set to , + /// the program ID of will be resolved to the default save data program ID of the calling program.
+ /// Calling programs may have permission to write to all, some or none of the save data's extra data. + /// If any bits are set in that the caller does not have the permissions + /// to write to, nothing will be written and will be returned. + ///
+ public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, + spaceId, InBuffer.FromStruct(in extraData), InBuffer.FromStruct(in extraDataMask)); + fs.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result FindSaveDataWithFilter(this FileSystemClientImpl fs, out SaveDataInfo saveInfo, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + UnsafeHelpers.SkipParamInit(out saveInfo); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Unsafe.SkipInit(out SaveDataInfo tempInfo); + OutBuffer saveInfoBuffer = OutBuffer.FromStruct(ref tempInfo); + + Result rc = fileSystemProxy.Get.FindSaveDataWithFilter(out long count, saveInfoBuffer, spaceId, in filter); + if (rc.IsFailure()) return rc; + + if (count == 0) + return ResultFs.TargetNotFound.Log(); + + saveInfo = tempInfo; + return Result.Success; + } + + public static Result CreateSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, + userId, 0); + if (rc.IsFailure()) return rc; + + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, + SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + var metaPolicy = new SaveDataMetaPolicy(SaveDataType.Account); + metaPolicy.GenerateMetaInfo(out SaveDataMetaInfo metaInfo); + + rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result CreateBcatSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + long size) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat, + Fs.SaveData.InvalidUserId, 0); + if (rc.IsFailure()) return rc; + + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, + SaveDataProperties.BcatSaveDataJournalSize, SystemProgramId.Bcat.Value, SaveDataFlags.None, + SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + var metaInfo = new SaveDataMetaInfo { - _reader.Destroy(); + Type = SaveDataMetaType.None, + Size = 0 + }; + + rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result CreateDeviceSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device, + Fs.SaveData.InvalidUserId, 0); + if (rc.IsFailure()) return rc; + + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, + SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + var metaPolicy = new SaveDataMetaPolicy(SaveDataType.Device); + metaPolicy.GenerateMetaInfo(out SaveDataMetaInfo metaInfo); + + rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result CreateCacheStorage(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Cache, + Fs.SaveData.InvalidUserId, 0, index); + if (rc.IsFailure()) return rc; + + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, + spaceId); + if (rc.IsFailure()) return rc; + + var metaInfo = new SaveDataMetaInfo + { + Type = SaveDataMetaType.None, + Size = 0 + }; + + rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result CreateSaveData(this FileSystemClientImpl fs, in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + return fileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, + in metaInfo, in hashSalt); + } + + public static Result DeleteSaveData(this FileSystemClientImpl fs, ulong saveDataId) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + return fileSystemProxy.Get.DeleteSaveDataFileSystem(saveDataId); + } + + public static Result DeleteSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, ulong saveDataId) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + } + + public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.DeleteSaveData(saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.DeleteSaveData(saveDataId); } - private Result ReadSaveDataInfoImpl(out long readCount, Span buffer) - { - Result rc = _reader.Get.Read(out readCount, OutBuffer.FromSpan(buffer)); - if (rc.IsFailure()) return rc.Miss(); + fs.Impl.AbortIfNeeded(rc); + return rc; + } - return Result.Success; + public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.DeleteSaveData(spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.DeleteSaveData(spaceId, saveDataId); } - public Result ReadSaveDataInfo(out long readCount, Span buffer) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result DeleteSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + UserId userId) + { + Result rc; + Span logBuffer = stackalloc byte[0x80]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - Result rc; - FileSystemClient fs = _fsClient; - Span logBuffer = stackalloc byte[0x50]; + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Delete(fs, spaceId, saveDataId, userId); + Tick end = fs.Hos.Os.GetSystemTick(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = ReadSaveDataInfoImpl(out readCount, buffer); - Tick end = fs.Hos.Os.GetSystemTick(); + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSize).AppendFormat(buffer.Length, 'd'); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = ReadSaveDataInfoImpl(out readCount, buffer); - } + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Delete(fs, spaceId, saveDataId, userId); + } - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + fs.Impl.AbortIfNeeded(rc); + return rc; - return Result.Success; + static Result Delete(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, + SaveDataType.System, userId, saveDataId); + if (rc.IsFailure()) return rc; + + return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute); } } - [SkipLocalsInit] - public static class SaveDataManagement + public static Result DeleteDeviceSaveData(this FileSystemClient fs, ApplicationId applicationId) { - public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, - out SaveDataExtraData extraData, ulong saveDataId) + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) { - UnsafeHelpers.SkipParamInit(out extraData); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Delete(fs, applicationId); + Tick end = fs.Hos.Os.GetSystemTick(); - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); - Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraData(OutBuffer.FromStruct(ref extraData), saveDataId); + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Delete(fs, applicationId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result Delete(FileSystemClient fs, ApplicationId applicationId) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), + SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); if (rc.IsFailure()) return rc; - return Result.Success; + return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId.User, in attribute); + } + } + + public static Result RegisterSaveDataAtomicDeletion(this FileSystemClient fs, + ReadOnlySpan saveDataIdList) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.RegisterSaveDataFileSystemAtomicDeletion(InBuffer.FromSpan(saveDataIdList)); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result OpenSaveDataIterator(this FileSystemClientImpl fs, + ref UniqueRef outIterator, SaveDataSpaceId spaceId) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + using var reader = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenSaveDataInfoReaderBySaveDataSpaceId(ref reader.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + using var iterator = new UniqueRef(new SaveDataIterator(fs.Fs, ref reader.Ref())); + + if (!iterator.HasValue) + return ResultFs.AllocationMemoryFailedInSaveDataManagementA.Log(); + + outIterator.Set(ref iterator.Ref()); + + return Result.Success; + } + + public static Result OpenSaveDataIterator(this FileSystemClientImpl fs, + ref UniqueRef outIterator, SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + using var reader = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenSaveDataInfoReaderWithFilter(ref reader.Ref(), spaceId, in filter); + if (rc.IsFailure()) return rc; + + using var iterator = new UniqueRef(new SaveDataIterator(fs.Fs, ref reader.Ref())); + + if (!iterator.HasValue) + return ResultFs.AllocationMemoryFailedInSaveDataManagementA.Log(); + + outIterator.Set(ref iterator.Ref()); + + return Result.Success; + } + + public static Result ReadSaveDataIteratorSaveDataInfo(out long readCount, Span buffer, + in SaveDataIterator iterator) + { + Result rc = iterator.ReadSaveDataInfo(out readCount, buffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result OpenSaveDataIterator(this FileSystemClient fs, ref UniqueRef outIterator, + SaveDataSpaceId spaceId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId); } - public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, - out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result OpenSaveDataIterator(this FileSystemClient fs, ref UniqueRef outIterator, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - UnsafeHelpers.SkipParamInit(out extraData); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId, in filter); + Tick end = fs.Hos.Os.GetSystemTick(); - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); - Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId( - OutBuffer.FromStruct(ref extraData), spaceId, saveDataId); - if (rc.IsFailure()) return rc; - - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId, in filter); } - public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, - out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - UnsafeHelpers.SkipParamInit(out extraData); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.FindSaveDataWithFilter(out info, spaceId, in filter); + Tick end = fs.Hos.Os.GetSystemTick(); - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); - Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataAttribute( - OutBuffer.FromStruct(ref extraData), spaceId, in attribute); - if (rc.IsFailure()) return rc; - - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.FindSaveDataWithFilter(out info, spaceId, in filter); } - public static Result ReadSaveDataFileSystemExtraData(this FileSystemClientImpl fs, - out SaveDataExtraData extraData, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, - in SaveDataExtraData extraDataMask) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - UnsafeHelpers.SkipParamInit(out extraData); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CreateSaveData(applicationId, userId, ownerId, size, journalSize, flags); + Tick end = fs.Hos.Os.GetSystemTick(); - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - Result rc = fileSystemProxy.Get.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute( - OutBuffer.FromStruct(ref extraData), spaceId, in attribute, InBuffer.FromStruct(in extraDataMask)); - if (rc.IsFailure()) return rc; - - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.CreateSaveData(applicationId, userId, ownerId, size, journalSize, flags); } - /// - /// Writes the of the provided - /// to the save data in the specified with the provided save data ID. - /// - /// The to use. - /// The containing the save data to be written to. - /// The save data ID of the save data to be written to. - /// The containing the data to write. - /// : The operation was successful.
- /// : The save data was not found.
- /// : Insufficient permissions.
- public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, - ulong saveDataId, in SaveDataExtraData extraData) + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, + ulong ownerId, long size, long journalSize, in HashSalt hashSalt, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = CreateSave(fs, applicationId, userId, ownerId, size, journalSize, in hashSalt, flags); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, - InBuffer.FromStruct(in extraData)); - fs.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = CreateSave(fs, applicationId, userId, ownerId, size, journalSize, in hashSalt, flags); } - /// - /// Writes the provided to the save data in the specified - /// with the provided save data ID. - /// - /// The to use. - /// The containing the save data to be written to. - /// The save data ID of the save data to be written to. - /// The to write to the save data. - /// A mask specifying which bits of - /// to write to the save's extra data. 0 = don't write, 1 = write. - /// : The operation was successful.
- /// : The save data was not found.
- /// : Insufficient permissions.
- /// - /// Calling programs may have permission to write to all, some or none of the save data's extra data. - /// If any bits are set in that the caller does not have the permissions - /// to write to, nothing will be written and will be returned. - /// - public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, - ulong saveDataId, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result CreateSave(FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, + ulong ownerId, long size, long journalSize, in HashSalt hashSalt, SaveDataFlags flags) { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, - InBuffer.FromStruct(in extraData), InBuffer.FromStruct(in extraDataMask)); - fs.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - /// - /// Writes the provided to the save data in the specified - /// that matches the provided key. - /// The mask specifies which parts of the extra data will be written to the save data. - /// - /// The to use. - /// The containing the save data to be written to. - /// The key for the save data. - /// The to write to the save data. - /// A mask specifying which bits of - /// to write to the save's extra data. 0 = don't write, 1 = write. - /// : The operation was successful.
- /// : The save data was not found.
- /// : Insufficient permissions.
- /// - /// If is set to , - /// the program ID of will be resolved to the default save data program ID of the calling program.
- /// Calling programs may have permission to write to all, some or none of the save data's extra data. - /// If any bits are set in that the caller does not have the permissions - /// to write to, nothing will be written and will be returned. - ///
- public static Result WriteSaveDataFileSystemExtraData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute, in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, - spaceId, InBuffer.FromStruct(in extraData), InBuffer.FromStruct(in extraDataMask)); - fs.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result FindSaveDataWithFilter(this FileSystemClientImpl fs, out SaveDataInfo saveInfo, - SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - UnsafeHelpers.SkipParamInit(out saveInfo); - - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Unsafe.SkipInit(out SaveDataInfo tempInfo); - OutBuffer saveInfoBuffer = OutBuffer.FromStruct(ref tempInfo); - - Result rc = fileSystemProxy.Get.FindSaveDataWithFilter(out long count, saveInfoBuffer, spaceId, in filter); - if (rc.IsFailure()) return rc; - - if (count == 0) - return ResultFs.TargetNotFound.Log(); - - saveInfo = tempInfo; - return Result.Success; - } - - public static Result CreateSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, - UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, userId, 0); @@ -251,1796 +697,1349 @@ namespace LibHac.Fs.Shim var metaPolicy = new SaveDataMetaPolicy(SaveDataType.Account); metaPolicy.GenerateMetaInfo(out SaveDataMetaInfo metaInfo); - rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; + return fileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaInfo, + in hashSalt); } + } - public static Result CreateBcatSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, - long size) + public static Result CreateBcatSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, long size) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CreateBcatSaveData(applicationId, size); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat, - Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd'); - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, - SaveDataProperties.BcatSaveDataJournalSize, SystemProgramId.Bcat.Value, SaveDataFlags.None, - SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - var metaInfo = new SaveDataMetaInfo - { - Type = SaveDataMetaType.None, - Size = 0 - }; - - rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.CreateBcatSaveData(applicationId, size); } - public static Result CreateDeviceSaveData(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateDeviceSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CreateDeviceSaveData(applicationId, ownerId, size, journalSize, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.CreateDeviceSaveData(applicationId, ownerId, size, journalSize, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateTemporaryStorage(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, + ulong ownerId, long size, SaveDataFlags flags) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Temporary, + Fs.SaveData.InvalidUserId, 0); + if (rc.IsFailure()) return rc; + + rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, 0, ownerId, flags, + SaveDataSpaceId.Temporary); + if (rc.IsFailure()) return rc; + + var metaInfo = new SaveDataMetaInfo + { + Type = SaveDataMetaType.None, + Size = 0 + }; + + return fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + } + + public static Result CreateTemporaryStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, + ulong ownerId, long size, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CreateTemporaryStorage(applicationId, ownerId, size, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.CreateTemporaryStorage(applicationId, ownerId, size, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, + SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CreateCacheStorage(applicationId, spaceId, ownerId, index, size, journalSize, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.CreateCacheStorage(applicationId, spaceId, ownerId, index, size, journalSize, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, + SaveDataSpaceId spaceId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + return CreateCacheStorage(fs, applicationId, spaceId, ownerId, 0, size, journalSize, flags); + } + + public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + return CreateCacheStorage(fs, applicationId, SaveDataSpaceId.User, ownerId, size, journalSize, flags); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, + ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x100]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = CreateSave(fs, spaceId, saveDataId, userId, ownerId, size, journalSize, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) + .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') + .Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = CreateSave(fs, spaceId, saveDataId, userId, ownerId, size, journalSize, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result CreateSave(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Device, - Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; - - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, - SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - var metaPolicy = new SaveDataMetaPolicy(SaveDataType.Device); - metaPolicy.GenerateMetaInfo(out SaveDataMetaInfo metaInfo); - - rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result CreateCacheStorage(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, - SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Cache, - Fs.SaveData.InvalidUserId, 0, index); + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, + SaveDataType.System, userId, saveDataId); if (rc.IsFailure()) return rc; rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, spaceId); if (rc.IsFailure()) return rc; - var metaInfo = new SaveDataMetaInfo - { - Type = SaveDataMetaType.None, - Size = 0 - }; - - rc = fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result CreateSaveData(this FileSystemClientImpl fs, in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - return fileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, - in metaInfo, in hashSalt); - } - - public static Result DeleteSaveData(this FileSystemClientImpl fs, ulong saveDataId) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - return fileSystemProxy.Get.DeleteSaveDataFileSystem(saveDataId); - } - - public static Result DeleteSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, ulong saveDataId) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); - } - - public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.DeleteSaveData(saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.DeleteSaveData(saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.DeleteSaveData(spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.DeleteSaveData(spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result DeleteSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - UserId userId) - { - Result rc; - Span logBuffer = stackalloc byte[0x80]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Delete(fs, spaceId, saveDataId, userId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Delete(fs, spaceId, saveDataId, userId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result Delete(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, - SaveDataType.System, userId, saveDataId); - if (rc.IsFailure()) return rc; - - return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute); - } - } - - public static Result DeleteDeviceSaveData(this FileSystemClient fs, ApplicationId applicationId) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Delete(fs, applicationId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Delete(fs, applicationId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result Delete(FileSystemClient fs, ApplicationId applicationId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), - SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; - - return fileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId.User, in attribute); - } - } - - public static Result RegisterSaveDataAtomicDeletion(this FileSystemClient fs, - ReadOnlySpan saveDataIdList) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.RegisterSaveDataFileSystemAtomicDeletion(InBuffer.FromSpan(saveDataIdList)); - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result OpenSaveDataIterator(this FileSystemClientImpl fs, - ref UniqueRef outIterator, SaveDataSpaceId spaceId) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - using var reader = new SharedRef(); - - Result rc = fileSystemProxy.Get.OpenSaveDataInfoReaderBySaveDataSpaceId(ref reader.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - using var iterator = new UniqueRef(new SaveDataIterator(fs.Fs, ref reader.Ref())); - - if (!iterator.HasValue) - return ResultFs.AllocationMemoryFailedInSaveDataManagementA.Log(); - - outIterator.Set(ref iterator.Ref()); - - return Result.Success; - } - - public static Result OpenSaveDataIterator(this FileSystemClientImpl fs, - ref UniqueRef outIterator, SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - using var reader = new SharedRef(); - - Result rc = fileSystemProxy.Get.OpenSaveDataInfoReaderWithFilter(ref reader.Ref(), spaceId, in filter); - if (rc.IsFailure()) return rc; - - using var iterator = new UniqueRef(new SaveDataIterator(fs.Fs, ref reader.Ref())); - - if (!iterator.HasValue) - return ResultFs.AllocationMemoryFailedInSaveDataManagementA.Log(); - - outIterator.Set(ref iterator.Ref()); - - return Result.Success; - } - - public static Result ReadSaveDataIteratorSaveDataInfo(out long readCount, Span buffer, - in SaveDataIterator iterator) - { - Result rc = iterator.ReadSaveDataInfo(out readCount, buffer); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public static Result OpenSaveDataIterator(this FileSystemClient fs, ref UniqueRef outIterator, - SaveDataSpaceId spaceId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result OpenSaveDataIterator(this FileSystemClient fs, ref UniqueRef outIterator, - SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId, in filter); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.OpenSaveDataIterator(ref outIterator, spaceId, in filter); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, - SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.FindSaveDataWithFilter(out info, spaceId, in filter); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.FindSaveDataWithFilter(out info, spaceId, in filter); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CreateSaveData(applicationId, userId, ownerId, size, journalSize, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.CreateSaveData(applicationId, userId, ownerId, size, journalSize, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, - ulong ownerId, long size, long journalSize, in HashSalt hashSalt, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = CreateSave(fs, applicationId, userId, ownerId, size, journalSize, in hashSalt, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = CreateSave(fs, applicationId, userId, ownerId, size, journalSize, in hashSalt, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result CreateSave(FileSystemClient fs, Ncm.ApplicationId applicationId, UserId userId, - ulong ownerId, long size, long journalSize, in HashSalt hashSalt, SaveDataFlags flags) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Account, - userId, 0); - if (rc.IsFailure()) return rc; - - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, - SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - var metaPolicy = new SaveDataMetaPolicy(SaveDataType.Account); - metaPolicy.GenerateMetaInfo(out SaveDataMetaInfo metaInfo); - - return fileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaInfo, - in hashSalt); - } - } - - public static Result CreateBcatSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, long size) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CreateBcatSaveData(applicationId, size); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.CreateBcatSaveData(applicationId, size); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateDeviceSaveData(this FileSystemClient fs, Ncm.ApplicationId applicationId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CreateDeviceSaveData(applicationId, ownerId, size, journalSize, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.CreateDeviceSaveData(applicationId, ownerId, size, journalSize, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateTemporaryStorage(this FileSystemClientImpl fs, Ncm.ApplicationId applicationId, - ulong ownerId, long size, SaveDataFlags flags) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Temporary, - Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; - - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, 0, ownerId, flags, - SaveDataSpaceId.Temporary); - if (rc.IsFailure()) return rc; - - var metaInfo = new SaveDataMetaInfo - { - Type = SaveDataMetaType.None, - Size = 0 - }; - - return fileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - } - - public static Result CreateTemporaryStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, - ulong ownerId, long size, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CreateTemporaryStorage(applicationId, ownerId, size, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.CreateTemporaryStorage(applicationId, ownerId, size, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, - SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CreateCacheStorage(applicationId, spaceId, ownerId, index, size, journalSize, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.CreateCacheStorage(applicationId, spaceId, ownerId, index, size, journalSize, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, - SaveDataSpaceId spaceId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - return CreateCacheStorage(fs, applicationId, spaceId, ownerId, 0, size, journalSize, flags); - } - - public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - return CreateCacheStorage(fs, applicationId, SaveDataSpaceId.User, ownerId, size, journalSize, flags); - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, - ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x100]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = CreateSave(fs, spaceId, saveDataId, userId, ownerId, size, journalSize, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) - .Append(LogSaveDataOwnerId).AppendFormat(ownerId, 'X') - .Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd') - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = CreateSave(fs, spaceId, saveDataId, userId, ownerId, size, journalSize, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result CreateSave(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, - SaveDataType.System, userId, saveDataId); - if (rc.IsFailure()) return rc; - - rc = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, size, journalSize, ownerId, flags, - spaceId); - if (rc.IsFailure()) return rc; - - return fileSystemProxy.Get.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); - } - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, - flags); - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size, - long journalSize, SaveDataFlags flags) - { - return CreateSystemSaveData(fs, saveDataId, userId, 0, size, journalSize, flags); - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size, - long journalSize, SaveDataFlags flags) - { - return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, ownerId, size, journalSize, flags); - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size, - long journalSize, SaveDataFlags flags) - { - return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, 0, size, journalSize, flags); - } - - public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - ulong ownerId, long size, long journalSize, SaveDataFlags flags) - { - return CreateSystemSaveData(fs, spaceId, saveDataId, Fs.SaveData.InvalidUserId, ownerId, size, journalSize, - flags); - } - - public static Result ExtendSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, ulong saveDataId, - long saveDataSize, long journalSize) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.ExtendSaveDataFileSystem(spaceId, saveDataId, saveDataSize, journalSize); - } - - public static Result ExtendSaveData(this FileSystemClient fs, ulong saveDataId, long saveDataSize, - long journalSize) - { - Result rc; - Span logBuffer = stackalloc byte[0x90]; - - var spaceId = SaveDataSpaceId.System; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result ExtendSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - long saveDataSize, long journalSize) - { - Result rc; - Span logBuffer = stackalloc byte[0x90]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result QuerySaveDataTotalSize(this FileSystemClientImpl fs, out long totalSize, long size, - long journalSize) - { - UnsafeHelpers.SkipParamInit(out totalSize); - - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.QuerySaveDataTotalSize(out long tempTotalSize, size, journalSize); - if (rc.IsFailure()) return rc.Miss(); - - totalSize = tempTotalSize; - return Result.Success; - } - - public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long size, - long journalSize) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataSize).AppendFormat(size, 'd') - .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataOwnerId(this FileSystemClient fs, out ulong ownerId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetOwnerId(fs, out ownerId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - // Note: Nintendo accidentally uses ", save_data_size: %ld" instead of ", savedataid: 0x%lX" - // for the format string. - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetOwnerId(fs, out ownerId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetOwnerId(FileSystemClient fs, out ulong ownerId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out ownerId); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); - if (rc.IsFailure()) return rc; - - ownerId = extraData.OwnerId; - return Result.Success; - } - } - - public static Result GetSaveDataOwnerId(this FileSystemClient fs, out ulong ownerId, SaveDataSpaceId spaceId, - ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetOwnerId(fs, out ownerId, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetOwnerId(fs, out ownerId, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetOwnerId(FileSystemClient fs, out ulong ownerId, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out ownerId); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveDataId); - if (rc.IsFailure()) return rc; - - ownerId = extraData.OwnerId; - return Result.Success; - } - } - - public static Result GetSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetFlags(fs, out flags, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetFlags(fs, out flags, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out flags); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); - if (rc.IsFailure()) return rc; - - flags = extraData.Flags; - return Result.Success; - } - } - - public static Result GetSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetFlags(fs, out flags, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetFlags(fs, out flags, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, SaveDataSpaceId spaceId, - ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out flags); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveDataId); - if (rc.IsFailure()) return rc; - - flags = extraData.Flags; - return Result.Success; - } - } - - public static Result GetSystemSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, - SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) - { - Result rc; - Span logBuffer = stackalloc byte[0x80]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetFlags(fs, out flags, spaceId, saveDataId, userId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetFlags(fs, out flags, spaceId, saveDataId, userId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, SaveDataSpaceId spaceId, - ulong saveDataId, UserId userId) - { - UnsafeHelpers.SkipParamInit(out flags); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, - SaveDataType.System, userId, saveDataId); - if (rc.IsFailure()) return rc; - - rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, in attribute); - if (rc.IsFailure()) return rc; - - flags = extraData.Flags; - return Result.Success; - } - } - - public static Result SetSaveDataFlags(this FileSystemClient fs, ulong saveDataId, SaveDataFlags flags) - { - return SetSaveDataFlags(fs, saveDataId, SaveDataSpaceId.System, flags); - } - - public static Result SetSaveDataFlags(this FileSystemClient fs, ulong saveDataId, SaveDataSpaceId spaceId, - SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0x70]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = SetFlags(fs, saveDataId, spaceId, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = SetFlags(fs, saveDataId, spaceId, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result SetFlags(FileSystemClient fs, ulong saveDataId, SaveDataSpaceId spaceId, SaveDataFlags flags) - { - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveDataId); - if (rc.IsFailure()) return rc; - - extraData.Flags = flags; - - return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData); - } - } - - public static Result SetSystemSaveDataFlags(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - UserId userId, SaveDataFlags flags) - { - Result rc; - Span logBuffer = stackalloc byte[0xA0]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = SetFlags(fs, spaceId, saveDataId, userId, flags); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = SetFlags(fs, spaceId, saveDataId, userId, flags); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result SetFlags(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId, - SaveDataFlags flags) - { - var extraDataMask = new SaveDataExtraData(); - extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); - - var extraData = new SaveDataExtraData(); - extraData.Flags = flags; - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, - SaveDataType.System, userId, saveDataId); - if (rc.IsFailure()) return rc; - - return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, in attribute, in extraData, in extraDataMask); - } - } - - public static Result GetSaveDataTimeStamp(this FileSystemClient fs, out PosixTime timeStamp, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetTimeStamp(fs, out timeStamp, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetTimeStamp(fs, out timeStamp, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetTimeStamp(FileSystemClient fs, out PosixTime timeStamp, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); - if (rc.IsFailure()) return rc; - - timeStamp = new PosixTime(extraData.TimeStamp); - return Result.Success; - } - } - - public static Result SetSaveDataTimeStamp(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - PosixTime timeStamp) - { - Result rc; - Span logBuffer = stackalloc byte[0x80]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = SetTimeStamp(fs, spaceId, saveDataId, timeStamp); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataTimeStamp).AppendFormat(timeStamp.Value, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = SetTimeStamp(fs, spaceId, saveDataId, timeStamp); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result SetTimeStamp(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - PosixTime timeStamp) - { - var extraDataMask = new SaveDataExtraData(); - extraDataMask.TimeStamp = unchecked((long)0xFFFFFFFFFFFFFFFF); - - var extraData = new SaveDataExtraData(); - extraData.TimeStamp = timeStamp.Value; - - return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); - } - } - - public static Result GetSaveDataTimeStamp(this FileSystemClient fs, out PosixTime timeStamp, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetTimeStamp(fs, out timeStamp, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetTimeStamp(fs, out timeStamp, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetTimeStamp(FileSystemClient fs, out PosixTime timeStamp, SaveDataSpaceId spaceId, - ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveDataId); - if (rc.IsFailure()) return rc; - - timeStamp = new PosixTime(extraData.TimeStamp); - return Result.Success; - } - } - - public static Result GetSaveDataAvailableSize(this FileSystemClientImpl fs, out long availableSize, - ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out availableSize); - - Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); - if (rc.IsFailure()) return rc; - - availableSize = extraData.DataSize; - return Result.Success; - } - - public static Result GetSaveDataAvailableSize(this FileSystemClientImpl fs, out long availableSize, - SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out availableSize); - - Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - - availableSize = extraData.DataSize; - return Result.Success; - } - - public static Result GetSaveDataAvailableSize(this FileSystemClient fs, out long availableSize, - ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataAvailableSize(this FileSystemClient fs, out long availableSize, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataJournalSize(this FileSystemClientImpl fs, out long journalSize, - ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out journalSize); - - Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); - if (rc.IsFailure()) return rc; - - journalSize = extraData.JournalSize; - return Result.Success; - } - - public static Result GetSaveDataJournalSize(this FileSystemClientImpl fs, out long journalSize, - SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out journalSize); - - Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - - journalSize = extraData.JournalSize; - return Result.Success; - } - - public static Result GetSaveDataJournalSize(this FileSystemClient fs, out long journalSize, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.GetSaveDataJournalSize(out journalSize, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.GetSaveDataJournalSize(out journalSize, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataJournalSize(this FileSystemClient fs, out long journalSize, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.GetSaveDataJournalSize(out journalSize, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.GetSaveDataJournalSize(out journalSize, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataCommitId(this FileSystemClient fs, out long commitId, SaveDataSpaceId spaceId, - ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetCommitId(fs, out commitId, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetCommitId(fs, out commitId, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetCommitId(FileSystemClient fs, out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out commitId); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.GetSaveDataCommitId(out commitId, spaceId, saveDataId); - } - } - - public static Result SetSaveDataCommitId(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, - long commitId) - { - Result rc; - Span logBuffer = stackalloc byte[0x80]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = SetCommitId(fs, spaceId, saveDataId, commitId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataCommitId).AppendFormat(commitId, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = SetCommitId(fs, spaceId, saveDataId, commitId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result SetCommitId(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, long commitId) - { - var extraDataMask = new SaveDataExtraData(); - extraDataMask.CommitId = unchecked((long)0xFFFFFFFFFFFFFFFF); - - var extraData = new SaveDataExtraData(); - extraData.CommitId = commitId; - - return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); - } - } - - public static Result QuerySaveDataInternalStorageTotalSize(this FileSystemClientImpl fs, out long size, - SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out size); - - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); - } - - public static Result QuerySaveDataInternalStorageTotalSize(this FileSystemClient fs, out long size, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc; - Span logBuffer = stackalloc byte[0x50]; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - - sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result VerifySaveData(this FileSystemClient fs, out bool isValid, ulong saveDataId, - Span workBuffer) - { - return VerifySaveData(fs, out isValid, SaveDataSpaceId.System, saveDataId, workBuffer); - } - - public static Result VerifySaveData(this FileSystemClient fs, out bool isValid, SaveDataSpaceId spaceId, - ulong saveDataId, Span workBuffer) - { - UnsafeHelpers.SkipParamInit(out isValid); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, - new OutBuffer(workBuffer)); - - if (ResultFs.DataCorrupted.Includes(rc)) - { - isValid = false; - return Result.Success; - } - - fs.Impl.AbortIfNeeded(rc); - - if (rc.IsSuccess()) - { - isValid = true; - return Result.Success; - } - - return rc; - } - - public static Result CorruptSaveDataForDebug(this FileSystemClient fs, ulong saveDataId) - { - return CorruptSaveDataForDebug(fs, SaveDataSpaceId.System, saveDataId); - } - - public static Result CorruptSaveDataForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, - ulong saveDataId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.CorruptSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result CorruptSaveDataForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, - ulong saveDataId, long offset) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset); - - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static void DisableAutoSaveDataCreation(this FileSystemClient fs) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.DisableAutoSaveDataCreation(); - - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - } - - public static Result DeleteCacheStorage(this FileSystemClient fs, int index) - { - Result rc; - Span logBuffer = stackalloc byte[0x20]; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Delete(fs, index); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogIndex).AppendFormat(index, 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = Delete(fs, index); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result Delete(FileSystemClient fs, int index) - { - if (index < 0) - return ResultFs.InvalidArgument.Log(); - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.DeleteCacheStorage((ushort)index); - } - } - - public static Result GetCacheStorageSize(this FileSystemClient fs, out long saveSize, out long journalSize, - int index) - { - Result rc; - Span logBuffer = stackalloc byte[0x60]; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetSize(fs, out saveSize, out journalSize, index); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogIndex).AppendFormat(index, 'd') - .Append(LogSaveDataSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in saveSize, rc), 'd') - .Append(LogSaveDataJournalSize) - .AppendFormat(AccessLogImpl.DereferenceOutValue(in journalSize, rc), 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetSize(fs, out saveSize, out journalSize, index); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetSize(FileSystemClient fs, out long saveSize, out long journalSize, int index) - { - UnsafeHelpers.SkipParamInit(out saveSize, out journalSize); - - if (index < 0) - return ResultFs.InvalidArgument.Log(); - - // Note: Nintendo gets the service object in the outer function and captures it for the inner function - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.GetCacheStorageSize(out saveSize, out journalSize, (ushort)index); - } - } - - public static Result UpdateSaveDataMacForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, - ulong saveDataId) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - return fileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId); - } - - public static Result ListApplicationAccessibleSaveDataOwnerId(this FileSystemClient fs, out int readCount, - Span idBuffer, Ncm.ApplicationId applicationId, int programIndex, int startIndex) - { - if (idBuffer.IsEmpty) - { - readCount = 0; - return Result.Success; - } - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - var programId = new ProgramId(applicationId.Value + (uint)programIndex); - OutBuffer idOutBuffer = OutBuffer.FromSpan(idBuffer); - - Result rc = fileSystemProxy.Get.ListAccessibleSaveDataOwnerId(out readCount, idOutBuffer, programId, startIndex, - idBuffer.Length); - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static Result GetSaveDataRestoreFlag(this FileSystemClient fs, out bool isRestoreFlagSet, - U8Span mountName) - { - UnsafeHelpers.SkipParamInit(out isRestoreFlagSet); - - Result rc; - FileSystemAccessor fileSystem; - Span logBuffer = stackalloc byte[0x40]; - - if (fs.Impl.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.Find(out fileSystem, mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = fs.Impl.Find(out fileSystem, mountName); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetRestoreFlagValue(fs, out isRestoreFlagSet, fileSystem); - Tick end = fs.Hos.Os.GetSystemTick(); - - ReadOnlySpan isSetString = - AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue( - AccessLogImpl.DereferenceOutValue(in isRestoreFlagSet, rc)); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName).Append(LogQuote) - .Append(LogRestoreFlag).Append(isSetString); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetRestoreFlagValue(fs, out isRestoreFlagSet, fileSystem); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetRestoreFlagValue(FileSystemClient fs, out bool isRestoreFlagSet, - FileSystemAccessor fileSystem) - { - Unsafe.SkipInit(out isRestoreFlagSet); - - if (fileSystem is null) - return ResultFs.NullptrArgument.Log(); - - Result rc = fileSystem.GetSaveDataAttribute(out SaveDataAttribute attribute); - if (rc.IsFailure()) return rc; - - if (attribute.ProgramId == Fs.SaveData.InvalidProgramId) - attribute.ProgramId = Fs.SaveData.AutoResolveCallerProgramId; - - var extraDataMask = new SaveDataExtraData(); - extraDataMask.Flags = SaveDataFlags.Restore; - - rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, - in attribute, in extraDataMask); - if (rc.IsFailure()) return rc; - - isRestoreFlagSet = extraData.Flags.HasFlag(SaveDataFlags.Restore); - return Result.Success; - } - } - - public static Result GetDeviceSaveDataSize(this FileSystemClient fs, out long saveSize, - out long journalSize, ApplicationId applicationId) - { - Result rc; - Span logBuffer = stackalloc byte[0x70]; - - if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = GetSize(fs, out saveSize, out journalSize, applicationId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') - .Append(LogSaveDataSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in saveSize, rc), 'd') - .Append(LogSaveDataJournalSize) - .AppendFormat(AccessLogImpl.DereferenceOutValue(in journalSize, rc), 'd'); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); - } - else - { - rc = GetSize(fs, out saveSize, out journalSize, applicationId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result GetSize(FileSystemClient fs, out long saveSize, out long journalSize, - ApplicationId applicationId) - { - UnsafeHelpers.SkipParamInit(out saveSize, out journalSize); - - var extraDataMask = new SaveDataExtraData(); - extraDataMask.DataSize = unchecked((long)0xFFFFFFFFFFFFFFFF); - extraDataMask.JournalSize = unchecked((long)0xFFFFFFFFFFFFFFFF); - - Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), - SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); - if (rc.IsFailure()) return rc; - - rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, - in attribute, in extraDataMask); - if (rc.IsFailure()) return rc; - - saveSize = extraData.DataSize; - journalSize = extraData.JournalSize; - - return Result.Success; - } + return fileSystemProxy.Get.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); } } -} \ No newline at end of file + + public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, + flags); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size, + long journalSize, SaveDataFlags flags) + { + return CreateSystemSaveData(fs, saveDataId, userId, 0, size, journalSize, flags); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size, + long journalSize, SaveDataFlags flags) + { + return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, ownerId, size, journalSize, flags); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size, + long journalSize, SaveDataFlags flags) + { + return CreateSystemSaveData(fs, saveDataId, Fs.SaveData.InvalidUserId, 0, size, journalSize, flags); + } + + public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + ulong ownerId, long size, long journalSize, SaveDataFlags flags) + { + return CreateSystemSaveData(fs, spaceId, saveDataId, Fs.SaveData.InvalidUserId, ownerId, size, journalSize, + flags); + } + + public static Result ExtendSaveData(this FileSystemClientImpl fs, SaveDataSpaceId spaceId, ulong saveDataId, + long saveDataSize, long journalSize) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.ExtendSaveDataFileSystem(spaceId, saveDataId, saveDataSize, journalSize); + } + + public static Result ExtendSaveData(this FileSystemClient fs, ulong saveDataId, long saveDataSize, + long journalSize) + { + Result rc; + Span logBuffer = stackalloc byte[0x90]; + + var spaceId = SaveDataSpaceId.System; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result ExtendSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + long saveDataSize, long journalSize) + { + Result rc; + Span logBuffer = stackalloc byte[0x90]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogSaveDataSize).AppendFormat(saveDataSize, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.ExtendSaveData(spaceId, saveDataId, saveDataSize, journalSize); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result QuerySaveDataTotalSize(this FileSystemClientImpl fs, out long totalSize, long size, + long journalSize) + { + UnsafeHelpers.SkipParamInit(out totalSize); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.QuerySaveDataTotalSize(out long tempTotalSize, size, journalSize); + if (rc.IsFailure()) return rc.Miss(); + + totalSize = tempTotalSize; + return Result.Success; + } + + public static Result QuerySaveDataTotalSize(this FileSystemClient fs, out long totalSize, long size, + long journalSize) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataSize).AppendFormat(size, 'd') + .Append(LogSaveDataJournalSize).AppendFormat(journalSize, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.QuerySaveDataTotalSize(out totalSize, size, journalSize); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataOwnerId(this FileSystemClient fs, out ulong ownerId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetOwnerId(fs, out ownerId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + // Note: Nintendo accidentally uses ", save_data_size: %ld" instead of ", savedataid: 0x%lX" + // for the format string. + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetOwnerId(fs, out ownerId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetOwnerId(FileSystemClient fs, out ulong ownerId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out ownerId); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); + if (rc.IsFailure()) return rc; + + ownerId = extraData.OwnerId; + return Result.Success; + } + } + + public static Result GetSaveDataOwnerId(this FileSystemClient fs, out ulong ownerId, SaveDataSpaceId spaceId, + ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetOwnerId(fs, out ownerId, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetOwnerId(fs, out ownerId, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetOwnerId(FileSystemClient fs, out ulong ownerId, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out ownerId); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveDataId); + if (rc.IsFailure()) return rc; + + ownerId = extraData.OwnerId; + return Result.Success; + } + } + + public static Result GetSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetFlags(fs, out flags, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetFlags(fs, out flags, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out flags); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); + if (rc.IsFailure()) return rc; + + flags = extraData.Flags; + return Result.Success; + } + } + + public static Result GetSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetFlags(fs, out flags, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetFlags(fs, out flags, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, SaveDataSpaceId spaceId, + ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out flags); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveDataId); + if (rc.IsFailure()) return rc; + + flags = extraData.Flags; + return Result.Success; + } + } + + public static Result GetSystemSaveDataFlags(this FileSystemClient fs, out SaveDataFlags flags, + SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) + { + Result rc; + Span logBuffer = stackalloc byte[0x80]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetFlags(fs, out flags, spaceId, saveDataId, userId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetFlags(fs, out flags, spaceId, saveDataId, userId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetFlags(FileSystemClient fs, out SaveDataFlags flags, SaveDataSpaceId spaceId, + ulong saveDataId, UserId userId) + { + UnsafeHelpers.SkipParamInit(out flags); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, + SaveDataType.System, userId, saveDataId); + if (rc.IsFailure()) return rc; + + rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, in attribute); + if (rc.IsFailure()) return rc; + + flags = extraData.Flags; + return Result.Success; + } + } + + public static Result SetSaveDataFlags(this FileSystemClient fs, ulong saveDataId, SaveDataFlags flags) + { + return SetSaveDataFlags(fs, saveDataId, SaveDataSpaceId.System, flags); + } + + public static Result SetSaveDataFlags(this FileSystemClient fs, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0x70]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = SetFlags(fs, saveDataId, spaceId, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = SetFlags(fs, saveDataId, spaceId, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result SetFlags(FileSystemClient fs, ulong saveDataId, SaveDataSpaceId spaceId, SaveDataFlags flags) + { + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveDataId); + if (rc.IsFailure()) return rc; + + extraData.Flags = flags; + + return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData); + } + } + + public static Result SetSystemSaveDataFlags(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + UserId userId, SaveDataFlags flags) + { + Result rc; + Span logBuffer = stackalloc byte[0xA0]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = SetFlags(fs, spaceId, saveDataId, userId, flags); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16) + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataFlags).AppendFormat((int)flags, 'X', 8); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = SetFlags(fs, spaceId, saveDataId, userId, flags); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result SetFlags(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId, + SaveDataFlags flags) + { + var extraDataMask = new SaveDataExtraData(); + extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); + + var extraData = new SaveDataExtraData(); + extraData.Flags = flags; + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, + SaveDataType.System, userId, saveDataId); + if (rc.IsFailure()) return rc; + + return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, in attribute, in extraData, in extraDataMask); + } + } + + public static Result GetSaveDataTimeStamp(this FileSystemClient fs, out PosixTime timeStamp, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetTimeStamp(fs, out timeStamp, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetTimeStamp(fs, out timeStamp, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetTimeStamp(FileSystemClient fs, out PosixTime timeStamp, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); + if (rc.IsFailure()) return rc; + + timeStamp = new PosixTime(extraData.TimeStamp); + return Result.Success; + } + } + + public static Result SetSaveDataTimeStamp(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + PosixTime timeStamp) + { + Result rc; + Span logBuffer = stackalloc byte[0x80]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = SetTimeStamp(fs, spaceId, saveDataId, timeStamp); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataTimeStamp).AppendFormat(timeStamp.Value, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = SetTimeStamp(fs, spaceId, saveDataId, timeStamp); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result SetTimeStamp(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + PosixTime timeStamp) + { + var extraDataMask = new SaveDataExtraData(); + extraDataMask.TimeStamp = unchecked((long)0xFFFFFFFFFFFFFFFF); + + var extraData = new SaveDataExtraData(); + extraData.TimeStamp = timeStamp.Value; + + return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); + } + } + + public static Result GetSaveDataTimeStamp(this FileSystemClient fs, out PosixTime timeStamp, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetTimeStamp(fs, out timeStamp, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetTimeStamp(fs, out timeStamp, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetTimeStamp(FileSystemClient fs, out PosixTime timeStamp, SaveDataSpaceId spaceId, + ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + Result rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveDataId); + if (rc.IsFailure()) return rc; + + timeStamp = new PosixTime(extraData.TimeStamp); + return Result.Success; + } + } + + public static Result GetSaveDataAvailableSize(this FileSystemClientImpl fs, out long availableSize, + ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out availableSize); + + Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); + if (rc.IsFailure()) return rc; + + availableSize = extraData.DataSize; + return Result.Success; + } + + public static Result GetSaveDataAvailableSize(this FileSystemClientImpl fs, out long availableSize, + SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out availableSize); + + Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + availableSize = extraData.DataSize; + return Result.Success; + } + + public static Result GetSaveDataAvailableSize(this FileSystemClient fs, out long availableSize, + ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataAvailableSize(this FileSystemClient fs, out long availableSize, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.GetSaveDataAvailableSize(out availableSize, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataJournalSize(this FileSystemClientImpl fs, out long journalSize, + ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out journalSize); + + Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, saveDataId); + if (rc.IsFailure()) return rc; + + journalSize = extraData.JournalSize; + return Result.Success; + } + + public static Result GetSaveDataJournalSize(this FileSystemClientImpl fs, out long journalSize, + SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out journalSize); + + Result rc = fs.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + journalSize = extraData.JournalSize; + return Result.Success; + } + + public static Result GetSaveDataJournalSize(this FileSystemClient fs, out long journalSize, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.GetSaveDataJournalSize(out journalSize, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.GetSaveDataJournalSize(out journalSize, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataJournalSize(this FileSystemClient fs, out long journalSize, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.GetSaveDataJournalSize(out journalSize, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.GetSaveDataJournalSize(out journalSize, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataCommitId(this FileSystemClient fs, out long commitId, SaveDataSpaceId spaceId, + ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetCommitId(fs, out commitId, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetCommitId(fs, out commitId, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetCommitId(FileSystemClient fs, out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out commitId); + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.GetSaveDataCommitId(out commitId, spaceId, saveDataId); + } + } + + public static Result SetSaveDataCommitId(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, + long commitId) + { + Result rc; + Span logBuffer = stackalloc byte[0x80]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = SetCommitId(fs, spaceId, saveDataId, commitId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataCommitId).AppendFormat(commitId, 'X', 16); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = SetCommitId(fs, spaceId, saveDataId, commitId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result SetCommitId(FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, long commitId) + { + var extraDataMask = new SaveDataExtraData(); + extraDataMask.CommitId = unchecked((long)0xFFFFFFFFFFFFFFFF); + + var extraData = new SaveDataExtraData(); + extraData.CommitId = commitId; + + return fs.Impl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in extraDataMask); + } + } + + public static Result QuerySaveDataInternalStorageTotalSize(this FileSystemClientImpl fs, out long size, + SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out size); + + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); + } + + public static Result QuerySaveDataInternalStorageTotalSize(this FileSystemClient fs, out long size, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc; + Span logBuffer = stackalloc byte[0x50]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System) && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + + sb.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result VerifySaveData(this FileSystemClient fs, out bool isValid, ulong saveDataId, + Span workBuffer) + { + return VerifySaveData(fs, out isValid, SaveDataSpaceId.System, saveDataId, workBuffer); + } + + public static Result VerifySaveData(this FileSystemClient fs, out bool isValid, SaveDataSpaceId spaceId, + ulong saveDataId, Span workBuffer) + { + UnsafeHelpers.SkipParamInit(out isValid); + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, + new OutBuffer(workBuffer)); + + if (ResultFs.DataCorrupted.Includes(rc)) + { + isValid = false; + return Result.Success; + } + + fs.Impl.AbortIfNeeded(rc); + + if (rc.IsSuccess()) + { + isValid = true; + return Result.Success; + } + + return rc; + } + + public static Result CorruptSaveDataForDebug(this FileSystemClient fs, ulong saveDataId) + { + return CorruptSaveDataForDebug(fs, SaveDataSpaceId.System, saveDataId); + } + + public static Result CorruptSaveDataForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, + ulong saveDataId) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.CorruptSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result CorruptSaveDataForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, + ulong saveDataId, long offset) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset); + + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static void DisableAutoSaveDataCreation(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.DisableAutoSaveDataCreation(); + + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + } + + public static Result DeleteCacheStorage(this FileSystemClient fs, int index) + { + Result rc; + Span logBuffer = stackalloc byte[0x20]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Delete(fs, index); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogIndex).AppendFormat(index, 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Delete(fs, index); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result Delete(FileSystemClient fs, int index) + { + if (index < 0) + return ResultFs.InvalidArgument.Log(); + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.DeleteCacheStorage((ushort)index); + } + } + + public static Result GetCacheStorageSize(this FileSystemClient fs, out long saveSize, out long journalSize, + int index) + { + Result rc; + Span logBuffer = stackalloc byte[0x60]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetSize(fs, out saveSize, out journalSize, index); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogIndex).AppendFormat(index, 'd') + .Append(LogSaveDataSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in saveSize, rc), 'd') + .Append(LogSaveDataJournalSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in journalSize, rc), 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetSize(fs, out saveSize, out journalSize, index); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetSize(FileSystemClient fs, out long saveSize, out long journalSize, int index) + { + UnsafeHelpers.SkipParamInit(out saveSize, out journalSize); + + if (index < 0) + return ResultFs.InvalidArgument.Log(); + + // Note: Nintendo gets the service object in the outer function and captures it for the inner function + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.GetCacheStorageSize(out saveSize, out journalSize, (ushort)index); + } + } + + public static Result UpdateSaveDataMacForDebug(this FileSystemClient fs, SaveDataSpaceId spaceId, + ulong saveDataId) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + return fileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId); + } + + public static Result ListApplicationAccessibleSaveDataOwnerId(this FileSystemClient fs, out int readCount, + Span idBuffer, Ncm.ApplicationId applicationId, int programIndex, int startIndex) + { + if (idBuffer.IsEmpty) + { + readCount = 0; + return Result.Success; + } + + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + var programId = new ProgramId(applicationId.Value + (uint)programIndex); + OutBuffer idOutBuffer = OutBuffer.FromSpan(idBuffer); + + Result rc = fileSystemProxy.Get.ListAccessibleSaveDataOwnerId(out readCount, idOutBuffer, programId, startIndex, + idBuffer.Length); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static Result GetSaveDataRestoreFlag(this FileSystemClient fs, out bool isRestoreFlagSet, + U8Span mountName) + { + UnsafeHelpers.SkipParamInit(out isRestoreFlagSet); + + Result rc; + FileSystemAccessor fileSystem; + Span logBuffer = stackalloc byte[0x40]; + + if (fs.Impl.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.Find(out fileSystem, mountName); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = fs.Impl.Find(out fileSystem, mountName); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog() && fileSystem.IsEnabledAccessLog()) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetRestoreFlagValue(fs, out isRestoreFlagSet, fileSystem); + Tick end = fs.Hos.Os.GetSystemTick(); + + ReadOnlySpan isSetString = + AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue( + AccessLogImpl.DereferenceOutValue(in isRestoreFlagSet, rc)); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName).Append(LogQuote) + .Append(LogRestoreFlag).Append(isSetString); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetRestoreFlagValue(fs, out isRestoreFlagSet, fileSystem); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetRestoreFlagValue(FileSystemClient fs, out bool isRestoreFlagSet, + FileSystemAccessor fileSystem) + { + Unsafe.SkipInit(out isRestoreFlagSet); + + if (fileSystem is null) + return ResultFs.NullptrArgument.Log(); + + Result rc = fileSystem.GetSaveDataAttribute(out SaveDataAttribute attribute); + if (rc.IsFailure()) return rc; + + if (attribute.ProgramId == Fs.SaveData.InvalidProgramId) + attribute.ProgramId = Fs.SaveData.AutoResolveCallerProgramId; + + var extraDataMask = new SaveDataExtraData(); + extraDataMask.Flags = SaveDataFlags.Restore; + + rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, + in attribute, in extraDataMask); + if (rc.IsFailure()) return rc; + + isRestoreFlagSet = extraData.Flags.HasFlag(SaveDataFlags.Restore); + return Result.Success; + } + } + + public static Result GetDeviceSaveDataSize(this FileSystemClient fs, out long saveSize, + out long journalSize, ApplicationId applicationId) + { + Result rc; + Span logBuffer = stackalloc byte[0x70]; + + if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(null)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = GetSize(fs, out saveSize, out journalSize, applicationId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X') + .Append(LogSaveDataSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in saveSize, rc), 'd') + .Append(LogSaveDataJournalSize) + .AppendFormat(AccessLogImpl.DereferenceOutValue(in journalSize, rc), 'd'); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = GetSize(fs, out saveSize, out journalSize, applicationId); + } + + fs.Impl.AbortIfNeeded(rc); + return rc; + + static Result GetSize(FileSystemClient fs, out long saveSize, out long journalSize, + ApplicationId applicationId) + { + UnsafeHelpers.SkipParamInit(out saveSize, out journalSize); + + var extraDataMask = new SaveDataExtraData(); + extraDataMask.DataSize = unchecked((long)0xFFFFFFFFFFFFFFFF); + extraDataMask.JournalSize = unchecked((long)0xFFFFFFFFFFFFFFFF); + + Result rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, new ProgramId(applicationId.Value), + SaveDataType.Device, Fs.SaveData.InvalidUserId, 0); + if (rc.IsFailure()) return rc; + + rc = fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId.User, + in attribute, in extraDataMask); + if (rc.IsFailure()) return rc; + + saveSize = extraData.DataSize; + journalSize = extraData.JournalSize; + + return Result.Success; + } + } +} diff --git a/src/LibHac/Fs/Shim/SdCard.cs b/src/LibHac/Fs/Shim/SdCard.cs index bce81ae0..2f12e400 100644 --- a/src/LibHac/Fs/Shim/SdCard.cs +++ b/src/LibHac/Fs/Shim/SdCard.cs @@ -10,202 +10,201 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +[SkipLocalsInit] +public static class SdCard { - [SkipLocalsInit] - public static class SdCard + private static Result OpenSdCardFileSystem(FileSystemClient fs, ref SharedRef outFileSystem) { - private static Result OpenSdCardFileSystem(FileSystemClient fs, ref SharedRef outFileSystem) + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + return OpenFileSystem(fs, ref fileSystemProxy.Ref(), ref outFileSystem); + + static Result OpenFileSystem(FileSystemClient fs, ref SharedRef fileSystemProxy, + ref SharedRef outFileSystem) { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + // Retry a few times if the storage device isn't ready yet + const int maxRetries = 10; + const int retryInterval = 1000; - return OpenFileSystem(fs, ref fileSystemProxy.Ref(), ref outFileSystem); - - static Result OpenFileSystem(FileSystemClient fs, ref SharedRef fileSystemProxy, - ref SharedRef outFileSystem) + for (int i = 0; i < maxRetries; i++) { - // Retry a few times if the storage device isn't ready yet - const int maxRetries = 10; - const int retryInterval = 1000; + Result rc = fileSystemProxy.Get.OpenSdCardFileSystem(ref outFileSystem); - for (int i = 0; i < maxRetries; i++) - { - Result rc = fileSystemProxy.Get.OpenSdCardFileSystem(ref outFileSystem); + if (rc.IsSuccess()) + break; - if (rc.IsSuccess()) - break; + if (!ResultFs.StorageDeviceNotReady.Includes(rc)) + return rc; - if (!ResultFs.StorageDeviceNotReady.Includes(rc)) - return rc; + if (i == maxRetries - 1) + return rc; - if (i == maxRetries - 1) - return rc; - - fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); - } - - return Result.Success; + fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); } - } - - private static Result RegisterFileSystem(FileSystemClient fs, U8Span mountName, - ref SharedRef fileSystem) - { - using var fileSystemAdapter = - new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem)); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInSdCardA.Log(); - - return fs.Register(mountName, ref fileSystemAdapter.Ref()); - } - - public static Result MountSdCard(this FileSystemClient fs, U8Span mountName) - { - Result rc; - Span logBuffer = stackalloc byte[0x30]; - - // Check if the mount name is valid - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = fs.Impl.CheckMountName(mountName); - Tick end = fs.Hos.Os.GetSystemTick(); - - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName); - logBuffer = sb.Buffer; - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = fs.Impl.CheckMountName(mountName); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Open the SD card file system - using var fileSystem = new SharedRef(); - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = OpenSdCardFileSystem(fs, ref fileSystem.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = OpenSdCardFileSystem(fs, ref fileSystem.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - // Mount the file system - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = RegisterFileSystem(fs, mountName, ref fileSystem.Ref()); - Tick end = fs.Hos.Os.GetSystemTick(); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); - } - else - { - rc = RegisterFileSystem(fs, mountName, ref fileSystem.Ref()); - } - - fs.Impl.AbortIfNeeded(rc); - if (rc.IsFailure()) return rc; - - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) - fs.Impl.EnableFileSystemAccessorAccessLog(mountName); return Result.Success; } + } - public static bool IsSdCardInserted(this FileSystemClient fs) + private static Result RegisterFileSystem(FileSystemClient fs, U8Span mountName, + ref SharedRef fileSystem) + { + using var fileSystemAdapter = + new UniqueRef(new FileSystemServiceObjectAdapter(ref fileSystem)); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInSdCardA.Log(); + + return fs.Register(mountName, ref fileSystemAdapter.Ref()); + } + + public static Result MountSdCard(this FileSystemClient fs, U8Span mountName) + { + Result rc; + Span logBuffer = stackalloc byte[0x30]; + + // Check if the mount name is valid + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - using var deviceOperator = new SharedRef(); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = fs.Impl.CheckMountName(mountName); + Tick end = fs.Hos.Os.GetSystemTick(); - Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName); + logBuffer = sb.Buffer; - rc = CheckIfInserted(fs, ref deviceOperator.Ref(), out bool isInserted); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = fs.Impl.CheckMountName(mountName); + } - return isInserted; + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; - static Result CheckIfInserted(FileSystemClient fs, ref SharedRef deviceOperator, - out bool isInserted) + // Open the SD card file system + using var fileSystem = new SharedRef(); + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = OpenSdCardFileSystem(fs, ref fileSystem.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = OpenSdCardFileSystem(fs, ref fileSystem.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + // Mount the file system + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + { + Tick start = fs.Hos.Os.GetSystemTick(); + rc = RegisterFileSystem(fs, mountName, ref fileSystem.Ref()); + Tick end = fs.Hos.Os.GetSystemTick(); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer)); + } + else + { + rc = RegisterFileSystem(fs, mountName, ref fileSystem.Ref()); + } + + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + fs.Impl.EnableFileSystemAccessorAccessLog(mountName); + + return Result.Success; + } + + public static bool IsSdCardInserted(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + using var deviceOperator = new SharedRef(); + + Result rc = fileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref()); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + rc = CheckIfInserted(fs, ref deviceOperator.Ref(), out bool isInserted); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return isInserted; + + static Result CheckIfInserted(FileSystemClient fs, ref SharedRef deviceOperator, + out bool isInserted) + { + UnsafeHelpers.SkipParamInit(out isInserted); + + // Retry a few times if the storage device isn't ready yet + const int maxRetries = 10; + const int retryInterval = 1000; + + for (int i = 0; i < maxRetries; i++) { - UnsafeHelpers.SkipParamInit(out isInserted); + Result rc = deviceOperator.Get.IsSdCardInserted(out isInserted); - // Retry a few times if the storage device isn't ready yet - const int maxRetries = 10; - const int retryInterval = 1000; + if (rc.IsSuccess()) + break; - for (int i = 0; i < maxRetries; i++) - { - Result rc = deviceOperator.Get.IsSdCardInserted(out isInserted); + if (!ResultFs.StorageDeviceNotReady.Includes(rc)) + return rc; - if (rc.IsSuccess()) - break; + if (i == maxRetries - 1) + return rc; - if (!ResultFs.StorageDeviceNotReady.Includes(rc)) - return rc; - - if (i == maxRetries - 1) - return rc; - - fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); - } - - return Result.Success; + fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval)); } - } - public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, in EncryptionSeed seed) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.SetSdCardEncryptionSeed(in seed); - fs.Impl.AbortIfNeeded(rc); - return rc; - } - - public static void SetSdCardAccessibility(this FileSystemClient fs, bool isAccessible) - { - Result rc = fs.Impl.SetSdCardAccessibility(isAccessible); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - } - - public static bool IsSdCardAccessible(this FileSystemClient fs) - { - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.IsSdCardAccessible(out bool isAccessible); - fs.Impl.LogResultErrorMessage(rc); - Abort.DoAbortUnless(rc.IsSuccess()); - - return isAccessible; - } - - public static Result SetSdCardAccessibility(this FileSystemClientImpl fs, bool isAccessible) - { - using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); - - Result rc = fileSystemProxy.Get.SetSdCardAccessibility(isAccessible); - fs.AbortIfNeeded(rc); - return rc; + return Result.Success; } } + + public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, in EncryptionSeed seed) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.SetSdCardEncryptionSeed(in seed); + fs.Impl.AbortIfNeeded(rc); + return rc; + } + + public static void SetSdCardAccessibility(this FileSystemClient fs, bool isAccessible) + { + Result rc = fs.Impl.SetSdCardAccessibility(isAccessible); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + } + + public static bool IsSdCardAccessible(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.IsSdCardAccessible(out bool isAccessible); + fs.Impl.LogResultErrorMessage(rc); + Abort.DoAbortUnless(rc.IsSuccess()); + + return isAccessible; + } + + public static Result SetSdCardAccessibility(this FileSystemClientImpl fs, bool isAccessible) + { + using SharedRef fileSystemProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.SetSdCardAccessibility(isAccessible); + fs.AbortIfNeeded(rc); + return rc; + } } diff --git a/src/LibHac/Fs/Shim/SystemSaveData.cs b/src/LibHac/Fs/Shim/SystemSaveData.cs index d1b55e84..c55c85c0 100644 --- a/src/LibHac/Fs/Shim/SystemSaveData.cs +++ b/src/LibHac/Fs/Shim/SystemSaveData.cs @@ -9,90 +9,89 @@ using static LibHac.Fs.Impl.AccessLogStrings; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.Fs.Shim +namespace LibHac.Fs.Shim; + +[SkipLocalsInit] +public static class SystemSaveData { - [SkipLocalsInit] - public static class SystemSaveData + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId) { - public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId) + return fs.MountSystemSaveData(mountName, saveDataId, Fs.SaveData.InvalidUserId); + } + + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, + ulong saveDataId) + { + return fs.MountSystemSaveData(mountName, spaceId, saveDataId, Fs.SaveData.InvalidUserId); + } + + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId, + UserId userId) + { + return fs.MountSystemSaveData(mountName, SaveDataSpaceId.System, saveDataId, userId); + } + + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, + SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) + { + Result rc; + Span logBuffer = stackalloc byte[0x90]; + + if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) { - return fs.MountSystemSaveData(mountName, saveDataId, Fs.SaveData.InvalidUserId); + Tick start = fs.Hos.Os.GetSystemTick(); + rc = Mount(fs, mountName, spaceId, saveDataId, userId); + Tick end = fs.Hos.Os.GetSystemTick(); + + var idString = new IdString(); + var sb = new U8StringBuilder(logBuffer, true); + sb.Append(LogName).Append(mountName) + .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) + .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') + .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); + + fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + } + else + { + rc = Mount(fs, mountName, spaceId, saveDataId, userId); } - public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, - ulong saveDataId) - { - return fs.MountSystemSaveData(mountName, spaceId, saveDataId, Fs.SaveData.InvalidUserId); - } + fs.Impl.AbortIfNeeded(rc); + return rc; - public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId, + static Result Mount(FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) { - return fs.MountSystemSaveData(mountName, SaveDataSpaceId.System, saveDataId, userId); - } + Result rc = fs.Impl.CheckMountName(mountName); + if (rc.IsFailure()) return rc; - public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, - SaveDataSpaceId spaceId, ulong saveDataId, UserId userId) - { - Result rc; - Span logBuffer = stackalloc byte[0x90]; + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System)) + rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, + SaveDataType.System, userId, saveDataId); + if (rc.IsFailure()) return rc; + + using var fileSystem = new SharedRef(); + + rc = fileSystemProxy.Get.OpenSaveDataFileSystemBySystemSaveDataId(ref fileSystem.Ref(), spaceId, in attribute); + if (rc.IsFailure()) return rc; + + var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref()); + using var fileSystemAdapter = new UniqueRef(fileSystemAdapterRaw); + + if (!fileSystemAdapter.HasValue) + return ResultFs.AllocationMemoryFailedInSystemSaveDataA.Log(); + + if (spaceId == SaveDataSpaceId.System) { - Tick start = fs.Hos.Os.GetSystemTick(); - rc = Mount(fs, mountName, spaceId, saveDataId, userId); - Tick end = fs.Hos.Os.GetSystemTick(); - - var idString = new IdString(); - var sb = new U8StringBuilder(logBuffer, true); - sb.Append(LogName).Append(mountName) - .Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId)) - .Append(LogSaveDataId).AppendFormat(saveDataId, 'X') - .Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16); - - fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer)); + using var mountNameGenerator = new UniqueRef(); + return fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), + ref mountNameGenerator.Ref(), false, false); } else { - rc = Mount(fs, mountName, spaceId, saveDataId, userId); - } - - fs.Impl.AbortIfNeeded(rc); - return rc; - - static Result Mount(FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, - UserId userId) - { - Result rc = fs.Impl.CheckMountName(mountName); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); - - rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId, - SaveDataType.System, userId, saveDataId); - if (rc.IsFailure()) return rc; - - using var fileSystem = new SharedRef(); - - rc = fileSystemProxy.Get.OpenSaveDataFileSystemBySystemSaveDataId(ref fileSystem.Ref(), spaceId, in attribute); - if (rc.IsFailure()) return rc; - - var fileSystemAdapterRaw = new FileSystemServiceObjectAdapter(ref fileSystem.Ref()); - using var fileSystemAdapter = new UniqueRef(fileSystemAdapterRaw); - - if (!fileSystemAdapter.HasValue) - return ResultFs.AllocationMemoryFailedInSystemSaveDataA.Log(); - - if (spaceId == SaveDataSpaceId.System) - { - using var mountNameGenerator = new UniqueRef(); - return fs.Register(mountName, fileSystemAdapterRaw, ref fileSystemAdapter.Ref(), - ref mountNameGenerator.Ref(), false, false); - } - else - { - return fs.Register(mountName, ref fileSystemAdapter.Ref()); - } + return fs.Register(mountName, ref fileSystemAdapter.Ref()); } } } diff --git a/src/LibHac/Fs/StringTraits.cs b/src/LibHac/Fs/StringTraits.cs index 0cca0a33..fd46a55f 100644 --- a/src/LibHac/Fs/StringTraits.cs +++ b/src/LibHac/Fs/StringTraits.cs @@ -1,11 +1,10 @@ -namespace LibHac.Fs +namespace LibHac.Fs; + +internal static class StringTraits { - internal static class StringTraits - { - public const byte DirectorySeparator = (byte)'/'; - public const byte AltDirectorySeparator = (byte)'\\'; - public const byte DriveSeparator = (byte)':'; - public const byte Dot = (byte)'.'; - public const byte NullTerminator = 0; - } + public const byte DirectorySeparator = (byte)'/'; + public const byte AltDirectorySeparator = (byte)'\\'; + public const byte DriveSeparator = (byte)':'; + public const byte Dot = (byte)'.'; + public const byte NullTerminator = 0; } diff --git a/src/LibHac/Fs/SubStorage.cs b/src/LibHac/Fs/SubStorage.cs index e141ac01..115d4a4b 100644 --- a/src/LibHac/Fs/SubStorage.cs +++ b/src/LibHac/Fs/SubStorage.cs @@ -2,225 +2,224 @@ using LibHac.Common; using LibHac.Diag; -namespace LibHac.Fs +namespace LibHac.Fs; + +/// +/// Presents a subsection of a base IStorage as a new IStorage. +/// +/// +/// A SubStorage presents a sub-range of an IStorage as a separate IStorage. +/// +/// The SubStorage doesn't check if the offset and size provided are actually in the base storage. +/// GetSize will return the size given to the SubStorage at initialization and will not query +/// the base storage's size. +/// +/// A SubStorage is non-resizable by default. may be used to mark +/// the SubStorage as resizable. The SubStorage may only be resized if the end of the SubStorage +/// is located at the end of the base storage. When resizing the SubStorage, the base storage +/// will be resized to the appropriate length. +/// +public class SubStorage : IStorage { + private SharedRef _sharedBaseStorage; + protected IStorage BaseStorage; + private long _offset; + private long _size; + private bool _isResizable; + /// - /// Presents a subsection of a base IStorage as a new IStorage. + /// Creates an uninitialized . It must be initialized with before using. + /// + public SubStorage() + { + BaseStorage = null; + _offset = 0; + _size = 0; + _isResizable = false; + } + + /// + /// Initializes a new as a copy of . + /// will not be disposed when the created is disposed. + /// + /// The to create a copy of. + public SubStorage(SubStorage other) + { + BaseStorage = other.BaseStorage; + _offset = other._offset; + _size = other._size; + _isResizable = other._isResizable; + _sharedBaseStorage = SharedRef.CreateCopy(in other._sharedBaseStorage); + } + + /// + /// Creates a from a subsection of another . + /// will not be disposed when the created is disposed. + /// + /// The base . Caller retains ownership. + /// The offset in the base storage at which to begin the created SubStorage. + /// The size of the created SubStorage. + public SubStorage(IStorage baseStorage, long offset, long size) + { + BaseStorage = baseStorage; + _offset = offset; + _size = size; + _isResizable = false; + + Assert.SdkRequiresNotNull(baseStorage); + Assert.SdkRequiresLessEqual(0, offset); + Assert.SdkRequiresLessEqual(0, size); + } + + /// + /// Creates a from a subsection of another . + /// will not be disposed when the created is disposed. + /// Any shared references to the base will be copied over. /// /// - /// A SubStorage presents a sub-range of an IStorage as a separate IStorage. - /// - /// The SubStorage doesn't check if the offset and size provided are actually in the base storage. - /// GetSize will return the size given to the SubStorage at initialization and will not query - /// the base storage's size. - /// - /// A SubStorage is non-resizable by default. may be used to mark - /// the SubStorage as resizable. The SubStorage may only be resized if the end of the SubStorage - /// is located at the end of the base storage. When resizing the SubStorage, the base storage - /// will be resized to the appropriate length. + /// The created SubStorage will directly use the base of + /// and will adjust the and accordingly. + /// This avoids the overhead of going through two SubStorage layers. /// - public class SubStorage : IStorage + /// The base SubStorage. + /// The offset in the base storage at which to begin the created SubStorage. + /// The size of the SubStorage. + public SubStorage(SubStorage other, long offset, long size) { - private SharedRef _sharedBaseStorage; - protected IStorage BaseStorage; - private long _offset; - private long _size; - private bool _isResizable; + BaseStorage = other.BaseStorage; + _offset = other._offset + offset; + _size = size; + _isResizable = false; + _sharedBaseStorage = SharedRef.CreateCopy(in other._sharedBaseStorage); - /// - /// Creates an uninitialized . It must be initialized with before using. - /// - public SubStorage() - { - BaseStorage = null; - _offset = 0; - _size = 0; - _isResizable = false; - } + Assert.SdkRequiresLessEqual(0, offset); + Assert.SdkRequiresLessEqual(0, size); + Assert.SdkRequires(other.IsValid()); + Assert.SdkRequiresGreaterEqual(other._size, offset + size); + } - /// - /// Initializes a new as a copy of . - /// will not be disposed when the created is disposed. - /// - /// The to create a copy of. - public SubStorage(SubStorage other) + /// + /// Creates a from a subsection of another . + /// Holds a reference to until disposed. + /// + /// The base . + /// The offset in the base storage at which to begin the created SubStorage. + /// The size of the created SubStorage. + public SubStorage(in SharedRef baseStorage, long offset, long size) + { + BaseStorage = baseStorage.Get; + _offset = offset; + _size = size; + _isResizable = false; + _sharedBaseStorage = SharedRef.CreateCopy(in baseStorage); + + Assert.SdkRequiresNotNull(baseStorage.Get); + Assert.SdkRequiresLessEqual(0, _offset); + Assert.SdkRequiresLessEqual(0, _size); + } + + public override void Dispose() + { + _sharedBaseStorage.Destroy(); + base.Dispose(); + } + + /// + /// Sets this to be a copy of . + /// Any shared references in will be copied. + /// + /// The used to initialize this one. + public void Set(SubStorage other) + { + if (this != other) { BaseStorage = other.BaseStorage; _offset = other._offset; _size = other._size; _isResizable = other._isResizable; - _sharedBaseStorage = SharedRef.CreateCopy(in other._sharedBaseStorage); - } - - /// - /// Creates a from a subsection of another . - /// will not be disposed when the created is disposed. - /// - /// The base . Caller retains ownership. - /// The offset in the base storage at which to begin the created SubStorage. - /// The size of the created SubStorage. - public SubStorage(IStorage baseStorage, long offset, long size) - { - BaseStorage = baseStorage; - _offset = offset; - _size = size; - _isResizable = false; - - Assert.SdkRequiresNotNull(baseStorage); - Assert.SdkRequiresLessEqual(0, offset); - Assert.SdkRequiresLessEqual(0, size); - } - - /// - /// Creates a from a subsection of another . - /// will not be disposed when the created is disposed. - /// Any shared references to the base will be copied over. - /// - /// - /// The created SubStorage will directly use the base of - /// and will adjust the and accordingly. - /// This avoids the overhead of going through two SubStorage layers. - /// - /// The base SubStorage. - /// The offset in the base storage at which to begin the created SubStorage. - /// The size of the SubStorage. - public SubStorage(SubStorage other, long offset, long size) - { - BaseStorage = other.BaseStorage; - _offset = other._offset + offset; - _size = size; - _isResizable = false; - _sharedBaseStorage = SharedRef.CreateCopy(in other._sharedBaseStorage); - - Assert.SdkRequiresLessEqual(0, offset); - Assert.SdkRequiresLessEqual(0, size); - Assert.SdkRequires(other.IsValid()); - Assert.SdkRequiresGreaterEqual(other._size, offset + size); - } - - /// - /// Creates a from a subsection of another . - /// Holds a reference to until disposed. - /// - /// The base . - /// The offset in the base storage at which to begin the created SubStorage. - /// The size of the created SubStorage. - public SubStorage(in SharedRef baseStorage, long offset, long size) - { - BaseStorage = baseStorage.Get; - _offset = offset; - _size = size; - _isResizable = false; - _sharedBaseStorage = SharedRef.CreateCopy(in baseStorage); - - Assert.SdkRequiresNotNull(baseStorage.Get); - Assert.SdkRequiresLessEqual(0, _offset); - Assert.SdkRequiresLessEqual(0, _size); - } - - public override void Dispose() - { - _sharedBaseStorage.Destroy(); - base.Dispose(); - } - - /// - /// Sets this to be a copy of . - /// Any shared references in will be copied. - /// - /// The used to initialize this one. - public void Set(SubStorage other) - { - if (this != other) - { - BaseStorage = other.BaseStorage; - _offset = other._offset; - _size = other._size; - _isResizable = other._isResizable; - _sharedBaseStorage.SetByCopy(in other._sharedBaseStorage); - } - } - - private bool IsValid() => BaseStorage != null; - - /// - /// Sets whether the is resizable or not. - /// - /// if the should - /// be resizable. if not. - public void SetResizable(bool isResizable) - { - _isResizable = isResizable; - } - - protected override Result DoRead(long offset, Span destination) - { - if (!IsValid()) return ResultFs.NotInitialized.Log(); - if (destination.Length == 0) return Result.Success; - - if (!CheckAccessRange(offset, destination.Length, _size)) - return ResultFs.OutOfRange.Log(); - - return BaseStorage.Read(_offset + offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - if (!IsValid()) return ResultFs.NotInitialized.Log(); - if (source.Length == 0) return Result.Success; - - if (!CheckAccessRange(offset, source.Length, _size)) - return ResultFs.OutOfRange.Log(); - - return BaseStorage.Write(_offset + offset, source); - } - - protected override Result DoFlush() - { - if (!IsValid()) return ResultFs.NotInitialized.Log(); - - return BaseStorage.Flush(); - } - - protected override Result DoSetSize(long size) - { - if (!IsValid()) return ResultFs.NotInitialized.Log(); - if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log(); - if (!CheckOffsetAndSize(_offset, size)) return ResultFs.InvalidSize.Log(); - - Result rc = BaseStorage.GetSize(out long currentSize); - if (rc.IsFailure()) return rc; - - if (currentSize != _offset + _size) - { - // SubStorage cannot be resized unless it is located at the end of the base storage. - return ResultFs.UnsupportedSetSizeForResizableSubStorage.Log(); - } - - rc = BaseStorage.SetSize(_offset + size); - if (rc.IsFailure()) return rc; - - _size = size; - return Result.Success; - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - if (!IsValid()) return ResultFs.NotInitialized.Log(); - - size = _size; - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - if (!IsValid()) return ResultFs.NotInitialized.Log(); - if (size == 0) return Result.Success; - if (!CheckOffsetAndSize(_offset, size)) return ResultFs.OutOfRange.Log(); - - return base.DoOperateRange(outBuffer, operationId, _offset + offset, size, inBuffer); + _sharedBaseStorage.SetByCopy(in other._sharedBaseStorage); } } + + private bool IsValid() => BaseStorage != null; + + /// + /// Sets whether the is resizable or not. + /// + /// if the should + /// be resizable. if not. + public void SetResizable(bool isResizable) + { + _isResizable = isResizable; + } + + protected override Result DoRead(long offset, Span destination) + { + if (!IsValid()) return ResultFs.NotInitialized.Log(); + if (destination.Length == 0) return Result.Success; + + if (!CheckAccessRange(offset, destination.Length, _size)) + return ResultFs.OutOfRange.Log(); + + return BaseStorage.Read(_offset + offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + if (!IsValid()) return ResultFs.NotInitialized.Log(); + if (source.Length == 0) return Result.Success; + + if (!CheckAccessRange(offset, source.Length, _size)) + return ResultFs.OutOfRange.Log(); + + return BaseStorage.Write(_offset + offset, source); + } + + protected override Result DoFlush() + { + if (!IsValid()) return ResultFs.NotInitialized.Log(); + + return BaseStorage.Flush(); + } + + protected override Result DoSetSize(long size) + { + if (!IsValid()) return ResultFs.NotInitialized.Log(); + if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log(); + if (!CheckOffsetAndSize(_offset, size)) return ResultFs.InvalidSize.Log(); + + Result rc = BaseStorage.GetSize(out long currentSize); + if (rc.IsFailure()) return rc; + + if (currentSize != _offset + _size) + { + // SubStorage cannot be resized unless it is located at the end of the base storage. + return ResultFs.UnsupportedSetSizeForResizableSubStorage.Log(); + } + + rc = BaseStorage.SetSize(_offset + size); + if (rc.IsFailure()) return rc; + + _size = size; + return Result.Success; + } + + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + if (!IsValid()) return ResultFs.NotInitialized.Log(); + + size = _size; + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + if (!IsValid()) return ResultFs.NotInitialized.Log(); + if (size == 0) return Result.Success; + if (!CheckOffsetAndSize(_offset, size)) return ResultFs.OutOfRange.Log(); + + return base.DoOperateRange(outBuffer, operationId, _offset + offset, size, inBuffer); + } } diff --git a/src/LibHac/Fs/UserId.cs b/src/LibHac/Fs/UserId.cs index e4d37178..661a8c37 100644 --- a/src/LibHac/Fs/UserId.cs +++ b/src/LibHac/Fs/UserId.cs @@ -3,57 +3,56 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Fs +namespace LibHac.Fs; + +[DebuggerDisplay("0x{ToString(),nq}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct UserId : IEquatable, IComparable, IComparable { - [DebuggerDisplay("0x{ToString(),nq}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct UserId : IEquatable, IComparable, IComparable + public static UserId InvalidId => default; + + public readonly Id128 Id; + + public UserId(ulong high, ulong low) { - public static UserId InvalidId => default; - - public readonly Id128 Id; - - public UserId(ulong high, ulong low) - { - Id = new Id128(high, low); - } - - public UserId(ReadOnlySpan uid) - { - Id = new Id128(uid); - } - - public override string ToString() - { - return $"{Id.High:X16}{Id.Low:X16}"; - } - - public bool Equals(UserId other) => Id == other.Id; - public override bool Equals(object obj) => obj is UserId other && Equals(other); - - public override int GetHashCode() => Id.GetHashCode(); - - public int CompareTo(UserId other) => Id.CompareTo(other.Id); - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is UserId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(UserId)}"); - } - - public void ToBytes(Span output) => Id.ToBytes(output); - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(UserId left, UserId right) => left.Equals(right); - public static bool operator !=(UserId left, UserId right) => !left.Equals(right); - - public static bool operator <(UserId left, UserId right) => left.CompareTo(right) < 0; - public static bool operator >(UserId left, UserId right) => left.CompareTo(right) > 0; - public static bool operator <=(UserId left, UserId right) => left.CompareTo(right) <= 0; - public static bool operator >=(UserId left, UserId right) => left.CompareTo(right) >= 0; + Id = new Id128(high, low); } + + public UserId(ReadOnlySpan uid) + { + Id = new Id128(uid); + } + + public override string ToString() + { + return $"{Id.High:X16}{Id.Low:X16}"; + } + + public bool Equals(UserId other) => Id == other.Id; + public override bool Equals(object obj) => obj is UserId other && Equals(other); + + public override int GetHashCode() => Id.GetHashCode(); + + public int CompareTo(UserId other) => Id.CompareTo(other.Id); + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is UserId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(UserId)}"); + } + + public void ToBytes(Span output) => Id.ToBytes(output); + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(UserId left, UserId right) => left.Equals(right); + public static bool operator !=(UserId left, UserId right) => !left.Equals(right); + + public static bool operator <(UserId left, UserId right) => left.CompareTo(right) < 0; + public static bool operator >(UserId left, UserId right) => left.CompareTo(right) > 0; + public static bool operator <=(UserId left, UserId right) => left.CompareTo(right) <= 0; + public static bool operator >=(UserId left, UserId right) => left.CompareTo(right) >= 0; } diff --git a/src/LibHac/FsSrv/AccessFailureManagementService.cs b/src/LibHac/FsSrv/AccessFailureManagementService.cs index c8df77d2..3940cc54 100644 --- a/src/LibHac/FsSrv/AccessFailureManagementService.cs +++ b/src/LibHac/FsSrv/AccessFailureManagementService.cs @@ -4,100 +4,99 @@ using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct AccessFailureManagementService { - public readonly struct AccessFailureManagementService + private readonly AccessFailureManagementServiceImpl _serviceImpl; + private readonly ulong _processId; + + public AccessFailureManagementService(AccessFailureManagementServiceImpl serviceImpl, ulong processId) { - private readonly AccessFailureManagementServiceImpl _serviceImpl; - private readonly ulong _processId; + _serviceImpl = serviceImpl; + _processId = processId; + } - public AccessFailureManagementService(AccessFailureManagementServiceImpl serviceImpl, ulong processId) - { - _serviceImpl = serviceImpl; - _processId = processId; - } + internal Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } - internal Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } + public Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outNotifier, + ulong processId, bool notifyOnDeepRetry) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outNotifier, - ulong processId, bool notifyOnDeepRetry) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.OpenAccessFailureDetectionEventNotifier)) + return ResultFs.PermissionDenied.Log(); - if (!programInfo.AccessControl.CanCall(OperationType.OpenAccessFailureDetectionEventNotifier)) - return ResultFs.PermissionDenied.Log(); + using var notifier = new UniqueRef(); - using var notifier = new UniqueRef(); + rc = _serviceImpl.CreateNotifier(ref notifier.Ref(), processId, notifyOnDeepRetry); + if (rc.IsFailure()) return rc; - rc = _serviceImpl.CreateNotifier(ref notifier.Ref(), processId, notifyOnDeepRetry); - if (rc.IsFailure()) return rc; + outNotifier.Set(ref notifier.Ref()); + return Result.Success; + } - outNotifier.Set(ref notifier.Ref()); - return Result.Success; - } + public Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle) + { + UnsafeHelpers.SkipParamInit(out eventHandle); - public Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle) - { - UnsafeHelpers.SkipParamInit(out eventHandle); + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.GetAccessFailureDetectionEvent)) + return ResultFs.PermissionDenied.Log(); - if (!programInfo.AccessControl.CanCall(OperationType.GetAccessFailureDetectionEvent)) - return ResultFs.PermissionDenied.Log(); + Svc.Handle handle = _serviceImpl.GetEvent(); + eventHandle = new NativeHandle(_serviceImpl.HorizonClient.Os, handle, false); - Svc.Handle handle = _serviceImpl.GetEvent(); - eventHandle = new NativeHandle(_serviceImpl.HorizonClient.Os, handle, false); + return Result.Success; + } - return Result.Success; - } + public Result IsAccessFailureDetected(out bool isDetected, ulong processId) + { + UnsafeHelpers.SkipParamInit(out isDetected); - public Result IsAccessFailureDetected(out bool isDetected, ulong processId) - { - UnsafeHelpers.SkipParamInit(out isDetected); + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.IsAccessFailureDetected)) + return ResultFs.PermissionDenied.Log(); - if (!programInfo.AccessControl.CanCall(OperationType.IsAccessFailureDetected)) - return ResultFs.PermissionDenied.Log(); + isDetected = _serviceImpl.IsAccessFailureDetectionNotified(processId); + return Result.Success; + } - isDetected = _serviceImpl.IsAccessFailureDetectionNotified(processId); - return Result.Success; - } + public Result ResolveAccessFailure(ulong processId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result ResolveAccessFailure(ulong processId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.ResolveAccessFailure)) + return ResultFs.PermissionDenied.Log(); - if (!programInfo.AccessControl.CanCall(OperationType.ResolveAccessFailure)) - return ResultFs.PermissionDenied.Log(); + _serviceImpl.ResetAccessFailureDetection(processId); - _serviceImpl.ResetAccessFailureDetection(processId); + // Todo: Modify ServiceContext - // Todo: Modify ServiceContext + return Result.Success; + } - return Result.Success; - } + public Result AbandonAccessFailure(ulong processId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result AbandonAccessFailure(ulong processId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.AbandonAccessFailure)) + return ResultFs.PermissionDenied.Log(); - if (!programInfo.AccessControl.CanCall(OperationType.AbandonAccessFailure)) - return ResultFs.PermissionDenied.Log(); + _serviceImpl.DisableAccessFailureDetection(processId); - _serviceImpl.DisableAccessFailureDetection(processId); + // Todo: Modify ServiceContext - // Todo: Modify ServiceContext - - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/FsSrv/AccessFailureManagementServiceImpl.cs b/src/LibHac/FsSrv/AccessFailureManagementServiceImpl.cs index 1c4b2d60..d703771d 100644 --- a/src/LibHac/FsSrv/AccessFailureManagementServiceImpl.cs +++ b/src/LibHac/FsSrv/AccessFailureManagementServiceImpl.cs @@ -4,66 +4,65 @@ using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; using LibHac.Svc; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class AccessFailureManagementServiceImpl { - public class AccessFailureManagementServiceImpl + private Configuration _config; + private AccessFailureDetectionEventManager _eventManager; + + internal HorizonClient HorizonClient => _config.FsServer.Hos; + + public AccessFailureManagementServiceImpl(in Configuration configuration) { - private Configuration _config; - private AccessFailureDetectionEventManager _eventManager; + _config = configuration; + _eventManager = new AccessFailureDetectionEventManager(); + } - internal HorizonClient HorizonClient => _config.FsServer.Hos; + // LibHac addition + public struct Configuration + { + public FileSystemServer FsServer; + } - public AccessFailureManagementServiceImpl(in Configuration configuration) - { - _config = configuration; - _eventManager = new AccessFailureDetectionEventManager(); - } + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); + } - // LibHac addition - public struct Configuration - { - public FileSystemServer FsServer; - } + public Result CreateNotifier(ref UniqueRef notifier, ulong processId, bool notifyOnDeepRetry) + { + return _eventManager.CreateNotifier(ref notifier, processId, notifyOnDeepRetry); + } - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + public void ResetAccessFailureDetection(ulong processId) + { + _eventManager.ResetAccessFailureDetection(processId); + } - public Result CreateNotifier(ref UniqueRef notifier, ulong processId, bool notifyOnDeepRetry) - { - return _eventManager.CreateNotifier(ref notifier, processId, notifyOnDeepRetry); - } + public void DisableAccessFailureDetection(ulong processId) + { + _eventManager.DisableAccessFailureDetection(processId); + } - public void ResetAccessFailureDetection(ulong processId) - { - _eventManager.ResetAccessFailureDetection(processId); - } + public void NotifyAccessFailureDetection(ulong processId) + { + _eventManager.NotifyAccessFailureDetection(processId); + } - public void DisableAccessFailureDetection(ulong processId) - { - _eventManager.DisableAccessFailureDetection(processId); - } + public bool IsAccessFailureDetectionNotified(ulong processId) + { + return _eventManager.IsAccessFailureDetectionNotified(processId); + } - public void NotifyAccessFailureDetection(ulong processId) - { - _eventManager.NotifyAccessFailureDetection(processId); - } + public Handle GetEvent() + { + return _eventManager.GetEvent(); + } - public bool IsAccessFailureDetectionNotified(ulong processId) - { - return _eventManager.IsAccessFailureDetectionNotified(processId); - } - - public Handle GetEvent() - { - return _eventManager.GetEvent(); - } - - public Result HandleResolubleAccessFailure(out bool wasDeferred, Result nonDeferredResult, ulong processId) - { - throw new NotImplementedException(); - } + public Result HandleResolubleAccessFailure(out bool wasDeferred, Result nonDeferredResult, ulong processId) + { + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSrv/AccessLogService.cs b/src/LibHac/FsSrv/AccessLogService.cs index 33b67002..549bebd4 100644 --- a/src/LibHac/FsSrv/AccessLogService.cs +++ b/src/LibHac/FsSrv/AccessLogService.cs @@ -3,74 +3,73 @@ using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +internal readonly struct AccessLogService { - internal readonly struct AccessLogService + private readonly AccessLogServiceImpl _serviceImpl; + private readonly ulong _processId; + + public AccessLogService(AccessLogServiceImpl serviceImpl, ulong processId) { - private readonly AccessLogServiceImpl _serviceImpl; - private readonly ulong _processId; - - public AccessLogService(AccessLogServiceImpl serviceImpl, ulong processId) - { - _serviceImpl = serviceImpl; - _processId = processId; - } - - public Result SetAccessLogMode(GlobalAccessLogMode mode) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.SetGlobalAccessLogMode)) - return ResultFs.PermissionDenied.Log(); - - _serviceImpl.SetAccessLogMode(mode); - return Result.Success; - } - - public Result GetAccessLogMode(out GlobalAccessLogMode mode) - { - mode = _serviceImpl.GetAccessLogMode(); - return Result.Success; - } - - public Result OutputAccessLogToSdCard(InBuffer textBuffer) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - return _serviceImpl.OutputAccessLogToSdCard(textBuffer.Buffer, programInfo.ProgramIdValue, _processId); - } - - public Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo) - { - throw new NotImplementedException(); - } - - public Result OutputMultiProgramTagAccessLog() - { - _serviceImpl.OutputAccessLogToSdCard(MultiProgramTag, _processId).IgnoreResult(); - return Result.Success; - } - - public Result FlushAccessLogOnSdCard() - { - throw new NotImplementedException(); - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } - - private static ReadOnlySpan MultiProgramTag => // FS_ACCESS: { multi_program_tag: true }\n - new[] - { - (byte) 'F', (byte) 'S', (byte) '_', (byte) 'A', (byte) 'C', (byte) 'C', (byte) 'E', (byte) 'S', - (byte) 'S', (byte) ':', (byte) ' ', (byte) '{', (byte) ' ', (byte) 'm', (byte) 'u', (byte) 'l', - (byte) 't', (byte) 'i', (byte) '_', (byte) 'p', (byte) 'r', (byte) 'o', (byte) 'g', (byte) 'r', - (byte) 'a', (byte) 'm', (byte) '_', (byte) 't', (byte) 'a', (byte) 'g', (byte) ':', (byte) ' ', - (byte) 't', (byte) 'r', (byte) 'u', (byte) 'e', (byte) ' ', (byte) '}', (byte) '\n' - }; + _serviceImpl = serviceImpl; + _processId = processId; } + + public Result SetAccessLogMode(GlobalAccessLogMode mode) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetGlobalAccessLogMode)) + return ResultFs.PermissionDenied.Log(); + + _serviceImpl.SetAccessLogMode(mode); + return Result.Success; + } + + public Result GetAccessLogMode(out GlobalAccessLogMode mode) + { + mode = _serviceImpl.GetAccessLogMode(); + return Result.Success; + } + + public Result OutputAccessLogToSdCard(InBuffer textBuffer) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + return _serviceImpl.OutputAccessLogToSdCard(textBuffer.Buffer, programInfo.ProgramIdValue, _processId); + } + + public Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo) + { + throw new NotImplementedException(); + } + + public Result OutputMultiProgramTagAccessLog() + { + _serviceImpl.OutputAccessLogToSdCard(MultiProgramTag, _processId).IgnoreResult(); + return Result.Success; + } + + public Result FlushAccessLogOnSdCard() + { + throw new NotImplementedException(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + + private static ReadOnlySpan MultiProgramTag => // FS_ACCESS: { multi_program_tag: true }\n + new[] + { + (byte) 'F', (byte) 'S', (byte) '_', (byte) 'A', (byte) 'C', (byte) 'C', (byte) 'E', (byte) 'S', + (byte) 'S', (byte) ':', (byte) ' ', (byte) '{', (byte) ' ', (byte) 'm', (byte) 'u', (byte) 'l', + (byte) 't', (byte) 'i', (byte) '_', (byte) 'p', (byte) 'r', (byte) 'o', (byte) 'g', (byte) 'r', + (byte) 'a', (byte) 'm', (byte) '_', (byte) 't', (byte) 'a', (byte) 'g', (byte) ':', (byte) ' ', + (byte) 't', (byte) 'r', (byte) 'u', (byte) 'e', (byte) ' ', (byte) '}', (byte) '\n' + }; } diff --git a/src/LibHac/FsSrv/AccessLogServiceImpl.cs b/src/LibHac/FsSrv/AccessLogServiceImpl.cs index fc33fefa..fd7dc5e7 100644 --- a/src/LibHac/FsSrv/AccessLogServiceImpl.cs +++ b/src/LibHac/FsSrv/AccessLogServiceImpl.cs @@ -2,65 +2,64 @@ using LibHac.Fs; using LibHac.FsSrv.Impl; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class AccessLogServiceImpl : IDisposable { - public class AccessLogServiceImpl : IDisposable + private Configuration _config; + private GlobalAccessLogMode _accessLogMode; + + public AccessLogServiceImpl(in Configuration configuration) { - private Configuration _config; - private GlobalAccessLogMode _accessLogMode; + _config = configuration; + } - public AccessLogServiceImpl(in Configuration configuration) - { - _config = configuration; - } + public void Dispose() + { - public void Dispose() - { + } - } + public struct Configuration + { + public ulong MinimumProgramIdForSdCardLog; - public struct Configuration - { - public ulong MinimumProgramIdForSdCardLog; + // LibHac additions + public FileSystemServer FsServer; + } - // LibHac additions - public FileSystemServer FsServer; - } + public void SetAccessLogMode(GlobalAccessLogMode mode) + { + _accessLogMode = mode; + } - public void SetAccessLogMode(GlobalAccessLogMode mode) - { - _accessLogMode = mode; - } + public GlobalAccessLogMode GetAccessLogMode() + { + return _accessLogMode; + } - public GlobalAccessLogMode GetAccessLogMode() - { - return _accessLogMode; - } + public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong processId) + { + throw new NotImplementedException(); + } - public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong processId) - { - throw new NotImplementedException(); - } + public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong programId, ulong processId) + { + throw new NotImplementedException(); + } - public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong programId, ulong processId) - { - throw new NotImplementedException(); - } + public Result FlushAccessLogSdCardWriter() + { + throw new NotImplementedException(); + } - public Result FlushAccessLogSdCardWriter() - { - throw new NotImplementedException(); - } + public Result FinalizeAccessLogSdCardWriter() + { + throw new NotImplementedException(); + } - public Result FinalizeAccessLogSdCardWriter() - { - throw new NotImplementedException(); - } - - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); } } diff --git a/src/LibHac/FsSrv/BaseFileSystemService.cs b/src/LibHac/FsSrv/BaseFileSystemService.cs index 001b18f9..4c18be83 100644 --- a/src/LibHac/FsSrv/BaseFileSystemService.cs +++ b/src/LibHac/FsSrv/BaseFileSystemService.cs @@ -10,319 +10,318 @@ using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using Path = LibHac.Fs.Path; using Utility = LibHac.FsSrv.Impl.Utility; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct BaseFileSystemService { - public readonly struct BaseFileSystemService + private readonly BaseFileSystemServiceImpl _serviceImpl; + private readonly ulong _processId; + + public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId) { - private readonly BaseFileSystemServiceImpl _serviceImpl; - private readonly ulong _processId; + _serviceImpl = serviceImpl; + _processId = processId; + } - public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId) + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return GetProgramInfo(out programInfo, _processId); + } + + private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _serviceImpl.GetProgramInfo(out programInfo, processId); + } + + private Result CheckCapabilityById(BaseFileSystemId id, ulong processId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo, processId); + if (rc.IsFailure()) return rc; + + if (id == BaseFileSystemId.TemporaryDirectory) { - _serviceImpl = serviceImpl; - _processId = processId; - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return GetProgramInfo(out programInfo, _processId); - } - - private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - return _serviceImpl.GetProgramInfo(out programInfo, processId); - } - - private Result CheckCapabilityById(BaseFileSystemId id, ulong processId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo, processId); - if (rc.IsFailure()) return rc; - - if (id == BaseFileSystemId.TemporaryDirectory) - { - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - } - else - { - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) - { - Result rc = CheckCapabilityById(fileSystemId, _processId); - if (rc.IsFailure()) return rc; - - // Open the file system - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenBaseFileSystem(ref fileSystem.Ref(), fileSystemId); - if (rc.IsFailure()) return rc; - - // Create an SF adapter for the file system - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref fileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) - { - Result rc = CheckCapabilityById(fileSystemId, _processId); - if (rc.IsFailure()) return rc; - - return _serviceImpl.FormatBaseFileSystem(fileSystemId); - } - - public Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, - BisPartitionId partitionId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - // Get the permissions the caller needs - AccessibilityType requiredAccess = partitionId switch - { - BisPartitionId.CalibrationFile => AccessibilityType.MountBisCalibrationFile, - BisPartitionId.SafeMode => AccessibilityType.MountBisSafeMode, - BisPartitionId.User => AccessibilityType.MountBisUser, - BisPartitionId.System => AccessibilityType.MountBisSystem, - BisPartitionId.SystemProperPartition => AccessibilityType.MountBisSystemProperPartition, - _ => AccessibilityType.NotMount - }; - - // Reject opening invalid partitions - if (requiredAccess == AccessibilityType.NotMount) - return ResultFs.InvalidArgument.Log(); - - // Verify the caller has the required permissions - Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(requiredAccess); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - const StorageType storageFlag = StorageType.Bis; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - // Normalize the path - using var pathNormalized = new Path(); - rc = pathNormalized.Initialize(rootPath.Str); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowEmptyPath(); - rc = pathNormalized.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - // Open the file system - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenBisFileSystem(ref fileSystem.Ref(), partitionId, false); - if (rc.IsFailure()) return rc; - - using var subDirFileSystem = new SharedRef(); - rc = Utility.CreateSubDirectoryFileSystem(ref subDirFileSystem.Ref(), ref fileSystem.Ref(), - in pathNormalized); - if (rc.IsFailure()) return rc; - - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref subDirFileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path) - { - throw new NotImplementedException(); - } - - public Result CreatePaddingFile(long size) - { - // File size must be non-negative - if (size < 0) - return ResultFs.InvalidSize.Log(); - - // Caller must have the FillBis permission - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) - return ResultFs.PermissionDenied.Log(); - - return _serviceImpl.CreatePaddingFile(size); - } - - public Result DeleteAllPaddingFiles() - { - // Caller must have the FillBis permission - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) - return ResultFs.PermissionDenied.Log(); - - return _serviceImpl.DeleteAllPaddingFiles(); - } - - public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, - GameCardPartition partitionId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead) - return ResultFs.PermissionDenied.Log(); - - using var fileSystem = new SharedRef(); - - rc = _serviceImpl.OpenGameCardFileSystem(ref fileSystem.Ref(), handle, partitionId); - if (rc.IsFailure()) return rc; - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result OpenSdCardFileSystem(ref SharedRef outFileSystem) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - const StorageType storageFlag = StorageType.Bis; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); - if (rc.IsFailure()) return rc; - - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result FormatSdCardFileSystem() - { - // Caller must have the FormatSdCard permission - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard)) - return ResultFs.PermissionDenied.Log(); - - return _serviceImpl.FormatSdCardProxyFileSystem(); - } - - public Result FormatSdCardDryRun() - { - // No permissions are needed to call this method - - return _serviceImpl.FormatSdCardProxyFileSystem(); - } - - public Result IsExFatSupported(out bool isSupported) - { - // No permissions are needed to call this method - - isSupported = _serviceImpl.IsExFatSupported(); - return Result.Success; - } - - public Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, - ImageDirectoryId directoryId) - { - // Caller must have the MountImageAndVideoStorage permission - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountImageAndVideoStorage); + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory); if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); - - // Get the base file system ID - BaseFileSystemId fileSystemId; - switch (directoryId) - { - case ImageDirectoryId.Nand: - fileSystemId = BaseFileSystemId.ImageDirectoryNand; - break; - case ImageDirectoryId.SdCard: - fileSystemId = BaseFileSystemId.ImageDirectorySdCard; - break; - default: - return ResultFs.InvalidArgument.Log(); - } - - using var baseFileSystem = new SharedRef(); - rc = _serviceImpl.OpenBaseFileSystem(ref baseFileSystem.Ref(), fileSystemId); - if (rc.IsFailure()) return rc; - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref baseFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; } - - public Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, - ulong transferMemorySize) + else { - // Caller must have the OpenBisWiper permission - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem); - if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper)) + if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); - - using var bisWiper = new UniqueRef(); - rc = _serviceImpl.OpenBisWiper(ref bisWiper.Ref(), transferMemoryHandle, transferMemorySize); - if (rc.IsFailure()) return rc; - - outBisWiper.Set(ref bisWiper.Ref()); - - return Result.Success; } + + return Result.Success; + } + + public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) + { + Result rc = CheckCapabilityById(fileSystemId, _processId); + if (rc.IsFailure()) return rc; + + // Open the file system + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenBaseFileSystem(ref fileSystem.Ref(), fileSystemId); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref fileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) + { + Result rc = CheckCapabilityById(fileSystemId, _processId); + if (rc.IsFailure()) return rc; + + return _serviceImpl.FormatBaseFileSystem(fileSystemId); + } + + public Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, + BisPartitionId partitionId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + // Get the permissions the caller needs + AccessibilityType requiredAccess = partitionId switch + { + BisPartitionId.CalibrationFile => AccessibilityType.MountBisCalibrationFile, + BisPartitionId.SafeMode => AccessibilityType.MountBisSafeMode, + BisPartitionId.User => AccessibilityType.MountBisUser, + BisPartitionId.System => AccessibilityType.MountBisSystem, + BisPartitionId.SystemProperPartition => AccessibilityType.MountBisSystemProperPartition, + _ => AccessibilityType.NotMount + }; + + // Reject opening invalid partitions + if (requiredAccess == AccessibilityType.NotMount) + return ResultFs.InvalidArgument.Log(); + + // Verify the caller has the required permissions + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(requiredAccess); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + const StorageType storageFlag = StorageType.Bis; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + // Normalize the path + using var pathNormalized = new Path(); + rc = pathNormalized.Initialize(rootPath.Str); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + // Open the file system + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenBisFileSystem(ref fileSystem.Ref(), partitionId, false); + if (rc.IsFailure()) return rc; + + using var subDirFileSystem = new SharedRef(); + rc = Utility.CreateSubDirectoryFileSystem(ref subDirFileSystem.Ref(), ref fileSystem.Ref(), + in pathNormalized); + if (rc.IsFailure()) return rc; + + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref subDirFileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path) + { + throw new NotImplementedException(); + } + + public Result CreatePaddingFile(long size) + { + // File size must be non-negative + if (size < 0) + return ResultFs.InvalidSize.Log(); + + // Caller must have the FillBis permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.CreatePaddingFile(size); + } + + public Result DeleteAllPaddingFiles() + { + // Caller must have the FillBis permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.DeleteAllPaddingFiles(); + } + + public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, + GameCardPartition partitionId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead) + return ResultFs.PermissionDenied.Log(); + + using var fileSystem = new SharedRef(); + + rc = _serviceImpl.OpenGameCardFileSystem(ref fileSystem.Ref(), handle, partitionId); + if (rc.IsFailure()) return rc; + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenSdCardFileSystem(ref SharedRef outFileSystem) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + const StorageType storageFlag = StorageType.Bis; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); + if (rc.IsFailure()) return rc; + + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result FormatSdCardFileSystem() + { + // Caller must have the FormatSdCard permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.FormatSdCardProxyFileSystem(); + } + + public Result FormatSdCardDryRun() + { + // No permissions are needed to call this method + + return _serviceImpl.FormatSdCardProxyFileSystem(); + } + + public Result IsExFatSupported(out bool isSupported) + { + // No permissions are needed to call this method + + isSupported = _serviceImpl.IsExFatSupported(); + return Result.Success; + } + + public Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, + ImageDirectoryId directoryId) + { + // Caller must have the MountImageAndVideoStorage permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountImageAndVideoStorage); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + // Get the base file system ID + BaseFileSystemId fileSystemId; + switch (directoryId) + { + case ImageDirectoryId.Nand: + fileSystemId = BaseFileSystemId.ImageDirectoryNand; + break; + case ImageDirectoryId.SdCard: + fileSystemId = BaseFileSystemId.ImageDirectorySdCard; + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + using var baseFileSystem = new SharedRef(); + rc = _serviceImpl.OpenBaseFileSystem(ref baseFileSystem.Ref(), fileSystemId); + if (rc.IsFailure()) return rc; + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref baseFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize) + { + // Caller must have the OpenBisWiper permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper)) + return ResultFs.PermissionDenied.Log(); + + using var bisWiper = new UniqueRef(); + rc = _serviceImpl.OpenBisWiper(ref bisWiper.Ref(), transferMemoryHandle, transferMemorySize); + if (rc.IsFailure()) return rc; + + outBisWiper.Set(ref bisWiper.Ref()); + + return Result.Success; } } diff --git a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs index a8bceb79..df8a43d7 100644 --- a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs @@ -7,121 +7,120 @@ using LibHac.FsSrv.Sf; using LibHac.Sf; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class BaseFileSystemServiceImpl { - public class BaseFileSystemServiceImpl + private Configuration _config; + + public delegate Result BisWiperCreator(ref UniqueRef outWiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize); + + public BaseFileSystemServiceImpl(in Configuration configuration) { - private Configuration _config; + _config = configuration; + } - public delegate Result BisWiperCreator(ref UniqueRef outWiper, NativeHandle transferMemoryHandle, - ulong transferMemorySize); + public struct Configuration + { + public IBuiltInStorageFileSystemCreator BisFileSystemCreator; + public IGameCardFileSystemCreator GameCardFileSystemCreator; + public ISdCardProxyFileSystemCreator SdCardFileSystemCreator; + // CurrentTimeFunction + // FatFileSystemCacheManager + // BaseFileSystemCreatorHolder + public BisWiperCreator BisWiperCreator; - public BaseFileSystemServiceImpl(in Configuration configuration) + // LibHac additions + public FileSystemServer FsServer; + } + + public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) + { + throw new NotImplementedException(); + } + + public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) + { + throw new NotImplementedException(); + } + + public Result OpenBisFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId) + { + return OpenBisFileSystem(ref outFileSystem, partitionId, false); + } + + public Result OpenBisFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId, + bool caseSensitive) + { + Result rc = _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CreatePaddingFile(long size) + { + throw new NotImplementedException(); + } + + public Result DeleteAllPaddingFiles() + { + throw new NotImplementedException(); + } + + public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, + GameCardPartition partitionId) + { + const int maxTries = 2; + Result rc = Result.Success; + + for (int i = 0; i < maxTries; i++) { - _config = configuration; + rc = _config.GameCardFileSystemCreator.Create(ref outFileSystem, handle, partitionId); + + if (!ResultFs.DataCorrupted.Includes(rc)) + break; } - public struct Configuration - { - public IBuiltInStorageFileSystemCreator BisFileSystemCreator; - public IGameCardFileSystemCreator GameCardFileSystemCreator; - public ISdCardProxyFileSystemCreator SdCardFileSystemCreator; - // CurrentTimeFunction - // FatFileSystemCacheManager - // BaseFileSystemCreatorHolder - public BisWiperCreator BisWiperCreator; + return rc; + } - // LibHac additions - public FileSystemServer FsServer; - } + public Result OpenSdCardProxyFileSystem(ref SharedRef outFileSystem) + { + return OpenSdCardProxyFileSystem(ref outFileSystem, false); + } - public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) - { - throw new NotImplementedException(); - } + public Result OpenSdCardProxyFileSystem(ref SharedRef outFileSystem, bool openCaseSensitive) + { + return _config.SdCardFileSystemCreator.Create(ref outFileSystem, openCaseSensitive); + } - public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) - { - throw new NotImplementedException(); - } + public Result FormatSdCardProxyFileSystem() + { + return _config.SdCardFileSystemCreator.Format(); + } - public Result OpenBisFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId) - { - return OpenBisFileSystem(ref outFileSystem, partitionId, false); - } + public Result FormatSdCardDryRun() + { + throw new NotImplementedException(); + } - public Result OpenBisFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId, - bool caseSensitive) - { - Result rc = _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive); - if (rc.IsFailure()) return rc.Miss(); + public bool IsExFatSupported() + { + // Returning false should probably be fine + return false; + } - return Result.Success; - } + public Result OpenBisWiper(ref UniqueRef outBisWiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize) + { + return _config.BisWiperCreator(ref outBisWiper, transferMemoryHandle, transferMemorySize); + } - public Result CreatePaddingFile(long size) - { - throw new NotImplementedException(); - } - - public Result DeleteAllPaddingFiles() - { - throw new NotImplementedException(); - } - - public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, - GameCardPartition partitionId) - { - const int maxTries = 2; - Result rc = Result.Success; - - for (int i = 0; i < maxTries; i++) - { - rc = _config.GameCardFileSystemCreator.Create(ref outFileSystem, handle, partitionId); - - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - return rc; - } - - public Result OpenSdCardProxyFileSystem(ref SharedRef outFileSystem) - { - return OpenSdCardProxyFileSystem(ref outFileSystem, false); - } - - public Result OpenSdCardProxyFileSystem(ref SharedRef outFileSystem, bool openCaseSensitive) - { - return _config.SdCardFileSystemCreator.Create(ref outFileSystem, openCaseSensitive); - } - - public Result FormatSdCardProxyFileSystem() - { - return _config.SdCardFileSystemCreator.Format(); - } - - public Result FormatSdCardDryRun() - { - throw new NotImplementedException(); - } - - public bool IsExFatSupported() - { - // Returning false should probably be fine - return false; - } - - public Result OpenBisWiper(ref UniqueRef outBisWiper, NativeHandle transferMemoryHandle, - ulong transferMemorySize) - { - return _config.BisWiperCreator(ref outBisWiper, transferMemoryHandle, transferMemorySize); - } - - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); } } diff --git a/src/LibHac/FsSrv/BaseStorageService.cs b/src/LibHac/FsSrv/BaseStorageService.cs index 5869ade0..6300c9e2 100644 --- a/src/LibHac/FsSrv/BaseStorageService.cs +++ b/src/LibHac/FsSrv/BaseStorageService.cs @@ -8,236 +8,235 @@ using LibHac.FsSystem; using IStorage = LibHac.Fs.IStorage; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct BaseStorageService { - public readonly struct BaseStorageService + private readonly BaseStorageServiceImpl _serviceImpl; + private readonly ulong _processId; + + public BaseStorageService(BaseStorageServiceImpl serviceImpl, ulong processId) { - private readonly BaseStorageServiceImpl _serviceImpl; - private readonly ulong _processId; - - public BaseStorageService(BaseStorageServiceImpl serviceImpl, ulong processId) - { - _serviceImpl = serviceImpl; - _processId = processId; - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } - - private static Result GetAccessibilityForOpenBisPartition(out Accessibility accessibility, ProgramInfo programInfo, - BisPartitionId partitionId) - { - UnsafeHelpers.SkipParamInit(out accessibility); - - AccessibilityType type = partitionId switch - { - BisPartitionId.BootPartition1Root => AccessibilityType.OpenBisPartitionBootPartition1Root, - BisPartitionId.BootPartition2Root => AccessibilityType.OpenBisPartitionBootPartition2Root, - BisPartitionId.UserDataRoot => AccessibilityType.OpenBisPartitionUserDataRoot, - BisPartitionId.BootConfigAndPackage2Part1 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part1, - BisPartitionId.BootConfigAndPackage2Part2 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part2, - BisPartitionId.BootConfigAndPackage2Part3 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part3, - BisPartitionId.BootConfigAndPackage2Part4 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part4, - BisPartitionId.BootConfigAndPackage2Part5 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part5, - BisPartitionId.BootConfigAndPackage2Part6 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part6, - BisPartitionId.CalibrationBinary => AccessibilityType.OpenBisPartitionCalibrationBinary, - BisPartitionId.CalibrationFile => AccessibilityType.OpenBisPartitionCalibrationFile, - BisPartitionId.SafeMode => AccessibilityType.OpenBisPartitionSafeMode, - BisPartitionId.User => AccessibilityType.OpenBisPartitionUser, - BisPartitionId.System => AccessibilityType.OpenBisPartitionSystem, - BisPartitionId.SystemProperEncryption => AccessibilityType.OpenBisPartitionSystemProperEncryption, - BisPartitionId.SystemProperPartition => AccessibilityType.OpenBisPartitionSystemProperPartition, - _ => (AccessibilityType)(-1) - }; - - if (type == (AccessibilityType)(-1)) - return ResultFs.InvalidArgument.Log(); - - accessibility = programInfo.AccessControl.GetAccessibilityFor(type); - return Result.Success; - } - - public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId id) - { - var storageFlag = StorageType.Bis; - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - rc = GetAccessibilityForOpenBisPartition(out Accessibility accessibility, programInfo, id); - if (rc.IsFailure()) return rc; - - bool canAccess = accessibility.CanRead && accessibility.CanWrite; - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - - using var storage = new SharedRef(); - rc = _serviceImpl.OpenBisStorage(ref storage.Ref(), id); - if (rc.IsFailure()) return rc; - - using var typeSetStorage = - new SharedRef(new StorageLayoutTypeSetStorage(ref storage.Ref(), storageFlag)); - - // Todo: Async storage - - using var storageAdapter = - new SharedRef(new StorageInterfaceAdapter(ref typeSetStorage.Ref())); - - outStorage.SetByMove(ref storageAdapter.Ref()); - - return Result.Success; - } - - public Result InvalidateBisCache() - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.InvalidateBisCache)) - return ResultFs.PermissionDenied.Log(); - - return _serviceImpl.InvalidateBisCache(); - } - - public Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, - GameCardPartitionRaw partitionId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.OpenGameCardStorage); - - bool canAccess = accessibility.CanRead && accessibility.CanWrite; - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - - using var storage = new SharedRef(); - rc = _serviceImpl.OpenGameCardPartition(ref storage.Ref(), handle, partitionId); - if (rc.IsFailure()) return rc; - - // Todo: Async storage - - using var storageAdapter = - new SharedRef(new StorageInterfaceAdapter(ref storage.Ref())); - - outStorage.SetByMove(ref storageAdapter.Ref()); - - return Result.Success; - } - - public Result OpenDeviceOperator(ref SharedRef outDeviceOperator) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc.Miss(); - - rc = _serviceImpl.OpenDeviceOperator(ref outDeviceOperator, programInfo.AccessControl); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.OpenSdCardDetectionEventNotifier)) - return ResultFs.PermissionDenied.Log(); - - throw new NotImplementedException(); - } - - public Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.OpenGameCardDetectionEventNotifier)) - return ResultFs.PermissionDenied.Log(); - - throw new NotImplementedException(); - } - - public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.SimulateDevice)) - return ResultFs.PermissionDenied.Log(); - - throw new NotImplementedException(); - } + _serviceImpl = serviceImpl; + _processId = processId; } - public class BaseStorageServiceImpl + private Result GetProgramInfo(out ProgramInfo programInfo) { - private Configuration _config; + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } - // LibHac addition - private SharedRef _deviceOperator; + private static Result GetAccessibilityForOpenBisPartition(out Accessibility accessibility, ProgramInfo programInfo, + BisPartitionId partitionId) + { + UnsafeHelpers.SkipParamInit(out accessibility); - public BaseStorageServiceImpl(in Configuration configuration) + AccessibilityType type = partitionId switch { - _config = configuration; - _deviceOperator = new SharedRef(configuration.DeviceOperator); - } + BisPartitionId.BootPartition1Root => AccessibilityType.OpenBisPartitionBootPartition1Root, + BisPartitionId.BootPartition2Root => AccessibilityType.OpenBisPartitionBootPartition2Root, + BisPartitionId.UserDataRoot => AccessibilityType.OpenBisPartitionUserDataRoot, + BisPartitionId.BootConfigAndPackage2Part1 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part1, + BisPartitionId.BootConfigAndPackage2Part2 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part2, + BisPartitionId.BootConfigAndPackage2Part3 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part3, + BisPartitionId.BootConfigAndPackage2Part4 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part4, + BisPartitionId.BootConfigAndPackage2Part5 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part5, + BisPartitionId.BootConfigAndPackage2Part6 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part6, + BisPartitionId.CalibrationBinary => AccessibilityType.OpenBisPartitionCalibrationBinary, + BisPartitionId.CalibrationFile => AccessibilityType.OpenBisPartitionCalibrationFile, + BisPartitionId.SafeMode => AccessibilityType.OpenBisPartitionSafeMode, + BisPartitionId.User => AccessibilityType.OpenBisPartitionUser, + BisPartitionId.System => AccessibilityType.OpenBisPartitionSystem, + BisPartitionId.SystemProperEncryption => AccessibilityType.OpenBisPartitionSystemProperEncryption, + BisPartitionId.SystemProperPartition => AccessibilityType.OpenBisPartitionSystemProperPartition, + _ => (AccessibilityType)(-1) + }; - public struct Configuration - { - public IBuiltInStorageCreator BisStorageCreator; - public IGameCardStorageCreator GameCardStorageCreator; + if (type == (AccessibilityType)(-1)) + return ResultFs.InvalidArgument.Log(); - // LibHac additions - public FileSystemServer FsServer; - // Todo: The DeviceOperator in FS uses mostly global state. Decide how to handle this. - public IDeviceOperator DeviceOperator; - } + accessibility = programInfo.AccessControl.GetAccessibilityFor(type); + return Result.Success; + } - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId id) + { + var storageFlag = StorageType.Bis; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); - public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId) - { - return _config.BisStorageCreator.Create(ref outStorage, partitionId); - } + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result InvalidateBisCache() - { - return _config.BisStorageCreator.InvalidateCache(); - } + rc = GetAccessibilityForOpenBisPartition(out Accessibility accessibility, programInfo, id); + if (rc.IsFailure()) return rc; - public Result OpenGameCardPartition(ref SharedRef outStorage, GameCardHandle handle, - GameCardPartitionRaw partitionId) - { - switch (partitionId) - { - case GameCardPartitionRaw.NormalReadOnly: - return _config.GameCardStorageCreator.CreateReadOnly(handle, ref outStorage); - case GameCardPartitionRaw.SecureReadOnly: - return _config.GameCardStorageCreator.CreateSecureReadOnly(handle, ref outStorage); - case GameCardPartitionRaw.RootWriteOnly: - return _config.GameCardStorageCreator.CreateWriteOnly(handle, ref outStorage); - default: - return ResultFs.InvalidArgument.Log(); - } - } + bool canAccess = accessibility.CanRead && accessibility.CanWrite; - // LibHac addition - internal Result OpenDeviceOperator(ref SharedRef outDeviceOperator, - AccessControl accessControl) - { - outDeviceOperator.SetByCopy(in _deviceOperator); - return Result.Success; - } + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + using var storage = new SharedRef(); + rc = _serviceImpl.OpenBisStorage(ref storage.Ref(), id); + if (rc.IsFailure()) return rc; + + using var typeSetStorage = + new SharedRef(new StorageLayoutTypeSetStorage(ref storage.Ref(), storageFlag)); + + // Todo: Async storage + + using var storageAdapter = + new SharedRef(new StorageInterfaceAdapter(ref typeSetStorage.Ref())); + + outStorage.SetByMove(ref storageAdapter.Ref()); + + return Result.Success; + } + + public Result InvalidateBisCache() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.InvalidateBisCache)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.InvalidateBisCache(); + } + + public Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.OpenGameCardStorage); + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + using var storage = new SharedRef(); + rc = _serviceImpl.OpenGameCardPartition(ref storage.Ref(), handle, partitionId); + if (rc.IsFailure()) return rc; + + // Todo: Async storage + + using var storageAdapter = + new SharedRef(new StorageInterfaceAdapter(ref storage.Ref())); + + outStorage.SetByMove(ref storageAdapter.Ref()); + + return Result.Success; + } + + public Result OpenDeviceOperator(ref SharedRef outDeviceOperator) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc.Miss(); + + rc = _serviceImpl.OpenDeviceOperator(ref outDeviceOperator, programInfo.AccessControl); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSdCardDetectionEventNotifier)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } + + public Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenGameCardDetectionEventNotifier)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } + + public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SimulateDevice)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } +} + +public class BaseStorageServiceImpl +{ + private Configuration _config; + + // LibHac addition + private SharedRef _deviceOperator; + + public BaseStorageServiceImpl(in Configuration configuration) + { + _config = configuration; + _deviceOperator = new SharedRef(configuration.DeviceOperator); + } + + public struct Configuration + { + public IBuiltInStorageCreator BisStorageCreator; + public IGameCardStorageCreator GameCardStorageCreator; + + // LibHac additions + public FileSystemServer FsServer; + // Todo: The DeviceOperator in FS uses mostly global state. Decide how to handle this. + public IDeviceOperator DeviceOperator; + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); + } + + public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId) + { + return _config.BisStorageCreator.Create(ref outStorage, partitionId); + } + + public Result InvalidateBisCache() + { + return _config.BisStorageCreator.InvalidateCache(); + } + + public Result OpenGameCardPartition(ref SharedRef outStorage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + switch (partitionId) + { + case GameCardPartitionRaw.NormalReadOnly: + return _config.GameCardStorageCreator.CreateReadOnly(handle, ref outStorage); + case GameCardPartitionRaw.SecureReadOnly: + return _config.GameCardStorageCreator.CreateSecureReadOnly(handle, ref outStorage); + case GameCardPartitionRaw.RootWriteOnly: + return _config.GameCardStorageCreator.CreateWriteOnly(handle, ref outStorage); + default: + return ResultFs.InvalidArgument.Log(); + } + } + + // LibHac addition + internal Result OpenDeviceOperator(ref SharedRef outDeviceOperator, + AccessControl accessControl) + { + outDeviceOperator.SetByCopy(in _deviceOperator); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index a9b484f8..9f0317cd 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -4,49 +4,48 @@ using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.Sf; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class DefaultFsServerObjects { - public class DefaultFsServerObjects + public FileSystemCreatorInterfaces FsCreators { get; set; } + public IDeviceOperator DeviceOperator { get; set; } + public EmulatedGameCard GameCard { get; set; } + public EmulatedSdCard SdCard { get; set; } + + public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet, + FileSystemServer fsServer) { - public FileSystemCreatorInterfaces FsCreators { get; set; } - public IDeviceOperator DeviceOperator { get; set; } - public EmulatedGameCard GameCard { get; set; } - public EmulatedSdCard SdCard { get; set; } + var creators = new FileSystemCreatorInterfaces(); + var gameCard = new EmulatedGameCard(keySet); + var sdCard = new EmulatedSdCard(); - public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet, - FileSystemServer fsServer) + var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard); + + using var sharedRootFileSystem = new SharedRef(rootFileSystem); + using SharedRef sharedRootFileSystemCopy = + SharedRef.CreateCopy(in sharedRootFileSystem); + + creators.RomFileSystemCreator = new RomFileSystemCreator(); + creators.PartitionFileSystemCreator = new PartitionFileSystemCreator(); + creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet); + creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); + creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator(); + creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null); + creators.GameCardStorageCreator = gcStorageCreator; + creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); + creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); + creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(ref sharedRootFileSystem.Ref()); + creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdCard, ref sharedRootFileSystemCopy.Ref()); + + var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard); + + return new DefaultFsServerObjects { - var creators = new FileSystemCreatorInterfaces(); - var gameCard = new EmulatedGameCard(keySet); - var sdCard = new EmulatedSdCard(); - - var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard); - - using var sharedRootFileSystem = new SharedRef(rootFileSystem); - using SharedRef sharedRootFileSystemCopy = - SharedRef.CreateCopy(in sharedRootFileSystem); - - creators.RomFileSystemCreator = new RomFileSystemCreator(); - creators.PartitionFileSystemCreator = new PartitionFileSystemCreator(); - creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet); - creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); - creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator(); - creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null); - creators.GameCardStorageCreator = gcStorageCreator; - creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); - creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); - creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(ref sharedRootFileSystem.Ref()); - creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdCard, ref sharedRootFileSystemCopy.Ref()); - - var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard); - - return new DefaultFsServerObjects - { - FsCreators = creators, - DeviceOperator = deviceOperator, - GameCard = gameCard, - SdCard = sdCard - }; - } + FsCreators = creators, + DeviceOperator = deviceOperator, + GameCard = gameCard, + SdCard = sdCard + }; } } diff --git a/src/LibHac/FsSrv/Delegates.cs b/src/LibHac/FsSrv/Delegates.cs index 05a9eb05..4ee0163c 100644 --- a/src/LibHac/FsSrv/Delegates.cs +++ b/src/LibHac/FsSrv/Delegates.cs @@ -1,14 +1,13 @@ using System; -namespace LibHac.FsSrv -{ - public delegate Result RandomDataGenerator(Span buffer); +namespace LibHac.FsSrv; - public delegate Result SaveTransferAesKeyGenerator(Span key, - SaveDataTransferCryptoConfiguration.KeyIndex index, ReadOnlySpan keySource, int keyGeneration); +public delegate Result RandomDataGenerator(Span buffer); - public delegate Result SaveTransferCmacGenerator(Span mac, ReadOnlySpan data, - SaveDataTransferCryptoConfiguration.KeyIndex index, int keyGeneration); +public delegate Result SaveTransferAesKeyGenerator(Span key, + SaveDataTransferCryptoConfiguration.KeyIndex index, ReadOnlySpan keySource, int keyGeneration); - public delegate Result PatrolAllocateCountGetter(out long successCount, out long failureCount); -} +public delegate Result SaveTransferCmacGenerator(Span mac, ReadOnlySpan data, + SaveDataTransferCryptoConfiguration.KeyIndex index, int keyGeneration); + +public delegate Result PatrolAllocateCountGetter(out long successCount, out long failureCount); diff --git a/src/LibHac/FsSrv/EmulatedDeviceOperator.cs b/src/LibHac/FsSrv/EmulatedDeviceOperator.cs index 1c195108..db5123c3 100644 --- a/src/LibHac/FsSrv/EmulatedDeviceOperator.cs +++ b/src/LibHac/FsSrv/EmulatedDeviceOperator.cs @@ -2,42 +2,41 @@ using LibHac.Fs; using LibHac.FsSrv.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class EmulatedDeviceOperator : IDeviceOperator { - public class EmulatedDeviceOperator : IDeviceOperator + private EmulatedGameCard GameCard { get; set; } + private EmulatedSdCard SdCard { get; set; } + + public EmulatedDeviceOperator(EmulatedGameCard gameCard, EmulatedSdCard sdCard) { - private EmulatedGameCard GameCard { get; set; } - private EmulatedSdCard SdCard { get; set; } + GameCard = gameCard; + SdCard = sdCard; + } - public EmulatedDeviceOperator(EmulatedGameCard gameCard, EmulatedSdCard sdCard) - { - GameCard = gameCard; - SdCard = sdCard; - } + public void Dispose() { } - public void Dispose() { } + public Result IsSdCardInserted(out bool isInserted) + { + isInserted = SdCard.IsSdCardInserted(); + return Result.Success; + } - public Result IsSdCardInserted(out bool isInserted) - { - isInserted = SdCard.IsSdCardInserted(); - return Result.Success; - } + public Result IsGameCardInserted(out bool isInserted) + { + isInserted = GameCard.IsGameCardInserted(); + return Result.Success; + } - public Result IsGameCardInserted(out bool isInserted) - { - isInserted = GameCard.IsGameCardInserted(); - return Result.Success; - } + public Result GetGameCardHandle(out GameCardHandle handle) + { + UnsafeHelpers.SkipParamInit(out handle); - public Result GetGameCardHandle(out GameCardHandle handle) - { - UnsafeHelpers.SkipParamInit(out handle); + if (!GameCard.IsGameCardInserted()) + return ResultFs.GameCardNotInsertedOnGetHandle.Log(); - if (!GameCard.IsGameCardInserted()) - return ResultFs.GameCardNotInsertedOnGetHandle.Log(); - - handle = GameCard.GetGameCardHandle(); - return Result.Success; - } + handle = GameCard.GetGameCardHandle(); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/EmulatedGameCard.cs b/src/LibHac/FsSrv/EmulatedGameCard.cs index 584fbd65..b2f837f2 100644 --- a/src/LibHac/FsSrv/EmulatedGameCard.cs +++ b/src/LibHac/FsSrv/EmulatedGameCard.cs @@ -3,122 +3,121 @@ using LibHac.Common; using LibHac.Common.Keys; using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class EmulatedGameCard { - public class EmulatedGameCard + private IStorage CardImageStorage { get; set; } + private int Handle { get; set; } + private XciHeader CardHeader { get; set; } + private Xci CardImage { get; set; } + private KeySet KeySet { get; set; } + + public EmulatedGameCard() { } + + public EmulatedGameCard(KeySet keySet) { - private IStorage CardImageStorage { get; set; } - private int Handle { get; set; } - private XciHeader CardHeader { get; set; } - private Xci CardImage { get; set; } - private KeySet KeySet { get; set; } + KeySet = keySet; + } + public GameCardHandle GetGameCardHandle() + { + return new GameCardHandle(Handle); + } - public EmulatedGameCard() { } + public bool IsGameCardHandleInvalid(GameCardHandle handle) + { + return Handle != handle.Value; + } - public EmulatedGameCard(KeySet keySet) + public bool IsGameCardInserted() + { + return CardImageStorage != null; + } + + public void InsertGameCard(IStorage cardImageStorage) + { + RemoveGameCard(); + + CardImageStorage = cardImageStorage; + + CardImage = new Xci(KeySet, cardImageStorage); + CardHeader = CardImage.Header; + } + + public void RemoveGameCard() + { + if (IsGameCardInserted()) { - KeySet = keySet; - } - public GameCardHandle GetGameCardHandle() - { - return new GameCardHandle(Handle); - } - - public bool IsGameCardHandleInvalid(GameCardHandle handle) - { - return Handle != handle.Value; - } - - public bool IsGameCardInserted() - { - return CardImageStorage != null; - } - - public void InsertGameCard(IStorage cardImageStorage) - { - RemoveGameCard(); - - CardImageStorage = cardImageStorage; - - CardImage = new Xci(KeySet, cardImageStorage); - CardHeader = CardImage.Header; - } - - public void RemoveGameCard() - { - if (IsGameCardInserted()) - { - CardImageStorage = null; - Handle++; - } - } - - internal Result GetXci(out Xci xci, GameCardHandle handle) - { - UnsafeHelpers.SkipParamInit(out xci); - - if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log(); - if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); - - xci = CardImage; - return Result.Success; - } - - public Result Read(GameCardHandle handle, long offset, Span destination) - { - if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log(); - if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); - - return CardImageStorage.Read(offset, destination); - } - - public Result GetGameCardImageHash(Span outBuffer) - { - if (outBuffer.Length < 0x20) return ResultFs.GameCardPreconditionViolation.Log(); - if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); - - CardHeader.ImageHash.CopyTo(outBuffer.Slice(0, 0x20)); - return Result.Success; - } - - public Result GetGameCardDeviceId(Span outBuffer) - { - if (outBuffer.Length < 0x10) return ResultFs.GameCardPreconditionViolation.Log(); - if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); - - // Skip the security mode check - - // Instead of caching the CardKeyArea data, read the value directly - return CardImageStorage.Read(0x7110, outBuffer.Slice(0, 0x10)); - } - - internal Result GetCardInfo(out GameCardInfo cardInfo, GameCardHandle handle) - { - UnsafeHelpers.SkipParamInit(out cardInfo); - - if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnGetCardInfo.Log(); - if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); - - cardInfo = GetCardInfoImpl(); - return Result.Success; - } - - private GameCardInfo GetCardInfoImpl() - { - var info = new GameCardInfo(); - - CardHeader.RootPartitionHeaderHash.AsSpan().CopyTo(info.RootPartitionHeaderHash); - info.PackageId = CardHeader.PackageId; - info.Size = GameCard.GetGameCardSizeBytes(CardHeader.GameCardSize); - info.RootPartitionOffset = CardHeader.RootPartitionOffset; - info.RootPartitionHeaderSize = CardHeader.RootPartitionHeaderSize; - info.SecureAreaOffset = GameCard.CardPageToOffset(CardHeader.LimAreaPage); - info.SecureAreaSize = info.Size - info.SecureAreaOffset; - info.UpdateVersion = CardHeader.UppVersion; - info.UpdateTitleId = CardHeader.UppId; - info.Attribute = CardHeader.Flags; - - return info; + CardImageStorage = null; + Handle++; } } + + internal Result GetXci(out Xci xci, GameCardHandle handle) + { + UnsafeHelpers.SkipParamInit(out xci); + + if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log(); + if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); + + xci = CardImage; + return Result.Success; + } + + public Result Read(GameCardHandle handle, long offset, Span destination) + { + if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log(); + if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); + + return CardImageStorage.Read(offset, destination); + } + + public Result GetGameCardImageHash(Span outBuffer) + { + if (outBuffer.Length < 0x20) return ResultFs.GameCardPreconditionViolation.Log(); + if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); + + CardHeader.ImageHash.CopyTo(outBuffer.Slice(0, 0x20)); + return Result.Success; + } + + public Result GetGameCardDeviceId(Span outBuffer) + { + if (outBuffer.Length < 0x10) return ResultFs.GameCardPreconditionViolation.Log(); + if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); + + // Skip the security mode check + + // Instead of caching the CardKeyArea data, read the value directly + return CardImageStorage.Read(0x7110, outBuffer.Slice(0, 0x10)); + } + + internal Result GetCardInfo(out GameCardInfo cardInfo, GameCardHandle handle) + { + UnsafeHelpers.SkipParamInit(out cardInfo); + + if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnGetCardInfo.Log(); + if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log(); + + cardInfo = GetCardInfoImpl(); + return Result.Success; + } + + private GameCardInfo GetCardInfoImpl() + { + var info = new GameCardInfo(); + + CardHeader.RootPartitionHeaderHash.AsSpan().CopyTo(info.RootPartitionHeaderHash); + info.PackageId = CardHeader.PackageId; + info.Size = GameCard.GetGameCardSizeBytes(CardHeader.GameCardSize); + info.RootPartitionOffset = CardHeader.RootPartitionOffset; + info.RootPartitionHeaderSize = CardHeader.RootPartitionHeaderSize; + info.SecureAreaOffset = GameCard.CardPageToOffset(CardHeader.LimAreaPage); + info.SecureAreaSize = info.Size - info.SecureAreaOffset; + info.UpdateVersion = CardHeader.UppVersion; + info.UpdateTitleId = CardHeader.UppId; + info.Attribute = CardHeader.Flags; + + return info; + } } diff --git a/src/LibHac/FsSrv/EmulatedSdCard.cs b/src/LibHac/FsSrv/EmulatedSdCard.cs index 450fddd3..183ca7b4 100644 --- a/src/LibHac/FsSrv/EmulatedSdCard.cs +++ b/src/LibHac/FsSrv/EmulatedSdCard.cs @@ -1,17 +1,16 @@ -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class EmulatedSdCard { - public class EmulatedSdCard + private bool IsInserted { get; set; } + + public bool IsSdCardInserted() { - private bool IsInserted { get; set; } + return IsInserted; + } - public bool IsSdCardInserted() - { - return IsInserted; - } - - public void SetSdCardInsertionStatus(bool isInserted) - { - IsInserted = isInserted; - } + public void SetSdCardInsertionStatus(bool isInserted) + { + IsInserted = isInserted; } } diff --git a/src/LibHac/FsSrv/ExternalKeySet.cs b/src/LibHac/FsSrv/ExternalKeySet.cs index 4dd1e9a9..41cfc3c1 100644 --- a/src/LibHac/FsSrv/ExternalKeySet.cs +++ b/src/LibHac/FsSrv/ExternalKeySet.cs @@ -3,100 +3,99 @@ using System.Collections.Generic; using LibHac.Fs; using LibHac.Spl; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class ExternalKeySet { - public class ExternalKeySet + private readonly object _locker = new object(); + + private Dictionary ExternalKeys { get; set; } = new Dictionary(); + + public Result Add(RightsId rightsId, AccessKey key) { - private readonly object _locker = new object(); - - private Dictionary ExternalKeys { get; set; } = new Dictionary(); - - public Result Add(RightsId rightsId, AccessKey key) + lock (_locker) { - lock (_locker) + if (ExternalKeys.TryGetValue(rightsId, out AccessKey existingKey)) { - if (ExternalKeys.TryGetValue(rightsId, out AccessKey existingKey)) - { - if (key == existingKey) - { - return Result.Success; - } - - return ResultFs.ExternalKeyAlreadyRegistered.Log(); - } - - ExternalKeys.Add(rightsId, key); - } - - return Result.Success; - } - - public Result Get(RightsId rightsId, out AccessKey key) - { - lock (_locker) - { - if (ExternalKeys.TryGetValue(rightsId, out key)) + if (key == existingKey) { return Result.Success; } - return ResultFs.NcaExternalKeyUnavailable.Log(); + return ResultFs.ExternalKeyAlreadyRegistered.Log(); } + + ExternalKeys.Add(rightsId, key); } - public bool Contains(RightsId rightsId) + return Result.Success; + } + + public Result Get(RightsId rightsId, out AccessKey key) + { + lock (_locker) { - lock (_locker) + if (ExternalKeys.TryGetValue(rightsId, out key)) { - return ExternalKeys.ContainsKey(rightsId); + return Result.Success; } - } - public bool Remove(RightsId rightsId) - { - lock (_locker) - { - return ExternalKeys.Remove(rightsId); - } - } - - public void Clear() - { - lock (_locker) - { - ExternalKeys.Clear(); - } - } - - public List<(RightsId rightsId, AccessKey key)> ToList() - { - lock (_locker) - { - var list = new List<(RightsId rightsId, AccessKey key)>(ExternalKeys.Count); - - foreach (KeyValuePair kvp in ExternalKeys) - { - list.Add((kvp.Key, kvp.Value)); - } - - return list; - } - } - - public void TrimExcess() => TrimExcess(0); - - public void TrimExcess(int capacity) - { - lock (_locker) - { - int newCapacity = Math.Max(capacity, ExternalKeys.Count); - ExternalKeys.TrimExcess(newCapacity); - } - } - - public void EnsureCapacity(int capacity) - { - ExternalKeys.EnsureCapacity(capacity); + return ResultFs.NcaExternalKeyUnavailable.Log(); } } + + public bool Contains(RightsId rightsId) + { + lock (_locker) + { + return ExternalKeys.ContainsKey(rightsId); + } + } + + public bool Remove(RightsId rightsId) + { + lock (_locker) + { + return ExternalKeys.Remove(rightsId); + } + } + + public void Clear() + { + lock (_locker) + { + ExternalKeys.Clear(); + } + } + + public List<(RightsId rightsId, AccessKey key)> ToList() + { + lock (_locker) + { + var list = new List<(RightsId rightsId, AccessKey key)>(ExternalKeys.Count); + + foreach (KeyValuePair kvp in ExternalKeys) + { + list.Add((kvp.Key, kvp.Value)); + } + + return list; + } + } + + public void TrimExcess() => TrimExcess(0); + + public void TrimExcess(int capacity) + { + lock (_locker) + { + int newCapacity = Math.Max(capacity, ExternalKeys.Count); + ExternalKeys.TrimExcess(newCapacity); + } + } + + public void EnsureCapacity(int capacity) + { + ExternalKeys.EnsureCapacity(capacity); + } } diff --git a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs index deced487..16332bcf 100644 --- a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs +++ b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs @@ -1,18 +1,17 @@ using LibHac.FsSrv.FsCreator; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class FileSystemProxyConfiguration { - public class FileSystemProxyConfiguration - { - public FileSystemCreatorInterfaces FsCreatorInterfaces { get; set; } - public BaseStorageServiceImpl BaseStorageService { get; set; } - public BaseFileSystemServiceImpl BaseFileSystemService { get; set; } - public NcaFileSystemServiceImpl NcaFileSystemService { get; set; } - public SaveDataFileSystemServiceImpl SaveDataFileSystemService { get; set; } - public AccessFailureManagementServiceImpl AccessFailureManagementService { get; set; } - public TimeServiceImpl TimeService { get; set; } - public StatusReportServiceImpl StatusReportService { get; set; } - public ProgramRegistryServiceImpl ProgramRegistryService { get; set; } - public AccessLogServiceImpl AccessLogService { get; set; } - } + public FileSystemCreatorInterfaces FsCreatorInterfaces { get; set; } + public BaseStorageServiceImpl BaseStorageService { get; set; } + public BaseFileSystemServiceImpl BaseFileSystemService { get; set; } + public NcaFileSystemServiceImpl NcaFileSystemService { get; set; } + public SaveDataFileSystemServiceImpl SaveDataFileSystemService { get; set; } + public AccessFailureManagementServiceImpl AccessFailureManagementService { get; set; } + public TimeServiceImpl TimeService { get; set; } + public StatusReportServiceImpl StatusReportService { get; set; } + public ProgramRegistryServiceImpl ProgramRegistryService { get; set; } + public AccessLogServiceImpl AccessLogService { get; set; } } diff --git a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs index ce9d08fb..e2576983 100644 --- a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs @@ -7,117 +7,116 @@ using LibHac.Fs.Shim; using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.Impl; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class FileSystemProxyCoreImpl { - public class FileSystemProxyCoreImpl + private FileSystemCreatorInterfaces _fsCreators; + private BaseFileSystemServiceImpl _baseFileSystemService; + private EncryptionSeed _sdEncryptionSeed; + + public FileSystemProxyCoreImpl(FileSystemCreatorInterfaces fsCreators, BaseFileSystemServiceImpl baseFsService) { - private FileSystemCreatorInterfaces _fsCreators; - private BaseFileSystemServiceImpl _baseFileSystemService; - private EncryptionSeed _sdEncryptionSeed; + _fsCreators = fsCreators; + _baseFileSystemService = baseFsService; + } - public FileSystemProxyCoreImpl(FileSystemCreatorInterfaces fsCreators, BaseFileSystemServiceImpl baseFsService) + public Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, + CloudBackupWorkStorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId) + { + // Hack around error CS8350. + const int pathBufferLength = 0x40; + Span buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span pathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); + + using var fileSystem = new SharedRef(); + + if (storageId == CustomStorageId.System) { - _fsCreators = fsCreators; - _baseFileSystemService = baseFsService; - } - - public Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, - CloudBackupWorkStorageId storageId) - { - throw new NotImplementedException(); - } - - public Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId) - { - // Hack around error CS8350. - const int pathBufferLength = 0x40; - Span buffer = stackalloc byte[pathBufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span pathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); - - using var fileSystem = new SharedRef(); - - if (storageId == CustomStorageId.System) - { - Result rc = _baseFileSystemService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.User); - if (rc.IsFailure()) return rc; - - using var path = new Path(); - rc = PathFunctions.SetUpFixedPathSingleEntry(ref path.Ref(), pathBuffer, - CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System)); - if (rc.IsFailure()) return rc; - - using SharedRef tempFs = SharedRef.CreateMove(ref fileSystem.Ref()); - rc = Utility.WrapSubDirectory(ref fileSystem.Ref(), ref tempFs.Ref(), in path, createIfMissing: true); - if (rc.IsFailure()) return rc; - } - else if (storageId == CustomStorageId.SdCard) - { - Result rc = _baseFileSystemService.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); - if (rc.IsFailure()) return rc; - - using var path = new Path(); - rc = PathFunctions.SetUpFixedPathDoubleEntry(ref path.Ref(), pathBuffer, - CommonPaths.SdCardNintendoRootDirectoryName, - CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System)); - if (rc.IsFailure()) return rc; - - using SharedRef tempFs = SharedRef.CreateMove(ref fileSystem.Ref()); - rc = Utility.WrapSubDirectory(ref fileSystem.Ref(), ref tempFs.Ref(), in path, createIfMissing: true); - if (rc.IsFailure()) return rc; - - tempFs.SetByMove(ref fileSystem.Ref()); - rc = _fsCreators.EncryptedFileSystemCreator.Create(ref fileSystem.Ref(), ref tempFs.Ref(), - IEncryptedFileSystemCreator.KeyId.CustomStorage, in _sdEncryptionSeed); - if (rc.IsFailure()) return rc; - } - else - { - return ResultFs.InvalidArgument.Log(); - } - - outFileSystem.SetByMove(ref fileSystem.Ref()); - return Result.Success; - } - - private Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path path) - { - using var pathHost = new Path(); - Result rc = pathHost.Initialize(in path); + Result rc = _baseFileSystemService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.User); if (rc.IsFailure()) return rc; - rc = _fsCreators.TargetManagerFileSystemCreator.NormalizeCaseOfPath(out bool isSupported, ref pathHost.Ref()); + using var path = new Path(); + rc = PathFunctions.SetUpFixedPathSingleEntry(ref path.Ref(), pathBuffer, + CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System)); if (rc.IsFailure()) return rc; - rc = _fsCreators.TargetManagerFileSystemCreator.Create(ref outFileSystem, in pathHost, isSupported, - ensureRootPathExists: false, Result.Success); + using SharedRef tempFs = SharedRef.CreateMove(ref fileSystem.Ref()); + rc = Utility.WrapSubDirectory(ref fileSystem.Ref(), ref tempFs.Ref(), in path, createIfMissing: true); + if (rc.IsFailure()) return rc; + } + else if (storageId == CustomStorageId.SdCard) + { + Result rc = _baseFileSystemService.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); if (rc.IsFailure()) return rc; - return Result.Success; - } + using var path = new Path(); + rc = PathFunctions.SetUpFixedPathDoubleEntry(ref path.Ref(), pathBuffer, + CommonPaths.SdCardNintendoRootDirectoryName, + CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System)); + if (rc.IsFailure()) return rc; - public Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path path, - bool openCaseSensitive) + using SharedRef tempFs = SharedRef.CreateMove(ref fileSystem.Ref()); + rc = Utility.WrapSubDirectory(ref fileSystem.Ref(), ref tempFs.Ref(), in path, createIfMissing: true); + if (rc.IsFailure()) return rc; + + tempFs.SetByMove(ref fileSystem.Ref()); + rc = _fsCreators.EncryptedFileSystemCreator.Create(ref fileSystem.Ref(), ref tempFs.Ref(), + IEncryptedFileSystemCreator.KeyId.CustomStorage, in _sdEncryptionSeed); + if (rc.IsFailure()) return rc; + } + else { - if (!path.IsEmpty() && openCaseSensitive) - { - Result rc = OpenHostFileSystem(ref outFileSystem, in path); - if (rc.IsFailure()) return rc; - } - else - { - Result rc = _fsCreators.TargetManagerFileSystemCreator.Create(ref outFileSystem, in path, - openCaseSensitive, ensureRootPathExists: false, Result.Success); - if (rc.IsFailure()) return rc; - } - - return Result.Success; + return ResultFs.InvalidArgument.Log(); } - public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + outFileSystem.SetByMove(ref fileSystem.Ref()); + return Result.Success; + } + + private Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path path) + { + using var pathHost = new Path(); + Result rc = pathHost.Initialize(in path); + if (rc.IsFailure()) return rc; + + rc = _fsCreators.TargetManagerFileSystemCreator.NormalizeCaseOfPath(out bool isSupported, ref pathHost.Ref()); + if (rc.IsFailure()) return rc; + + rc = _fsCreators.TargetManagerFileSystemCreator.Create(ref outFileSystem, in pathHost, isSupported, + ensureRootPathExists: false, Result.Success); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path path, + bool openCaseSensitive) + { + if (!path.IsEmpty() && openCaseSensitive) { - _sdEncryptionSeed = seed; - return Result.Success; + Result rc = OpenHostFileSystem(ref outFileSystem, in path); + if (rc.IsFailure()) return rc; } + else + { + Result rc = _fsCreators.TargetManagerFileSystemCreator.Create(ref outFileSystem, in path, + openCaseSensitive, ensureRootPathExists: false, Result.Success); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + _sdEncryptionSeed = seed; + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs index 8a806490..35e54fa0 100644 --- a/src/LibHac/FsSrv/FileSystemProxyImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -14,1142 +14,1141 @@ using IStorageSf = LibHac.FsSrv.Sf.IStorage; using Path = LibHac.Fs.Path; using static LibHac.Fs.StringTraits; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public static class FileSystemProxyImplGlobalMethods { - public static class FileSystemProxyImplGlobalMethods + public static void InitializeFileSystemProxy(this FileSystemServer fsSrv, + FileSystemProxyConfiguration configuration) { - public static void InitializeFileSystemProxy(this FileSystemServer fsSrv, - FileSystemProxyConfiguration configuration) - { - ref FileSystemProxyImplGlobals g = ref fsSrv.Globals.FileSystemProxyImpl; - - g.FileSystemProxyCoreImpl.Set(new FileSystemProxyCoreImpl(configuration.FsCreatorInterfaces, - configuration.BaseFileSystemService)); - - g.BaseStorageServiceImpl = configuration.BaseStorageService; - g.BaseFileSystemServiceImpl = configuration.BaseFileSystemService; - g.NcaFileSystemServiceImpl = configuration.NcaFileSystemService; - g.SaveDataFileSystemServiceImpl = configuration.SaveDataFileSystemService; - g.AccessFailureManagementServiceImpl = configuration.AccessFailureManagementService; - g.TimeServiceImpl = configuration.TimeService; - g.StatusReportServiceImpl = configuration.StatusReportService; - g.ProgramRegistryServiceImpl = configuration.ProgramRegistryService; - g.AccessLogServiceImpl = configuration.AccessLogService; - } - } - - internal struct FileSystemProxyImplGlobals - { - public NcaFileSystemServiceImpl NcaFileSystemServiceImpl; - public SaveDataFileSystemServiceImpl SaveDataFileSystemServiceImpl; - public BaseStorageServiceImpl BaseStorageServiceImpl; - public BaseFileSystemServiceImpl BaseFileSystemServiceImpl; - public AccessFailureManagementServiceImpl AccessFailureManagementServiceImpl; - public TimeServiceImpl TimeServiceImpl; - public StatusReportServiceImpl StatusReportServiceImpl; - public ProgramRegistryServiceImpl ProgramRegistryServiceImpl; - public AccessLogServiceImpl AccessLogServiceImpl; - public Optional FileSystemProxyCoreImpl; - } - - /// - /// Dispatches calls to the main file system service interface. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader - { - private FileSystemProxyCoreImpl _fsProxyCore; - private SharedRef _ncaFsService; - private SharedRef _saveFsService; - private ulong _currentProcess; - - // LibHac addition - private FileSystemServer _fsServer; - private ref FileSystemProxyImplGlobals Globals => ref _fsServer.Globals.FileSystemProxyImpl; - - internal FileSystemProxyImpl(FileSystemServer server) - { - _fsServer = server; - - _fsProxyCore = Globals.FileSystemProxyCoreImpl.Value; - _currentProcess = ulong.MaxValue; - } - - public void Dispose() - { - _ncaFsService.Destroy(); - _saveFsService.Destroy(); - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - var registry = new ProgramRegistryImpl(_fsServer); - return registry.GetProgramInfo(out programInfo, _currentProcess); - } - - private Result GetNcaFileSystemService(out NcaFileSystemService ncaFsService) - { - if (!_ncaFsService.HasValue) - { - UnsafeHelpers.SkipParamInit(out ncaFsService); - return ResultFs.PreconditionViolation.Log(); - } - - ncaFsService = _ncaFsService.Get; - return Result.Success; - } - - private Result GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService) - { - if (!_saveFsService.HasValue) - { - UnsafeHelpers.SkipParamInit(out saveFsService); - return ResultFs.PreconditionViolation.Log(); - } - - saveFsService = _saveFsService.Get; - return Result.Success; - } - - private BaseStorageService GetBaseStorageService() - { - return new BaseStorageService(Globals.BaseStorageServiceImpl, _currentProcess); - } - - private BaseFileSystemService GetBaseFileSystemService() - { - return new BaseFileSystemService(Globals.BaseFileSystemServiceImpl, _currentProcess); - } - - private AccessFailureManagementService GetAccessFailureManagementService() - { - return new AccessFailureManagementService(Globals.AccessFailureManagementServiceImpl, _currentProcess); - } - - private TimeService GetTimeService() - { - return new TimeService(Globals.TimeServiceImpl, _currentProcess); - } - - private StatusReportService GetStatusReportService() - { - return new StatusReportService(Globals.StatusReportServiceImpl); - } - - private ProgramIndexRegistryService GetProgramIndexRegistryService() - { - return new ProgramIndexRegistryService(Globals.ProgramRegistryServiceImpl, _currentProcess); - } - - private AccessLogService GetAccessLogService() - { - return new AccessLogService(Globals.AccessLogServiceImpl, _currentProcess); - } - - public Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, - ulong id, FileSystemProxyType fsType) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenFileSystemWithId(ref outFileSystem, in path, id, fsType); - } - - public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, - ProgramId programId, FileSystemProxyType fsType) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenFileSystemWithPatch(ref outFileSystem, programId, fsType); - } - - public Result OpenCodeFileSystem(ref SharedRef fileSystem, - out CodeVerificationData verificationData, in FspPath path, ProgramId programId) - { - UnsafeHelpers.SkipParamInit(out verificationData); - - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenCodeFileSystem(ref fileSystem, out verificationData, in path, programId); - } - - public Result SetCurrentProcess(ulong processId) - { - _currentProcess = processId; - - // Initialize the NCA file system service - using SharedRef ncaFsService = - NcaFileSystemService.CreateShared(Globals.NcaFileSystemServiceImpl, processId); - _ncaFsService.SetByMove(ref ncaFsService.Ref()); - - using SharedRef saveFsService = - SaveDataFileSystemService.CreateShared(Globals.SaveDataFileSystemServiceImpl, processId); - _saveFsService.SetByMove(ref saveFsService.Ref()); - - return Result.Success; - } - - public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) - { - UnsafeHelpers.SkipParamInit(out freeSpaceSize); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.GetFreeSpaceSizeForSaveData(out freeSpaceSize, spaceId); - } - - public Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataFileSystemByCurrentProcess(ref outFileSystem); - } - - public Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, - ProgramId programId) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataFileSystemByProgramId(ref outFileSystem, programId); - } - - public Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataStorageByCurrentProcess(ref outStorage); - } - - public Result OpenDataStorageByProgramId(ref SharedRef outStorage, - ProgramId programId) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataStorageByProgramId(ref outStorage, programId); - } - - public Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, - StorageId storageId) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataStorageByDataId(ref outStorage, dataId, storageId); - } - - public Result OpenPatchDataStorageByCurrentProcess(ref SharedRef outStorage) - { - return ResultFs.TargetNotFound.Log(); - } - - public Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, - byte programIndex) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataFileSystemWithProgramIndex(ref outFileSystem, programIndex); - } - - public Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, - byte programIndex) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenDataStorageWithProgramIndex(ref outStorage, programIndex); - } - - public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.RegisterSaveDataFileSystemAtomicDeletion(saveDataIds); - } - - public Result DeleteSaveDataFileSystem(ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.DeleteSaveDataFileSystem(saveDataId); - } - - public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); - } - - public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute); - } - - public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.UpdateSaveDataMacForDebug(spaceId, saveDataId); - } - - public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); - } - - public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaInfo, - in hashSalt); - } - - public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); - } - - public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, - long journalSize) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize); - } - - public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataFileSystem(ref outFileSystem, spaceId, in attribute); - } - - public Result OpenReadOnlySaveDataFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenReadOnlySaveDataFileSystem(ref outFileSystem, spaceId, in attribute); - } - - public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataFileSystemBySystemSaveDataId(ref outFileSystem, spaceId, in attribute); - } - - public Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ReadSaveDataFileSystemExtraData(extraDataBuffer, saveDataId); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(extraDataBuffer, spaceId, - in attribute); - } - - public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(extraDataBuffer, spaceId, - in attribute, maskBuffer); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(extraDataBuffer, spaceId, saveDataId); - } - - public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, - InBuffer extraDataBuffer) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, extraDataBuffer); - } - - public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, - SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, - extraDataBuffer, maskBuffer); - } - - public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, - InBuffer extraDataBuffer, InBuffer maskBuffer) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, extraDataBuffer, - maskBuffer); - } - - public Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, - ImageDirectoryId directoryId) - { - return GetBaseFileSystemService().OpenImageDirectoryFileSystem(ref outFileSystem, directoryId); - } - - public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) - { - return GetBaseFileSystemService().OpenBaseFileSystem(ref outFileSystem, fileSystemId); - } - - public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) - { - return GetBaseFileSystemService().FormatBaseFileSystem(fileSystemId); - } - - public Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, - BisPartitionId partitionId) - { - return GetBaseFileSystemService().OpenBisFileSystem(ref outFileSystem, in rootPath, partitionId); - } - - public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId) - { - return GetBaseStorageService().OpenBisStorage(ref outStorage, partitionId); - } - - public Result InvalidateBisCache() - { - return GetBaseStorageService().InvalidateBisCache(); - } - - public Result OpenHostFileSystem(ref SharedRef outFileSystem, in FspPath path) - { - return OpenHostFileSystemWithOption(ref outFileSystem, in path, MountHostOption.None); - } - - public Result OpenHostFileSystemWithOption(ref SharedRef outFileSystem, - in FspPath path, MountHostOption option) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountHost); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - using var hostFileSystem = new SharedRef(); - using var pathNormalized = new Path(); - - if (path.Str.At(0) == DirectorySeparator && path.Str.At(1) != DirectorySeparator) - { - rc = pathNormalized.Initialize(path.Str.Slice(1)); - if (rc.IsFailure()) return rc; - } - else - { - rc = pathNormalized.InitializeWithReplaceUnc(path.Str); - if (rc.IsFailure()) return rc; - } - - var flags = new PathFlags(); - flags.AllowWindowsPath(); - flags.AllowRelativePath(); - flags.AllowEmptyPath(); - - rc = pathNormalized.Normalize(flags); - if (rc.IsFailure()) return rc; - - bool isCaseSensitive = option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive); - - rc = _fsProxyCore.OpenHostFileSystem(ref hostFileSystem.Ref(), in pathNormalized, isCaseSensitive); - if (rc.IsFailure()) return rc; - - var adapterFlags = new PathFlags(); - if (path.Str.At(0) == NullTerminator) - adapterFlags.AllowWindowsPath(); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref hostFileSystem.Ref(), adapterFlags, false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result OpenSdCardFileSystem(ref SharedRef outFileSystem) - { - return GetBaseFileSystemService().OpenSdCardFileSystem(ref outFileSystem); - } - - public Result FormatSdCardFileSystem() - { - return GetBaseFileSystemService().FormatSdCardFileSystem(); - } - - public Result FormatSdCardDryRun() - { - return GetBaseFileSystemService().FormatSdCardDryRun(); - } - - public Result IsExFatSupported(out bool isSupported) - { - return GetBaseFileSystemService().IsExFatSupported(out isSupported); - } - - public Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, - GameCardPartitionRaw partitionId) - { - return GetBaseStorageService().OpenGameCardStorage(ref outStorage, handle, partitionId); - } - - public Result OpenDeviceOperator(ref SharedRef outDeviceOperator) - { - return GetBaseStorageService().OpenDeviceOperator(ref outDeviceOperator); - } - - public Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier) - { - return GetBaseStorageService().OpenSdCardDetectionEventNotifier(ref outEventNotifier); - } - - public Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier) - { - return GetBaseStorageService().OpenGameCardDetectionEventNotifier(ref outEventNotifier); - } - - public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) - { - return GetBaseStorageService().SimulateDeviceDetectionEvent(port, mode, signalEvent); - } - - public Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenSystemDataUpdateEventNotifier(ref outEventNotifier); - } - - public Result NotifySystemDataUpdateEvent() - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.NotifySystemDataUpdateEvent(); - } - - public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataInfoReader(ref outInfoReader); - } - - public Result OpenSaveDataInfoReaderBySaveDataSpaceId( - ref SharedRef outInfoReader, SaveDataSpaceId spaceId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataInfoReaderBySaveDataSpaceId(ref outInfoReader, spaceId); - } - - public Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, - SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataInfoReaderWithFilter(ref outInfoReader, spaceId, in filter); - } - - public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, - in SaveDataFilter filter) - { - UnsafeHelpers.SkipParamInit(out count); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.FindSaveDataWithFilter(out count, saveDataInfoBuffer, spaceId, in filter); - } - - public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataInternalStorageFileSystem(ref outFileSystem, spaceId, saveDataId); - } - - public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out size); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); - } - - public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out commitId); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.GetSaveDataCommitId(out commitId, spaceId, saveDataId); - } - - public Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader); - } - - public Result OpenSaveDataMetaFile(ref SharedRef outFile, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute, SaveDataMetaType type) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataMetaFile(ref outFile, spaceId, in attribute, type); - } - - public Result DeleteCacheStorage(ushort index) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.DeleteCacheStorage(index); - } - - public Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index) - { - UnsafeHelpers.SkipParamInit(out dataSize, out journalSize); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.GetCacheStorageSize(out dataSize, out journalSize, index); - } - - public Result OpenSaveDataTransferManager(ref SharedRef outTransferManager) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataTransferManager(ref outTransferManager); - } - - public Result OpenSaveDataTransferManagerVersion2( - ref SharedRef outTransferManager) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataTransferManagerVersion2(ref outTransferManager); - } - - public Result OpenSaveDataTransferManagerForSaveDataRepair( - ref SharedRef outTransferManager) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataTransferManagerForSaveDataRepair(ref outTransferManager); - } - - public Result OpenSaveDataTransferManagerForRepair( - ref SharedRef outTransferManager) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataTransferManagerForRepair(ref outTransferManager); - } - - public Result OpenSaveDataTransferProhibiter(ref SharedRef outProhibiter, - Ncm.ApplicationId applicationId) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataTransferProhibiter(ref outProhibiter, applicationId); - } - - public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, - int startIndex, int bufferIdCount) - { - UnsafeHelpers.SkipParamInit(out readCount); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.ListAccessibleSaveDataOwnerId(out readCount, idBuffer, programId, startIndex, - bufferIdCount); - } - - public Result OpenSaveDataMover(ref SharedRef outSaveDataMover, SaveDataSpaceId sourceSpaceId, - SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenSaveDataMover(ref outSaveDataMover, sourceSpaceId, destinationSpaceId, - workBufferHandle, workBufferSize); - } - - public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) - { - return Result.Success; - } - - public Result SetSaveDataRootPath(in FspPath path) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.SetSaveDataRootPath(in path); - } - - public Result UnsetSaveDataRootPath() - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.UnsetSaveDataRootPath(); - } - - public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, - ContentStorageId storageId) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc.Miss(); - - return ncaFsService.OpenContentStorageFileSystem(ref outFileSystem, storageId); - } - - public Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, - CloudBackupWorkStorageId storageId) - { - var storageFlag = StorageType.NonGameCard; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountCloudBackupWorkStorage); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - using var fileSystem = new SharedRef(); - rc = _fsProxyCore.OpenCloudBackupWorkStorageFileSystem(ref fileSystem.Ref(), storageId); - if (rc.IsFailure()) return rc; - - // Add all the wrappers for the file system - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId) - { - var storageFlag = StorageType.NonGameCard; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - AccessibilityType accessType = storageId > CustomStorageId.SdCard - ? AccessibilityType.NotMount - : AccessibilityType.MountCustomStorage; - - Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(accessType); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - using var fileSystem = new SharedRef(); - rc = _fsProxyCore.OpenCustomStorageFileSystem(ref fileSystem.Ref(), storageId); - if (rc.IsFailure()) return rc; - - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, - GameCardPartition partitionId) - { - return GetBaseFileSystemService().OpenGameCardFileSystem(ref outFileSystem, handle, partitionId); - } - - public Result IsArchivedProgram(out bool isArchived, ulong processId) - { - UnsafeHelpers.SkipParamInit(out isArchived); - - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.IsArchivedProgram(out isArchived, processId); - } - - public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) - { - UnsafeHelpers.SkipParamInit(out totalSize); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.QuerySaveDataTotalSize(out totalSize, dataSize, journalSize); - } - - public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) - { - return GetTimeService().SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); - } - - public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out rightsId); - - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.GetRightsId(out rightsId, programId, storageId); - } - - public Result GetRightsIdByPath(out RightsId rightsId, in FspPath path) - { - return GetRightsIdAndKeyGenerationByPath(out rightsId, out _, in path); - } - - public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path) - { - UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration); - - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in path); - } - - public Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.RegisterExternalKey(in rightsId, in externalKey); - } - - public Result UnregisterExternalKey(in RightsId rightsId) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.UnregisterExternalKey(in rightsId); - } - - public Result UnregisterAllExternalKey() - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.UnregisterAllExternalKey(); - } - - public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) - return ResultFs.PermissionDenied.Log(); - - rc = _fsProxyCore.SetSdCardEncryptionSeed(in seed); - if (rc.IsFailure()) return rc; - - rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - rc = saveFsService.SetSdCardEncryptionSeed(in seed); - if (rc.IsFailure()) return rc; - - rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.SetSdCardEncryptionSeed(in seed); - } - - public Result GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo) - { - return GetStatusReportService().GetAndClearFileSystemProxyErrorInfo(out errorInfo); - } - - public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount) - { - return GetProgramIndexRegistryService() - .RegisterProgramIndexMapInfo(programIndexMapInfoBuffer, programCount); - } - - public Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path) - { - return GetBaseFileSystemService().SetBisRootForHost(partitionId, in path); - } - - public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, - OutBuffer readBuffer) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, readBuffer); - } - - public Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer) - { - return VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId, readBuffer); - } - - public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset); - } - - public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) - { - // Corrupt both of the save data headers - Result rc = CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0); - if (rc.IsFailure()) return rc; - - return CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0x4000); - } - - public Result CorruptSaveDataFileSystem(ulong saveDataId) - { - return CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId); - } - - public Result CreatePaddingFile(long size) - { - return GetBaseFileSystemService().CreatePaddingFile(size); - } - - public Result DeleteAllPaddingFiles() - { - return GetBaseFileSystemService().DeleteAllPaddingFiles(); - } - - public Result DisableAutoSaveDataCreation() - { - return Result.Success; - } - - public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode) - { - return GetAccessLogService().SetAccessLogMode(mode); - } - - public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode) - { - return GetAccessLogService().GetAccessLogMode(out mode); - } - - public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) - { - return GetProgramIndexRegistryService().GetProgramIndex(out programIndex, out programCount); - } - - public Result OutputAccessLogToSdCard(InBuffer textBuffer) - { - return GetAccessLogService().OutputAccessLogToSdCard(textBuffer); - } - - public Result OutputMultiProgramTagAccessLog() - { - return GetAccessLogService().OutputMultiProgramTagAccessLog(); - } - - public Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo) - { - return GetAccessLogService().OutputApplicationInfoAccessLog(in applicationInfo); - } - - public Result FlushAccessLogOnSdCard() - { - return GetAccessLogService().FlushAccessLogOnSdCard(); - } - - public Result RegisterUpdatePartition() - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.RegisterUpdatePartition(); - } - - public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) - { - Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); - if (rc.IsFailure()) return rc; - - return ncaFsService.OpenRegisteredUpdatePartition(ref outFileSystem); - } - - public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) - { - return GetStatusReportService().GetAndClearMemoryReportInfo(out reportInfo); - } - - public Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType) - { - return GetStatusReportService().GetFsStackUsage(out stackUsage, threadType); - } - - public Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OverrideSaveDataTransferTokenSignVerificationKey(key); - } - - public Result SetSdCardAccessibility(bool isAccessible) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.SetSdCardAccessibility(isAccessible); - } - - public Result IsSdCardAccessible(out bool isAccessible) - { - UnsafeHelpers.SkipParamInit(out isAccessible); - - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.IsSdCardAccessible(out isAccessible); - } - - public Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outEventNotifier, - ulong processId, bool notifyOnDeepRetry) - { - return GetAccessFailureManagementService() - .OpenAccessFailureDetectionEventNotifier(ref outEventNotifier, processId, notifyOnDeepRetry); - } - - public Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle) - { - return GetAccessFailureManagementService().GetAccessFailureDetectionEvent(out eventHandle); - } - - public Result IsAccessFailureDetected(out bool isDetected, ulong processId) - { - return GetAccessFailureManagementService().IsAccessFailureDetected(out isDetected, processId); - } - - public Result ResolveAccessFailure(ulong processId) - { - return GetAccessFailureManagementService().ResolveAccessFailure(processId); - } - - public Result AbandonAccessFailure(ulong processId) - { - return GetAccessFailureManagementService().AbandonAccessFailure(processId); - } - - public Result OpenMultiCommitManager(ref SharedRef outCommitManager) - { - Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); - if (rc.IsFailure()) return rc; - - return saveFsService.OpenMultiCommitManager(ref outCommitManager); - } - - public Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, - ulong transferMemorySize) - { - return GetBaseFileSystemService().OpenBisWiper(ref outBisWiper, transferMemoryHandle, transferMemorySize); - } + ref FileSystemProxyImplGlobals g = ref fsSrv.Globals.FileSystemProxyImpl; + + g.FileSystemProxyCoreImpl.Set(new FileSystemProxyCoreImpl(configuration.FsCreatorInterfaces, + configuration.BaseFileSystemService)); + + g.BaseStorageServiceImpl = configuration.BaseStorageService; + g.BaseFileSystemServiceImpl = configuration.BaseFileSystemService; + g.NcaFileSystemServiceImpl = configuration.NcaFileSystemService; + g.SaveDataFileSystemServiceImpl = configuration.SaveDataFileSystemService; + g.AccessFailureManagementServiceImpl = configuration.AccessFailureManagementService; + g.TimeServiceImpl = configuration.TimeService; + g.StatusReportServiceImpl = configuration.StatusReportService; + g.ProgramRegistryServiceImpl = configuration.ProgramRegistryService; + g.AccessLogServiceImpl = configuration.AccessLogService; + } +} + +internal struct FileSystemProxyImplGlobals +{ + public NcaFileSystemServiceImpl NcaFileSystemServiceImpl; + public SaveDataFileSystemServiceImpl SaveDataFileSystemServiceImpl; + public BaseStorageServiceImpl BaseStorageServiceImpl; + public BaseFileSystemServiceImpl BaseFileSystemServiceImpl; + public AccessFailureManagementServiceImpl AccessFailureManagementServiceImpl; + public TimeServiceImpl TimeServiceImpl; + public StatusReportServiceImpl StatusReportServiceImpl; + public ProgramRegistryServiceImpl ProgramRegistryServiceImpl; + public AccessLogServiceImpl AccessLogServiceImpl; + public Optional FileSystemProxyCoreImpl; +} + +/// +/// Dispatches calls to the main file system service interface. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader +{ + private FileSystemProxyCoreImpl _fsProxyCore; + private SharedRef _ncaFsService; + private SharedRef _saveFsService; + private ulong _currentProcess; + + // LibHac addition + private FileSystemServer _fsServer; + private ref FileSystemProxyImplGlobals Globals => ref _fsServer.Globals.FileSystemProxyImpl; + + internal FileSystemProxyImpl(FileSystemServer server) + { + _fsServer = server; + + _fsProxyCore = Globals.FileSystemProxyCoreImpl.Value; + _currentProcess = ulong.MaxValue; + } + + public void Dispose() + { + _ncaFsService.Destroy(); + _saveFsService.Destroy(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + var registry = new ProgramRegistryImpl(_fsServer); + return registry.GetProgramInfo(out programInfo, _currentProcess); + } + + private Result GetNcaFileSystemService(out NcaFileSystemService ncaFsService) + { + if (!_ncaFsService.HasValue) + { + UnsafeHelpers.SkipParamInit(out ncaFsService); + return ResultFs.PreconditionViolation.Log(); + } + + ncaFsService = _ncaFsService.Get; + return Result.Success; + } + + private Result GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService) + { + if (!_saveFsService.HasValue) + { + UnsafeHelpers.SkipParamInit(out saveFsService); + return ResultFs.PreconditionViolation.Log(); + } + + saveFsService = _saveFsService.Get; + return Result.Success; + } + + private BaseStorageService GetBaseStorageService() + { + return new BaseStorageService(Globals.BaseStorageServiceImpl, _currentProcess); + } + + private BaseFileSystemService GetBaseFileSystemService() + { + return new BaseFileSystemService(Globals.BaseFileSystemServiceImpl, _currentProcess); + } + + private AccessFailureManagementService GetAccessFailureManagementService() + { + return new AccessFailureManagementService(Globals.AccessFailureManagementServiceImpl, _currentProcess); + } + + private TimeService GetTimeService() + { + return new TimeService(Globals.TimeServiceImpl, _currentProcess); + } + + private StatusReportService GetStatusReportService() + { + return new StatusReportService(Globals.StatusReportServiceImpl); + } + + private ProgramIndexRegistryService GetProgramIndexRegistryService() + { + return new ProgramIndexRegistryService(Globals.ProgramRegistryServiceImpl, _currentProcess); + } + + private AccessLogService GetAccessLogService() + { + return new AccessLogService(Globals.AccessLogServiceImpl, _currentProcess); + } + + public Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, + ulong id, FileSystemProxyType fsType) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenFileSystemWithId(ref outFileSystem, in path, id, fsType); + } + + public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, + ProgramId programId, FileSystemProxyType fsType) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenFileSystemWithPatch(ref outFileSystem, programId, fsType); + } + + public Result OpenCodeFileSystem(ref SharedRef fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId) + { + UnsafeHelpers.SkipParamInit(out verificationData); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenCodeFileSystem(ref fileSystem, out verificationData, in path, programId); + } + + public Result SetCurrentProcess(ulong processId) + { + _currentProcess = processId; + + // Initialize the NCA file system service + using SharedRef ncaFsService = + NcaFileSystemService.CreateShared(Globals.NcaFileSystemServiceImpl, processId); + _ncaFsService.SetByMove(ref ncaFsService.Ref()); + + using SharedRef saveFsService = + SaveDataFileSystemService.CreateShared(Globals.SaveDataFileSystemServiceImpl, processId); + _saveFsService.SetByMove(ref saveFsService.Ref()); + + return Result.Success; + } + + public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + { + UnsafeHelpers.SkipParamInit(out freeSpaceSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetFreeSpaceSizeForSaveData(out freeSpaceSize, spaceId); + } + + public Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataFileSystemByCurrentProcess(ref outFileSystem); + } + + public Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, + ProgramId programId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataFileSystemByProgramId(ref outFileSystem, programId); + } + + public Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataStorageByCurrentProcess(ref outStorage); + } + + public Result OpenDataStorageByProgramId(ref SharedRef outStorage, + ProgramId programId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataStorageByProgramId(ref outStorage, programId); + } + + public Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, + StorageId storageId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataStorageByDataId(ref outStorage, dataId, storageId); + } + + public Result OpenPatchDataStorageByCurrentProcess(ref SharedRef outStorage) + { + return ResultFs.TargetNotFound.Log(); + } + + public Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, + byte programIndex) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataFileSystemWithProgramIndex(ref outFileSystem, programIndex); + } + + public Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, + byte programIndex) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenDataStorageWithProgramIndex(ref outStorage, programIndex); + } + + public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.RegisterSaveDataFileSystemAtomicDeletion(saveDataIds); + } + + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystem(saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute); + } + + public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.UpdateSaveDataMacForDebug(spaceId, saveDataId); + } + + public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + } + + public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaInfo, + in hashSalt); + } + + public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); + } + + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, + long journalSize) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize); + } + + public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataFileSystem(ref outFileSystem, spaceId, in attribute); + } + + public Result OpenReadOnlySaveDataFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenReadOnlySaveDataFileSystem(ref outFileSystem, spaceId, in attribute); + } + + public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataFileSystemBySystemSaveDataId(ref outFileSystem, spaceId, in attribute); + } + + public Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraData(extraDataBuffer, saveDataId); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(extraDataBuffer, spaceId, + in attribute); + } + + public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(extraDataBuffer, spaceId, + in attribute, maskBuffer); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(extraDataBuffer, spaceId, saveDataId); + } + + public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraDataBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, extraDataBuffer); + } + + public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, + SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, + extraDataBuffer, maskBuffer); + } + + public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraDataBuffer, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, extraDataBuffer, + maskBuffer); + } + + public Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, + ImageDirectoryId directoryId) + { + return GetBaseFileSystemService().OpenImageDirectoryFileSystem(ref outFileSystem, directoryId); + } + + public Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId) + { + return GetBaseFileSystemService().OpenBaseFileSystem(ref outFileSystem, fileSystemId); + } + + public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) + { + return GetBaseFileSystemService().FormatBaseFileSystem(fileSystemId); + } + + public Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, + BisPartitionId partitionId) + { + return GetBaseFileSystemService().OpenBisFileSystem(ref outFileSystem, in rootPath, partitionId); + } + + public Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId) + { + return GetBaseStorageService().OpenBisStorage(ref outStorage, partitionId); + } + + public Result InvalidateBisCache() + { + return GetBaseStorageService().InvalidateBisCache(); + } + + public Result OpenHostFileSystem(ref SharedRef outFileSystem, in FspPath path) + { + return OpenHostFileSystemWithOption(ref outFileSystem, in path, MountHostOption.None); + } + + public Result OpenHostFileSystemWithOption(ref SharedRef outFileSystem, + in FspPath path, MountHostOption option) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountHost); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + using var hostFileSystem = new SharedRef(); + using var pathNormalized = new Path(); + + if (path.Str.At(0) == DirectorySeparator && path.Str.At(1) != DirectorySeparator) + { + rc = pathNormalized.Initialize(path.Str.Slice(1)); + if (rc.IsFailure()) return rc; + } + else + { + rc = pathNormalized.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; + } + + var flags = new PathFlags(); + flags.AllowWindowsPath(); + flags.AllowRelativePath(); + flags.AllowEmptyPath(); + + rc = pathNormalized.Normalize(flags); + if (rc.IsFailure()) return rc; + + bool isCaseSensitive = option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive); + + rc = _fsProxyCore.OpenHostFileSystem(ref hostFileSystem.Ref(), in pathNormalized, isCaseSensitive); + if (rc.IsFailure()) return rc; + + var adapterFlags = new PathFlags(); + if (path.Str.At(0) == NullTerminator) + adapterFlags.AllowWindowsPath(); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref hostFileSystem.Ref(), adapterFlags, false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenSdCardFileSystem(ref SharedRef outFileSystem) + { + return GetBaseFileSystemService().OpenSdCardFileSystem(ref outFileSystem); + } + + public Result FormatSdCardFileSystem() + { + return GetBaseFileSystemService().FormatSdCardFileSystem(); + } + + public Result FormatSdCardDryRun() + { + return GetBaseFileSystemService().FormatSdCardDryRun(); + } + + public Result IsExFatSupported(out bool isSupported) + { + return GetBaseFileSystemService().IsExFatSupported(out isSupported); + } + + public Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + return GetBaseStorageService().OpenGameCardStorage(ref outStorage, handle, partitionId); + } + + public Result OpenDeviceOperator(ref SharedRef outDeviceOperator) + { + return GetBaseStorageService().OpenDeviceOperator(ref outDeviceOperator); + } + + public Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier) + { + return GetBaseStorageService().OpenSdCardDetectionEventNotifier(ref outEventNotifier); + } + + public Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier) + { + return GetBaseStorageService().OpenGameCardDetectionEventNotifier(ref outEventNotifier); + } + + public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) + { + return GetBaseStorageService().SimulateDeviceDetectionEvent(port, mode, signalEvent); + } + + public Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenSystemDataUpdateEventNotifier(ref outEventNotifier); + } + + public Result NotifySystemDataUpdateEvent() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.NotifySystemDataUpdateEvent(); + } + + public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataInfoReader(ref outInfoReader); + } + + public Result OpenSaveDataInfoReaderBySaveDataSpaceId( + ref SharedRef outInfoReader, SaveDataSpaceId spaceId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataInfoReaderBySaveDataSpaceId(ref outInfoReader, spaceId); + } + + public Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataInfoReaderWithFilter(ref outInfoReader, spaceId, in filter); + } + + public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, + in SaveDataFilter filter) + { + UnsafeHelpers.SkipParamInit(out count); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.FindSaveDataWithFilter(out count, saveDataInfoBuffer, spaceId, in filter); + } + + public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataInternalStorageFileSystem(ref outFileSystem, spaceId, saveDataId); + } + + public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out size); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); + } + + public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out commitId); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetSaveDataCommitId(out commitId, spaceId, saveDataId); + } + + public Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader); + } + + public Result OpenSaveDataMetaFile(ref SharedRef outFile, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType type) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataMetaFile(ref outFile, spaceId, in attribute, type); + } + + public Result DeleteCacheStorage(ushort index) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteCacheStorage(index); + } + + public Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index) + { + UnsafeHelpers.SkipParamInit(out dataSize, out journalSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetCacheStorageSize(out dataSize, out journalSize, index); + } + + public Result OpenSaveDataTransferManager(ref SharedRef outTransferManager) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataTransferManager(ref outTransferManager); + } + + public Result OpenSaveDataTransferManagerVersion2( + ref SharedRef outTransferManager) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataTransferManagerVersion2(ref outTransferManager); + } + + public Result OpenSaveDataTransferManagerForSaveDataRepair( + ref SharedRef outTransferManager) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataTransferManagerForSaveDataRepair(ref outTransferManager); + } + + public Result OpenSaveDataTransferManagerForRepair( + ref SharedRef outTransferManager) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataTransferManagerForRepair(ref outTransferManager); + } + + public Result OpenSaveDataTransferProhibiter(ref SharedRef outProhibiter, + Ncm.ApplicationId applicationId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataTransferProhibiter(ref outProhibiter, applicationId); + } + + public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, + int startIndex, int bufferIdCount) + { + UnsafeHelpers.SkipParamInit(out readCount); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ListAccessibleSaveDataOwnerId(out readCount, idBuffer, programId, startIndex, + bufferIdCount); + } + + public Result OpenSaveDataMover(ref SharedRef outSaveDataMover, SaveDataSpaceId sourceSpaceId, + SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenSaveDataMover(ref outSaveDataMover, sourceSpaceId, destinationSpaceId, + workBufferHandle, workBufferSize); + } + + public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) + { + return Result.Success; + } + + public Result SetSaveDataRootPath(in FspPath path) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.SetSaveDataRootPath(in path); + } + + public Result UnsetSaveDataRootPath() + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.UnsetSaveDataRootPath(); + } + + public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, + ContentStorageId storageId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc.Miss(); + + return ncaFsService.OpenContentStorageFileSystem(ref outFileSystem, storageId); + } + + public Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, + CloudBackupWorkStorageId storageId) + { + var storageFlag = StorageType.NonGameCard; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountCloudBackupWorkStorage); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + using var fileSystem = new SharedRef(); + rc = _fsProxyCore.OpenCloudBackupWorkStorageFileSystem(ref fileSystem.Ref(), storageId); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId) + { + var storageFlag = StorageType.NonGameCard; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + AccessibilityType accessType = storageId > CustomStorageId.SdCard + ? AccessibilityType.NotMount + : AccessibilityType.MountCustomStorage; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(accessType); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + using var fileSystem = new SharedRef(); + rc = _fsProxyCore.OpenCustomStorageFileSystem(ref fileSystem.Ref(), storageId); + if (rc.IsFailure()) return rc; + + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, + GameCardPartition partitionId) + { + return GetBaseFileSystemService().OpenGameCardFileSystem(ref outFileSystem, handle, partitionId); + } + + public Result IsArchivedProgram(out bool isArchived, ulong processId) + { + UnsafeHelpers.SkipParamInit(out isArchived); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.IsArchivedProgram(out isArchived, processId); + } + + public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + { + UnsafeHelpers.SkipParamInit(out totalSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.QuerySaveDataTotalSize(out totalSize, dataSize, journalSize); + } + + public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) + { + return GetTimeService().SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); + } + + public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out rightsId); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.GetRightsId(out rightsId, programId, storageId); + } + + public Result GetRightsIdByPath(out RightsId rightsId, in FspPath path) + { + return GetRightsIdAndKeyGenerationByPath(out rightsId, out _, in path); + } + + public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path) + { + UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in path); + } + + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.RegisterExternalKey(in rightsId, in externalKey); + } + + public Result UnregisterExternalKey(in RightsId rightsId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.UnregisterExternalKey(in rightsId); + } + + public Result UnregisterAllExternalKey() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.UnregisterAllExternalKey(); + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) + return ResultFs.PermissionDenied.Log(); + + rc = _fsProxyCore.SetSdCardEncryptionSeed(in seed); + if (rc.IsFailure()) return rc; + + rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + rc = saveFsService.SetSdCardEncryptionSeed(in seed); + if (rc.IsFailure()) return rc; + + rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.SetSdCardEncryptionSeed(in seed); + } + + public Result GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo) + { + return GetStatusReportService().GetAndClearFileSystemProxyErrorInfo(out errorInfo); + } + + public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount) + { + return GetProgramIndexRegistryService() + .RegisterProgramIndexMapInfo(programIndexMapInfoBuffer, programCount); + } + + public Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path) + { + return GetBaseFileSystemService().SetBisRootForHost(partitionId, in path); + } + + public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, + OutBuffer readBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, readBuffer); + } + + public Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer) + { + return VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId, readBuffer); + } + + public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset); + } + + public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + // Corrupt both of the save data headers + Result rc = CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0); + if (rc.IsFailure()) return rc; + + return CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0x4000); + } + + public Result CorruptSaveDataFileSystem(ulong saveDataId) + { + return CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId); + } + + public Result CreatePaddingFile(long size) + { + return GetBaseFileSystemService().CreatePaddingFile(size); + } + + public Result DeleteAllPaddingFiles() + { + return GetBaseFileSystemService().DeleteAllPaddingFiles(); + } + + public Result DisableAutoSaveDataCreation() + { + return Result.Success; + } + + public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode) + { + return GetAccessLogService().SetAccessLogMode(mode); + } + + public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode) + { + return GetAccessLogService().GetAccessLogMode(out mode); + } + + public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) + { + return GetProgramIndexRegistryService().GetProgramIndex(out programIndex, out programCount); + } + + public Result OutputAccessLogToSdCard(InBuffer textBuffer) + { + return GetAccessLogService().OutputAccessLogToSdCard(textBuffer); + } + + public Result OutputMultiProgramTagAccessLog() + { + return GetAccessLogService().OutputMultiProgramTagAccessLog(); + } + + public Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo) + { + return GetAccessLogService().OutputApplicationInfoAccessLog(in applicationInfo); + } + + public Result FlushAccessLogOnSdCard() + { + return GetAccessLogService().FlushAccessLogOnSdCard(); + } + + public Result RegisterUpdatePartition() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.RegisterUpdatePartition(); + } + + public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.OpenRegisteredUpdatePartition(ref outFileSystem); + } + + public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) + { + return GetStatusReportService().GetAndClearMemoryReportInfo(out reportInfo); + } + + public Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType) + { + return GetStatusReportService().GetFsStackUsage(out stackUsage, threadType); + } + + public Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OverrideSaveDataTransferTokenSignVerificationKey(key); + } + + public Result SetSdCardAccessibility(bool isAccessible) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.SetSdCardAccessibility(isAccessible); + } + + public Result IsSdCardAccessible(out bool isAccessible) + { + UnsafeHelpers.SkipParamInit(out isAccessible); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.IsSdCardAccessible(out isAccessible); + } + + public Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outEventNotifier, + ulong processId, bool notifyOnDeepRetry) + { + return GetAccessFailureManagementService() + .OpenAccessFailureDetectionEventNotifier(ref outEventNotifier, processId, notifyOnDeepRetry); + } + + public Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle) + { + return GetAccessFailureManagementService().GetAccessFailureDetectionEvent(out eventHandle); + } + + public Result IsAccessFailureDetected(out bool isDetected, ulong processId) + { + return GetAccessFailureManagementService().IsAccessFailureDetected(out isDetected, processId); + } + + public Result ResolveAccessFailure(ulong processId) + { + return GetAccessFailureManagementService().ResolveAccessFailure(processId); + } + + public Result AbandonAccessFailure(ulong processId) + { + return GetAccessFailureManagementService().AbandonAccessFailure(processId); + } + + public Result OpenMultiCommitManager(ref SharedRef outCommitManager) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenMultiCommitManager(ref outCommitManager); + } + + public Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize) + { + return GetBaseFileSystemService().OpenBisWiper(ref outBisWiper, transferMemoryHandle, transferMemorySize); } } diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index 4b9f615d..1c6adecd 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -1,67 +1,66 @@ using LibHac.FsSrv.Impl; using LibHac.FsSrv.Storage; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class FileSystemServer { - public class FileSystemServer + internal FileSystemServerGlobals Globals; + + public FileSystemServerImpl Impl => new FileSystemServerImpl(this); + public StorageService Storage => new StorageService(this); + internal HorizonClient Hos => Globals.Hos; + + /// + /// Creates a new and registers its services using the provided HOS client. + /// + /// The that will be used by this server. + public FileSystemServer(HorizonClient horizonClient) { - internal FileSystemServerGlobals Globals; - - public FileSystemServerImpl Impl => new FileSystemServerImpl(this); - public StorageService Storage => new StorageService(this); - internal HorizonClient Hos => Globals.Hos; - - /// - /// Creates a new and registers its services using the provided HOS client. - /// - /// The that will be used by this server. - public FileSystemServer(HorizonClient horizonClient) - { - Globals.Initialize(horizonClient, this); - } - } - - internal struct FileSystemServerGlobals - { - public HorizonClient Hos; - public object InitMutex; - public FileSystemProxyImplGlobals FileSystemProxyImpl; - public ProgramRegistryImplGlobals ProgramRegistryImpl; - public DeviceEventSimulatorGlobals DeviceEventSimulator; - public AccessControlGlobals AccessControl; - public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory; - public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage; - public MultiCommitManagerGlobals MultiCommitManager; - public LocationResolverSetGlobals LocationResolverSet; - - public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) - { - Hos = horizonClient; - InitMutex = new object(); - - SaveDataSharedFileStorage.Initialize(fsServer); - MultiCommitManager.Initialize(); - LocationResolverSet.Initialize(); - } - } - - // Functions in the nn::fssrv::storage namespace use this struct. - public readonly struct StorageService - { - internal readonly FileSystemServer FsSrv; - internal HorizonClient Hos => FsSrv.Hos; - internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals; - - internal StorageService(FileSystemServer parentServer) => FsSrv = parentServer; - } - - // Functions in the nn::fssrv::detail namespace use this struct. - public readonly struct FileSystemServerImpl - { - internal readonly FileSystemServer FsSrv; - internal HorizonClient Hos => FsSrv.Hos; - internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals; - - internal FileSystemServerImpl(FileSystemServer parentServer) => FsSrv = parentServer; + Globals.Initialize(horizonClient, this); } } + +internal struct FileSystemServerGlobals +{ + public HorizonClient Hos; + public object InitMutex; + public FileSystemProxyImplGlobals FileSystemProxyImpl; + public ProgramRegistryImplGlobals ProgramRegistryImpl; + public DeviceEventSimulatorGlobals DeviceEventSimulator; + public AccessControlGlobals AccessControl; + public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory; + public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage; + public MultiCommitManagerGlobals MultiCommitManager; + public LocationResolverSetGlobals LocationResolverSet; + + public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) + { + Hos = horizonClient; + InitMutex = new object(); + + SaveDataSharedFileStorage.Initialize(fsServer); + MultiCommitManager.Initialize(); + LocationResolverSet.Initialize(); + } +} + +// Functions in the nn::fssrv::storage namespace use this struct. +public readonly struct StorageService +{ + internal readonly FileSystemServer FsSrv; + internal HorizonClient Hos => FsSrv.Hos; + internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals; + + internal StorageService(FileSystemServer parentServer) => FsSrv = parentServer; +} + +// Functions in the nn::fssrv::detail namespace use this struct. +public readonly struct FileSystemServerImpl +{ + internal readonly FileSystemServer FsSrv; + internal HorizonClient Hos => FsSrv.Hos; + internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals; + + internal FileSystemServerImpl(FileSystemServer parentServer) => FsSrv = parentServer; +} diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index 64b8cc5d..93cd083c 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -9,268 +9,267 @@ using LibHac.FsSrv.Storage; using LibHac.FsSystem; using LibHac.Sm; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public static class FileSystemServerInitializer { - public static class FileSystemServerInitializer - { - private const ulong SpeedEmulationProgramIdMinimum = 0x100000000000000; - private const ulong SpeedEmulationProgramIdMaximum = 0x100000000001FFF; + private const ulong SpeedEmulationProgramIdMinimum = 0x100000000000000; + private const ulong SpeedEmulationProgramIdMaximum = 0x100000000001FFF; - private const int BufferManagerHeapSize = 1024 * 1024 * 14; - private const int BufferManagerCacheSize = 1024; - private const int BufferManagerBlockSize = 0x4000; - - /// - /// Initializes a with the provided . - /// - /// The that was created with. - /// The to initialize. - /// The config for initializing . - public static void InitializeWithConfig(HorizonClient client, FileSystemServer server, FileSystemServerConfig config) - { - if (config.FsCreators == null) - throw new ArgumentException("FsCreators must not be null"); - - if (config.DeviceOperator == null) - throw new ArgumentException("DeviceOperator must not be null"); - - server.SetDebugFlagEnabled(false); - server.Storage.InitializeStorageDeviceManagerFactory(null); - - FileSystemProxyConfiguration fspConfig = InitializeFileSystemProxy(server, config); - - using SharedRef fileSystemProxy = server.Impl.GetFileSystemProxyServiceObject(); - ulong processId = client.Os.GetCurrentProcessId().Value; - fileSystemProxy.Get.SetCurrentProcess(processId).IgnoreResult(); - - client.Fs.Impl.InitializeDfcFileSystemProxyServiceObject(ref fileSystemProxy.Ref()); - - InitializeFileSystemProxyServer(client, server); - - var saveService = new SaveDataFileSystemService(fspConfig.SaveDataFileSystemService, processId); - - saveService.CleanUpTemporaryStorage().IgnoreResult(); - saveService.CleanUpSaveData().IgnoreResult(); - saveService.CompleteSaveDataExtension().IgnoreResult(); - saveService.FixSaveData().IgnoreResult(); - saveService.RecoverMultiCommit().IgnoreResult(); - - // NS usually takes care of this - if (client.Fs.IsSdCardInserted()) - client.Fs.SetSdCardAccessibility(true); - } - - private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server, - FileSystemServerConfig config) - { - var random = new Random(); - RandomDataGenerator randomGenerator = buffer => - { - random.NextBytes(buffer); - return Result.Success; - }; - - var bufferManager = new FileSystemBufferManager(); - Memory heapBuffer = GC.AllocateArray(BufferManagerHeapSize, true); - bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize); - - var saveDataIndexerManager = new SaveDataIndexerManager(server.Hos.Fs, Fs.SaveData.SaveIndexerId, - new ArrayPoolMemoryResource(), new SdHandleManager(), false); - - var programRegistryConfig = new ProgramRegistryServiceImpl.Configuration(); - programRegistryConfig.FsServer = server; - - var programRegistryService = new ProgramRegistryServiceImpl(in programRegistryConfig); - - server.InitializeProgramRegistryImpl(programRegistryService); - - var baseStorageConfig = new BaseStorageServiceImpl.Configuration(); - baseStorageConfig.BisStorageCreator = config.FsCreators.BuiltInStorageCreator; - baseStorageConfig.GameCardStorageCreator = config.FsCreators.GameCardStorageCreator; - baseStorageConfig.FsServer = server; - baseStorageConfig.DeviceOperator = config.DeviceOperator; - var baseStorageService = new BaseStorageServiceImpl(in baseStorageConfig); - - var timeService = new TimeServiceImpl(server); - - var baseFsServiceConfig = new BaseFileSystemServiceImpl.Configuration(); - baseFsServiceConfig.BisFileSystemCreator = config.FsCreators.BuiltInStorageFileSystemCreator; - baseFsServiceConfig.GameCardFileSystemCreator = config.FsCreators.GameCardFileSystemCreator; - baseFsServiceConfig.SdCardFileSystemCreator = config.FsCreators.SdCardFileSystemCreator; - baseFsServiceConfig.BisWiperCreator = BisWiper.CreateWiper; - baseFsServiceConfig.FsServer = server; - var baseFsService = new BaseFileSystemServiceImpl(in baseFsServiceConfig); - - var accessFailureManagementServiceConfig = new AccessFailureManagementServiceImpl.Configuration(); - accessFailureManagementServiceConfig.FsServer = server; - - var accessFailureManagementService = - new AccessFailureManagementServiceImpl(in accessFailureManagementServiceConfig); - - var speedEmulationRange = - new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdMinimum, - SpeedEmulationProgramIdMaximum); - - var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration(); - ncaFsServiceConfig.BaseFsService = baseFsService; - ncaFsServiceConfig.LocalFsCreator = config.FsCreators.LocalFileSystemCreator; - ncaFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; - ncaFsServiceConfig.PartitionFsCreator = config.FsCreators.PartitionFileSystemCreator; - ncaFsServiceConfig.RomFsCreator = config.FsCreators.RomFileSystemCreator; - ncaFsServiceConfig.StorageOnNcaCreator = config.FsCreators.StorageOnNcaCreator; - ncaFsServiceConfig.SubDirectoryFsCreator = config.FsCreators.SubDirectoryFileSystemCreator; - ncaFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; - ncaFsServiceConfig.ProgramRegistryService = programRegistryService; - ncaFsServiceConfig.AccessFailureManagementService = accessFailureManagementService; - ncaFsServiceConfig.SpeedEmulationRange = speedEmulationRange; - ncaFsServiceConfig.FsServer = server; - - var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig, config.ExternalKeySet); - - var saveFsServiceConfig = new SaveDataFileSystemServiceImpl.Configuration(); - saveFsServiceConfig.BaseFsService = baseFsService; - saveFsServiceConfig.TimeService = timeService; - saveFsServiceConfig.LocalFsCreator = config.FsCreators.LocalFileSystemCreator; - saveFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; - saveFsServiceConfig.SaveFsCreator = config.FsCreators.SaveDataFileSystemCreator; - saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; - saveFsServiceConfig.ProgramRegistryService = programRegistryService; - saveFsServiceConfig.BufferManager = bufferManager; - saveFsServiceConfig.GenerateRandomData = randomGenerator; - saveFsServiceConfig.IsPseudoSaveData = () => true; - saveFsServiceConfig.MaxSaveFsCacheCount = 1; - saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; - saveFsServiceConfig.FsServer = server; - - var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig); - - var statusReportServiceConfig = new StatusReportServiceImpl.Configuration(); - statusReportServiceConfig.NcaFsServiceImpl = ncaFsService; - statusReportServiceConfig.SaveFsServiceImpl = saveFsService; - statusReportServiceConfig.BufferManagerMemoryReport = null; - statusReportServiceConfig.ExpHeapMemoryReport = null; - statusReportServiceConfig.BufferPoolMemoryReport = null; - statusReportServiceConfig.GetPatrolAllocateCounts = null; - statusReportServiceConfig.MainThreadStackUsageReporter = new DummyStackUsageReporter(); - statusReportServiceConfig.IpcWorkerThreadStackUsageReporter = new DummyStackUsageReporter(); - statusReportServiceConfig.PipeLineWorkerThreadStackUsageReporter = new DummyStackUsageReporter(); - statusReportServiceConfig.FsServer = server; - - var statusReportService = new StatusReportServiceImpl(in statusReportServiceConfig); - - var accessLogServiceConfig = new AccessLogServiceImpl.Configuration(); - accessLogServiceConfig.MinimumProgramIdForSdCardLog = 0x0100000000003000; - accessLogServiceConfig.FsServer = server; - - var accessLogService = new AccessLogServiceImpl(in accessLogServiceConfig); - - var fspConfig = new FileSystemProxyConfiguration - { - FsCreatorInterfaces = config.FsCreators, - BaseStorageService = baseStorageService, - BaseFileSystemService = baseFsService, - NcaFileSystemService = ncaFsService, - SaveDataFileSystemService = saveFsService, - AccessFailureManagementService = accessFailureManagementService, - TimeService = timeService, - StatusReportService = statusReportService, - ProgramRegistryService = programRegistryService, - AccessLogService = accessLogService - }; - - server.InitializeFileSystemProxy(fspConfig); - return fspConfig; - } - - private static void InitializeFileSystemProxyServer(HorizonClient client, FileSystemServer server) - { - client.Sm.RegisterService(new FileSystemProxyService(server), "fsp-srv").IgnoreResult(); - client.Sm.RegisterService(new FileSystemProxyForLoaderService(server), "fsp-ldr").IgnoreResult(); - client.Sm.RegisterService(new ProgramRegistryService(server), "fsp-pr").IgnoreResult(); - } - - private class FileSystemProxyService : IServiceObject - { - private readonly FileSystemServer _server; - - public FileSystemProxyService(FileSystemServer server) - { - _server = server; - } - - public Result GetServiceObject(ref SharedRef serviceObject) - { - using SharedRef derivedObject = _server.Impl.GetFileSystemProxyServiceObject(); - serviceObject.SetByMove(ref derivedObject.Ref()); - return Result.Success; - } - - public void Dispose() { } - } - - private class FileSystemProxyForLoaderService : IServiceObject - { - private readonly FileSystemServer _server; - - public FileSystemProxyForLoaderService(FileSystemServer server) - { - _server = server; - } - - public Result GetServiceObject(ref SharedRef serviceObject) - { - using SharedRef derivedObject = _server.Impl.GetFileSystemProxyForLoaderServiceObject(); - serviceObject.SetByMove(ref derivedObject.Ref()); - return Result.Success; - } - - public void Dispose() { } - } - - private class ProgramRegistryService : IServiceObject - { - private readonly FileSystemServer _server; - - public ProgramRegistryService(FileSystemServer server) - { - _server = server; - } - - public Result GetServiceObject(ref SharedRef serviceObject) - { - using SharedRef derivedObject = _server.Impl.GetProgramRegistryServiceObject(); - serviceObject.SetByMove(ref derivedObject.Ref()); - return Result.Success; - } - - public void Dispose() { } - } - - private class DummyStackUsageReporter : IStackUsageReporter - { - public uint GetStackUsage() => 0; - } - } + private const int BufferManagerHeapSize = 1024 * 1024 * 14; + private const int BufferManagerCacheSize = 1024; + private const int BufferManagerBlockSize = 0x4000; /// - /// Contains the configuration for creating a new . + /// Initializes a with the provided . /// - public class FileSystemServerConfig + /// The that was created with. + /// The to initialize. + /// The config for initializing . + public static void InitializeWithConfig(HorizonClient client, FileSystemServer server, FileSystemServerConfig config) { - /// - /// The used for creating filesystems. - /// - public FileSystemCreatorInterfaces FsCreators { get; set; } + if (config.FsCreators == null) + throw new ArgumentException("FsCreators must not be null"); - /// - /// An for managing the gamecard and SD card. - /// - public IDeviceOperator DeviceOperator { get; set; } + if (config.DeviceOperator == null) + throw new ArgumentException("DeviceOperator must not be null"); - /// - /// A keyset containing rights IDs and title keys. - /// If null, an empty set will be created. - /// - public ExternalKeySet ExternalKeySet { get; set; } + server.SetDebugFlagEnabled(false); + server.Storage.InitializeStorageDeviceManagerFactory(null); + + FileSystemProxyConfiguration fspConfig = InitializeFileSystemProxy(server, config); + + using SharedRef fileSystemProxy = server.Impl.GetFileSystemProxyServiceObject(); + ulong processId = client.Os.GetCurrentProcessId().Value; + fileSystemProxy.Get.SetCurrentProcess(processId).IgnoreResult(); + + client.Fs.Impl.InitializeDfcFileSystemProxyServiceObject(ref fileSystemProxy.Ref()); + + InitializeFileSystemProxyServer(client, server); + + var saveService = new SaveDataFileSystemService(fspConfig.SaveDataFileSystemService, processId); + + saveService.CleanUpTemporaryStorage().IgnoreResult(); + saveService.CleanUpSaveData().IgnoreResult(); + saveService.CompleteSaveDataExtension().IgnoreResult(); + saveService.FixSaveData().IgnoreResult(); + saveService.RecoverMultiCommit().IgnoreResult(); + + // NS usually takes care of this + if (client.Fs.IsSdCardInserted()) + client.Fs.SetSdCardAccessibility(true); + } + + private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server, + FileSystemServerConfig config) + { + var random = new Random(); + RandomDataGenerator randomGenerator = buffer => + { + random.NextBytes(buffer); + return Result.Success; + }; + + var bufferManager = new FileSystemBufferManager(); + Memory heapBuffer = GC.AllocateArray(BufferManagerHeapSize, true); + bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize); + + var saveDataIndexerManager = new SaveDataIndexerManager(server.Hos.Fs, Fs.SaveData.SaveIndexerId, + new ArrayPoolMemoryResource(), new SdHandleManager(), false); + + var programRegistryConfig = new ProgramRegistryServiceImpl.Configuration(); + programRegistryConfig.FsServer = server; + + var programRegistryService = new ProgramRegistryServiceImpl(in programRegistryConfig); + + server.InitializeProgramRegistryImpl(programRegistryService); + + var baseStorageConfig = new BaseStorageServiceImpl.Configuration(); + baseStorageConfig.BisStorageCreator = config.FsCreators.BuiltInStorageCreator; + baseStorageConfig.GameCardStorageCreator = config.FsCreators.GameCardStorageCreator; + baseStorageConfig.FsServer = server; + baseStorageConfig.DeviceOperator = config.DeviceOperator; + var baseStorageService = new BaseStorageServiceImpl(in baseStorageConfig); + + var timeService = new TimeServiceImpl(server); + + var baseFsServiceConfig = new BaseFileSystemServiceImpl.Configuration(); + baseFsServiceConfig.BisFileSystemCreator = config.FsCreators.BuiltInStorageFileSystemCreator; + baseFsServiceConfig.GameCardFileSystemCreator = config.FsCreators.GameCardFileSystemCreator; + baseFsServiceConfig.SdCardFileSystemCreator = config.FsCreators.SdCardFileSystemCreator; + baseFsServiceConfig.BisWiperCreator = BisWiper.CreateWiper; + baseFsServiceConfig.FsServer = server; + var baseFsService = new BaseFileSystemServiceImpl(in baseFsServiceConfig); + + var accessFailureManagementServiceConfig = new AccessFailureManagementServiceImpl.Configuration(); + accessFailureManagementServiceConfig.FsServer = server; + + var accessFailureManagementService = + new AccessFailureManagementServiceImpl(in accessFailureManagementServiceConfig); + + var speedEmulationRange = + new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdMinimum, + SpeedEmulationProgramIdMaximum); + + var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration(); + ncaFsServiceConfig.BaseFsService = baseFsService; + ncaFsServiceConfig.LocalFsCreator = config.FsCreators.LocalFileSystemCreator; + ncaFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; + ncaFsServiceConfig.PartitionFsCreator = config.FsCreators.PartitionFileSystemCreator; + ncaFsServiceConfig.RomFsCreator = config.FsCreators.RomFileSystemCreator; + ncaFsServiceConfig.StorageOnNcaCreator = config.FsCreators.StorageOnNcaCreator; + ncaFsServiceConfig.SubDirectoryFsCreator = config.FsCreators.SubDirectoryFileSystemCreator; + ncaFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; + ncaFsServiceConfig.ProgramRegistryService = programRegistryService; + ncaFsServiceConfig.AccessFailureManagementService = accessFailureManagementService; + ncaFsServiceConfig.SpeedEmulationRange = speedEmulationRange; + ncaFsServiceConfig.FsServer = server; + + var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig, config.ExternalKeySet); + + var saveFsServiceConfig = new SaveDataFileSystemServiceImpl.Configuration(); + saveFsServiceConfig.BaseFsService = baseFsService; + saveFsServiceConfig.TimeService = timeService; + saveFsServiceConfig.LocalFsCreator = config.FsCreators.LocalFileSystemCreator; + saveFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; + saveFsServiceConfig.SaveFsCreator = config.FsCreators.SaveDataFileSystemCreator; + saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; + saveFsServiceConfig.ProgramRegistryService = programRegistryService; + saveFsServiceConfig.BufferManager = bufferManager; + saveFsServiceConfig.GenerateRandomData = randomGenerator; + saveFsServiceConfig.IsPseudoSaveData = () => true; + saveFsServiceConfig.MaxSaveFsCacheCount = 1; + saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; + saveFsServiceConfig.FsServer = server; + + var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig); + + var statusReportServiceConfig = new StatusReportServiceImpl.Configuration(); + statusReportServiceConfig.NcaFsServiceImpl = ncaFsService; + statusReportServiceConfig.SaveFsServiceImpl = saveFsService; + statusReportServiceConfig.BufferManagerMemoryReport = null; + statusReportServiceConfig.ExpHeapMemoryReport = null; + statusReportServiceConfig.BufferPoolMemoryReport = null; + statusReportServiceConfig.GetPatrolAllocateCounts = null; + statusReportServiceConfig.MainThreadStackUsageReporter = new DummyStackUsageReporter(); + statusReportServiceConfig.IpcWorkerThreadStackUsageReporter = new DummyStackUsageReporter(); + statusReportServiceConfig.PipeLineWorkerThreadStackUsageReporter = new DummyStackUsageReporter(); + statusReportServiceConfig.FsServer = server; + + var statusReportService = new StatusReportServiceImpl(in statusReportServiceConfig); + + var accessLogServiceConfig = new AccessLogServiceImpl.Configuration(); + accessLogServiceConfig.MinimumProgramIdForSdCardLog = 0x0100000000003000; + accessLogServiceConfig.FsServer = server; + + var accessLogService = new AccessLogServiceImpl(in accessLogServiceConfig); + + var fspConfig = new FileSystemProxyConfiguration + { + FsCreatorInterfaces = config.FsCreators, + BaseStorageService = baseStorageService, + BaseFileSystemService = baseFsService, + NcaFileSystemService = ncaFsService, + SaveDataFileSystemService = saveFsService, + AccessFailureManagementService = accessFailureManagementService, + TimeService = timeService, + StatusReportService = statusReportService, + ProgramRegistryService = programRegistryService, + AccessLogService = accessLogService + }; + + server.InitializeFileSystemProxy(fspConfig); + return fspConfig; + } + + private static void InitializeFileSystemProxyServer(HorizonClient client, FileSystemServer server) + { + client.Sm.RegisterService(new FileSystemProxyService(server), "fsp-srv").IgnoreResult(); + client.Sm.RegisterService(new FileSystemProxyForLoaderService(server), "fsp-ldr").IgnoreResult(); + client.Sm.RegisterService(new ProgramRegistryService(server), "fsp-pr").IgnoreResult(); + } + + private class FileSystemProxyService : IServiceObject + { + private readonly FileSystemServer _server; + + public FileSystemProxyService(FileSystemServer server) + { + _server = server; + } + + public Result GetServiceObject(ref SharedRef serviceObject) + { + using SharedRef derivedObject = _server.Impl.GetFileSystemProxyServiceObject(); + serviceObject.SetByMove(ref derivedObject.Ref()); + return Result.Success; + } + + public void Dispose() { } + } + + private class FileSystemProxyForLoaderService : IServiceObject + { + private readonly FileSystemServer _server; + + public FileSystemProxyForLoaderService(FileSystemServer server) + { + _server = server; + } + + public Result GetServiceObject(ref SharedRef serviceObject) + { + using SharedRef derivedObject = _server.Impl.GetFileSystemProxyForLoaderServiceObject(); + serviceObject.SetByMove(ref derivedObject.Ref()); + return Result.Success; + } + + public void Dispose() { } + } + + private class ProgramRegistryService : IServiceObject + { + private readonly FileSystemServer _server; + + public ProgramRegistryService(FileSystemServer server) + { + _server = server; + } + + public Result GetServiceObject(ref SharedRef serviceObject) + { + using SharedRef derivedObject = _server.Impl.GetProgramRegistryServiceObject(); + serviceObject.SetByMove(ref derivedObject.Ref()); + return Result.Success; + } + + public void Dispose() { } + } + + private class DummyStackUsageReporter : IStackUsageReporter + { + public uint GetStackUsage() => 0; } } + +/// +/// Contains the configuration for creating a new . +/// +public class FileSystemServerConfig +{ + /// + /// The used for creating filesystems. + /// + public FileSystemCreatorInterfaces FsCreators { get; set; } + + /// + /// An for managing the gamecard and SD card. + /// + public IDeviceOperator DeviceOperator { get; set; } + + /// + /// A keyset containing rights IDs and title keys. + /// If null, an empty set will be created. + /// + public ExternalKeySet ExternalKeySet { get; set; } +} diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs index 80951c16..9255f1b2 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs @@ -4,135 +4,134 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv.Impl; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +/// +/// Provides objects for the built-in storage (BIS) partitions. +/// +/// +/// An provides s of the +/// four BIS partitions: CalibrationFile, SafeMode, User and System. +/// The source of each partition is determined by the (optionally) provided +/// .
+/// There are multiple ways the source of a partition can be specified in the configuration. In order of precedence: +/// +/// Set an object with .
+/// The IFileSystem will directly be used for the specified partition.
+/// Set a path with .
+/// The source for the partition will be the provided path in the root file system. e.g. at /my/path in the root FS. +/// The root file system must be set in the configuration when using this option.
+/// Only set the root file system in the configuration.
+/// The source of the partition will be at its default path in the root file system.
+/// Default paths for each partition:
+/// : /bis/cal
+/// : /bis/safe
+/// : /bis/user
+/// : /bis/system
+///
+public class EmulatedBisFileSystemCreator : IBuiltInStorageFileSystemCreator { + private EmulatedBisFileSystemCreatorConfig Config { get; } + /// - /// Provides objects for the built-in storage (BIS) partitions. + /// Initializes an with the default + /// using the provided . + /// Each partition will be located at their default paths in this IFileSystem. /// - /// - /// An provides s of the - /// four BIS partitions: CalibrationFile, SafeMode, User and System. - /// The source of each partition is determined by the (optionally) provided - /// .
- /// There are multiple ways the source of a partition can be specified in the configuration. In order of precedence: - /// - /// Set an object with .
- /// The IFileSystem will directly be used for the specified partition.
- /// Set a path with .
- /// The source for the partition will be the provided path in the root file system. e.g. at /my/path in the root FS. - /// The root file system must be set in the configuration when using this option.
- /// Only set the root file system in the configuration.
- /// The source of the partition will be at its default path in the root file system.
- /// Default paths for each partition:
- /// : /bis/cal
- /// : /bis/safe
- /// : /bis/user
- /// : /bis/system
- ///
- public class EmulatedBisFileSystemCreator : IBuiltInStorageFileSystemCreator + /// The to use as the root file system. + public EmulatedBisFileSystemCreator(ref SharedRef rootFileSystem) { - private EmulatedBisFileSystemCreatorConfig Config { get; } + Config = new EmulatedBisFileSystemCreatorConfig(); + Config.SetRootFileSystem(ref rootFileSystem).ThrowIfFailure(); + } - /// - /// Initializes an with the default - /// using the provided . - /// Each partition will be located at their default paths in this IFileSystem. - /// - /// The to use as the root file system. - public EmulatedBisFileSystemCreator(ref SharedRef rootFileSystem) + /// + /// Initializes an with the provided configuration. + /// + /// The configuration to use. + public EmulatedBisFileSystemCreator(EmulatedBisFileSystemCreatorConfig config) + { + Config = config; + } + + // Todo: Make case sensitive + public Result Create(ref SharedRef outFileSystem, BisPartitionId partitionId, bool caseSensitive) + { + if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + + using var fileSystem = new SharedRef(); + if (Config.TryGetFileSystem(ref fileSystem.Ref(), partitionId)) { - Config = new EmulatedBisFileSystemCreatorConfig(); - Config.SetRootFileSystem(ref rootFileSystem).ThrowIfFailure(); - } - - /// - /// Initializes an with the provided configuration. - /// - /// The configuration to use. - public EmulatedBisFileSystemCreator(EmulatedBisFileSystemCreatorConfig config) - { - Config = config; - } - - // Todo: Make case sensitive - public Result Create(ref SharedRef outFileSystem, BisPartitionId partitionId, bool caseSensitive) - { - if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); - - using var fileSystem = new SharedRef(); - if (Config.TryGetFileSystem(ref fileSystem.Ref(), partitionId)) - { - outFileSystem.SetByMove(ref fileSystem.Ref()); - return Result.Success; - } - - using var rootFileSystem = new SharedRef(); - - if (!Config.TryGetRootFileSystem(ref rootFileSystem.Ref())) - { - return ResultFs.PreconditionViolation.Log(); - } - - using var bisRootPath = new Path(); - Result rc = bisRootPath.Initialize(GetPartitionPath(partitionId).ToU8String()); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowEmptyPath(); - rc = bisRootPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - using var partitionFileSystem = new SharedRef(); - rc = Utility.WrapSubDirectory(ref partitionFileSystem.Ref(), ref rootFileSystem.Ref(), in bisRootPath, true); - if (rc.IsFailure()) return rc; - - outFileSystem.SetByMove(ref partitionFileSystem.Ref()); + outFileSystem.SetByMove(ref fileSystem.Ref()); return Result.Success; } - public Result SetBisRoot(BisPartitionId partitionId, string rootPath) + using var rootFileSystem = new SharedRef(); + + if (!Config.TryGetRootFileSystem(ref rootFileSystem.Ref())) { - return Config.SetPath(rootPath, partitionId); + return ResultFs.PreconditionViolation.Log(); } - private bool IsValidPartitionId(BisPartitionId id) + using var bisRootPath = new Path(); + Result rc = bisRootPath.Initialize(GetPartitionPath(partitionId).ToU8String()); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + rc = bisRootPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + using var partitionFileSystem = new SharedRef(); + rc = Utility.WrapSubDirectory(ref partitionFileSystem.Ref(), ref rootFileSystem.Ref(), in bisRootPath, true); + if (rc.IsFailure()) return rc; + + outFileSystem.SetByMove(ref partitionFileSystem.Ref()); + return Result.Success; + } + + public Result SetBisRoot(BisPartitionId partitionId, string rootPath) + { + return Config.SetPath(rootPath, partitionId); + } + + private bool IsValidPartitionId(BisPartitionId id) + { + return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System; + } + + private string GetPartitionPath(BisPartitionId id) + { + if (Config.TryGetPartitionPath(out string path, id)) { - return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System; + return path; } - private string GetPartitionPath(BisPartitionId id) + return GetDefaultPartitionPath(id); + } + + /// + /// Gets the default path for the specified partition. + /// + /// The partition ID of the path to get. + /// The default path for the specified partition. + /// These paths are the same paths that Nintendo uses on Windows. + private string GetDefaultPartitionPath(BisPartitionId id) + { + Debug.Assert(IsValidPartitionId(id)); + + switch (id) { - if (Config.TryGetPartitionPath(out string path, id)) - { - return path; - } - - return GetDefaultPartitionPath(id); - } - - /// - /// Gets the default path for the specified partition. - /// - /// The partition ID of the path to get. - /// The default path for the specified partition. - /// These paths are the same paths that Nintendo uses on Windows. - private string GetDefaultPartitionPath(BisPartitionId id) - { - Debug.Assert(IsValidPartitionId(id)); - - switch (id) - { - case BisPartitionId.CalibrationFile: - return "/bis/cal"; - case BisPartitionId.SafeMode: - return "/bis/safe"; - case BisPartitionId.User: - return "/bis/user"; - case BisPartitionId.System: - return "/bis/system"; - default: - return string.Empty; - } + case BisPartitionId.CalibrationFile: + return "/bis/cal"; + case BisPartitionId.SafeMode: + return "/bis/safe"; + case BisPartitionId.User: + return "/bis/user"; + case BisPartitionId.System: + return "/bis/system"; + default: + return string.Empty; } } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreatorConfig.cs b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreatorConfig.cs index e337f273..60b97216 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreatorConfig.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreatorConfig.cs @@ -3,91 +3,90 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +/// +/// Configuration for that specifies how each +/// BIS partition is opened. +/// +public class EmulatedBisFileSystemCreatorConfig { - /// - /// Configuration for that specifies how each - /// BIS partition is opened. - /// - public class EmulatedBisFileSystemCreatorConfig + private const int ValidPartitionCount = 4; + + private SharedRef _rootFileSystem; + + private SharedRef[] PartitionFileSystems { get; } = new SharedRef[ValidPartitionCount]; + private string[] PartitionPaths { get; } = new string[ValidPartitionCount]; + + public Result SetRootFileSystem(ref SharedRef fileSystem) { - private const int ValidPartitionCount = 4; + if (!fileSystem.HasValue) return ResultFs.NullptrArgument.Log(); + if (_rootFileSystem.HasValue) return ResultFs.PreconditionViolation.Log(); - private SharedRef _rootFileSystem; + _rootFileSystem.SetByMove(ref fileSystem); - private SharedRef[] PartitionFileSystems { get; } = new SharedRef[ValidPartitionCount]; - private string[] PartitionPaths { get; } = new string[ValidPartitionCount]; + return Result.Success; + } - public Result SetRootFileSystem(ref SharedRef fileSystem) + public Result SetFileSystem(ref UniqueRef fileSystem, BisPartitionId partitionId) + { + if (!fileSystem.HasValue) return ResultFs.NullptrArgument.Log(); + if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + + PartitionFileSystems[GetArrayIndex(partitionId)].Set(ref fileSystem); + + return Result.Success; + } + + public Result SetPath(string path, BisPartitionId partitionId) + { + if (path == null) return ResultFs.NullptrArgument.Log(); + if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + + PartitionPaths[GetArrayIndex(partitionId)] = path; + + return Result.Success; + } + + public bool TryGetRootFileSystem(ref SharedRef outFileSystem) + { + outFileSystem.SetByCopy(in _rootFileSystem); + + return outFileSystem.HasValue; + } + + public bool TryGetFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId) + { + if (!IsValidPartitionId(partitionId)) + return false; + + outFileSystem.SetByCopy(in PartitionFileSystems[GetArrayIndex(partitionId)]); + + return outFileSystem.HasValue; + } + + public bool TryGetPartitionPath(out string path, BisPartitionId partitionId) + { + if (!IsValidPartitionId(partitionId)) { - if (!fileSystem.HasValue) return ResultFs.NullptrArgument.Log(); - if (_rootFileSystem.HasValue) return ResultFs.PreconditionViolation.Log(); - - _rootFileSystem.SetByMove(ref fileSystem); - - return Result.Success; + UnsafeHelpers.SkipParamInit(out path); + return false; } - public Result SetFileSystem(ref UniqueRef fileSystem, BisPartitionId partitionId) - { - if (!fileSystem.HasValue) return ResultFs.NullptrArgument.Log(); - if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + path = PartitionPaths[GetArrayIndex(partitionId)]; - PartitionFileSystems[GetArrayIndex(partitionId)].Set(ref fileSystem); + return path != null; + } - return Result.Success; - } + private int GetArrayIndex(BisPartitionId id) + { + Debug.Assert(IsValidPartitionId(id)); - public Result SetPath(string path, BisPartitionId partitionId) - { - if (path == null) return ResultFs.NullptrArgument.Log(); - if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + return id - BisPartitionId.CalibrationFile; + } - PartitionPaths[GetArrayIndex(partitionId)] = path; - - return Result.Success; - } - - public bool TryGetRootFileSystem(ref SharedRef outFileSystem) - { - outFileSystem.SetByCopy(in _rootFileSystem); - - return outFileSystem.HasValue; - } - - public bool TryGetFileSystem(ref SharedRef outFileSystem, BisPartitionId partitionId) - { - if (!IsValidPartitionId(partitionId)) - return false; - - outFileSystem.SetByCopy(in PartitionFileSystems[GetArrayIndex(partitionId)]); - - return outFileSystem.HasValue; - } - - public bool TryGetPartitionPath(out string path, BisPartitionId partitionId) - { - if (!IsValidPartitionId(partitionId)) - { - UnsafeHelpers.SkipParamInit(out path); - return false; - } - - path = PartitionPaths[GetArrayIndex(partitionId)]; - - return path != null; - } - - private int GetArrayIndex(BisPartitionId id) - { - Debug.Assert(IsValidPartitionId(id)); - - return id - BisPartitionId.CalibrationFile; - } - - private bool IsValidPartitionId(BisPartitionId id) - { - return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System; - } + private bool IsValidPartitionId(BisPartitionId id) + { + return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System; } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs index 61ffa891..a70b1d7d 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs @@ -2,36 +2,35 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class EmulatedGameCardFsCreator : IGameCardFileSystemCreator { - public class EmulatedGameCardFsCreator : IGameCardFileSystemCreator + // ReSharper disable once NotAccessedField.Local + private EmulatedGameCardStorageCreator _storageCreator; + private EmulatedGameCard _gameCard; + + public EmulatedGameCardFsCreator(EmulatedGameCardStorageCreator storageCreator, EmulatedGameCard gameCard) { - // ReSharper disable once NotAccessedField.Local - private EmulatedGameCardStorageCreator _storageCreator; - private EmulatedGameCard _gameCard; + _storageCreator = storageCreator; + _gameCard = gameCard; + } - public EmulatedGameCardFsCreator(EmulatedGameCardStorageCreator storageCreator, EmulatedGameCard gameCard) + public Result Create(ref SharedRef outFileSystem, GameCardHandle handle, + GameCardPartition partitionType) + { + // Use the old xci code temporarily + + Result rc = _gameCard.GetXci(out Xci xci, handle); + if (rc.IsFailure()) return rc; + + if (!xci.HasPartition((XciPartitionType)partitionType)) { - _storageCreator = storageCreator; - _gameCard = gameCard; + return ResultFs.PartitionNotFound.Log(); } - public Result Create(ref SharedRef outFileSystem, GameCardHandle handle, - GameCardPartition partitionType) - { - // Use the old xci code temporarily - - Result rc = _gameCard.GetXci(out Xci xci, handle); - if (rc.IsFailure()) return rc; - - if (!xci.HasPartition((XciPartitionType)partitionType)) - { - return ResultFs.PartitionNotFound.Log(); - } - - XciPartition fs = xci.OpenPartition((XciPartitionType)partitionType); - outFileSystem.Reset(fs); - return Result.Success; - } + XciPartition fs = xci.OpenPartition((XciPartitionType)partitionType); + outFileSystem.Reset(fs); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs index 9c3acad4..c803eb7e 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs @@ -2,123 +2,122 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class EmulatedGameCardStorageCreator : IGameCardStorageCreator { - public class EmulatedGameCardStorageCreator : IGameCardStorageCreator + private EmulatedGameCard GameCard { get; } + + public EmulatedGameCardStorageCreator(EmulatedGameCard gameCard) + { + GameCard = gameCard; + } + + public Result CreateReadOnly(GameCardHandle handle, ref SharedRef outStorage) + { + if (GameCard.IsGameCardHandleInvalid(handle)) + { + return ResultFs.InvalidGameCardHandleOnOpenNormalPartition.Log(); + } + + using var baseStorage = new SharedRef(new ReadOnlyGameCardStorage(GameCard, handle)); + + Result rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); + if (rc.IsFailure()) return rc; + + outStorage.Reset(new SubStorage(in baseStorage, 0, cardInfo.SecureAreaOffset)); + return Result.Success; + } + + public Result CreateSecureReadOnly(GameCardHandle handle, ref SharedRef outStorage) + { + if (GameCard.IsGameCardHandleInvalid(handle)) + { + return ResultFs.InvalidGameCardHandleOnOpenSecurePartition.Log(); + } + + Span deviceId = stackalloc byte[0x10]; + Span imageHash = stackalloc byte[0x20]; + + Result rc = GameCard.GetGameCardDeviceId(deviceId); + if (rc.IsFailure()) return rc; + + rc = GameCard.GetGameCardImageHash(imageHash); + if (rc.IsFailure()) return rc; + + using var baseStorage = + new SharedRef(new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash)); + + rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); + if (rc.IsFailure()) return rc; + + outStorage.Reset(new SubStorage(in baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize)); + return Result.Success; + } + + public Result CreateWriteOnly(GameCardHandle handle, ref SharedRef outStorage) + { + throw new NotImplementedException(); + } + + private class ReadOnlyGameCardStorage : IStorage { private EmulatedGameCard GameCard { get; } + private GameCardHandle Handle { get; set; } - public EmulatedGameCardStorageCreator(EmulatedGameCard gameCard) + // ReSharper disable once UnusedAutoPropertyAccessor.Local + private bool IsSecureMode { get; } + private byte[] DeviceId { get; } = new byte[0x10]; + private byte[] ImageHash { get; } = new byte[0x20]; + + public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle) { GameCard = gameCard; + Handle = handle; } - public Result CreateReadOnly(GameCardHandle handle, ref SharedRef outStorage) + public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle, + ReadOnlySpan deviceId, ReadOnlySpan imageHash) { - if (GameCard.IsGameCardHandleInvalid(handle)) - { - return ResultFs.InvalidGameCardHandleOnOpenNormalPartition.Log(); - } + GameCard = gameCard; + Handle = handle; + IsSecureMode = true; + deviceId.CopyTo(DeviceId); + imageHash.CopyTo(ImageHash); + } - using var baseStorage = new SharedRef(new ReadOnlyGameCardStorage(GameCard, handle)); + protected override Result DoRead(long offset, Span destination) + { + // In secure mode, if Handle is old and the card's device ID and + // header hash are still the same, Handle is updated to the new handle - Result rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); - if (rc.IsFailure()) return rc; + return GameCard.Read(Handle, offset, destination); + } - outStorage.Reset(new SubStorage(in baseStorage, 0, cardInfo.SecureAreaOffset)); + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedWriteForReadOnlyGameCardStorage.Log(); + } + + protected override Result DoFlush() + { return Result.Success; } - public Result CreateSecureReadOnly(GameCardHandle handle, ref SharedRef outStorage) + protected override Result DoSetSize(long size) { - if (GameCard.IsGameCardHandleInvalid(handle)) - { - return ResultFs.InvalidGameCardHandleOnOpenSecurePartition.Log(); - } + return ResultFs.UnsupportedSetSizeForReadOnlyGameCardStorage.Log(); + } - Span deviceId = stackalloc byte[0x10]; - Span imageHash = stackalloc byte[0x20]; + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); - Result rc = GameCard.GetGameCardDeviceId(deviceId); + Result rc = GameCard.GetCardInfo(out GameCardInfo info, Handle); if (rc.IsFailure()) return rc; - rc = GameCard.GetGameCardImageHash(imageHash); - if (rc.IsFailure()) return rc; - - using var baseStorage = - new SharedRef(new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash)); - - rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); - if (rc.IsFailure()) return rc; - - outStorage.Reset(new SubStorage(in baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize)); + size = info.Size; return Result.Success; } - - public Result CreateWriteOnly(GameCardHandle handle, ref SharedRef outStorage) - { - throw new NotImplementedException(); - } - - private class ReadOnlyGameCardStorage : IStorage - { - private EmulatedGameCard GameCard { get; } - private GameCardHandle Handle { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - private bool IsSecureMode { get; } - private byte[] DeviceId { get; } = new byte[0x10]; - private byte[] ImageHash { get; } = new byte[0x20]; - - public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle) - { - GameCard = gameCard; - Handle = handle; - } - - public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle, - ReadOnlySpan deviceId, ReadOnlySpan imageHash) - { - GameCard = gameCard; - Handle = handle; - IsSecureMode = true; - deviceId.CopyTo(DeviceId); - imageHash.CopyTo(ImageHash); - } - - protected override Result DoRead(long offset, Span destination) - { - // In secure mode, if Handle is old and the card's device ID and - // header hash are still the same, Handle is updated to the new handle - - return GameCard.Read(Handle, offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return ResultFs.UnsupportedWriteForReadOnlyGameCardStorage.Log(); - } - - protected override Result DoFlush() - { - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedSetSizeForReadOnlyGameCardStorage.Log(); - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - Result rc = GameCard.GetCardInfo(out GameCardInfo info, Handle); - if (rc.IsFailure()) return rc; - - size = info.Size; - return Result.Success; - } - } } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs index d2d60cbc..ef4a5234 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs @@ -5,85 +5,84 @@ using LibHac.Fs.Fsa; using LibHac.FsSrv.Impl; using LibHac.Util; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable { - public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable + private const string DefaultPath = "/sdcard"; + + private EmulatedSdCard _sdCard; + private SharedRef _rootFileSystem; + private SharedRef _sdCardFileSystem; + private string _path; + + public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, ref SharedRef rootFileSystem) { - private const string DefaultPath = "/sdcard"; + _sdCard = sdCard; + _rootFileSystem = SharedRef.CreateMove(ref rootFileSystem); + } - private EmulatedSdCard _sdCard; - private SharedRef _rootFileSystem; - private SharedRef _sdCardFileSystem; - private string _path; + public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, ref SharedRef rootFileSystem, string path) + { + _sdCard = sdCard; + _rootFileSystem = SharedRef.CreateMove(ref rootFileSystem); + _path = path; + } - public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, ref SharedRef rootFileSystem) + public void Dispose() + { + _rootFileSystem.Destroy(); + _sdCardFileSystem.Destroy(); + } + + public Result Create(ref SharedRef outFileSystem, bool openCaseSensitive) + { + if (!_sdCard.IsSdCardInserted()) { - _sdCard = sdCard; - _rootFileSystem = SharedRef.CreateMove(ref rootFileSystem); + return ResultFs.PortSdCardNoDevice.Log(); } - public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, ref SharedRef rootFileSystem, string path) + if (_sdCardFileSystem.HasValue) { - _sdCard = sdCard; - _rootFileSystem = SharedRef.CreateMove(ref rootFileSystem); - _path = path; - } - - public void Dispose() - { - _rootFileSystem.Destroy(); - _sdCardFileSystem.Destroy(); - } - - public Result Create(ref SharedRef outFileSystem, bool openCaseSensitive) - { - if (!_sdCard.IsSdCardInserted()) - { - return ResultFs.PortSdCardNoDevice.Log(); - } - - if (_sdCardFileSystem.HasValue) - { - outFileSystem.SetByCopy(in _sdCardFileSystem); - - return Result.Success; - } - - if (!_rootFileSystem.HasValue) - { - return ResultFs.PreconditionViolation.Log(); - } - - string path = _path ?? DefaultPath; - - using var sdCardPath = new Path(); - Result rc = sdCardPath.Initialize(StringUtils.StringToUtf8(path)); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowEmptyPath(); - rc = sdCardPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - // Todo: Add ProxyFileSystem? - - using SharedRef fileSystem = SharedRef.CreateCopy(in _rootFileSystem); - rc = Utility.WrapSubDirectory(ref _sdCardFileSystem, ref fileSystem.Ref(), in sdCardPath, true); - if (rc.IsFailure()) return rc; - outFileSystem.SetByCopy(in _sdCardFileSystem); return Result.Success; } - public Result Format(bool removeFromFatFsCache) + if (!_rootFileSystem.HasValue) { - throw new NotImplementedException(); + return ResultFs.PreconditionViolation.Log(); } - public Result Format() - { - throw new NotImplementedException(); - } + string path = _path ?? DefaultPath; + + using var sdCardPath = new Path(); + Result rc = sdCardPath.Initialize(StringUtils.StringToUtf8(path)); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + rc = sdCardPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + // Todo: Add ProxyFileSystem? + + using SharedRef fileSystem = SharedRef.CreateCopy(in _rootFileSystem); + rc = Utility.WrapSubDirectory(ref _sdCardFileSystem, ref fileSystem.Ref(), in sdCardPath, true); + if (rc.IsFailure()) return rc; + + outFileSystem.SetByCopy(in _sdCardFileSystem); + + return Result.Success; + } + + public Result Format(bool removeFromFatFsCache) + { + throw new NotImplementedException(); + } + + public Result Format() + { + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs index 95c40ec9..2a71193a 100644 --- a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs @@ -5,33 +5,32 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using static LibHac.FsSrv.FsCreator.IEncryptedFileSystemCreator; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator { - public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator + private KeySet KeySet { get; } + + public EncryptedFileSystemCreator(KeySet keySet) { - private KeySet KeySet { get; } + KeySet = keySet; + } - public EncryptedFileSystemCreator(KeySet keySet) + public Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed) + { + if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage) { - KeySet = keySet; + return ResultFs.InvalidArgument.Log(); } - public Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed) - { - if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage) - { - return ResultFs.InvalidArgument.Log(); - } + // todo: "proper" key generation instead of a lazy hack + KeySet.SetSdSeed(encryptionSeed.Value); - // todo: "proper" key generation instead of a lazy hack - KeySet.SetSdSeed(encryptionSeed.Value); + using var encryptedFileSystem = new SharedRef(new AesXtsFileSystem(ref baseFileSystem, + KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(), 0x4000)); - using var encryptedFileSystem = new SharedRef(new AesXtsFileSystem(ref baseFileSystem, - KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(), 0x4000)); + outEncryptedFileSystem.SetByMove(ref encryptedFileSystem.Ref()); - outEncryptedFileSystem.SetByMove(ref encryptedFileSystem.Ref()); - - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/FileSystemCreatorInterfaces.cs b/src/LibHac/FsSrv/FsCreator/FileSystemCreatorInterfaces.cs index 556a35e9..46235ad9 100644 --- a/src/LibHac/FsSrv/FsCreator/FileSystemCreatorInterfaces.cs +++ b/src/LibHac/FsSrv/FsCreator/FileSystemCreatorInterfaces.cs @@ -1,22 +1,21 @@ -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class FileSystemCreatorInterfaces { - public class FileSystemCreatorInterfaces - { - public IRomFileSystemCreator RomFileSystemCreator { get; set; } - public IPartitionFileSystemCreator PartitionFileSystemCreator { get; set; } - public IStorageOnNcaCreator StorageOnNcaCreator { get; set; } - public IFatFileSystemCreator FatFileSystemCreator { get; set; } - public ILocalFileSystemCreator LocalFileSystemCreator { get; set; } - public ITargetManagerFileSystemCreator TargetManagerFileSystemCreator { get; set; } - public ISubDirectoryFileSystemCreator SubDirectoryFileSystemCreator { get; set; } - public IBuiltInStorageCreator BuiltInStorageCreator { get; set; } - public ISdStorageCreator SdStorageCreator { get; set; } - public ISaveDataFileSystemCreator SaveDataFileSystemCreator { get; set; } - public IGameCardStorageCreator GameCardStorageCreator { get; set; } - public IGameCardFileSystemCreator GameCardFileSystemCreator { get; set; } - public IEncryptedFileSystemCreator EncryptedFileSystemCreator { get; set; } - public IMemoryStorageCreator MemoryStorageCreator { get; set; } - public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; } - public ISdCardProxyFileSystemCreator SdCardFileSystemCreator { get; set; } - } + public IRomFileSystemCreator RomFileSystemCreator { get; set; } + public IPartitionFileSystemCreator PartitionFileSystemCreator { get; set; } + public IStorageOnNcaCreator StorageOnNcaCreator { get; set; } + public IFatFileSystemCreator FatFileSystemCreator { get; set; } + public ILocalFileSystemCreator LocalFileSystemCreator { get; set; } + public ITargetManagerFileSystemCreator TargetManagerFileSystemCreator { get; set; } + public ISubDirectoryFileSystemCreator SubDirectoryFileSystemCreator { get; set; } + public IBuiltInStorageCreator BuiltInStorageCreator { get; set; } + public ISdStorageCreator SdStorageCreator { get; set; } + public ISaveDataFileSystemCreator SaveDataFileSystemCreator { get; set; } + public IGameCardStorageCreator GameCardStorageCreator { get; set; } + public IGameCardFileSystemCreator GameCardFileSystemCreator { get; set; } + public IEncryptedFileSystemCreator EncryptedFileSystemCreator { get; set; } + public IMemoryStorageCreator MemoryStorageCreator { get; set; } + public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; } + public ISdCardProxyFileSystemCreator SdCardFileSystemCreator { get; set; } } diff --git a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageCreator.cs index 090bd63b..46447f4d 100644 --- a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageCreator.cs @@ -1,11 +1,10 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IBuiltInStorageCreator { - public interface IBuiltInStorageCreator - { - Result Create(ref SharedRef outStorage, BisPartitionId partitionId); - Result InvalidateCache(); - } + Result Create(ref SharedRef outStorage, BisPartitionId partitionId); + Result InvalidateCache(); } diff --git a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs index 3718666b..ad412044 100644 --- a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs @@ -2,10 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IBuiltInStorageFileSystemCreator { - public interface IBuiltInStorageFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, BisPartitionId partitionId, bool caseSensitive); - } -} \ No newline at end of file + Result Create(ref SharedRef outFileSystem, BisPartitionId partitionId, bool caseSensitive); +} diff --git a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs index cf83e39a..ce2f91ab 100644 --- a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs @@ -2,18 +2,17 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator -{ - public interface IEncryptedFileSystemCreator - { - public enum KeyId - { - Save = 0, - Content = 1, - CustomStorage = 2 - } +namespace LibHac.FsSrv.FsCreator; - Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, - KeyId idIndex, in EncryptionSeed encryptionSeed); +public interface IEncryptedFileSystemCreator +{ + public enum KeyId + { + Save = 0, + Content = 1, + CustomStorage = 2 } + + Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, + KeyId idIndex, in EncryptionSeed encryptionSeed); } diff --git a/src/LibHac/FsSrv/FsCreator/IFatFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IFatFileSystemCreator.cs index d985ec53..4f1fb92f 100644 --- a/src/LibHac/FsSrv/FsCreator/IFatFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IFatFileSystemCreator.cs @@ -3,14 +3,13 @@ using LibHac.Fat; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator -{ - public interface IFatFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage, - FatAttribute attribute, int driveId, Result invalidFatFormatResult, Result usableSpaceNotEnoughResult); +namespace LibHac.FsSrv.FsCreator; - Result Format(ref SharedRef partitionStorage, FatAttribute attribute, FatFormatParam formatParam, - int driveId, Result invalidFatFormatResult, Result usableSpaceNotEnoughResult); - } -} \ No newline at end of file +public interface IFatFileSystemCreator +{ + Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage, + FatAttribute attribute, int driveId, Result invalidFatFormatResult, Result usableSpaceNotEnoughResult); + + Result Format(ref SharedRef partitionStorage, FatAttribute attribute, FatFormatParam formatParam, + int driveId, Result invalidFatFormatResult, Result usableSpaceNotEnoughResult); +} diff --git a/src/LibHac/FsSrv/FsCreator/IGameCardFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IGameCardFileSystemCreator.cs index c23da2fa..8fc65336 100644 --- a/src/LibHac/FsSrv/FsCreator/IGameCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IGameCardFileSystemCreator.cs @@ -2,10 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IGameCardFileSystemCreator { - public interface IGameCardFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, GameCardHandle handle, GameCardPartition partitionType); - } -} \ No newline at end of file + Result Create(ref SharedRef outFileSystem, GameCardHandle handle, GameCardPartition partitionType); +} diff --git a/src/LibHac/FsSrv/FsCreator/IGameCardStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/IGameCardStorageCreator.cs index f73bcd48..b1ac8e25 100644 --- a/src/LibHac/FsSrv/FsCreator/IGameCardStorageCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IGameCardStorageCreator.cs @@ -1,12 +1,11 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IGameCardStorageCreator { - public interface IGameCardStorageCreator - { - Result CreateReadOnly(GameCardHandle handle, ref SharedRef outStorage); - Result CreateSecureReadOnly(GameCardHandle handle, ref SharedRef outStorage); - Result CreateWriteOnly(GameCardHandle handle, ref SharedRef outStorage); - } -} \ No newline at end of file + Result CreateReadOnly(GameCardHandle handle, ref SharedRef outStorage); + Result CreateSecureReadOnly(GameCardHandle handle, ref SharedRef outStorage); + Result CreateWriteOnly(GameCardHandle handle, ref SharedRef outStorage); +} diff --git a/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs index c978e93b..439af2dc 100644 --- a/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ILocalFileSystemCreator.cs @@ -1,10 +1,9 @@ using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ILocalFileSystemCreator { - public interface ILocalFileSystemCreator - { - Result Create(out IFileSystem fileSystem, bool someBool); - Result Create(out IFileSystem fileSystem, string path, bool openCaseSensitive); - } -} \ No newline at end of file + Result Create(out IFileSystem fileSystem, bool someBool); + Result Create(out IFileSystem fileSystem, string path, bool openCaseSensitive); +} diff --git a/src/LibHac/FsSrv/FsCreator/IMemoryStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/IMemoryStorageCreator.cs index e153d645..95f87274 100644 --- a/src/LibHac/FsSrv/FsCreator/IMemoryStorageCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IMemoryStorageCreator.cs @@ -1,11 +1,10 @@ using System; using LibHac.Fs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IMemoryStorageCreator { - public interface IMemoryStorageCreator - { - Result Create(out IStorage storage, out Memory buffer, int storageId); - Result RegisterBuffer(int storageId, Memory buffer); - } + Result Create(out IStorage storage, out Memory buffer, int storageId); + Result RegisterBuffer(int storageId, Memory buffer); } diff --git a/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs index 9c9a914c..c1d82fe2 100644 --- a/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IPartitionFileSystemCreator.cs @@ -2,10 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IPartitionFileSystemCreator { - public interface IPartitionFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage); - } + Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage); } diff --git a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs index 95ac0f20..08c45ee9 100644 --- a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs @@ -2,10 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IRomFileSystemCreator { - public interface IRomFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage); - } + Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage); } diff --git a/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs index a1ca67f2..ac66ca69 100644 --- a/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs @@ -4,22 +4,21 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ISaveDataFileSystemCreator { - public interface ISaveDataFileSystemCreator - { - Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode); + Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode); - Result Create(ref SharedRef outFileSystem, - ref SharedRef outExtraDataAccessor, - ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, - bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, - ISaveDataCommitTimeStampGetter timeStampGetter); + Result Create(ref SharedRef outFileSystem, + ref SharedRef outExtraDataAccessor, + ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, + bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, + ISaveDataCommitTimeStampGetter timeStampGetter); - Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, - ref SharedRef baseFileSystem); + Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, + ref SharedRef baseFileSystem); - void SetSdCardEncryptionSeed(ReadOnlySpan seed); - } -} \ No newline at end of file + void SetSdCardEncryptionSeed(ReadOnlySpan seed); +} diff --git a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs index 644cc140..01831291 100644 --- a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs @@ -1,24 +1,23 @@ using LibHac.Common; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ISdCardProxyFileSystemCreator { - public interface ISdCardProxyFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, bool openCaseSensitive); + Result Create(ref SharedRef outFileSystem, bool openCaseSensitive); - /// - /// Formats the SD card. - /// - /// Should the SD card file system be removed from the - /// FAT file system cache? - /// The of the operation. - Result Format(bool removeFromFatFsCache); + /// + /// Formats the SD card. + /// + /// Should the SD card file system be removed from the + /// FAT file system cache? + /// The of the operation. + Result Format(bool removeFromFatFsCache); - /// - /// Automatically closes all open proxy file system entries and formats the SD card. - /// - /// The of the operation. - Result Format(); - } + /// + /// Automatically closes all open proxy file system entries and formats the SD card. + /// + /// The of the operation. + Result Format(); } diff --git a/src/LibHac/FsSrv/FsCreator/ISdStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/ISdStorageCreator.cs index f52c0a8f..d9746976 100644 --- a/src/LibHac/FsSrv/FsCreator/ISdStorageCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISdStorageCreator.cs @@ -1,9 +1,8 @@ using LibHac.Fs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ISdStorageCreator { - public interface ISdStorageCreator - { - Result Create(out IStorage storage); - } + Result Create(out IStorage storage); } diff --git a/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs b/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs index a66c1739..2ab4a2d6 100644 --- a/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IStorageOnNcaCreator.cs @@ -3,13 +3,12 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem.NcaUtils; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface IStorageOnNcaCreator { - public interface IStorageOnNcaCreator - { - Result Create(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs); - Result CreateWithPatch(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs); - Result OpenNca(out Nca nca, IStorage ncaStorage); - Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca); - } + Result Create(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs); + Result CreateWithPatch(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs); + Result OpenNca(out Nca nca, IStorage ncaStorage); + Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca); } diff --git a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs index 88282d53..859fbb8e 100644 --- a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs @@ -2,10 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ISubDirectoryFileSystemCreator { - public interface ISubDirectoryFileSystemCreator - { - Result Create(ref SharedRef outSubDirFileSystem, ref SharedRef baseFileSystem, in Path path); - } -} \ No newline at end of file + Result Create(ref SharedRef outSubDirFileSystem, ref SharedRef baseFileSystem, in Path path); +} diff --git a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs index 8ed68497..b4e0aae6 100644 --- a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs @@ -2,11 +2,10 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public interface ITargetManagerFileSystemCreator { - public interface ITargetManagerFileSystemCreator - { - Result Create(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult); - Result NormalizeCaseOfPath(out bool isSupported, ref Path path); - } -} \ No newline at end of file + Result Create(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult); + Result NormalizeCaseOfPath(out bool isSupported, ref Path path); +} diff --git a/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs index 266e7922..a5d4bcac 100644 --- a/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/PartitionFileSystemCreator.cs @@ -4,20 +4,19 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.FsSystem.Impl; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class PartitionFileSystemCreator : IPartitionFileSystemCreator { - public class PartitionFileSystemCreator : IPartitionFileSystemCreator + public Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage) { - public Result Create(ref SharedRef outFileSystem, ref SharedRef baseStorage) - { - using var partitionFs = - new SharedRef>(new PartitionFileSystemCore()); + using var partitionFs = + new SharedRef>(new PartitionFileSystemCore()); - Result rc = partitionFs.Get.Initialize(ref baseStorage); - if (rc.IsFailure()) return rc.Miss(); + Result rc = partitionFs.Get.Initialize(ref baseStorage); + if (rc.IsFailure()) return rc.Miss(); - outFileSystem.SetByMove(ref partitionFs.Ref()); - return Result.Success; - } + outFileSystem.SetByMove(ref partitionFs.Ref()); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs index 71d2892b..a03eff17 100644 --- a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs @@ -3,15 +3,14 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem.RomFs; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class RomFileSystemCreator : IRomFileSystemCreator { - public class RomFileSystemCreator : IRomFileSystemCreator + // todo: Implement properly + public Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage) { - // todo: Implement properly - public Result Create(ref SharedRef outFileSystem, ref SharedRef romFsStorage) - { - outFileSystem.Reset(new RomFsFileSystem(ref romFsStorage)); - return Result.Success; - } + outFileSystem.Reset(new RomFsFileSystem(ref romFsStorage)); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs index 41891268..d25367be 100644 --- a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs @@ -11,117 +11,116 @@ using LibHac.Util; using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator { - public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator + private IBufferManager _bufferManager; + private RandomDataGenerator _randomGenerator; + + // LibHac Additions + private KeySet _keySet; + private FileSystemServer _fsServer; + + public SaveDataFileSystemCreator(FileSystemServer fsServer, KeySet keySet, IBufferManager bufferManager, + RandomDataGenerator randomGenerator) { - private IBufferManager _bufferManager; - private RandomDataGenerator _randomGenerator; + _bufferManager = bufferManager; + _randomGenerator = randomGenerator; + _fsServer = fsServer; + _keySet = keySet; + } - // LibHac Additions - private KeySet _keySet; - private FileSystemServer _fsServer; + public Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode) + { + throw new NotImplementedException(); + } - public SaveDataFileSystemCreator(FileSystemServer fsServer, KeySet keySet, IBufferManager bufferManager, - RandomDataGenerator randomGenerator) + public Result Create(ref SharedRef outFileSystem, + ref SharedRef outExtraDataAccessor, + ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, + bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, + ISaveDataCommitTimeStampGetter timeStampGetter) + { + // Hack around error CS8350. + Span buffer = stackalloc byte[0x12]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, 0x12); + + Assert.SdkRequiresNotNull(cacheManager); + + using var saveImageName = new Path(); + Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; + + rc = baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in saveImageName); + + if (rc.IsFailure()) { - _bufferManager = bufferManager; - _randomGenerator = randomGenerator; - _fsServer = fsServer; - _keySet = keySet; + return ResultFs.PathNotFound.Includes(rc) ? ResultFs.TargetNotFound.LogConverted(rc) : rc; } - public Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode) + if (type == DirectoryEntryType.Directory) { - throw new NotImplementedException(); - } + if (!allowDirectorySaveData) + return ResultFs.InvalidSaveDataEntryType.Log(); - public Result Create(ref SharedRef outFileSystem, - ref SharedRef outExtraDataAccessor, - ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, - bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, - ISaveDataCommitTimeStampGetter timeStampGetter) - { - // Hack around error CS8350. - Span buffer = stackalloc byte[0x12]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, 0x12); + using var baseFs = + new UniqueRef(new SubdirectoryFileSystem(ref baseFileSystem)); - Assert.SdkRequiresNotNull(cacheManager); + if (!baseFs.HasValue) + return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorA.Log(); - using var saveImageName = new Path(); - Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + rc = baseFs.Get.Initialize(in saveImageName); if (rc.IsFailure()) return rc; - rc = baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in saveImageName); + using UniqueRef tempFs = UniqueRef.Create(ref baseFs.Ref()); + using var saveDirFs = new SharedRef( + new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs)); - if (rc.IsFailure()) - { - return ResultFs.PathNotFound.Includes(rc) ? ResultFs.TargetNotFound.LogConverted(rc) : rc; - } + rc = saveDirFs.Get.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported, + isMultiCommitSupported, !openReadOnly); + if (rc.IsFailure()) return rc; - if (type == DirectoryEntryType.Directory) - { - if (!allowDirectorySaveData) - return ResultFs.InvalidSaveDataEntryType.Log(); + outFileSystem.SetByCopy(in saveDirFs); + outExtraDataAccessor.SetByCopy(in saveDirFs); - using var baseFs = - new UniqueRef(new SubdirectoryFileSystem(ref baseFileSystem)); - - if (!baseFs.HasValue) - return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorA.Log(); - - rc = baseFs.Get.Initialize(in saveImageName); - if (rc.IsFailure()) return rc; - - using UniqueRef tempFs = UniqueRef.Create(ref baseFs.Ref()); - using var saveDirFs = new SharedRef( - new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs)); - - rc = saveDirFs.Get.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported, - isMultiCommitSupported, !openReadOnly); - if (rc.IsFailure()) return rc; - - outFileSystem.SetByCopy(in saveDirFs); - outExtraDataAccessor.SetByCopy(in saveDirFs); - - return Result.Success; - } - else - { - using var fileStorage = new SharedRef(); - - Optional openType = - openShared ? new Optional(OpenType.Normal) : new Optional(); - - rc = _fsServer.OpenSaveDataStorage(ref fileStorage.Ref(), ref baseFileSystem, spaceId, saveDataId, - OpenMode.ReadWrite, openType); - if (rc.IsFailure()) return rc; - - if (!isJournalingSupported) - { - throw new NotImplementedException(); - } - - using var saveFs = new SharedRef(new SaveDataFileSystem(_keySet, fileStorage.Get, - IntegrityCheckLevel.ErrorOnInvalid, false)); - - // Todo: ISaveDataExtraDataAccessor - - return Result.Success; - } + return Result.Success; } - - public Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, - ref SharedRef baseFileSystem) + else { - throw new NotImplementedException(); - } + using var fileStorage = new SharedRef(); - public void SetSdCardEncryptionSeed(ReadOnlySpan seed) - { - throw new NotImplementedException(); + Optional openType = + openShared ? new Optional(OpenType.Normal) : new Optional(); + + rc = _fsServer.OpenSaveDataStorage(ref fileStorage.Ref(), ref baseFileSystem, spaceId, saveDataId, + OpenMode.ReadWrite, openType); + if (rc.IsFailure()) return rc; + + if (!isJournalingSupported) + { + throw new NotImplementedException(); + } + + using var saveFs = new SharedRef(new SaveDataFileSystem(_keySet, fileStorage.Get, + IntegrityCheckLevel.ErrorOnInvalid, false)); + + // Todo: ISaveDataExtraDataAccessor + + return Result.Success; } } -} \ No newline at end of file + + public Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, + ref SharedRef baseFileSystem) + { + throw new NotImplementedException(); + } + + public void SetSdCardEncryptionSeed(ReadOnlySpan seed) + { + throw new NotImplementedException(); + } +} diff --git a/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs b/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs index bcfb2f12..13839dc2 100644 --- a/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/StorageOnNcaCreator.cs @@ -7,73 +7,72 @@ using LibHac.FsSystem; using LibHac.FsSystem.Impl; using LibHac.FsSystem.NcaUtils; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class StorageOnNcaCreator : IStorageOnNcaCreator { - public class StorageOnNcaCreator : IStorageOnNcaCreator + // ReSharper disable once UnusedMember.Local + private bool IsEnabledProgramVerification { get; set; } + private KeySet KeySet { get; } + + public StorageOnNcaCreator(KeySet keySet) { - // ReSharper disable once UnusedMember.Local - private bool IsEnabledProgramVerification { get; set; } - private KeySet KeySet { get; } + KeySet = keySet; + } - public StorageOnNcaCreator(KeySet keySet) + // todo: Implement NcaReader and other Nca classes + public Result Create(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca nca, + int fsIndex, bool isCodeFs) + { + UnsafeHelpers.SkipParamInit(out fsHeader); + + Result rc = OpenStorage(out IStorage storageTemp, nca, fsIndex); + if (rc.IsFailure()) return rc; + + if (isCodeFs) { - KeySet = keySet; - } - - // todo: Implement NcaReader and other Nca classes - public Result Create(ref SharedRef outStorage, out NcaFsHeader fsHeader, Nca nca, - int fsIndex, bool isCodeFs) - { - UnsafeHelpers.SkipParamInit(out fsHeader); - - Result rc = OpenStorage(out IStorage storageTemp, nca, fsIndex); - if (rc.IsFailure()) return rc; - - if (isCodeFs) + using (var codeFs = new PartitionFileSystemCore()) { - using (var codeFs = new PartitionFileSystemCore()) - { - rc = codeFs.Initialize(storageTemp); - if (rc.IsFailure()) return rc; + rc = codeFs.Initialize(storageTemp); + if (rc.IsFailure()) return rc; - rc = VerifyAcidSignature(codeFs, nca); - if (rc.IsFailure()) return rc; - } + rc = VerifyAcidSignature(codeFs, nca); + if (rc.IsFailure()) return rc; } - - outStorage.Reset(storageTemp); - fsHeader = nca.GetFsHeader(fsIndex); - - return Result.Success; } - public Result CreateWithPatch(ref SharedRef outStorage, out NcaFsHeader fsHeader, - Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs) - { - throw new NotImplementedException(); - } + outStorage.Reset(storageTemp); + fsHeader = nca.GetFsHeader(fsIndex); - public Result OpenNca(out Nca nca, IStorage ncaStorage) - { - nca = new Nca(KeySet, ncaStorage); - return Result.Success; - } + return Result.Success; + } - public Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca) - { - // todo - return Result.Success; - } + public Result CreateWithPatch(ref SharedRef outStorage, out NcaFsHeader fsHeader, + Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs) + { + throw new NotImplementedException(); + } - private Result OpenStorage(out IStorage storage, Nca nca, int fsIndex) - { - UnsafeHelpers.SkipParamInit(out storage); + public Result OpenNca(out Nca nca, IStorage ncaStorage) + { + nca = new Nca(KeySet, ncaStorage); + return Result.Success; + } - if (!nca.SectionExists(fsIndex)) - return ResultFs.PartitionNotFound.Log(); + public Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca) + { + // todo + return Result.Success; + } - storage = nca.OpenStorage(fsIndex, IntegrityCheckLevel.ErrorOnInvalid); - return Result.Success; - } + private Result OpenStorage(out IStorage storage, Nca nca, int fsIndex) + { + UnsafeHelpers.SkipParamInit(out storage); + + if (!nca.SectionExists(fsIndex)) + return ResultFs.PartitionNotFound.Log(); + + storage = nca.OpenStorage(fsIndex, IntegrityCheckLevel.ErrorOnInvalid); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs index febeded0..a0dd0206 100644 --- a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs @@ -3,30 +3,29 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.FsCreator +namespace LibHac.FsSrv.FsCreator; + +public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator { - public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator + public Result Create(ref SharedRef outSubDirFileSystem, ref SharedRef baseFileSystem, + in Path path) { - public Result Create(ref SharedRef outSubDirFileSystem, ref SharedRef baseFileSystem, - in Path path) - { - using var directory = new UniqueRef(); + using var directory = new UniqueRef(); - Result rc = baseFileSystem.Get.OpenDirectory(ref directory.Ref(), in path, OpenDirectoryMode.Directory); - if (rc.IsFailure()) return rc; + Result rc = baseFileSystem.Get.OpenDirectory(ref directory.Ref(), in path, OpenDirectoryMode.Directory); + if (rc.IsFailure()) return rc; - directory.Reset(); + directory.Reset(); - using var subFs = new SharedRef(new SubdirectoryFileSystem(ref baseFileSystem)); + using var subFs = new SharedRef(new SubdirectoryFileSystem(ref baseFileSystem)); - if (!subFs.HasValue) - return ResultFs.AllocationMemoryFailedInSubDirectoryFileSystemCreatorA.Log(); + if (!subFs.HasValue) + return ResultFs.AllocationMemoryFailedInSubDirectoryFileSystemCreatorA.Log(); - rc = subFs.Get.Initialize(in path); - if (rc.IsFailure()) return rc; + rc = subFs.Get.Initialize(in path); + if (rc.IsFailure()) return rc; - outSubDirFileSystem.SetByMove(ref subFs.Ref()); - return Result.Success; - } + outSubDirFileSystem.SetByMove(ref subFs.Ref()); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs index 63fbcccb..9eba9875 100644 --- a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs @@ -3,19 +3,18 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.FsCreator -{ - public class TargetManagerFileSystemCreator : ITargetManagerFileSystemCreator - { - public Result Create(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive, - bool ensureRootPathExists, Result pathNotFoundResult) - { - throw new NotImplementedException(); - } +namespace LibHac.FsSrv.FsCreator; - public Result NormalizeCaseOfPath(out bool isSupported, ref Path path) - { - throw new NotImplementedException(); - } +public class TargetManagerFileSystemCreator : ITargetManagerFileSystemCreator +{ + public Result Create(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive, + bool ensureRootPathExists, Result pathNotFoundResult) + { + throw new NotImplementedException(); + } + + public Result NormalizeCaseOfPath(out bool isSupported, ref Path path) + { + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSrv/GameCardHandle.cs b/src/LibHac/FsSrv/GameCardHandle.cs index 0c77017b..3ccfb923 100644 --- a/src/LibHac/FsSrv/GameCardHandle.cs +++ b/src/LibHac/FsSrv/GameCardHandle.cs @@ -1,20 +1,19 @@ using System; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct GameCardHandle : IEquatable { - public readonly struct GameCardHandle : IEquatable + public readonly int Value; + + public GameCardHandle(int value) { - public readonly int Value; - - public GameCardHandle(int value) - { - Value = value; - } - - public override bool Equals(object obj) => obj is GameCardHandle handle && Equals(handle); - public bool Equals(GameCardHandle other) => Value == other.Value; - public override int GetHashCode() => Value.GetHashCode(); - public static bool operator ==(GameCardHandle left, GameCardHandle right) => left.Equals(right); - public static bool operator !=(GameCardHandle left, GameCardHandle right) => !(left == right); + Value = value; } + + public override bool Equals(object obj) => obj is GameCardHandle handle && Equals(handle); + public bool Equals(GameCardHandle other) => Value == other.Value; + public override int GetHashCode() => Value.GetHashCode(); + public static bool operator ==(GameCardHandle left, GameCardHandle right) => left.Equals(right); + public static bool operator !=(GameCardHandle left, GameCardHandle right) => !(left == right); } diff --git a/src/LibHac/FsSrv/GameCardInfo.cs b/src/LibHac/FsSrv/GameCardInfo.cs index 438ea936..e965568a 100644 --- a/src/LibHac/FsSrv/GameCardInfo.cs +++ b/src/LibHac/FsSrv/GameCardInfo.cs @@ -1,18 +1,17 @@ using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +internal class GameCardInfo { - internal class GameCardInfo - { - public byte[] RootPartitionHeaderHash { get; } = new byte[0x20]; - public ulong PackageId { get; set; } - public long Size { get; set; } - public long RootPartitionOffset { get; set; } - public long RootPartitionHeaderSize { get; set; } - public long SecureAreaOffset { get; set; } - public long SecureAreaSize { get; set; } - public int UpdateVersion { get; set; } - public ulong UpdateTitleId { get; set; } - public GameCardAttribute Attribute { get; set; } - } + public byte[] RootPartitionHeaderHash { get; } = new byte[0x20]; + public ulong PackageId { get; set; } + public long Size { get; set; } + public long RootPartitionOffset { get; set; } + public long RootPartitionHeaderSize { get; set; } + public long SecureAreaOffset { get; set; } + public long SecureAreaSize { get; set; } + public int UpdateVersion { get; set; } + public ulong UpdateTitleId { get; set; } + public GameCardAttribute Attribute { get; set; } } diff --git a/src/LibHac/FsSrv/IDeviceHandleManager.cs b/src/LibHac/FsSrv/IDeviceHandleManager.cs index a6b274a5..057fa8a3 100644 --- a/src/LibHac/FsSrv/IDeviceHandleManager.cs +++ b/src/LibHac/FsSrv/IDeviceHandleManager.cs @@ -1,10 +1,9 @@ using LibHac.FsSrv.Storage; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public interface IDeviceHandleManager { - public interface IDeviceHandleManager - { - Result GetHandle(out StorageDeviceHandle handle); - bool IsValid(in StorageDeviceHandle handle); - } + Result GetHandle(out StorageDeviceHandle handle); + bool IsValid(in StorageDeviceHandle handle); } diff --git a/src/LibHac/FsSrv/ISaveDataIndexer.cs b/src/LibHac/FsSrv/ISaveDataIndexer.cs index 841f6e47..31d7ae1c 100644 --- a/src/LibHac/FsSrv/ISaveDataIndexer.cs +++ b/src/LibHac/FsSrv/ISaveDataIndexer.cs @@ -2,148 +2,147 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Indexes save data metadata, holding key-value pairs of types and +/// respectively. +/// +/// +/// Each value contains information about the save data +/// including its size and current state, as well as its and save data +/// ID which represent the save data's storage location on disk. +/// +public interface ISaveDataIndexer : IDisposable { /// - /// Indexes save data metadata, holding key-value pairs of types and - /// respectively. + /// Commit any changes made to the save data index. + /// + /// The of the operation. + Result Commit(); + + /// + /// Rollback any changes made to the save data index since the last commit. + /// + /// The of the operation. + Result Rollback(); + + /// + /// Remove all entries from the save data index and set the index to its initial state. + /// + /// The of the operation. + Result Reset(); + + /// + /// Adds a new key to the index and returns the save data ID assigned to it. + /// The created value will only contain the assigned save data ID. + /// Fails if the key already exists. + /// + /// If the method returns successfully, contains the + /// save data ID assigned to the new entry. + /// Save data IDs are assigned using a counter that is incremented for each added save. + /// The key to add. + /// The of the operation. + Result Publish(out ulong saveDataId, in SaveDataAttribute key); + + /// + /// Retrieves the for the specified key. + /// + /// If the method returns successfully, contains the + /// save data ID assigned to the new entry. + /// The key of the value to get. + /// The of the operation. + Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key); + + /// + /// Adds a key with a pre-specified static save data ID to the index. /// /// - /// Each value contains information about the save data - /// including its size and current state, as well as its and save data - /// ID which represent the save data's storage location on disk. + /// Adding a save data ID that is already in the index is not allowed. Adding a static ID that might + /// conflict with a future dynamically-assigned ID should be avoided, otherwise there will be two saves + /// with the same ID. + /// FileSystemProxy avoids this by setting the high bit on static IDs. e.g. 0x8000000000000015 /// - public interface ISaveDataIndexer : IDisposable - { - /// - /// Commit any changes made to the save data index. - /// - /// The of the operation. - Result Commit(); + /// The key to add. + /// The of the operation. + Result PutStaticSaveDataIdIndex(in SaveDataAttribute key); - /// - /// Rollback any changes made to the save data index since the last commit. - /// - /// The of the operation. - Result Rollback(); + /// + /// Determines if there are any non-reserved entry slots remaining in the index. + /// + /// If the has a fixed number of entries, a portion of + /// those entries may be reserved for internal use, + /// if there are any non-reserved entries remaining, + /// otherwise . + bool IsRemainedReservedOnly(); - /// - /// Remove all entries from the save data index and set the index to its initial state. - /// - /// The of the operation. - Result Reset(); + /// + /// Removes the save data with the specified save data ID from the index. + /// + /// The ID of the save to be removed. + /// The of the operation. + Result Delete(ulong saveDataId); - /// - /// Adds a new key to the index and returns the save data ID assigned to it. - /// The created value will only contain the assigned save data ID. - /// Fails if the key already exists. - /// - /// If the method returns successfully, contains the - /// save data ID assigned to the new entry. - /// Save data IDs are assigned using a counter that is incremented for each added save. - /// The key to add. - /// The of the operation. - Result Publish(out ulong saveDataId, in SaveDataAttribute key); + /// + /// Sets the in the specified save data's value. + /// + /// The save data ID of the save data to modify. + /// The new for the specified save data. + /// The of the operation. + Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId); - /// - /// Retrieves the for the specified key. - /// - /// If the method returns successfully, contains the - /// save data ID assigned to the new entry. - /// The key of the value to get. - /// The of the operation. - Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key); + /// + /// Sets the size in the specified save data's value. + /// + /// The save data ID of the save data to modify. + /// The new size for the specified save data. + /// The of the operation. + Result SetSize(ulong saveDataId, long size); - /// - /// Adds a key with a pre-specified static save data ID to the index. - /// - /// - /// Adding a save data ID that is already in the index is not allowed. Adding a static ID that might - /// conflict with a future dynamically-assigned ID should be avoided, otherwise there will be two saves - /// with the same ID. - /// FileSystemProxy avoids this by setting the high bit on static IDs. e.g. 0x8000000000000015 - /// - /// The key to add. - /// The of the operation. - Result PutStaticSaveDataIdIndex(in SaveDataAttribute key); + /// + /// Sets the in the specified save data's value. + /// + /// The save data ID of the save data to modify. + /// The new for the specified save data. + /// The of the operation. + Result SetState(ulong saveDataId, SaveDataState state); - /// - /// Determines if there are any non-reserved entry slots remaining in the index. - /// - /// If the has a fixed number of entries, a portion of - /// those entries may be reserved for internal use, - /// if there are any non-reserved entries remaining, - /// otherwise . - bool IsRemainedReservedOnly(); + /// + /// Gets the key of the specified save data ID. + /// + /// If the method returns successfully, contains the + /// key of the specified save data ID. + /// The save data ID to locate. + /// The of the operation. + Result GetKey(out SaveDataAttribute key, ulong saveDataId); - /// - /// Removes the save data with the specified save data ID from the index. - /// - /// The ID of the save to be removed. - /// The of the operation. - Result Delete(ulong saveDataId); + /// + /// Gets the value of the specified save data ID. + /// + /// If the method returns successfully, contains the + /// value of the specified save data ID. + /// The save data ID to locate. + /// The of the operation. + Result GetValue(out SaveDataIndexerValue value, ulong saveDataId); - /// - /// Sets the in the specified save data's value. - /// - /// The save data ID of the save data to modify. - /// The new for the specified save data. - /// The of the operation. - Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId); + /// + /// Sets a new value to a key that already exists in the index. + /// + /// The key of the value to set. + /// The new value to associate with the specified key. + /// The of the operation. + Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value); - /// - /// Sets the size in the specified save data's value. - /// - /// The save data ID of the save data to modify. - /// The new size for the specified save data. - /// The of the operation. - Result SetSize(ulong saveDataId, long size); + /// + /// Gets the number of elements currently in the . + /// + /// The current element count. + int GetIndexCount(); - /// - /// Sets the in the specified save data's value. - /// - /// The save data ID of the save data to modify. - /// The new for the specified save data. - /// The of the operation. - Result SetState(ulong saveDataId, SaveDataState state); - - /// - /// Gets the key of the specified save data ID. - /// - /// If the method returns successfully, contains the - /// key of the specified save data ID. - /// The save data ID to locate. - /// The of the operation. - Result GetKey(out SaveDataAttribute key, ulong saveDataId); - - /// - /// Gets the value of the specified save data ID. - /// - /// If the method returns successfully, contains the - /// value of the specified save data ID. - /// The save data ID to locate. - /// The of the operation. - Result GetValue(out SaveDataIndexerValue value, ulong saveDataId); - - /// - /// Sets a new value to a key that already exists in the index. - /// - /// The key of the value to set. - /// The new value to associate with the specified key. - /// The of the operation. - Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value); - - /// - /// Gets the number of elements currently in the . - /// - /// The current element count. - int GetIndexCount(); - - /// - /// Returns an that iterates through the . - /// - /// If the method returns successfully, contains the created . - /// The of the operation. - Result OpenSaveDataInfoReader(ref SharedRef outInfoReader); - } -} \ No newline at end of file + /// + /// Returns an that iterates through the . + /// + /// If the method returns successfully, contains the created . + /// The of the operation. + Result OpenSaveDataInfoReader(ref SharedRef outInfoReader); +} diff --git a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs index da47aa9a..7a4fb4f4 100644 --- a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs @@ -1,12 +1,11 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public interface ISaveDataIndexerManager { - public interface ISaveDataIndexerManager - { - Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, SaveDataSpaceId spaceId); - void ResetIndexer(SaveDataSpaceId spaceId); - void InvalidateIndexer(SaveDataSpaceId spaceId); - } -} \ No newline at end of file + Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, SaveDataSpaceId spaceId); + void ResetIndexer(SaveDataSpaceId spaceId); + void InvalidateIndexer(SaveDataSpaceId spaceId); +} diff --git a/src/LibHac/FsSrv/IStackUsageReporter.cs b/src/LibHac/FsSrv/IStackUsageReporter.cs index e13e6e38..427bc3bc 100644 --- a/src/LibHac/FsSrv/IStackUsageReporter.cs +++ b/src/LibHac/FsSrv/IStackUsageReporter.cs @@ -1,7 +1,6 @@ -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public interface IStackUsageReporter { - public interface IStackUsageReporter - { - uint GetStackUsage(); - } + uint GetStackUsage(); } diff --git a/src/LibHac/FsSrv/Impl/AccessControl.cs b/src/LibHac/FsSrv/Impl/AccessControl.cs index 75fe8cb2..a40758f7 100644 --- a/src/LibHac/FsSrv/Impl/AccessControl.cs +++ b/src/LibHac/FsSrv/Impl/AccessControl.cs @@ -7,909 +7,908 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Util; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public static class AccessControlGlobalMethods { - public static class AccessControlGlobalMethods + public static void SetDebugFlagEnabled(this FileSystemServer fsSrv, bool isEnabled) { - public static void SetDebugFlagEnabled(this FileSystemServer fsSrv, bool isEnabled) - { - fsSrv.Globals.AccessControl.DebugFlag = isEnabled; - } + fsSrv.Globals.AccessControl.DebugFlag = isEnabled; } +} - internal struct AccessControlGlobals +internal struct AccessControlGlobals +{ + public bool DebugFlag; +} + +/// +/// Controls access to FS resources for a single process. +/// +/// Each process has it's own FS permissions. Every time a process tries to access various FS resources +/// or perform certain actions, this class determines if the process has the permissions to do so. +///
Based on FS 10.0.0 (nnSdk 10.4.0)
+public class AccessControl +{ + private FileSystemServer FsServer { get; } + private ref AccessControlGlobals Globals => ref FsServer.Globals.AccessControl; + + private Optional AccessBits { get; } + private LinkedList ContentOwners { get; } = new LinkedList(); + private LinkedList SaveDataOwners { get; } = new LinkedList(); + + public AccessControl(FileSystemServer fsServer, ReadOnlySpan accessControlData, + ReadOnlySpan accessControlDescriptor) : this(fsServer, accessControlData, accessControlDescriptor, + GetAccessBitsMask(fsServer.Globals.AccessControl.DebugFlag)) + { } + + public AccessControl(FileSystemServer fsServer, ReadOnlySpan accessControlData, + ReadOnlySpan accessControlDescriptor, ulong accessFlagMask) { - public bool DebugFlag; - } + FsServer = fsServer; - /// - /// Controls access to FS resources for a single process. - /// - /// Each process has it's own FS permissions. Every time a process tries to access various FS resources - /// or perform certain actions, this class determines if the process has the permissions to do so. - ///
Based on FS 10.0.0 (nnSdk 10.4.0)
- public class AccessControl - { - private FileSystemServer FsServer { get; } - private ref AccessControlGlobals Globals => ref FsServer.Globals.AccessControl; - - private Optional AccessBits { get; } - private LinkedList ContentOwners { get; } = new LinkedList(); - private LinkedList SaveDataOwners { get; } = new LinkedList(); - - public AccessControl(FileSystemServer fsServer, ReadOnlySpan accessControlData, - ReadOnlySpan accessControlDescriptor) : this(fsServer, accessControlData, accessControlDescriptor, - GetAccessBitsMask(fsServer.Globals.AccessControl.DebugFlag)) - { } - - public AccessControl(FileSystemServer fsServer, ReadOnlySpan accessControlData, - ReadOnlySpan accessControlDescriptor, ulong accessFlagMask) + // No permissions are given if any of the access control buffers are empty + if (accessControlData.IsEmpty || accessControlDescriptor.IsEmpty) { - FsServer = fsServer; + AccessBits = new AccessControlBits(0); + return; + } - // No permissions are given if any of the access control buffers are empty - if (accessControlData.IsEmpty || accessControlDescriptor.IsEmpty) + // Verify the buffers are at least the minimum size + Abort.DoAbortUnless(accessControlData.Length >= Unsafe.SizeOf()); + Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf()); + + // Cast the input buffers to their respective struct types + ref readonly AccessControlDescriptor descriptor = + ref SpanHelpers.AsReadOnlyStruct(accessControlDescriptor); + + ref readonly AccessControlDataHeader data = + ref SpanHelpers.AsReadOnlyStruct(accessControlData); + + // Verify that the versions match and are valid + if (data.Version == 0 || data.Version != descriptor.Version) + { + AccessBits = new AccessControlBits(0); + return; + } + + AccessBits = new AccessControlBits(descriptor.AccessFlags & accessFlagMask & data.AccessFlags); + + // Verify the buffers are long enough to hold the content owner info + Abort.DoAbortUnless(accessControlData.Length >= data.ContentOwnerInfoOffset + data.ContentOwnerInfoSize); + Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf() + + descriptor.ContentOwnerIdCount * sizeof(ulong)); + + // Read and validate the content owner IDs in the access control data + if (data.ContentOwnerInfoSize > 0) + { + int infoCount = + BinaryPrimitives.ReadInt32LittleEndian(accessControlData.Slice(data.ContentOwnerInfoOffset)); + + // Get the list of content owner IDs in the descriptor, if any + ReadOnlySpan allowedIds = MemoryMarshal.Cast( + accessControlDescriptor.Slice(Unsafe.SizeOf(), + descriptor.ContentOwnerIdCount * sizeof(ulong))); + + // Get the list of content owner IDs + ReadOnlySpan ids = MemoryMarshal.Cast( + accessControlData.Slice(data.ContentOwnerInfoOffset + sizeof(int), infoCount * sizeof(ulong))); + + // Verify the size in the header matches the actual size of the info + Abort.DoAbortUnless(data.ContentOwnerInfoSize == infoCount * sizeof(long)); + + foreach (ulong id in ids) { - AccessBits = new AccessControlBits(0); - return; - } + bool isIdAllowed; - // Verify the buffers are at least the minimum size - Abort.DoAbortUnless(accessControlData.Length >= Unsafe.SizeOf()); - Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf()); - - // Cast the input buffers to their respective struct types - ref readonly AccessControlDescriptor descriptor = - ref SpanHelpers.AsReadOnlyStruct(accessControlDescriptor); - - ref readonly AccessControlDataHeader data = - ref SpanHelpers.AsReadOnlyStruct(accessControlData); - - // Verify that the versions match and are valid - if (data.Version == 0 || data.Version != descriptor.Version) - { - AccessBits = new AccessControlBits(0); - return; - } - - AccessBits = new AccessControlBits(descriptor.AccessFlags & accessFlagMask & data.AccessFlags); - - // Verify the buffers are long enough to hold the content owner info - Abort.DoAbortUnless(accessControlData.Length >= data.ContentOwnerInfoOffset + data.ContentOwnerInfoSize); - Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf() + - descriptor.ContentOwnerIdCount * sizeof(ulong)); - - // Read and validate the content owner IDs in the access control data - if (data.ContentOwnerInfoSize > 0) - { - int infoCount = - BinaryPrimitives.ReadInt32LittleEndian(accessControlData.Slice(data.ContentOwnerInfoOffset)); - - // Get the list of content owner IDs in the descriptor, if any - ReadOnlySpan allowedIds = MemoryMarshal.Cast( - accessControlDescriptor.Slice(Unsafe.SizeOf(), - descriptor.ContentOwnerIdCount * sizeof(ulong))); - - // Get the list of content owner IDs - ReadOnlySpan ids = MemoryMarshal.Cast( - accessControlData.Slice(data.ContentOwnerInfoOffset + sizeof(int), infoCount * sizeof(ulong))); - - // Verify the size in the header matches the actual size of the info - Abort.DoAbortUnless(data.ContentOwnerInfoSize == infoCount * sizeof(long)); - - foreach (ulong id in ids) + if (allowedIds.Length > 0) { - bool isIdAllowed; - - if (allowedIds.Length > 0) - { - // The descriptor contains a list of allowed content owner IDs. Check if the ID is in that list - isIdAllowed = allowedIds.IndexOf(id) != -1; - } - else - { - // The descriptor contains a range of allowed content owner IDs. Check if the ID is in that range - isIdAllowed = descriptor.ContentOwnerIdMin == 0 && descriptor.ContentOwnerIdMax == 0 || - id >= descriptor.ContentOwnerIdMin && id <= descriptor.ContentOwnerIdMax; - } - - - if (isIdAllowed) - { - ContentOwners.AddFirst(new ContentOwnerInfo(id)); - } - } - } - - // Verify the buffers are long enough to hold the save data owner info - Abort.DoAbortUnless(accessControlData.Length >= data.SaveDataOwnerInfoOffset + data.SaveDataOwnerInfoSize); - Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf() + - descriptor.ContentOwnerIdCount * sizeof(ulong) + descriptor.SaveDataOwnerIdCount * sizeof(ulong)); - - if (data.SaveDataOwnerInfoSize > 0) - { - int infoCount = - BinaryPrimitives.ReadInt32LittleEndian(accessControlData.Slice(data.SaveDataOwnerInfoOffset)); - - // Get the list of save data owner IDs in the descriptor, if any - int allowedIdsOffset = Unsafe.SizeOf() + - descriptor.ContentOwnerIdCount * sizeof(ulong); - ReadOnlySpan allowedIds = MemoryMarshal.Cast( - accessControlDescriptor.Slice(allowedIdsOffset, descriptor.SaveDataOwnerIdCount * sizeof(ulong))); - - // Get the lists of savedata owner accessibilities and IDs - ReadOnlySpan accessibilities = - accessControlData.Slice(data.SaveDataOwnerInfoOffset + sizeof(int), infoCount); - - // The ID list must be 4-byte aligned - int idsOffset = Alignment.AlignUp(data.SaveDataOwnerInfoOffset + sizeof(int) + infoCount, 4); - ReadOnlySpan ids = MemoryMarshal.Cast( - accessControlData.Slice(idsOffset, infoCount * sizeof(ulong))); - - // Verify the size in the header matches the actual size of the info - Abort.DoAbortUnless(data.SaveDataOwnerInfoSize == - idsOffset - data.SaveDataOwnerInfoOffset + infoCount * sizeof(long)); - - for (int i = 0; i < ids.Length; i++) - { - var accessibility = new Accessibility(accessibilities[i]); - ulong id = ids[i]; - - bool isIdAllowed; - - if (allowedIds.Length > 0) - { - // The descriptor contains a list of allowed save data owner IDs. Check if the ID is in that list - isIdAllowed = allowedIds.IndexOf(id) != -1; - } - else - { - // The descriptor contains a range of allowed save data owner IDs. Check if the ID is in that range - isIdAllowed = descriptor.SaveDataOwnerIdMin == 0 && descriptor.SaveDataOwnerIdMax == 0 || - id >= descriptor.SaveDataOwnerIdMin && id <= descriptor.SaveDataOwnerIdMax; - } - - if (isIdAllowed) - { - SaveDataOwners.AddFirst(new SaveDataOwnerInfo(id, accessibility)); - } - } - } - } - - private static ulong GetAccessBitsMask(bool isDebugMode) - { - return isDebugMode ? 0xFFFFFFFFFFFFFFFF : 0x3FFFFFFFFFFFFFFF; - } - - public bool HasContentOwnerId(ulong ownerId) - { - foreach (ContentOwnerInfo info in ContentOwners) - { - if (info.Id == ownerId) - return true; - } - - return false; - } - - public Accessibility GetAccessibilitySaveDataOwnedBy(ulong ownerId) - { - foreach (SaveDataOwnerInfo info in SaveDataOwners) - { - if (info.Id == ownerId) - return info.Accessibility; - } - - return new Accessibility(false, false); - } - - public void ListSaveDataOwnedId(out int outCount, Span outIds, int startIndex) - { - // If there's no output buffer, return the number of owned IDs - if (outIds.Length == 0) - { - outCount = SaveDataOwners.Count; - return; - } - - int preCount = 0; - int outIndex = 0; - - foreach (SaveDataOwnerInfo info in SaveDataOwners) - { - // Stop reading if the buffer's full - if (outIndex == outIds.Length) - break; - - // Skip IDs until we get to startIndex - if (preCount < startIndex) - { - preCount++; + // The descriptor contains a list of allowed content owner IDs. Check if the ID is in that list + isIdAllowed = allowedIds.IndexOf(id) != -1; } else { - // Write the ID to the buffer - outIds[outIndex] = new Ncm.ApplicationId(info.Id); - outIndex++; + // The descriptor contains a range of allowed content owner IDs. Check if the ID is in that range + isIdAllowed = descriptor.ContentOwnerIdMin == 0 && descriptor.ContentOwnerIdMax == 0 || + id >= descriptor.ContentOwnerIdMin && id <= descriptor.ContentOwnerIdMax; + } + + + if (isIdAllowed) + { + ContentOwners.AddFirst(new ContentOwnerInfo(id)); } } - - outCount = outIndex; } - public bool CanCall(OperationType operation) + // Verify the buffers are long enough to hold the save data owner info + Abort.DoAbortUnless(accessControlData.Length >= data.SaveDataOwnerInfoOffset + data.SaveDataOwnerInfoSize); + Abort.DoAbortUnless(accessControlDescriptor.Length >= Unsafe.SizeOf() + + descriptor.ContentOwnerIdCount * sizeof(ulong) + descriptor.SaveDataOwnerIdCount * sizeof(ulong)); + + if (data.SaveDataOwnerInfoSize > 0) { - // ReSharper disable once PossibleInvalidOperationException - AccessControlBits accessBits = AccessBits.Value; + int infoCount = + BinaryPrimitives.ReadInt32LittleEndian(accessControlData.Slice(data.SaveDataOwnerInfoOffset)); - switch (operation) + // Get the list of save data owner IDs in the descriptor, if any + int allowedIdsOffset = Unsafe.SizeOf() + + descriptor.ContentOwnerIdCount * sizeof(ulong); + ReadOnlySpan allowedIds = MemoryMarshal.Cast( + accessControlDescriptor.Slice(allowedIdsOffset, descriptor.SaveDataOwnerIdCount * sizeof(ulong))); + + // Get the lists of savedata owner accessibilities and IDs + ReadOnlySpan accessibilities = + accessControlData.Slice(data.SaveDataOwnerInfoOffset + sizeof(int), infoCount); + + // The ID list must be 4-byte aligned + int idsOffset = Alignment.AlignUp(data.SaveDataOwnerInfoOffset + sizeof(int) + infoCount, 4); + ReadOnlySpan ids = MemoryMarshal.Cast( + accessControlData.Slice(idsOffset, infoCount * sizeof(ulong))); + + // Verify the size in the header matches the actual size of the info + Abort.DoAbortUnless(data.SaveDataOwnerInfoSize == + idsOffset - data.SaveDataOwnerInfoOffset + infoCount * sizeof(long)); + + for (int i = 0; i < ids.Length; i++) { - case OperationType.InvalidateBisCache: - return accessBits.CanInvalidateBisCache(); - case OperationType.EraseMmc: - return accessBits.CanEraseMmc(); - case OperationType.GetGameCardDeviceCertificate: - return accessBits.CanGetGameCardDeviceCertificate(); - case OperationType.GetGameCardIdSet: - return accessBits.CanGetGameCardIdSet(); - case OperationType.FinalizeGameCardDriver: - return accessBits.CanFinalizeGameCardDriver(); - case OperationType.GetGameCardAsicInfo: - return accessBits.CanGetGameCardAsicInfo(); - case OperationType.CreateSaveData: - return accessBits.CanCreateSaveData(); - case OperationType.DeleteSaveData: - return accessBits.CanDeleteSaveData(); - case OperationType.CreateSystemSaveData: - return accessBits.CanCreateSystemSaveData(); - case OperationType.CreateOthersSystemSaveData: - return accessBits.CanCreateOthersSystemSaveData(); - case OperationType.DeleteSystemSaveData: - return accessBits.CanDeleteSystemSaveData(); - case OperationType.OpenSaveDataInfoReader: - return accessBits.CanOpenSaveDataInfoReader(); - case OperationType.OpenSaveDataInfoReaderForSystem: - return accessBits.CanOpenSaveDataInfoReaderForSystem(); - case OperationType.OpenSaveDataInfoReaderForInternal: - return accessBits.CanOpenSaveDataInfoReaderForInternal(); - case OperationType.OpenSaveDataMetaFile: - return accessBits.CanOpenSaveDataMetaFile(); - case OperationType.SetCurrentPosixTime: - return accessBits.CanSetCurrentPosixTime(); - case OperationType.ReadSaveDataFileSystemExtraData: - return accessBits.CanReadSaveDataFileSystemExtraData(); - case OperationType.SetGlobalAccessLogMode: - return accessBits.CanSetGlobalAccessLogMode(); - case OperationType.SetSpeedEmulationMode: - return accessBits.CanSetSpeedEmulationMode(); - case OperationType.FillBis: - return accessBits.CanFillBis(); - case OperationType.CorruptSaveData: - return accessBits.CanCorruptSaveData(); - case OperationType.CorruptSystemSaveData: - return accessBits.CanCorruptSystemSaveData(); - case OperationType.VerifySaveData: - return accessBits.CanVerifySaveData(); - case OperationType.DebugSaveData: - return accessBits.CanDebugSaveData(); - case OperationType.FormatSdCard: - return accessBits.CanFormatSdCard(); - case OperationType.GetRightsId: - return accessBits.CanGetRightsId(); - case OperationType.RegisterExternalKey: - return accessBits.CanRegisterExternalKey(); - case OperationType.SetEncryptionSeed: - return accessBits.CanSetEncryptionSeed(); - case OperationType.WriteSaveDataFileSystemExtraDataTimeStamp: - return accessBits.CanWriteSaveDataFileSystemExtraDataTimeStamp(); - case OperationType.WriteSaveDataFileSystemExtraDataFlags: - return accessBits.CanWriteSaveDataFileSystemExtraDataFlags(); - case OperationType.WriteSaveDataFileSystemExtraDataCommitId: - return accessBits.CanWriteSaveDataFileSystemExtraDataCommitId(); - case OperationType.WriteSaveDataFileSystemExtraDataAll: - return accessBits.CanWriteSaveDataFileSystemExtraDataAll(); - case OperationType.ExtendSaveData: - return accessBits.CanExtendSaveData(); - case OperationType.ExtendSystemSaveData: - return accessBits.CanExtendSystemSaveData(); - case OperationType.ExtendOthersSystemSaveData: - return accessBits.CanExtendOthersSystemSaveData(); - case OperationType.RegisterUpdatePartition: - return accessBits.CanRegisterUpdatePartition() && Globals.DebugFlag; - case OperationType.OpenSaveDataTransferManager: - return accessBits.CanOpenSaveDataTransferManager(); - case OperationType.OpenSaveDataTransferManagerVersion2: - return accessBits.CanOpenSaveDataTransferManagerVersion2(); - case OperationType.OpenSaveDataTransferManagerForSaveDataRepair: - return accessBits.CanOpenSaveDataTransferManagerForSaveDataRepair(); - case OperationType.OpenSaveDataTransferManagerForSaveDataRepairTool: - return accessBits.CanOpenSaveDataTransferManagerForSaveDataRepairTool(); - case OperationType.OpenSaveDataTransferProhibiter: - return accessBits.CanOpenSaveDataTransferProhibiter(); - case OperationType.OpenSaveDataMover: - return accessBits.CanOpenSaveDataMover(); - case OperationType.OpenBisWiper: - return accessBits.CanOpenBisWiper(); - case OperationType.ListAccessibleSaveDataOwnerId: - return accessBits.CanListAccessibleSaveDataOwnerId(); - case OperationType.ControlMmcPatrol: - return accessBits.CanControlMmcPatrol(); - case OperationType.OverrideSaveDataTransferTokenSignVerificationKey: - return accessBits.CanOverrideSaveDataTransferTokenSignVerificationKey(); - case OperationType.OpenSdCardDetectionEventNotifier: - return accessBits.CanOpenSdCardDetectionEventNotifier(); - case OperationType.OpenGameCardDetectionEventNotifier: - return accessBits.CanOpenGameCardDetectionEventNotifier(); - case OperationType.OpenSystemDataUpdateEventNotifier: - return accessBits.CanOpenSystemDataUpdateEventNotifier(); - case OperationType.NotifySystemDataUpdateEvent: - return accessBits.CanNotifySystemDataUpdateEvent(); - case OperationType.OpenAccessFailureDetectionEventNotifier: - return accessBits.CanOpenAccessFailureDetectionEventNotifier(); - case OperationType.GetAccessFailureDetectionEvent: - return accessBits.CanGetAccessFailureDetectionEvent(); - case OperationType.IsAccessFailureDetected: - return accessBits.CanIsAccessFailureDetected(); - case OperationType.ResolveAccessFailure: - return accessBits.CanResolveAccessFailure(); - case OperationType.AbandonAccessFailure: - return accessBits.CanAbandonAccessFailure(); - case OperationType.QuerySaveDataInternalStorageTotalSize: - return accessBits.CanQuerySaveDataInternalStorageTotalSize(); - case OperationType.GetSaveDataCommitId: - return accessBits.CanGetSaveDataCommitId(); - case OperationType.SetSdCardAccessibility: - return accessBits.CanSetSdCardAccessibility(); - case OperationType.SimulateDevice: - return accessBits.CanSimulateDevice(); - case OperationType.CreateSaveDataWithHashSalt: - return accessBits.CanCreateSaveDataWithHashSalt(); - case OperationType.RegisterProgramIndexMapInfo: - return accessBits.CanRegisterProgramIndexMapInfo(); - case OperationType.ChallengeCardExistence: - return accessBits.CanChallengeCardExistence(); - case OperationType.CreateOwnSaveData: - return accessBits.CanCreateOwnSaveData(); - case OperationType.ReadOwnSaveDataFileSystemExtraData: - return accessBits.CanReadOwnSaveDataFileSystemExtraData(); - case OperationType.ExtendOwnSaveData: - return accessBits.CanExtendOwnSaveData(); - case OperationType.OpenOwnSaveDataTransferProhibiter: - return accessBits.CanOpenOwnSaveDataTransferProhibiter(); - case OperationType.FindOwnSaveDataWithFilter: - return accessBits.CanFindOwnSaveDataWithFilter(); - default: - Abort.UnexpectedDefault(); - return default; - } - } + var accessibility = new Accessibility(accessibilities[i]); + ulong id = ids[i]; - public Accessibility GetAccessibilityFor(AccessibilityType type) - { - // ReSharper disable once PossibleInvalidOperationException - AccessControlBits accessBits = AccessBits.Value; + bool isIdAllowed; - switch (type) - { - case AccessibilityType.MountLogo: - return new Accessibility(accessBits.CanMountLogoRead(), false); - case AccessibilityType.MountContentMeta: - return new Accessibility(accessBits.CanMountContentMetaRead(), false); - case AccessibilityType.MountContentControl: - return new Accessibility(accessBits.CanMountContentControlRead(), false); - case AccessibilityType.MountContentManual: - return new Accessibility(accessBits.CanMountContentManualRead(), false); - case AccessibilityType.MountContentData: - return new Accessibility(accessBits.CanMountContentDataRead(), false); - case AccessibilityType.MountApplicationPackage: - return new Accessibility(accessBits.CanMountApplicationPackageRead(), false); - case AccessibilityType.MountSaveDataStorage: - return new Accessibility(accessBits.CanMountSaveDataStorageRead(), accessBits.CanMountSaveDataStorageWrite()); - case AccessibilityType.MountContentStorage: - return new Accessibility(accessBits.CanMountContentStorageRead(), accessBits.CanMountContentStorageWrite()); - case AccessibilityType.MountImageAndVideoStorage: - return new Accessibility(accessBits.CanMountImageAndVideoStorageRead(), accessBits.CanMountImageAndVideoStorageWrite()); - case AccessibilityType.MountCloudBackupWorkStorage: - return new Accessibility(accessBits.CanMountCloudBackupWorkStorageRead(), accessBits.CanMountCloudBackupWorkStorageWrite()); - case AccessibilityType.MountCustomStorage: - return new Accessibility(accessBits.CanMountCustomStorage0Read(), accessBits.CanMountCustomStorage0Write()); - case AccessibilityType.MountBisCalibrationFile: - return new Accessibility(accessBits.CanMountBisCalibrationFileRead(), accessBits.CanMountBisCalibrationFileWrite()); - case AccessibilityType.MountBisSafeMode: - return new Accessibility(accessBits.CanMountBisSafeModeRead(), accessBits.CanMountBisSafeModeWrite()); - case AccessibilityType.MountBisUser: - return new Accessibility(accessBits.CanMountBisUserRead(), accessBits.CanMountBisUserWrite()); - case AccessibilityType.MountBisSystem: - return new Accessibility(accessBits.CanMountBisSystemRead(), accessBits.CanMountBisSystemWrite()); - case AccessibilityType.MountBisSystemProperEncryption: - return new Accessibility(accessBits.CanMountBisSystemProperEncryptionRead(), accessBits.CanMountBisSystemProperEncryptionWrite()); - case AccessibilityType.MountBisSystemProperPartition: - return new Accessibility(accessBits.CanMountBisSystemProperPartitionRead(), accessBits.CanMountBisSystemProperPartitionWrite()); - case AccessibilityType.MountSdCard: - return new Accessibility(accessBits.CanMountSdCardRead(), accessBits.CanMountSdCardWrite()); - case AccessibilityType.MountGameCard: - return new Accessibility(accessBits.CanMountGameCardRead(), false); - case AccessibilityType.MountDeviceSaveData: - return new Accessibility(accessBits.CanMountDeviceSaveDataRead(), accessBits.CanMountDeviceSaveDataWrite()); - case AccessibilityType.MountSystemSaveData: - return new Accessibility(accessBits.CanMountSystemSaveDataRead(), accessBits.CanMountSystemSaveDataWrite()); - case AccessibilityType.MountOthersSaveData: - return new Accessibility(accessBits.CanMountOthersSaveDataRead(), accessBits.CanMountOthersSaveDataWrite()); - case AccessibilityType.MountOthersSystemSaveData: - return new Accessibility(accessBits.CanMountOthersSystemSaveDataRead(), accessBits.CanMountOthersSystemSaveDataWrite()); - case AccessibilityType.OpenBisPartitionBootPartition1Root: - return new Accessibility(accessBits.CanOpenBisPartitionBootPartition1RootRead(), accessBits.CanOpenBisPartitionBootPartition1RootWrite()); - case AccessibilityType.OpenBisPartitionBootPartition2Root: - return new Accessibility(accessBits.CanOpenBisPartitionBootPartition2RootRead(), accessBits.CanOpenBisPartitionBootPartition2RootWrite()); - case AccessibilityType.OpenBisPartitionUserDataRoot: - return new Accessibility(accessBits.CanOpenBisPartitionUserDataRootRead(), accessBits.CanOpenBisPartitionUserDataRootWrite()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part1: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part1Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part1Write()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part2: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part2Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part2Write()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part3: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part3Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part3Write()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part4: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part4Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part4Write()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part5: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part5Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part5Write()); - case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part6: - return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part6Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part6Write()); - case AccessibilityType.OpenBisPartitionCalibrationBinary: - return new Accessibility(accessBits.CanOpenBisPartitionCalibrationBinaryRead(), accessBits.CanOpenBisPartitionCalibrationFileWrite()); - case AccessibilityType.OpenBisPartitionCalibrationFile: - return new Accessibility(accessBits.CanOpenBisPartitionCalibrationFileRead(), accessBits.CanOpenBisPartitionCalibrationBinaryWrite()); - case AccessibilityType.OpenBisPartitionSafeMode: - return new Accessibility(accessBits.CanOpenBisPartitionSafeModeRead(), accessBits.CanOpenBisPartitionSafeModeWrite()); - case AccessibilityType.OpenBisPartitionUser: - return new Accessibility(accessBits.CanOpenBisPartitionUserRead(), accessBits.CanOpenBisPartitionUserWrite()); - case AccessibilityType.OpenBisPartitionSystem: - return new Accessibility(accessBits.CanOpenBisPartitionSystemRead(), accessBits.CanOpenBisPartitionSystemWrite()); - case AccessibilityType.OpenBisPartitionSystemProperEncryption: - return new Accessibility(accessBits.CanOpenBisPartitionSystemProperEncryptionRead(), accessBits.CanOpenBisPartitionSystemProperEncryptionWrite()); - case AccessibilityType.OpenBisPartitionSystemProperPartition: - return new Accessibility(accessBits.CanOpenBisPartitionSystemProperPartitionRead(), accessBits.CanOpenBisPartitionSystemProperPartitionWrite()); - case AccessibilityType.OpenSdCardStorage: - return new Accessibility(accessBits.CanOpenSdCardStorageRead(), accessBits.CanOpenSdCardStorageWrite()); - case AccessibilityType.OpenGameCardStorage: - return new Accessibility(accessBits.CanOpenGameCardStorageRead(), accessBits.CanOpenGameCardStorageWrite()); - case AccessibilityType.MountSystemDataPrivate: - return new Accessibility(accessBits.CanMountSystemDataPrivateRead(), false); - case AccessibilityType.MountHost: - return new Accessibility(accessBits.CanMountHostRead(), accessBits.CanMountHostWrite()); - case AccessibilityType.MountRegisteredUpdatePartition: - return new Accessibility(accessBits.CanMountRegisteredUpdatePartitionRead() && Globals.DebugFlag, false); - case AccessibilityType.MountSaveDataInternalStorage: - return new Accessibility(accessBits.CanOpenSaveDataInternalStorageRead(), accessBits.CanOpenSaveDataInternalStorageWrite()); - case AccessibilityType.MountTemporaryDirectory: - return new Accessibility(accessBits.CanMountTemporaryDirectoryRead(), accessBits.CanMountTemporaryDirectoryWrite()); - case AccessibilityType.MountAllBaseFileSystem: - return new Accessibility(accessBits.CanMountAllBaseFileSystemRead(), accessBits.CanMountAllBaseFileSystemWrite()); - case AccessibilityType.NotMount: - return new Accessibility(false, false); - default: - Abort.UnexpectedDefault(); - return default; + if (allowedIds.Length > 0) + { + // The descriptor contains a list of allowed save data owner IDs. Check if the ID is in that list + isIdAllowed = allowedIds.IndexOf(id) != -1; + } + else + { + // The descriptor contains a range of allowed save data owner IDs. Check if the ID is in that range + isIdAllowed = descriptor.SaveDataOwnerIdMin == 0 && descriptor.SaveDataOwnerIdMax == 0 || + id >= descriptor.SaveDataOwnerIdMin && id <= descriptor.SaveDataOwnerIdMax; + } + + if (isIdAllowed) + { + SaveDataOwners.AddFirst(new SaveDataOwnerInfo(id, accessibility)); + } } } } - internal readonly struct ContentOwnerInfo + private static ulong GetAccessBitsMask(bool isDebugMode) { - public readonly ulong Id; + return isDebugMode ? 0xFFFFFFFFFFFFFFFF : 0x3FFFFFFFFFFFFFFF; + } - public ContentOwnerInfo(ulong id) + public bool HasContentOwnerId(ulong ownerId) + { + foreach (ContentOwnerInfo info in ContentOwners) { - Id = id; + if (info.Id == ownerId) + return true; + } + + return false; + } + + public Accessibility GetAccessibilitySaveDataOwnedBy(ulong ownerId) + { + foreach (SaveDataOwnerInfo info in SaveDataOwners) + { + if (info.Id == ownerId) + return info.Accessibility; + } + + return new Accessibility(false, false); + } + + public void ListSaveDataOwnedId(out int outCount, Span outIds, int startIndex) + { + // If there's no output buffer, return the number of owned IDs + if (outIds.Length == 0) + { + outCount = SaveDataOwners.Count; + return; + } + + int preCount = 0; + int outIndex = 0; + + foreach (SaveDataOwnerInfo info in SaveDataOwners) + { + // Stop reading if the buffer's full + if (outIndex == outIds.Length) + break; + + // Skip IDs until we get to startIndex + if (preCount < startIndex) + { + preCount++; + } + else + { + // Write the ID to the buffer + outIds[outIndex] = new Ncm.ApplicationId(info.Id); + outIndex++; + } + } + + outCount = outIndex; + } + + public bool CanCall(OperationType operation) + { + // ReSharper disable once PossibleInvalidOperationException + AccessControlBits accessBits = AccessBits.Value; + + switch (operation) + { + case OperationType.InvalidateBisCache: + return accessBits.CanInvalidateBisCache(); + case OperationType.EraseMmc: + return accessBits.CanEraseMmc(); + case OperationType.GetGameCardDeviceCertificate: + return accessBits.CanGetGameCardDeviceCertificate(); + case OperationType.GetGameCardIdSet: + return accessBits.CanGetGameCardIdSet(); + case OperationType.FinalizeGameCardDriver: + return accessBits.CanFinalizeGameCardDriver(); + case OperationType.GetGameCardAsicInfo: + return accessBits.CanGetGameCardAsicInfo(); + case OperationType.CreateSaveData: + return accessBits.CanCreateSaveData(); + case OperationType.DeleteSaveData: + return accessBits.CanDeleteSaveData(); + case OperationType.CreateSystemSaveData: + return accessBits.CanCreateSystemSaveData(); + case OperationType.CreateOthersSystemSaveData: + return accessBits.CanCreateOthersSystemSaveData(); + case OperationType.DeleteSystemSaveData: + return accessBits.CanDeleteSystemSaveData(); + case OperationType.OpenSaveDataInfoReader: + return accessBits.CanOpenSaveDataInfoReader(); + case OperationType.OpenSaveDataInfoReaderForSystem: + return accessBits.CanOpenSaveDataInfoReaderForSystem(); + case OperationType.OpenSaveDataInfoReaderForInternal: + return accessBits.CanOpenSaveDataInfoReaderForInternal(); + case OperationType.OpenSaveDataMetaFile: + return accessBits.CanOpenSaveDataMetaFile(); + case OperationType.SetCurrentPosixTime: + return accessBits.CanSetCurrentPosixTime(); + case OperationType.ReadSaveDataFileSystemExtraData: + return accessBits.CanReadSaveDataFileSystemExtraData(); + case OperationType.SetGlobalAccessLogMode: + return accessBits.CanSetGlobalAccessLogMode(); + case OperationType.SetSpeedEmulationMode: + return accessBits.CanSetSpeedEmulationMode(); + case OperationType.FillBis: + return accessBits.CanFillBis(); + case OperationType.CorruptSaveData: + return accessBits.CanCorruptSaveData(); + case OperationType.CorruptSystemSaveData: + return accessBits.CanCorruptSystemSaveData(); + case OperationType.VerifySaveData: + return accessBits.CanVerifySaveData(); + case OperationType.DebugSaveData: + return accessBits.CanDebugSaveData(); + case OperationType.FormatSdCard: + return accessBits.CanFormatSdCard(); + case OperationType.GetRightsId: + return accessBits.CanGetRightsId(); + case OperationType.RegisterExternalKey: + return accessBits.CanRegisterExternalKey(); + case OperationType.SetEncryptionSeed: + return accessBits.CanSetEncryptionSeed(); + case OperationType.WriteSaveDataFileSystemExtraDataTimeStamp: + return accessBits.CanWriteSaveDataFileSystemExtraDataTimeStamp(); + case OperationType.WriteSaveDataFileSystemExtraDataFlags: + return accessBits.CanWriteSaveDataFileSystemExtraDataFlags(); + case OperationType.WriteSaveDataFileSystemExtraDataCommitId: + return accessBits.CanWriteSaveDataFileSystemExtraDataCommitId(); + case OperationType.WriteSaveDataFileSystemExtraDataAll: + return accessBits.CanWriteSaveDataFileSystemExtraDataAll(); + case OperationType.ExtendSaveData: + return accessBits.CanExtendSaveData(); + case OperationType.ExtendSystemSaveData: + return accessBits.CanExtendSystemSaveData(); + case OperationType.ExtendOthersSystemSaveData: + return accessBits.CanExtendOthersSystemSaveData(); + case OperationType.RegisterUpdatePartition: + return accessBits.CanRegisterUpdatePartition() && Globals.DebugFlag; + case OperationType.OpenSaveDataTransferManager: + return accessBits.CanOpenSaveDataTransferManager(); + case OperationType.OpenSaveDataTransferManagerVersion2: + return accessBits.CanOpenSaveDataTransferManagerVersion2(); + case OperationType.OpenSaveDataTransferManagerForSaveDataRepair: + return accessBits.CanOpenSaveDataTransferManagerForSaveDataRepair(); + case OperationType.OpenSaveDataTransferManagerForSaveDataRepairTool: + return accessBits.CanOpenSaveDataTransferManagerForSaveDataRepairTool(); + case OperationType.OpenSaveDataTransferProhibiter: + return accessBits.CanOpenSaveDataTransferProhibiter(); + case OperationType.OpenSaveDataMover: + return accessBits.CanOpenSaveDataMover(); + case OperationType.OpenBisWiper: + return accessBits.CanOpenBisWiper(); + case OperationType.ListAccessibleSaveDataOwnerId: + return accessBits.CanListAccessibleSaveDataOwnerId(); + case OperationType.ControlMmcPatrol: + return accessBits.CanControlMmcPatrol(); + case OperationType.OverrideSaveDataTransferTokenSignVerificationKey: + return accessBits.CanOverrideSaveDataTransferTokenSignVerificationKey(); + case OperationType.OpenSdCardDetectionEventNotifier: + return accessBits.CanOpenSdCardDetectionEventNotifier(); + case OperationType.OpenGameCardDetectionEventNotifier: + return accessBits.CanOpenGameCardDetectionEventNotifier(); + case OperationType.OpenSystemDataUpdateEventNotifier: + return accessBits.CanOpenSystemDataUpdateEventNotifier(); + case OperationType.NotifySystemDataUpdateEvent: + return accessBits.CanNotifySystemDataUpdateEvent(); + case OperationType.OpenAccessFailureDetectionEventNotifier: + return accessBits.CanOpenAccessFailureDetectionEventNotifier(); + case OperationType.GetAccessFailureDetectionEvent: + return accessBits.CanGetAccessFailureDetectionEvent(); + case OperationType.IsAccessFailureDetected: + return accessBits.CanIsAccessFailureDetected(); + case OperationType.ResolveAccessFailure: + return accessBits.CanResolveAccessFailure(); + case OperationType.AbandonAccessFailure: + return accessBits.CanAbandonAccessFailure(); + case OperationType.QuerySaveDataInternalStorageTotalSize: + return accessBits.CanQuerySaveDataInternalStorageTotalSize(); + case OperationType.GetSaveDataCommitId: + return accessBits.CanGetSaveDataCommitId(); + case OperationType.SetSdCardAccessibility: + return accessBits.CanSetSdCardAccessibility(); + case OperationType.SimulateDevice: + return accessBits.CanSimulateDevice(); + case OperationType.CreateSaveDataWithHashSalt: + return accessBits.CanCreateSaveDataWithHashSalt(); + case OperationType.RegisterProgramIndexMapInfo: + return accessBits.CanRegisterProgramIndexMapInfo(); + case OperationType.ChallengeCardExistence: + return accessBits.CanChallengeCardExistence(); + case OperationType.CreateOwnSaveData: + return accessBits.CanCreateOwnSaveData(); + case OperationType.ReadOwnSaveDataFileSystemExtraData: + return accessBits.CanReadOwnSaveDataFileSystemExtraData(); + case OperationType.ExtendOwnSaveData: + return accessBits.CanExtendOwnSaveData(); + case OperationType.OpenOwnSaveDataTransferProhibiter: + return accessBits.CanOpenOwnSaveDataTransferProhibiter(); + case OperationType.FindOwnSaveDataWithFilter: + return accessBits.CanFindOwnSaveDataWithFilter(); + default: + Abort.UnexpectedDefault(); + return default; } } - internal readonly struct SaveDataOwnerInfo + public Accessibility GetAccessibilityFor(AccessibilityType type) { - public readonly ulong Id; - public readonly Accessibility Accessibility; + // ReSharper disable once PossibleInvalidOperationException + AccessControlBits accessBits = AccessBits.Value; - public SaveDataOwnerInfo(ulong id, Accessibility accessibility) + switch (type) { - Id = id; - Accessibility = accessibility; + case AccessibilityType.MountLogo: + return new Accessibility(accessBits.CanMountLogoRead(), false); + case AccessibilityType.MountContentMeta: + return new Accessibility(accessBits.CanMountContentMetaRead(), false); + case AccessibilityType.MountContentControl: + return new Accessibility(accessBits.CanMountContentControlRead(), false); + case AccessibilityType.MountContentManual: + return new Accessibility(accessBits.CanMountContentManualRead(), false); + case AccessibilityType.MountContentData: + return new Accessibility(accessBits.CanMountContentDataRead(), false); + case AccessibilityType.MountApplicationPackage: + return new Accessibility(accessBits.CanMountApplicationPackageRead(), false); + case AccessibilityType.MountSaveDataStorage: + return new Accessibility(accessBits.CanMountSaveDataStorageRead(), accessBits.CanMountSaveDataStorageWrite()); + case AccessibilityType.MountContentStorage: + return new Accessibility(accessBits.CanMountContentStorageRead(), accessBits.CanMountContentStorageWrite()); + case AccessibilityType.MountImageAndVideoStorage: + return new Accessibility(accessBits.CanMountImageAndVideoStorageRead(), accessBits.CanMountImageAndVideoStorageWrite()); + case AccessibilityType.MountCloudBackupWorkStorage: + return new Accessibility(accessBits.CanMountCloudBackupWorkStorageRead(), accessBits.CanMountCloudBackupWorkStorageWrite()); + case AccessibilityType.MountCustomStorage: + return new Accessibility(accessBits.CanMountCustomStorage0Read(), accessBits.CanMountCustomStorage0Write()); + case AccessibilityType.MountBisCalibrationFile: + return new Accessibility(accessBits.CanMountBisCalibrationFileRead(), accessBits.CanMountBisCalibrationFileWrite()); + case AccessibilityType.MountBisSafeMode: + return new Accessibility(accessBits.CanMountBisSafeModeRead(), accessBits.CanMountBisSafeModeWrite()); + case AccessibilityType.MountBisUser: + return new Accessibility(accessBits.CanMountBisUserRead(), accessBits.CanMountBisUserWrite()); + case AccessibilityType.MountBisSystem: + return new Accessibility(accessBits.CanMountBisSystemRead(), accessBits.CanMountBisSystemWrite()); + case AccessibilityType.MountBisSystemProperEncryption: + return new Accessibility(accessBits.CanMountBisSystemProperEncryptionRead(), accessBits.CanMountBisSystemProperEncryptionWrite()); + case AccessibilityType.MountBisSystemProperPartition: + return new Accessibility(accessBits.CanMountBisSystemProperPartitionRead(), accessBits.CanMountBisSystemProperPartitionWrite()); + case AccessibilityType.MountSdCard: + return new Accessibility(accessBits.CanMountSdCardRead(), accessBits.CanMountSdCardWrite()); + case AccessibilityType.MountGameCard: + return new Accessibility(accessBits.CanMountGameCardRead(), false); + case AccessibilityType.MountDeviceSaveData: + return new Accessibility(accessBits.CanMountDeviceSaveDataRead(), accessBits.CanMountDeviceSaveDataWrite()); + case AccessibilityType.MountSystemSaveData: + return new Accessibility(accessBits.CanMountSystemSaveDataRead(), accessBits.CanMountSystemSaveDataWrite()); + case AccessibilityType.MountOthersSaveData: + return new Accessibility(accessBits.CanMountOthersSaveDataRead(), accessBits.CanMountOthersSaveDataWrite()); + case AccessibilityType.MountOthersSystemSaveData: + return new Accessibility(accessBits.CanMountOthersSystemSaveDataRead(), accessBits.CanMountOthersSystemSaveDataWrite()); + case AccessibilityType.OpenBisPartitionBootPartition1Root: + return new Accessibility(accessBits.CanOpenBisPartitionBootPartition1RootRead(), accessBits.CanOpenBisPartitionBootPartition1RootWrite()); + case AccessibilityType.OpenBisPartitionBootPartition2Root: + return new Accessibility(accessBits.CanOpenBisPartitionBootPartition2RootRead(), accessBits.CanOpenBisPartitionBootPartition2RootWrite()); + case AccessibilityType.OpenBisPartitionUserDataRoot: + return new Accessibility(accessBits.CanOpenBisPartitionUserDataRootRead(), accessBits.CanOpenBisPartitionUserDataRootWrite()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part1: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part1Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part1Write()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part2: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part2Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part2Write()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part3: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part3Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part3Write()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part4: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part4Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part4Write()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part5: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part5Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part5Write()); + case AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part6: + return new Accessibility(accessBits.CanOpenBisPartitionBootConfigAndPackage2Part6Read(), accessBits.CanOpenBisPartitionBootConfigAndPackage2Part6Write()); + case AccessibilityType.OpenBisPartitionCalibrationBinary: + return new Accessibility(accessBits.CanOpenBisPartitionCalibrationBinaryRead(), accessBits.CanOpenBisPartitionCalibrationFileWrite()); + case AccessibilityType.OpenBisPartitionCalibrationFile: + return new Accessibility(accessBits.CanOpenBisPartitionCalibrationFileRead(), accessBits.CanOpenBisPartitionCalibrationBinaryWrite()); + case AccessibilityType.OpenBisPartitionSafeMode: + return new Accessibility(accessBits.CanOpenBisPartitionSafeModeRead(), accessBits.CanOpenBisPartitionSafeModeWrite()); + case AccessibilityType.OpenBisPartitionUser: + return new Accessibility(accessBits.CanOpenBisPartitionUserRead(), accessBits.CanOpenBisPartitionUserWrite()); + case AccessibilityType.OpenBisPartitionSystem: + return new Accessibility(accessBits.CanOpenBisPartitionSystemRead(), accessBits.CanOpenBisPartitionSystemWrite()); + case AccessibilityType.OpenBisPartitionSystemProperEncryption: + return new Accessibility(accessBits.CanOpenBisPartitionSystemProperEncryptionRead(), accessBits.CanOpenBisPartitionSystemProperEncryptionWrite()); + case AccessibilityType.OpenBisPartitionSystemProperPartition: + return new Accessibility(accessBits.CanOpenBisPartitionSystemProperPartitionRead(), accessBits.CanOpenBisPartitionSystemProperPartitionWrite()); + case AccessibilityType.OpenSdCardStorage: + return new Accessibility(accessBits.CanOpenSdCardStorageRead(), accessBits.CanOpenSdCardStorageWrite()); + case AccessibilityType.OpenGameCardStorage: + return new Accessibility(accessBits.CanOpenGameCardStorageRead(), accessBits.CanOpenGameCardStorageWrite()); + case AccessibilityType.MountSystemDataPrivate: + return new Accessibility(accessBits.CanMountSystemDataPrivateRead(), false); + case AccessibilityType.MountHost: + return new Accessibility(accessBits.CanMountHostRead(), accessBits.CanMountHostWrite()); + case AccessibilityType.MountRegisteredUpdatePartition: + return new Accessibility(accessBits.CanMountRegisteredUpdatePartitionRead() && Globals.DebugFlag, false); + case AccessibilityType.MountSaveDataInternalStorage: + return new Accessibility(accessBits.CanOpenSaveDataInternalStorageRead(), accessBits.CanOpenSaveDataInternalStorageWrite()); + case AccessibilityType.MountTemporaryDirectory: + return new Accessibility(accessBits.CanMountTemporaryDirectoryRead(), accessBits.CanMountTemporaryDirectoryWrite()); + case AccessibilityType.MountAllBaseFileSystem: + return new Accessibility(accessBits.CanMountAllBaseFileSystemRead(), accessBits.CanMountAllBaseFileSystemWrite()); + case AccessibilityType.NotMount: + return new Accessibility(false, false); + default: + Abort.UnexpectedDefault(); + return default; } } - - public readonly struct Accessibility - { - private readonly byte _value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Accessibility(bool canRead, bool canWrite) - { - int readValue = canRead ? 1 : 0; - int writeValue = canWrite ? 1 : 0; - _value = (byte)(writeValue << 1 | readValue); - } - - public Accessibility(byte value) - { - _value = value; - } - - public bool CanRead => (_value & 1) == 1; - public bool CanWrite => ((_value >> 1) & 1) == 1; - } - - public readonly struct AccessControlBits - { - public readonly ulong Value; - - public AccessControlBits(ulong value) - { - Value = value; - } - - [Flags] - public enum Bits : ulong - { - None = 0, - ApplicationInfo = 1UL << 0, - BootModeControl = 1UL << 1, - Calibration = 1UL << 2, - SystemSaveData = 1UL << 3, - GameCard = 1UL << 4, - SaveDataBackUp = 1UL << 5, - SaveDataManagement = 1UL << 6, - BisAllRaw = 1UL << 7, - GameCardRaw = 1UL << 8, - GameCardPrivate = 1UL << 9, - SetTime = 1UL << 10, - ContentManager = 1UL << 11, - ImageManager = 1UL << 12, - CreateSaveData = 1UL << 13, - SystemSaveDataManagement = 1UL << 14, - BisFileSystem = 1UL << 15, - SystemUpdate = 1UL << 16, - SaveDataMeta = 1UL << 17, - DeviceSaveData = 1UL << 18, - SettingsControl = 1UL << 19, - SystemData = 1UL << 20, - SdCard = 1UL << 21, - Host = 1UL << 22, - FillBis = 1UL << 23, - CorruptSaveData = 1UL << 24, - SaveDataForDebug = 1UL << 25, - FormatSdCard = 1UL << 26, - GetRightsId = 1UL << 27, - RegisterExternalKey = 1UL << 28, - RegisterUpdatePartition = 1UL << 29, - SaveDataTransfer = 1UL << 30, - DeviceDetection = 1UL << 31, - AccessFailureResolution = 1UL << 32, - SaveDataTransferVersion2 = 1UL << 33, - RegisterProgramIndexMapInfo = 1UL << 34, - CreateOwnSaveData = 1UL << 35, - MoveCacheStorage = 1UL << 36, - Debug = 1UL << 62, - FullPermission = 1UL << 63 - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool Has(Bits bits) - { - return ((Bits)Value & (Bits.FullPermission | bits)) != 0; - } - - public bool CanAbandonAccessFailure() => Has(Bits.AccessFailureResolution); - public bool CanChallengeCardExistence() => Has(Bits.GameCard); - public bool CanControlMmcPatrol() => Has(Bits.None); - public bool CanCorruptSaveData() => Has(Bits.Debug | Bits.CorruptSaveData); - public bool CanCorruptSystemSaveData() => Has(Bits.CorruptSaveData | Bits.SaveDataManagement | Bits.SaveDataBackUp); - public bool CanCreateOthersSystemSaveData() => Has(Bits.SaveDataBackUp); - public bool CanCreateOwnSaveData() => Has(Bits.CreateOwnSaveData); - public bool CanCreateSaveData() => Has(Bits.CreateSaveData | Bits.SaveDataBackUp); - public bool CanCreateSaveDataWithHashSalt() => Has(Bits.None); - public bool CanCreateSystemSaveData() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); - public bool CanDebugSaveData() => Has(Bits.Debug | Bits.SaveDataForDebug); - public bool CanDeleteSaveData() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); - public bool CanDeleteSystemSaveData() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataBackUp | Bits.SystemSaveData); - public bool CanEraseMmc() => Has(Bits.BisAllRaw); - public bool CanExtendOthersSystemSaveData() => Has(Bits.SaveDataBackUp); - public bool CanExtendOwnSaveData() => Has(Bits.CreateOwnSaveData); - public bool CanExtendSaveData() => Has(Bits.CreateSaveData | Bits.SaveDataBackUp); - public bool CanExtendSystemSaveData() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); - public bool CanFillBis() => Has(Bits.Debug | Bits.FillBis); - public bool CanFinalizeGameCardDriver() => Has(Bits.GameCardPrivate); - public bool CanFindOwnSaveDataWithFilter() => Has(Bits.CreateOwnSaveData); - public bool CanFormatSdCard() => Has(Bits.FormatSdCard); - public bool CanGetAccessFailureDetectionEvent() => Has(Bits.AccessFailureResolution); - public bool CanGetGameCardAsicInfo() => Has(Bits.GameCardPrivate); - public bool CanGetGameCardDeviceCertificate() => Has(Bits.GameCard); - public bool CanGetGameCardIdSet() => Has(Bits.GameCard); - public bool CanGetRightsId() => Has(Bits.GetRightsId); - public bool CanGetSaveDataCommitId() => Has(Bits.SaveDataTransferVersion2 | Bits.SaveDataBackUp); - public bool CanInvalidateBisCache() => Has(Bits.BisAllRaw); - public bool CanIsAccessFailureDetected() => Has(Bits.AccessFailureResolution); - public bool CanListAccessibleSaveDataOwnerId() => Has(Bits.SaveDataTransferVersion2 | Bits.SaveDataTransfer | Bits.CreateSaveData); - public bool CanMountAllBaseFileSystemRead() => Has(Bits.None); - public bool CanMountAllBaseFileSystemWrite() => Has(Bits.None); - public bool CanMountApplicationPackageRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountBisCalibrationFileRead() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanMountBisCalibrationFileWrite() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanMountBisSafeModeRead() => Has(Bits.BisAllRaw); - public bool CanMountBisSafeModeWrite() => Has(Bits.BisAllRaw); - public bool CanMountBisSystemProperEncryptionRead() => Has(Bits.BisAllRaw); - public bool CanMountBisSystemProperEncryptionWrite() => Has(Bits.BisAllRaw); - public bool CanMountBisSystemProperPartitionRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountBisSystemProperPartitionWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountBisSystemRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountBisSystemWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountBisUserRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountBisUserWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); - public bool CanMountCloudBackupWorkStorageRead() => Has(Bits.SaveDataTransferVersion2); - public bool CanMountCloudBackupWorkStorageWrite() => Has(Bits.SaveDataTransferVersion2); - public bool CanMountContentControlRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountContentDataRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountContentManualRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountContentMetaRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountContentStorageRead() => Has(Bits.ContentManager); - public bool CanMountContentStorageWrite() => Has(Bits.ContentManager); - public bool CanMountCustomStorage0Read() => Has(Bits.None); - public bool CanMountCustomStorage0Write() => Has(Bits.None); - public bool CanMountDeviceSaveDataRead() => Has(Bits.DeviceSaveData | Bits.SaveDataBackUp); - public bool CanMountDeviceSaveDataWrite() => Has(Bits.DeviceSaveData | Bits.SaveDataBackUp); - public bool CanMountGameCardRead() => Has(Bits.GameCard); - public bool CanMountHostRead() => Has(Bits.Debug | Bits.Host); - public bool CanMountHostWrite() => Has(Bits.Debug | Bits.Host); - public bool CanMountImageAndVideoStorageRead() => Has(Bits.ImageManager); - public bool CanMountImageAndVideoStorageWrite() => Has(Bits.ImageManager); - public bool CanMountLogoRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); - public bool CanMountOthersSaveDataRead() => Has(Bits.SaveDataBackUp); - public bool CanMountOthersSaveDataWrite() => Has(Bits.SaveDataBackUp); - public bool CanMountOthersSystemSaveDataRead() => Has(Bits.SaveDataBackUp); - public bool CanMountOthersSystemSaveDataWrite() => Has(Bits.SaveDataBackUp); - public bool CanMountRegisteredUpdatePartitionRead() => Has(Bits.SystemUpdate); - public bool CanMountSaveDataStorageRead() => Has(Bits.None); - public bool CanMountSaveDataStorageWrite() => Has(Bits.None); - public bool CanMountSdCardRead() => Has(Bits.Debug | Bits.SdCard); - public bool CanMountSdCardWrite() => Has(Bits.Debug | Bits.SdCard); - public bool CanMountSystemDataPrivateRead() => Has(Bits.SystemData | Bits.SystemSaveData); - public bool CanMountSystemSaveDataRead() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); - public bool CanMountSystemSaveDataWrite() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); - public bool CanMountTemporaryDirectoryRead() => Has(Bits.Debug); - public bool CanMountTemporaryDirectoryWrite() => Has(Bits.Debug); - public bool CanNotifySystemDataUpdateEvent() => Has(Bits.SystemUpdate); - public bool CanOpenAccessFailureDetectionEventNotifier() => Has(Bits.AccessFailureResolution); - public bool CanOpenBisPartitionBootConfigAndPackage2Part1Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part1Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part2Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part2Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part3Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part3Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part4Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part4Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part5Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part5Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part6Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootConfigAndPackage2Part6Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootPartition1RootRead() => Has(Bits.SystemUpdate | Bits.BisAllRaw | Bits.BootModeControl); - public bool CanOpenBisPartitionBootPartition1RootWrite() => Has(Bits.SystemUpdate | Bits.BisAllRaw | Bits.BootModeControl); - public bool CanOpenBisPartitionBootPartition2RootRead() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionBootPartition2RootWrite() => Has(Bits.SystemUpdate | Bits.BisAllRaw); - public bool CanOpenBisPartitionCalibrationBinaryRead() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanOpenBisPartitionCalibrationBinaryWrite() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanOpenBisPartitionCalibrationFileRead() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanOpenBisPartitionCalibrationFileWrite() => Has(Bits.BisAllRaw | Bits.Calibration); - public bool CanOpenBisPartitionSafeModeRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSafeModeWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemProperEncryptionRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemProperEncryptionWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemProperPartitionRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemProperPartitionWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionSystemWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionUserDataRootRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionUserDataRootWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionUserRead() => Has(Bits.BisAllRaw); - public bool CanOpenBisPartitionUserWrite() => Has(Bits.BisAllRaw); - public bool CanOpenBisWiper() => Has(Bits.ContentManager); - public bool CanOpenGameCardDetectionEventNotifier() => Has(Bits.DeviceDetection | Bits.GameCardRaw | Bits.GameCard); - public bool CanOpenGameCardStorageRead() => Has(Bits.GameCardRaw); - public bool CanOpenGameCardStorageWrite() => Has(Bits.GameCardRaw); - public bool CanOpenOwnSaveDataTransferProhibiter() => Has(Bits.CreateOwnSaveData); - public bool CanOpenSaveDataInfoReader() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); - public bool CanOpenSaveDataInfoReaderForInternal() => Has(Bits.SaveDataManagement); - public bool CanOpenSaveDataInfoReaderForSystem() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataBackUp); - public bool CanOpenSaveDataInternalStorageRead() => Has(Bits.None); - public bool CanOpenSaveDataInternalStorageWrite() => Has(Bits.None); - public bool CanOpenSaveDataMetaFile() => Has(Bits.SaveDataMeta); - public bool CanOpenSaveDataMover() => Has(Bits.MoveCacheStorage); - public bool CanOpenSaveDataTransferManager() => Has(Bits.SaveDataTransfer); - public bool CanOpenSaveDataTransferManagerForSaveDataRepair() => Has(Bits.SaveDataTransferVersion2); - public bool CanOpenSaveDataTransferManagerForSaveDataRepairTool() => Has(Bits.None); - public bool CanOpenSaveDataTransferManagerVersion2() => Has(Bits.SaveDataTransferVersion2); - public bool CanOpenSaveDataTransferProhibiter() => Has(Bits.SaveDataTransferVersion2 | Bits.CreateSaveData); - public bool CanOpenSdCardDetectionEventNotifier() => Has(Bits.DeviceDetection | Bits.SdCard); - public bool CanOpenSdCardStorageRead() => Has(Bits.Debug | Bits.SdCard); - public bool CanOpenSdCardStorageWrite() => Has(Bits.Debug | Bits.SdCard); - public bool CanOpenSystemDataUpdateEventNotifier() => Has(Bits.SystemData | Bits.SystemSaveData); - public bool CanOverrideSaveDataTransferTokenSignVerificationKey() => Has(Bits.None); - public bool CanQuerySaveDataInternalStorageTotalSize() => Has(Bits.SaveDataTransfer); - public bool CanReadOwnSaveDataFileSystemExtraData() => Has(Bits.CreateOwnSaveData); - public bool CanReadSaveDataFileSystemExtraData() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataManagement | Bits.SaveDataBackUp); - public bool CanRegisterExternalKey() => Has(Bits.RegisterExternalKey); - public bool CanRegisterProgramIndexMapInfo() => Has(Bits.RegisterProgramIndexMapInfo); - public bool CanRegisterUpdatePartition() => Has(Bits.RegisterUpdatePartition); - public bool CanResolveAccessFailure() => Has(Bits.AccessFailureResolution); - public bool CanSetCurrentPosixTime() => Has(Bits.SetTime); - public bool CanSetEncryptionSeed() => Has(Bits.ContentManager); - public bool CanSetGlobalAccessLogMode() => Has(Bits.SettingsControl); - public bool CanSetSdCardAccessibility() => Has(Bits.SdCard); - public bool CanSetSpeedEmulationMode() => Has(Bits.SettingsControl); - public bool CanSimulateDevice() => Has(Bits.Debug); - public bool CanVerifySaveData() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); - public bool CanWriteSaveDataFileSystemExtraDataAll() => Has(Bits.None); - public bool CanWriteSaveDataFileSystemExtraDataCommitId() => Has(Bits.SaveDataBackUp); - public bool CanWriteSaveDataFileSystemExtraDataFlags() => Has(Bits.SaveDataTransferVersion2 | Bits.SystemSaveDataManagement | Bits.SaveDataBackUp); - public bool CanWriteSaveDataFileSystemExtraDataTimeStamp() => Has(Bits.SaveDataBackUp); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x2C)] - internal struct AccessControlDescriptor - { - [FieldOffset(0x00)] public byte Version; - [FieldOffset(0x01)] public byte ContentOwnerIdCount; - [FieldOffset(0x02)] public byte SaveDataOwnerIdCount; - [FieldOffset(0x04)] public ulong AccessFlags; - [FieldOffset(0x0C)] public ulong ContentOwnerIdMin; - [FieldOffset(0x14)] public ulong ContentOwnerIdMax; - [FieldOffset(0x1C)] public ulong SaveDataOwnerIdMin; - [FieldOffset(0x24)] public ulong SaveDataOwnerIdMax; - // public ulong ContentOwnerIds[ContentOwnerIdCount]; - // public ulong SaveDataOwnerIds[SaveDataOwnerIdCount]; - } - - [StructLayout(LayoutKind.Explicit, Size = 0x1C)] - internal struct AccessControlDataHeader - { - [FieldOffset(0x00)] public byte Version; - [FieldOffset(0x04)] public ulong AccessFlags; - [FieldOffset(0x0C)] public int ContentOwnerInfoOffset; - [FieldOffset(0x10)] public int ContentOwnerInfoSize; - [FieldOffset(0x14)] public int SaveDataOwnerInfoOffset; - [FieldOffset(0x18)] public int SaveDataOwnerInfoSize; - - // [FieldOffset(ContentOwnerInfoOffset)] - // public int ContentOwnerInfoCount; - // public ulong ContentOwnerIds[ContentOwnerInfoCount]; - - // [FieldOffset(SaveDataOwnerInfoOffset)] - // public int SaveDataOwnerInfoCount; - // public byte Accessibilities[SaveDataOwnerInfoCount]; - // Next field is 4-byte aligned - // public byte SaveDataOwnerIds[SaveDataOwnerInfoCount]; - } - - public enum OperationType - { - InvalidateBisCache, - EraseMmc, - GetGameCardDeviceCertificate, - GetGameCardIdSet, - FinalizeGameCardDriver, - GetGameCardAsicInfo, - CreateSaveData, - DeleteSaveData, - CreateSystemSaveData, - CreateOthersSystemSaveData, - DeleteSystemSaveData, - OpenSaveDataInfoReader, - OpenSaveDataInfoReaderForSystem, - OpenSaveDataInfoReaderForInternal, - OpenSaveDataMetaFile, - SetCurrentPosixTime, - ReadSaveDataFileSystemExtraData, - SetGlobalAccessLogMode, - SetSpeedEmulationMode, - Debug, - FillBis, - CorruptSaveData, - CorruptSystemSaveData, - VerifySaveData, - DebugSaveData, - FormatSdCard, - GetRightsId, - RegisterExternalKey, - SetEncryptionSeed, - WriteSaveDataFileSystemExtraDataTimeStamp, - WriteSaveDataFileSystemExtraDataFlags, - WriteSaveDataFileSystemExtraDataCommitId, - WriteSaveDataFileSystemExtraDataAll, - ExtendSaveData, - ExtendSystemSaveData, - ExtendOthersSystemSaveData, - RegisterUpdatePartition, - OpenSaveDataTransferManager, - OpenSaveDataTransferManagerVersion2, - OpenSaveDataTransferManagerForSaveDataRepair, - OpenSaveDataTransferManagerForSaveDataRepairTool, - OpenSaveDataTransferProhibiter, - OpenSaveDataMover, - OpenBisWiper, - ListAccessibleSaveDataOwnerId, - ControlMmcPatrol, - OverrideSaveDataTransferTokenSignVerificationKey, - OpenSdCardDetectionEventNotifier, - OpenGameCardDetectionEventNotifier, - OpenSystemDataUpdateEventNotifier, - NotifySystemDataUpdateEvent, - OpenAccessFailureDetectionEventNotifier, - GetAccessFailureDetectionEvent, - IsAccessFailureDetected, - ResolveAccessFailure, - AbandonAccessFailure, - QuerySaveDataInternalStorageTotalSize, - GetSaveDataCommitId, - SetSdCardAccessibility, - SimulateDevice, - CreateSaveDataWithHashSalt, - RegisterProgramIndexMapInfo, - ChallengeCardExistence, - CreateOwnSaveData, - DeleteOwnSaveData, - ReadOwnSaveDataFileSystemExtraData, - ExtendOwnSaveData, - OpenOwnSaveDataTransferProhibiter, - FindOwnSaveDataWithFilter - } - - public enum AccessibilityType - { - MountLogo, - MountContentMeta, - MountContentControl, - MountContentManual, - MountContentData, - MountApplicationPackage, - MountSaveDataStorage, - MountContentStorage, - MountImageAndVideoStorage, - MountCloudBackupWorkStorage, - MountCustomStorage, - MountBisCalibrationFile, - MountBisSafeMode, - MountBisUser, - MountBisSystem, - MountBisSystemProperEncryption, - MountBisSystemProperPartition, - MountSdCard, - MountGameCard, - MountDeviceSaveData, - MountSystemSaveData, - MountOthersSaveData, - MountOthersSystemSaveData, - OpenBisPartitionBootPartition1Root, - OpenBisPartitionBootPartition2Root, - OpenBisPartitionUserDataRoot, - OpenBisPartitionBootConfigAndPackage2Part1, - OpenBisPartitionBootConfigAndPackage2Part2, - OpenBisPartitionBootConfigAndPackage2Part3, - OpenBisPartitionBootConfigAndPackage2Part4, - OpenBisPartitionBootConfigAndPackage2Part5, - OpenBisPartitionBootConfigAndPackage2Part6, - OpenBisPartitionCalibrationBinary, - OpenBisPartitionCalibrationFile, - OpenBisPartitionSafeMode, - OpenBisPartitionUser, - OpenBisPartitionSystem, - OpenBisPartitionSystemProperEncryption, - OpenBisPartitionSystemProperPartition, - OpenSdCardStorage, - OpenGameCardStorage, - MountSystemDataPrivate, - MountHost, - MountRegisteredUpdatePartition, - MountSaveDataInternalStorage, - MountTemporaryDirectory, - MountAllBaseFileSystem, - NotMount - } +} + +internal readonly struct ContentOwnerInfo +{ + public readonly ulong Id; + + public ContentOwnerInfo(ulong id) + { + Id = id; + } +} + +internal readonly struct SaveDataOwnerInfo +{ + public readonly ulong Id; + public readonly Accessibility Accessibility; + + public SaveDataOwnerInfo(ulong id, Accessibility accessibility) + { + Id = id; + Accessibility = accessibility; + } +} + +public readonly struct Accessibility +{ + private readonly byte _value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Accessibility(bool canRead, bool canWrite) + { + int readValue = canRead ? 1 : 0; + int writeValue = canWrite ? 1 : 0; + _value = (byte)(writeValue << 1 | readValue); + } + + public Accessibility(byte value) + { + _value = value; + } + + public bool CanRead => (_value & 1) == 1; + public bool CanWrite => ((_value >> 1) & 1) == 1; +} + +public readonly struct AccessControlBits +{ + public readonly ulong Value; + + public AccessControlBits(ulong value) + { + Value = value; + } + + [Flags] + public enum Bits : ulong + { + None = 0, + ApplicationInfo = 1UL << 0, + BootModeControl = 1UL << 1, + Calibration = 1UL << 2, + SystemSaveData = 1UL << 3, + GameCard = 1UL << 4, + SaveDataBackUp = 1UL << 5, + SaveDataManagement = 1UL << 6, + BisAllRaw = 1UL << 7, + GameCardRaw = 1UL << 8, + GameCardPrivate = 1UL << 9, + SetTime = 1UL << 10, + ContentManager = 1UL << 11, + ImageManager = 1UL << 12, + CreateSaveData = 1UL << 13, + SystemSaveDataManagement = 1UL << 14, + BisFileSystem = 1UL << 15, + SystemUpdate = 1UL << 16, + SaveDataMeta = 1UL << 17, + DeviceSaveData = 1UL << 18, + SettingsControl = 1UL << 19, + SystemData = 1UL << 20, + SdCard = 1UL << 21, + Host = 1UL << 22, + FillBis = 1UL << 23, + CorruptSaveData = 1UL << 24, + SaveDataForDebug = 1UL << 25, + FormatSdCard = 1UL << 26, + GetRightsId = 1UL << 27, + RegisterExternalKey = 1UL << 28, + RegisterUpdatePartition = 1UL << 29, + SaveDataTransfer = 1UL << 30, + DeviceDetection = 1UL << 31, + AccessFailureResolution = 1UL << 32, + SaveDataTransferVersion2 = 1UL << 33, + RegisterProgramIndexMapInfo = 1UL << 34, + CreateOwnSaveData = 1UL << 35, + MoveCacheStorage = 1UL << 36, + Debug = 1UL << 62, + FullPermission = 1UL << 63 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool Has(Bits bits) + { + return ((Bits)Value & (Bits.FullPermission | bits)) != 0; + } + + public bool CanAbandonAccessFailure() => Has(Bits.AccessFailureResolution); + public bool CanChallengeCardExistence() => Has(Bits.GameCard); + public bool CanControlMmcPatrol() => Has(Bits.None); + public bool CanCorruptSaveData() => Has(Bits.Debug | Bits.CorruptSaveData); + public bool CanCorruptSystemSaveData() => Has(Bits.CorruptSaveData | Bits.SaveDataManagement | Bits.SaveDataBackUp); + public bool CanCreateOthersSystemSaveData() => Has(Bits.SaveDataBackUp); + public bool CanCreateOwnSaveData() => Has(Bits.CreateOwnSaveData); + public bool CanCreateSaveData() => Has(Bits.CreateSaveData | Bits.SaveDataBackUp); + public bool CanCreateSaveDataWithHashSalt() => Has(Bits.None); + public bool CanCreateSystemSaveData() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); + public bool CanDebugSaveData() => Has(Bits.Debug | Bits.SaveDataForDebug); + public bool CanDeleteSaveData() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); + public bool CanDeleteSystemSaveData() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataBackUp | Bits.SystemSaveData); + public bool CanEraseMmc() => Has(Bits.BisAllRaw); + public bool CanExtendOthersSystemSaveData() => Has(Bits.SaveDataBackUp); + public bool CanExtendOwnSaveData() => Has(Bits.CreateOwnSaveData); + public bool CanExtendSaveData() => Has(Bits.CreateSaveData | Bits.SaveDataBackUp); + public bool CanExtendSystemSaveData() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); + public bool CanFillBis() => Has(Bits.Debug | Bits.FillBis); + public bool CanFinalizeGameCardDriver() => Has(Bits.GameCardPrivate); + public bool CanFindOwnSaveDataWithFilter() => Has(Bits.CreateOwnSaveData); + public bool CanFormatSdCard() => Has(Bits.FormatSdCard); + public bool CanGetAccessFailureDetectionEvent() => Has(Bits.AccessFailureResolution); + public bool CanGetGameCardAsicInfo() => Has(Bits.GameCardPrivate); + public bool CanGetGameCardDeviceCertificate() => Has(Bits.GameCard); + public bool CanGetGameCardIdSet() => Has(Bits.GameCard); + public bool CanGetRightsId() => Has(Bits.GetRightsId); + public bool CanGetSaveDataCommitId() => Has(Bits.SaveDataTransferVersion2 | Bits.SaveDataBackUp); + public bool CanInvalidateBisCache() => Has(Bits.BisAllRaw); + public bool CanIsAccessFailureDetected() => Has(Bits.AccessFailureResolution); + public bool CanListAccessibleSaveDataOwnerId() => Has(Bits.SaveDataTransferVersion2 | Bits.SaveDataTransfer | Bits.CreateSaveData); + public bool CanMountAllBaseFileSystemRead() => Has(Bits.None); + public bool CanMountAllBaseFileSystemWrite() => Has(Bits.None); + public bool CanMountApplicationPackageRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountBisCalibrationFileRead() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanMountBisCalibrationFileWrite() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanMountBisSafeModeRead() => Has(Bits.BisAllRaw); + public bool CanMountBisSafeModeWrite() => Has(Bits.BisAllRaw); + public bool CanMountBisSystemProperEncryptionRead() => Has(Bits.BisAllRaw); + public bool CanMountBisSystemProperEncryptionWrite() => Has(Bits.BisAllRaw); + public bool CanMountBisSystemProperPartitionRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountBisSystemProperPartitionWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountBisSystemRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountBisSystemWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountBisUserRead() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountBisUserWrite() => Has(Bits.BisFileSystem | Bits.BisAllRaw); + public bool CanMountCloudBackupWorkStorageRead() => Has(Bits.SaveDataTransferVersion2); + public bool CanMountCloudBackupWorkStorageWrite() => Has(Bits.SaveDataTransferVersion2); + public bool CanMountContentControlRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountContentDataRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountContentManualRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountContentMetaRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountContentStorageRead() => Has(Bits.ContentManager); + public bool CanMountContentStorageWrite() => Has(Bits.ContentManager); + public bool CanMountCustomStorage0Read() => Has(Bits.None); + public bool CanMountCustomStorage0Write() => Has(Bits.None); + public bool CanMountDeviceSaveDataRead() => Has(Bits.DeviceSaveData | Bits.SaveDataBackUp); + public bool CanMountDeviceSaveDataWrite() => Has(Bits.DeviceSaveData | Bits.SaveDataBackUp); + public bool CanMountGameCardRead() => Has(Bits.GameCard); + public bool CanMountHostRead() => Has(Bits.Debug | Bits.Host); + public bool CanMountHostWrite() => Has(Bits.Debug | Bits.Host); + public bool CanMountImageAndVideoStorageRead() => Has(Bits.ImageManager); + public bool CanMountImageAndVideoStorageWrite() => Has(Bits.ImageManager); + public bool CanMountLogoRead() => Has(Bits.ContentManager | Bits.ApplicationInfo); + public bool CanMountOthersSaveDataRead() => Has(Bits.SaveDataBackUp); + public bool CanMountOthersSaveDataWrite() => Has(Bits.SaveDataBackUp); + public bool CanMountOthersSystemSaveDataRead() => Has(Bits.SaveDataBackUp); + public bool CanMountOthersSystemSaveDataWrite() => Has(Bits.SaveDataBackUp); + public bool CanMountRegisteredUpdatePartitionRead() => Has(Bits.SystemUpdate); + public bool CanMountSaveDataStorageRead() => Has(Bits.None); + public bool CanMountSaveDataStorageWrite() => Has(Bits.None); + public bool CanMountSdCardRead() => Has(Bits.Debug | Bits.SdCard); + public bool CanMountSdCardWrite() => Has(Bits.Debug | Bits.SdCard); + public bool CanMountSystemDataPrivateRead() => Has(Bits.SystemData | Bits.SystemSaveData); + public bool CanMountSystemSaveDataRead() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); + public bool CanMountSystemSaveDataWrite() => Has(Bits.SaveDataBackUp | Bits.SystemSaveData); + public bool CanMountTemporaryDirectoryRead() => Has(Bits.Debug); + public bool CanMountTemporaryDirectoryWrite() => Has(Bits.Debug); + public bool CanNotifySystemDataUpdateEvent() => Has(Bits.SystemUpdate); + public bool CanOpenAccessFailureDetectionEventNotifier() => Has(Bits.AccessFailureResolution); + public bool CanOpenBisPartitionBootConfigAndPackage2Part1Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part1Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part2Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part2Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part3Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part3Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part4Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part4Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part5Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part5Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part6Read() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootConfigAndPackage2Part6Write() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootPartition1RootRead() => Has(Bits.SystemUpdate | Bits.BisAllRaw | Bits.BootModeControl); + public bool CanOpenBisPartitionBootPartition1RootWrite() => Has(Bits.SystemUpdate | Bits.BisAllRaw | Bits.BootModeControl); + public bool CanOpenBisPartitionBootPartition2RootRead() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionBootPartition2RootWrite() => Has(Bits.SystemUpdate | Bits.BisAllRaw); + public bool CanOpenBisPartitionCalibrationBinaryRead() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanOpenBisPartitionCalibrationBinaryWrite() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanOpenBisPartitionCalibrationFileRead() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanOpenBisPartitionCalibrationFileWrite() => Has(Bits.BisAllRaw | Bits.Calibration); + public bool CanOpenBisPartitionSafeModeRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSafeModeWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemProperEncryptionRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemProperEncryptionWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemProperPartitionRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemProperPartitionWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionSystemWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionUserDataRootRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionUserDataRootWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionUserRead() => Has(Bits.BisAllRaw); + public bool CanOpenBisPartitionUserWrite() => Has(Bits.BisAllRaw); + public bool CanOpenBisWiper() => Has(Bits.ContentManager); + public bool CanOpenGameCardDetectionEventNotifier() => Has(Bits.DeviceDetection | Bits.GameCardRaw | Bits.GameCard); + public bool CanOpenGameCardStorageRead() => Has(Bits.GameCardRaw); + public bool CanOpenGameCardStorageWrite() => Has(Bits.GameCardRaw); + public bool CanOpenOwnSaveDataTransferProhibiter() => Has(Bits.CreateOwnSaveData); + public bool CanOpenSaveDataInfoReader() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); + public bool CanOpenSaveDataInfoReaderForInternal() => Has(Bits.SaveDataManagement); + public bool CanOpenSaveDataInfoReaderForSystem() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataBackUp); + public bool CanOpenSaveDataInternalStorageRead() => Has(Bits.None); + public bool CanOpenSaveDataInternalStorageWrite() => Has(Bits.None); + public bool CanOpenSaveDataMetaFile() => Has(Bits.SaveDataMeta); + public bool CanOpenSaveDataMover() => Has(Bits.MoveCacheStorage); + public bool CanOpenSaveDataTransferManager() => Has(Bits.SaveDataTransfer); + public bool CanOpenSaveDataTransferManagerForSaveDataRepair() => Has(Bits.SaveDataTransferVersion2); + public bool CanOpenSaveDataTransferManagerForSaveDataRepairTool() => Has(Bits.None); + public bool CanOpenSaveDataTransferManagerVersion2() => Has(Bits.SaveDataTransferVersion2); + public bool CanOpenSaveDataTransferProhibiter() => Has(Bits.SaveDataTransferVersion2 | Bits.CreateSaveData); + public bool CanOpenSdCardDetectionEventNotifier() => Has(Bits.DeviceDetection | Bits.SdCard); + public bool CanOpenSdCardStorageRead() => Has(Bits.Debug | Bits.SdCard); + public bool CanOpenSdCardStorageWrite() => Has(Bits.Debug | Bits.SdCard); + public bool CanOpenSystemDataUpdateEventNotifier() => Has(Bits.SystemData | Bits.SystemSaveData); + public bool CanOverrideSaveDataTransferTokenSignVerificationKey() => Has(Bits.None); + public bool CanQuerySaveDataInternalStorageTotalSize() => Has(Bits.SaveDataTransfer); + public bool CanReadOwnSaveDataFileSystemExtraData() => Has(Bits.CreateOwnSaveData); + public bool CanReadSaveDataFileSystemExtraData() => Has(Bits.SystemSaveDataManagement | Bits.SaveDataManagement | Bits.SaveDataBackUp); + public bool CanRegisterExternalKey() => Has(Bits.RegisterExternalKey); + public bool CanRegisterProgramIndexMapInfo() => Has(Bits.RegisterProgramIndexMapInfo); + public bool CanRegisterUpdatePartition() => Has(Bits.RegisterUpdatePartition); + public bool CanResolveAccessFailure() => Has(Bits.AccessFailureResolution); + public bool CanSetCurrentPosixTime() => Has(Bits.SetTime); + public bool CanSetEncryptionSeed() => Has(Bits.ContentManager); + public bool CanSetGlobalAccessLogMode() => Has(Bits.SettingsControl); + public bool CanSetSdCardAccessibility() => Has(Bits.SdCard); + public bool CanSetSpeedEmulationMode() => Has(Bits.SettingsControl); + public bool CanSimulateDevice() => Has(Bits.Debug); + public bool CanVerifySaveData() => Has(Bits.SaveDataManagement | Bits.SaveDataBackUp); + public bool CanWriteSaveDataFileSystemExtraDataAll() => Has(Bits.None); + public bool CanWriteSaveDataFileSystemExtraDataCommitId() => Has(Bits.SaveDataBackUp); + public bool CanWriteSaveDataFileSystemExtraDataFlags() => Has(Bits.SaveDataTransferVersion2 | Bits.SystemSaveDataManagement | Bits.SaveDataBackUp); + public bool CanWriteSaveDataFileSystemExtraDataTimeStamp() => Has(Bits.SaveDataBackUp); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x2C)] +internal struct AccessControlDescriptor +{ + [FieldOffset(0x00)] public byte Version; + [FieldOffset(0x01)] public byte ContentOwnerIdCount; + [FieldOffset(0x02)] public byte SaveDataOwnerIdCount; + [FieldOffset(0x04)] public ulong AccessFlags; + [FieldOffset(0x0C)] public ulong ContentOwnerIdMin; + [FieldOffset(0x14)] public ulong ContentOwnerIdMax; + [FieldOffset(0x1C)] public ulong SaveDataOwnerIdMin; + [FieldOffset(0x24)] public ulong SaveDataOwnerIdMax; + // public ulong ContentOwnerIds[ContentOwnerIdCount]; + // public ulong SaveDataOwnerIds[SaveDataOwnerIdCount]; +} + +[StructLayout(LayoutKind.Explicit, Size = 0x1C)] +internal struct AccessControlDataHeader +{ + [FieldOffset(0x00)] public byte Version; + [FieldOffset(0x04)] public ulong AccessFlags; + [FieldOffset(0x0C)] public int ContentOwnerInfoOffset; + [FieldOffset(0x10)] public int ContentOwnerInfoSize; + [FieldOffset(0x14)] public int SaveDataOwnerInfoOffset; + [FieldOffset(0x18)] public int SaveDataOwnerInfoSize; + + // [FieldOffset(ContentOwnerInfoOffset)] + // public int ContentOwnerInfoCount; + // public ulong ContentOwnerIds[ContentOwnerInfoCount]; + + // [FieldOffset(SaveDataOwnerInfoOffset)] + // public int SaveDataOwnerInfoCount; + // public byte Accessibilities[SaveDataOwnerInfoCount]; + // Next field is 4-byte aligned + // public byte SaveDataOwnerIds[SaveDataOwnerInfoCount]; +} + +public enum OperationType +{ + InvalidateBisCache, + EraseMmc, + GetGameCardDeviceCertificate, + GetGameCardIdSet, + FinalizeGameCardDriver, + GetGameCardAsicInfo, + CreateSaveData, + DeleteSaveData, + CreateSystemSaveData, + CreateOthersSystemSaveData, + DeleteSystemSaveData, + OpenSaveDataInfoReader, + OpenSaveDataInfoReaderForSystem, + OpenSaveDataInfoReaderForInternal, + OpenSaveDataMetaFile, + SetCurrentPosixTime, + ReadSaveDataFileSystemExtraData, + SetGlobalAccessLogMode, + SetSpeedEmulationMode, + Debug, + FillBis, + CorruptSaveData, + CorruptSystemSaveData, + VerifySaveData, + DebugSaveData, + FormatSdCard, + GetRightsId, + RegisterExternalKey, + SetEncryptionSeed, + WriteSaveDataFileSystemExtraDataTimeStamp, + WriteSaveDataFileSystemExtraDataFlags, + WriteSaveDataFileSystemExtraDataCommitId, + WriteSaveDataFileSystemExtraDataAll, + ExtendSaveData, + ExtendSystemSaveData, + ExtendOthersSystemSaveData, + RegisterUpdatePartition, + OpenSaveDataTransferManager, + OpenSaveDataTransferManagerVersion2, + OpenSaveDataTransferManagerForSaveDataRepair, + OpenSaveDataTransferManagerForSaveDataRepairTool, + OpenSaveDataTransferProhibiter, + OpenSaveDataMover, + OpenBisWiper, + ListAccessibleSaveDataOwnerId, + ControlMmcPatrol, + OverrideSaveDataTransferTokenSignVerificationKey, + OpenSdCardDetectionEventNotifier, + OpenGameCardDetectionEventNotifier, + OpenSystemDataUpdateEventNotifier, + NotifySystemDataUpdateEvent, + OpenAccessFailureDetectionEventNotifier, + GetAccessFailureDetectionEvent, + IsAccessFailureDetected, + ResolveAccessFailure, + AbandonAccessFailure, + QuerySaveDataInternalStorageTotalSize, + GetSaveDataCommitId, + SetSdCardAccessibility, + SimulateDevice, + CreateSaveDataWithHashSalt, + RegisterProgramIndexMapInfo, + ChallengeCardExistence, + CreateOwnSaveData, + DeleteOwnSaveData, + ReadOwnSaveDataFileSystemExtraData, + ExtendOwnSaveData, + OpenOwnSaveDataTransferProhibiter, + FindOwnSaveDataWithFilter +} + +public enum AccessibilityType +{ + MountLogo, + MountContentMeta, + MountContentControl, + MountContentManual, + MountContentData, + MountApplicationPackage, + MountSaveDataStorage, + MountContentStorage, + MountImageAndVideoStorage, + MountCloudBackupWorkStorage, + MountCustomStorage, + MountBisCalibrationFile, + MountBisSafeMode, + MountBisUser, + MountBisSystem, + MountBisSystemProperEncryption, + MountBisSystemProperPartition, + MountSdCard, + MountGameCard, + MountDeviceSaveData, + MountSystemSaveData, + MountOthersSaveData, + MountOthersSystemSaveData, + OpenBisPartitionBootPartition1Root, + OpenBisPartitionBootPartition2Root, + OpenBisPartitionUserDataRoot, + OpenBisPartitionBootConfigAndPackage2Part1, + OpenBisPartitionBootConfigAndPackage2Part2, + OpenBisPartitionBootConfigAndPackage2Part3, + OpenBisPartitionBootConfigAndPackage2Part4, + OpenBisPartitionBootConfigAndPackage2Part5, + OpenBisPartitionBootConfigAndPackage2Part6, + OpenBisPartitionCalibrationBinary, + OpenBisPartitionCalibrationFile, + OpenBisPartitionSafeMode, + OpenBisPartitionUser, + OpenBisPartitionSystem, + OpenBisPartitionSystemProperEncryption, + OpenBisPartitionSystemProperPartition, + OpenSdCardStorage, + OpenGameCardStorage, + MountSystemDataPrivate, + MountHost, + MountRegisteredUpdatePartition, + MountSaveDataInternalStorage, + MountTemporaryDirectory, + MountAllBaseFileSystem, + NotMount } diff --git a/src/LibHac/FsSrv/Impl/AccessFailureDetectionEventManager.cs b/src/LibHac/FsSrv/Impl/AccessFailureDetectionEventManager.cs index 95c400b9..1e429afa 100644 --- a/src/LibHac/FsSrv/Impl/AccessFailureDetectionEventManager.cs +++ b/src/LibHac/FsSrv/Impl/AccessFailureDetectionEventManager.cs @@ -3,38 +3,37 @@ using LibHac.Common; using LibHac.FsSrv.Sf; using LibHac.Svc; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public class AccessFailureDetectionEventManager { - public class AccessFailureDetectionEventManager + public Result CreateNotifier(ref UniqueRef notifier, ulong processId, bool notifyOnDeepRetry) { - public Result CreateNotifier(ref UniqueRef notifier, ulong processId, bool notifyOnDeepRetry) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public void NotifyAccessFailureDetection(ulong processId) - { - throw new NotImplementedException(); - } + public void NotifyAccessFailureDetection(ulong processId) + { + throw new NotImplementedException(); + } - public void ResetAccessFailureDetection(ulong processId) - { - throw new NotImplementedException(); - } + public void ResetAccessFailureDetection(ulong processId) + { + throw new NotImplementedException(); + } - public void DisableAccessFailureDetection(ulong processId) - { - throw new NotImplementedException(); - } + public void DisableAccessFailureDetection(ulong processId) + { + throw new NotImplementedException(); + } - public bool IsAccessFailureDetectionNotified(ulong processId) - { - throw new NotImplementedException(); - } + public bool IsAccessFailureDetectionNotified(ulong processId) + { + throw new NotImplementedException(); + } - public Handle GetEvent() - { - throw new NotImplementedException(); - } + public Handle GetEvent() + { + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs index 77fac6bd..0b78afd0 100644 --- a/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs @@ -3,19 +3,18 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.Impl -{ - public class AsynchronousAccessFileSystem : ForwardingFileSystem - { - public AsynchronousAccessFileSystem(ref SharedRef baseFileSystem) : base( - ref baseFileSystem) - { } +namespace LibHac.FsSrv.Impl; - // ReSharper disable once RedundantOverriddenMember - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - // Todo: Implement - return base.DoOpenFile(ref outFile, path, mode); - } +public class AsynchronousAccessFileSystem : ForwardingFileSystem +{ + public AsynchronousAccessFileSystem(ref SharedRef baseFileSystem) : base( + ref baseFileSystem) + { } + + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(ref outFile, path, mode); } } diff --git a/src/LibHac/FsSrv/Impl/BisWiper.cs b/src/LibHac/FsSrv/Impl/BisWiper.cs index 773dc7e4..24f97fe1 100644 --- a/src/LibHac/FsSrv/Impl/BisWiper.cs +++ b/src/LibHac/FsSrv/Impl/BisWiper.cs @@ -3,32 +3,31 @@ using LibHac.Common; using LibHac.FsSrv.Sf; using LibHac.Sf; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +internal class BisWiper : IWiper { - internal class BisWiper : IWiper + // ReSharper disable UnusedParameter.Local + public BisWiper(NativeHandle memoryHandle, ulong memorySize) { } + // ReSharper restore UnusedParameter.Local + + public Result Startup(out long spaceToWipe) { - // ReSharper disable UnusedParameter.Local - public BisWiper(NativeHandle memoryHandle, ulong memorySize) { } - // ReSharper restore UnusedParameter.Local + throw new NotImplementedException(); + } - public Result Startup(out long spaceToWipe) - { - throw new NotImplementedException(); - } + public Result Process(out long remainingSpaceToWipe) + { + throw new NotImplementedException(); + } - public Result Process(out long remainingSpaceToWipe) - { - throw new NotImplementedException(); - } + public static Result CreateWiper(ref UniqueRef outWiper, NativeHandle memoryHandle, ulong memorySize) + { + outWiper.Reset(new BisWiper(memoryHandle, memorySize)); + return Result.Success; + } - public static Result CreateWiper(ref UniqueRef outWiper, NativeHandle memoryHandle, ulong memorySize) - { - outWiper.Reset(new BisWiper(memoryHandle, memorySize)); - return Result.Success; - } - - public void Dispose() - { - } + public void Dispose() + { } } diff --git a/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs index 8e8f464f..04f088a9 100644 --- a/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs @@ -3,42 +3,41 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public class DeepRetryFileSystem : ForwardingFileSystem { - public class DeepRetryFileSystem : ForwardingFileSystem + // ReSharper disable once NotAccessedField.Local + private WeakRef _selfReference; + private SharedRef _accessFailureManager; + + protected DeepRetryFileSystem(ref SharedRef baseFileSystem, + ref SharedRef accessFailureManager) : base(ref baseFileSystem) { - // ReSharper disable once NotAccessedField.Local - private WeakRef _selfReference; - private SharedRef _accessFailureManager; + _accessFailureManager = SharedRef.CreateMove(ref accessFailureManager); + } - protected DeepRetryFileSystem(ref SharedRef baseFileSystem, - ref SharedRef accessFailureManager) : base(ref baseFileSystem) - { - _accessFailureManager = SharedRef.CreateMove(ref accessFailureManager); - } + public static SharedRef CreateShared(ref SharedRef baseFileSystem, + ref SharedRef accessFailureManager) + { + using var retryFileSystem = new SharedRef( + new DeepRetryFileSystem(ref baseFileSystem, ref accessFailureManager)); - public static SharedRef CreateShared(ref SharedRef baseFileSystem, - ref SharedRef accessFailureManager) - { - using var retryFileSystem = new SharedRef( - new DeepRetryFileSystem(ref baseFileSystem, ref accessFailureManager)); + retryFileSystem.Get._selfReference.Set(in retryFileSystem.Ref()); - retryFileSystem.Get._selfReference.Set(in retryFileSystem.Ref()); + return SharedRef.CreateMove(ref retryFileSystem.Ref()); + } - return SharedRef.CreateMove(ref retryFileSystem.Ref()); - } + public override void Dispose() + { + _accessFailureManager.Destroy(); + base.Dispose(); + } - public override void Dispose() - { - _accessFailureManager.Destroy(); - base.Dispose(); - } - - // ReSharper disable once RedundantOverriddenMember - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - // Todo: Implement - return base.DoOpenFile(ref outFile, path, mode); - } + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(ref outFile, path, mode); } } diff --git a/src/LibHac/FsSrv/Impl/DeviceEventSimulationStorage.cs b/src/LibHac/FsSrv/Impl/DeviceEventSimulationStorage.cs index a230dd98..34d5e886 100644 --- a/src/LibHac/FsSrv/Impl/DeviceEventSimulationStorage.cs +++ b/src/LibHac/FsSrv/Impl/DeviceEventSimulationStorage.cs @@ -2,64 +2,63 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +/// +/// An for simulating device failures +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class DeviceEventSimulationStorage : IStorage { - /// - /// An for simulating device failures - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class DeviceEventSimulationStorage : IStorage + private SharedRef _baseStorage; + private IDeviceEventSimulator _eventSimulator; + + public DeviceEventSimulationStorage(ref SharedRef baseStorage, IDeviceEventSimulator eventSimulator) { - private SharedRef _baseStorage; - private IDeviceEventSimulator _eventSimulator; + _baseStorage = SharedRef.CreateMove(ref baseStorage); + _eventSimulator = eventSimulator; + } - public DeviceEventSimulationStorage(ref SharedRef baseStorage, IDeviceEventSimulator eventSimulator) - { - _baseStorage = SharedRef.CreateMove(ref baseStorage); - _eventSimulator = eventSimulator; - } + public override void Dispose() + { + _baseStorage.Destroy(); + base.Dispose(); + } - public override void Dispose() - { - _baseStorage.Destroy(); - base.Dispose(); - } + protected override Result DoRead(long offset, Span destination) + { + Result rc = _eventSimulator.CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation.Read); + if (rc.IsFailure()) return rc; - protected override Result DoRead(long offset, Span destination) - { - Result rc = _eventSimulator.CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation.Read); - if (rc.IsFailure()) return rc; + return _baseStorage.Get.Read(offset, destination); + } - return _baseStorage.Get.Read(offset, destination); - } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + Result rc = _eventSimulator.CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation.Write); + if (rc.IsFailure()) return rc; - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - Result rc = _eventSimulator.CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation.Write); - if (rc.IsFailure()) return rc; + return _baseStorage.Get.Write(offset, source); + } - return _baseStorage.Get.Write(offset, source); - } + protected override Result DoFlush() + { + return _baseStorage.Get.Flush(); + } - protected override Result DoFlush() - { - return _baseStorage.Get.Flush(); - } + protected override Result DoSetSize(long size) + { + return _baseStorage.Get.SetSize(size); + } - protected override Result DoSetSize(long size) - { - return _baseStorage.Get.SetSize(size); - } + protected override Result DoGetSize(out long size) + { + return _baseStorage.Get.GetSize(out size); + } - protected override Result DoGetSize(out long size) - { - return _baseStorage.Get.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); } } diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 89e86b5d..41a4b1c2 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -15,583 +15,582 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using PathSf = LibHac.FsSrv.Sf.Path; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +/// +/// Wraps an to allow interfacing with it via the interface over IPC. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public class FileInterfaceAdapter : IFileSf { - /// - /// Wraps an to allow interfacing with it via the interface over IPC. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public class FileInterfaceAdapter : IFileSf + private SharedRef _parentFs; + private UniqueRef _baseFile; + private bool _allowAllOperations; + + public FileInterfaceAdapter(ref UniqueRef baseFile, + ref SharedRef parentFileSystem, bool allowAllOperations) { - private SharedRef _parentFs; - private UniqueRef _baseFile; - private bool _allowAllOperations; + _parentFs = SharedRef.CreateMove(ref parentFileSystem); + _baseFile = new UniqueRef(ref baseFile); + _allowAllOperations = allowAllOperations; + } - public FileInterfaceAdapter(ref UniqueRef baseFile, - ref SharedRef parentFileSystem, bool allowAllOperations) + public void Dispose() + { + _baseFile.Destroy(); + _parentFs.Destroy(); + } + + public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out bytesRead); + + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (destination.Size < 0 || destination.Size < (int)size) + return ResultFs.InvalidSize.Log(); + + Result rc = Result.Success; + long tmpBytesRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) { - _parentFs = SharedRef.CreateMove(ref parentFileSystem); - _baseFile = new UniqueRef(ref baseFile); - _allowAllOperations = allowAllOperations; + rc = _baseFile.Get.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; } - public void Dispose() + if (rc.IsFailure()) return rc; + + bytesRead = tmpBytesRead; + return Result.Success; + } + + public Result Write(long offset, InBuffer source, long size, WriteOption option) + { + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (source.Size < 0 || source.Size < (int)size) + return ResultFs.InvalidSize.Log(); + + using var scopedPriorityChanger = + new ScopedThreadPriorityChangerByAccessPriority(ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write); + + return _baseFile.Get.Write(offset, source.Buffer.Slice(0, (int)size), option); + } + + public Result Flush() + { + return _baseFile.Get.Flush(); + } + + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + return _baseFile.Get.SetSize(size); + } + + public Result GetSize(out long size) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out size); + + Result rc = Result.Success; + long tmpSize = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) { - _baseFile.Destroy(); - _parentFs.Destroy(); + rc = _baseFile.Get.GetSize(out tmpSize); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; } - public Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option) + if (rc.IsFailure()) return rc; + + size = tmpSize; + return Result.Success; + } + + public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + UnsafeHelpers.SkipParamInit(out rangeInfo); + rangeInfo.Clear(); + + if (operationId == (int)OperationId.QueryRange) { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out bytesRead); - - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (destination.Size < 0 || destination.Size < (int)size) - return ResultFs.InvalidSize.Log(); - - Result rc = Result.Success; - long tmpBytesRead = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseFile.Get.Read(out tmpBytesRead, offset, destination.Buffer.Slice(0, (int)size), option); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } + Unsafe.SkipInit(out QueryRangeInfo info); + Result rc = _baseFile.Get.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset, + size, ReadOnlySpan.Empty); if (rc.IsFailure()) return rc; - bytesRead = tmpBytesRead; - return Result.Success; + rangeInfo.Merge(in info); } - - public Result Write(long offset, InBuffer source, long size, WriteOption option) + else if (operationId == (int)OperationId.InvalidateCache) { - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (source.Size < 0 || source.Size < (int)size) - return ResultFs.InvalidSize.Log(); - - using var scopedPriorityChanger = - new ScopedThreadPriorityChangerByAccessPriority(ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write); - - return _baseFile.Get.Write(offset, source.Buffer.Slice(0, (int)size), option); - } - - public Result Flush() - { - return _baseFile.Get.Flush(); - } - - public Result SetSize(long size) - { - if (size < 0) - return ResultFs.InvalidSize.Log(); - - return _baseFile.Get.SetSize(size); - } - - public Result GetSize(out long size) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out size); - - Result rc = Result.Success; - long tmpSize = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseFile.Get.GetSize(out tmpSize); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - + Result rc = _baseFile.Get.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); if (rc.IsFailure()) return rc; - - size = tmpSize; - return Result.Success; } - public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + return Result.Success; + } + + public Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size) + { + static Result PermissionCheck(OperationId operationId, FileInterfaceAdapter fileAdapter) { - UnsafeHelpers.SkipParamInit(out rangeInfo); - rangeInfo.Clear(); - - if (operationId == (int)OperationId.QueryRange) + if (operationId == OperationId.QueryUnpreparedRange || + operationId == OperationId.QueryLazyLoadCompletionRate || + operationId == OperationId.SetLazyLoadPriority) { - Unsafe.SkipInit(out QueryRangeInfo info); - - Result rc = _baseFile.Get.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset, - size, ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - - rangeInfo.Merge(in info); - } - else if (operationId == (int)OperationId.InvalidateCache) - { - Result rc = _baseFile.Get.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, - ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - public Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size) - { - static Result PermissionCheck(OperationId operationId, FileInterfaceAdapter fileAdapter) - { - if (operationId == OperationId.QueryUnpreparedRange || - operationId == OperationId.QueryLazyLoadCompletionRate || - operationId == OperationId.SetLazyLoadPriority) - { - return Result.Success; - } - - if (!fileAdapter._allowAllOperations) - return ResultFs.PermissionDenied.Log(); - return Result.Success; } - Result rc = PermissionCheck((OperationId)operationId, this); - if (rc.IsFailure()) return rc; - - rc = _baseFile.Get.OperateRange(outBuffer.Buffer, (OperationId)operationId, offset, size, inBuffer.Buffer); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - /// - /// Wraps an to allow interfacing with it via the interface over IPC. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public class DirectoryInterfaceAdapter : IDirectorySf - { - private SharedRef _parentFs; - private UniqueRef _baseDirectory; - - public DirectoryInterfaceAdapter(ref UniqueRef baseDirectory, - ref SharedRef parentFileSystem) - { - _parentFs = SharedRef.CreateMove(ref parentFileSystem); - _baseDirectory = new UniqueRef(ref baseDirectory); - } - - public void Dispose() - { - _baseDirectory.Destroy(); - _parentFs.Destroy(); - } - - public Result Read(out long entriesRead, OutBuffer entryBuffer) - { - const int maxTryCount = 2; - UnsafeHelpers.SkipParamInit(out entriesRead); - - Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); - - Result rc = Result.Success; - long numRead = 0; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseDirectory.Get.Read(out numRead, entries); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - entriesRead = numRead; - return Result.Success; - } - - public Result GetEntryCount(out long entryCount) - { - UnsafeHelpers.SkipParamInit(out entryCount); - - Result rc = _baseDirectory.Get.GetEntryCount(out long count); - if (rc.IsFailure()) return rc; - - entryCount = count; - return Result.Success; - } - } - - /// - /// Wraps an to allow interfacing with it via the interface over IPC. - /// All incoming paths are normalized before they are passed to the base . - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public class FileSystemInterfaceAdapter : IFileSystemSf - { - private SharedRef _baseFileSystem; - private PathFlags _pathFlags; - - // This field is always false in FS 12.0.0. Not sure what it's actually used for. - private bool _allowAllOperations; - - // In FS, FileSystemInterfaceAdapter is derived from ISharedObject, so that's used for ref-counting when - // creating files and directories. We don't have an ISharedObject, so a self-reference is used instead. - private WeakRef _selfReference; - - private FileSystemInterfaceAdapter(ref SharedRef fileSystem, - bool allowAllOperations) - { - _baseFileSystem = SharedRef.CreateMove(ref fileSystem); - _allowAllOperations = allowAllOperations; - } - - private FileSystemInterfaceAdapter(ref SharedRef fileSystem, PathFlags flags, - bool allowAllOperations) - { - _baseFileSystem = SharedRef.CreateMove(ref fileSystem); - _pathFlags = flags; - _allowAllOperations = allowAllOperations; - } - - public static SharedRef CreateShared(ref SharedRef baseFileSystem, bool allowAllOperations) - { - var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, allowAllOperations); - using var sharedAdapter = new SharedRef(adapter); - - adapter._selfReference.Set(in sharedAdapter); - - return SharedRef.CreateMove(ref sharedAdapter.Ref()); - } - - public static SharedRef CreateShared( - ref SharedRef baseFileSystem, PathFlags flags, bool allowAllOperations) - { - var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, flags, allowAllOperations); - using var sharedAdapter = new SharedRef(adapter); - - adapter._selfReference.Set(in sharedAdapter); - - return SharedRef.CreateMove(ref sharedAdapter.Ref()); - } - - public void Dispose() - { - _baseFileSystem.Destroy(); - } - - private static ReadOnlySpan RootDir => new[] { (byte)'/' }; - - private Result SetUpPath(ref Path fsPath, in PathSf sfPath) - { - Result rc; - - if (_pathFlags.IsWindowsPathAllowed()) - { - rc = fsPath.InitializeWithReplaceUnc(sfPath.Str); - if (rc.IsFailure()) return rc; - } - else - { - rc = fsPath.Initialize(sfPath.Str); - if (rc.IsFailure()) return rc; - } - - rc = fsPath.Normalize(_pathFlags); - if (rc.IsFailure()) return rc; + if (!fileAdapter._allowAllOperations) + return ResultFs.PermissionDenied.Log(); return Result.Success; } - public Result CreateFile(in PathSf path, long size, int option) - { - if (size < 0) - return ResultFs.InvalidSize.Log(); + Result rc = PermissionCheck((OperationId)operationId, this); + if (rc.IsFailure()) return rc; - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFile.Get.OperateRange(outBuffer.Buffer, (OperationId)operationId, offset, size, inBuffer.Buffer); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.Get.CreateFile(in pathNormalized, size, (CreateFileOptions)option); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result DeleteFile(in PathSf path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.DeleteFile(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result CreateDirectory(in PathSf path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - if (pathNormalized == RootDir) - return ResultFs.PathAlreadyExists.Log(); - - rc = _baseFileSystem.Get.CreateDirectory(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result DeleteDirectory(in PathSf path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - if (pathNormalized == RootDir) - return ResultFs.DirectoryNotDeletable.Log(); - - rc = _baseFileSystem.Get.DeleteDirectory(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result DeleteDirectoryRecursively(in PathSf path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - if (pathNormalized == RootDir) - return ResultFs.DirectoryNotDeletable.Log(); - - rc = _baseFileSystem.Get.DeleteDirectoryRecursively(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result CleanDirectoryRecursively(in PathSf path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.CleanDirectoryRecursively(in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result RenameFile(in PathSf currentPath, in PathSf newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), in currentPath); - if (rc.IsFailure()) return rc; - - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), in newPath); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.RenameFile(in currentPathNormalized, in newPathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result RenameDirectory(in PathSf currentPath, in PathSf newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), in currentPath); - if (rc.IsFailure()) return rc; - - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), in newPath); - if (rc.IsFailure()) return rc; - - if (PathUtility.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString())) - return ResultFs.DirectoryNotRenamable.Log(); - - rc = _baseFileSystem.Get.RenameDirectory(in currentPathNormalized, in newPathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result GetEntryType(out uint entryType, in PathSf path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in pathNormalized); - if (rc.IsFailure()) return rc; - - entryType = (uint)type; - return Result.Success; - } - - public Result GetFreeSpaceSize(out long freeSpace, in PathSf path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.GetFreeSpaceSize(out long space, in pathNormalized); - if (rc.IsFailure()) return rc; - - freeSpace = space; - return Result.Success; - } - - public Result GetTotalSpaceSize(out long totalSpace, in PathSf path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.GetTotalSpaceSize(out long space, in pathNormalized); - if (rc.IsFailure()) return rc; - - totalSpace = space; - return Result.Success; - } - - public Result OpenFile(ref SharedRef outFile, in PathSf path, uint mode) - { - const int maxTryCount = 2; - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(); - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseFileSystem.Get.OpenFile(ref file.Ref(), in pathNormalized, (OpenMode)mode); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - using SharedRef selfReference = - SharedRef.Create(in _selfReference); - - var adapter = new FileInterfaceAdapter(ref file.Ref(), ref selfReference.Ref(), _allowAllOperations); - outFile.Reset(adapter); - - return Result.Success; - } - - public Result OpenDirectory(ref SharedRef outDirectory, in PathSf path, uint mode) - { - const int maxTryCount = 2; - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - using var directory = new UniqueRef(); - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseFileSystem.Get.OpenDirectory(ref directory.Ref(), in pathNormalized, - (OpenDirectoryMode)mode); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - if (rc.IsFailure()) return rc; - - using SharedRef selfReference = - SharedRef.Create(in _selfReference); - - var adapter = new DirectoryInterfaceAdapter(ref directory.Ref(), ref selfReference.Ref()); - outDirectory.Reset(adapter); - - return Result.Success; - } - - public Result Commit() - { - return _baseFileSystem.Get.Commit(); - } - - public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in PathSf path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.GetFileTimeStampRaw(out FileTimeStampRaw tempTimeStamp, in pathNormalized); - if (rc.IsFailure()) return rc; - - timeStamp = tempTimeStamp; - return Result.Success; - } - - public Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in PathSf path) - { - static Result PermissionCheck(QueryId queryId, FileSystemInterfaceAdapter fsAdapter) - { - if (queryId == QueryId.SetConcatenationFileAttribute || - queryId == QueryId.IsSignedSystemPartitionOnSdCardValid || - queryId == QueryId.QueryUnpreparedFileInformation) - { - return Result.Success; - } - - if (!fsAdapter._allowAllOperations) - return ResultFs.PermissionDenied.Log(); - - return Result.Success; - } - - Result rc = PermissionCheck((QueryId)queryId, this); - if (rc.IsFailure()) return rc; - - using var pathNormalized = new Path(); - rc = SetUpPath(ref pathNormalized.Ref(), in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.Get.QueryEntry(outBuffer.Buffer, inBuffer.Buffer, (QueryId)queryId, - in pathNormalized); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result GetImpl(ref SharedRef fileSystem) - { - fileSystem.SetByCopy(in _baseFileSystem); - return Result.Success; - } + return Result.Success; + } +} + +/// +/// Wraps an to allow interfacing with it via the interface over IPC. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public class DirectoryInterfaceAdapter : IDirectorySf +{ + private SharedRef _parentFs; + private UniqueRef _baseDirectory; + + public DirectoryInterfaceAdapter(ref UniqueRef baseDirectory, + ref SharedRef parentFileSystem) + { + _parentFs = SharedRef.CreateMove(ref parentFileSystem); + _baseDirectory = new UniqueRef(ref baseDirectory); + } + + public void Dispose() + { + _baseDirectory.Destroy(); + _parentFs.Destroy(); + } + + public Result Read(out long entriesRead, OutBuffer entryBuffer) + { + const int maxTryCount = 2; + UnsafeHelpers.SkipParamInit(out entriesRead); + + Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); + + Result rc = Result.Success; + long numRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseDirectory.Get.Read(out numRead, entries); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + entriesRead = numRead; + return Result.Success; + } + + public Result GetEntryCount(out long entryCount) + { + UnsafeHelpers.SkipParamInit(out entryCount); + + Result rc = _baseDirectory.Get.GetEntryCount(out long count); + if (rc.IsFailure()) return rc; + + entryCount = count; + return Result.Success; + } +} + +/// +/// Wraps an to allow interfacing with it via the interface over IPC. +/// All incoming paths are normalized before they are passed to the base . +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public class FileSystemInterfaceAdapter : IFileSystemSf +{ + private SharedRef _baseFileSystem; + private PathFlags _pathFlags; + + // This field is always false in FS 12.0.0. Not sure what it's actually used for. + private bool _allowAllOperations; + + // In FS, FileSystemInterfaceAdapter is derived from ISharedObject, so that's used for ref-counting when + // creating files and directories. We don't have an ISharedObject, so a self-reference is used instead. + private WeakRef _selfReference; + + private FileSystemInterfaceAdapter(ref SharedRef fileSystem, + bool allowAllOperations) + { + _baseFileSystem = SharedRef.CreateMove(ref fileSystem); + _allowAllOperations = allowAllOperations; + } + + private FileSystemInterfaceAdapter(ref SharedRef fileSystem, PathFlags flags, + bool allowAllOperations) + { + _baseFileSystem = SharedRef.CreateMove(ref fileSystem); + _pathFlags = flags; + _allowAllOperations = allowAllOperations; + } + + public static SharedRef CreateShared(ref SharedRef baseFileSystem, bool allowAllOperations) + { + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, allowAllOperations); + using var sharedAdapter = new SharedRef(adapter); + + adapter._selfReference.Set(in sharedAdapter); + + return SharedRef.CreateMove(ref sharedAdapter.Ref()); + } + + public static SharedRef CreateShared( + ref SharedRef baseFileSystem, PathFlags flags, bool allowAllOperations) + { + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, flags, allowAllOperations); + using var sharedAdapter = new SharedRef(adapter); + + adapter._selfReference.Set(in sharedAdapter); + + return SharedRef.CreateMove(ref sharedAdapter.Ref()); + } + + public void Dispose() + { + _baseFileSystem.Destroy(); + } + + private static ReadOnlySpan RootDir => new[] { (byte)'/' }; + + private Result SetUpPath(ref Path fsPath, in PathSf sfPath) + { + Result rc; + + if (_pathFlags.IsWindowsPathAllowed()) + { + rc = fsPath.InitializeWithReplaceUnc(sfPath.Str); + if (rc.IsFailure()) return rc; + } + else + { + rc = fsPath.Initialize(sfPath.Str); + if (rc.IsFailure()) return rc; + } + + rc = fsPath.Normalize(_pathFlags); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CreateFile(in PathSf path, long size, int option) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.CreateFile(in pathNormalized, size, (CreateFileOptions)option); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result DeleteFile(in PathSf path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.DeleteFile(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CreateDirectory(in PathSf path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.PathAlreadyExists.Log(); + + rc = _baseFileSystem.Get.CreateDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result DeleteDirectory(in PathSf path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.DirectoryNotDeletable.Log(); + + rc = _baseFileSystem.Get.DeleteDirectory(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result DeleteDirectoryRecursively(in PathSf path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + if (pathNormalized == RootDir) + return ResultFs.DirectoryNotDeletable.Log(); + + rc = _baseFileSystem.Get.DeleteDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result CleanDirectoryRecursively(in PathSf path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.CleanDirectoryRecursively(in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result RenameFile(in PathSf currentPath, in PathSf newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), in currentPath); + if (rc.IsFailure()) return rc; + + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), in newPath); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.RenameFile(in currentPathNormalized, in newPathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result RenameDirectory(in PathSf currentPath, in PathSf newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), in currentPath); + if (rc.IsFailure()) return rc; + + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), in newPath); + if (rc.IsFailure()) return rc; + + if (PathUtility.IsSubPath(currentPathNormalized.GetString(), newPathNormalized.GetString())) + return ResultFs.DirectoryNotRenamable.Log(); + + rc = _baseFileSystem.Get.RenameDirectory(in currentPathNormalized, in newPathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result GetEntryType(out uint entryType, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in pathNormalized); + if (rc.IsFailure()) return rc; + + entryType = (uint)type; + return Result.Success; + } + + public Result GetFreeSpaceSize(out long freeSpace, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.GetFreeSpaceSize(out long space, in pathNormalized); + if (rc.IsFailure()) return rc; + + freeSpace = space; + return Result.Success; + } + + public Result GetTotalSpaceSize(out long totalSpace, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.GetTotalSpaceSize(out long space, in pathNormalized); + if (rc.IsFailure()) return rc; + + totalSpace = space; + return Result.Success; + } + + public Result OpenFile(ref SharedRef outFile, in PathSf path, uint mode) + { + const int maxTryCount = 2; + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(); + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFileSystem.Get.OpenFile(ref file.Ref(), in pathNormalized, (OpenMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + using SharedRef selfReference = + SharedRef.Create(in _selfReference); + + var adapter = new FileInterfaceAdapter(ref file.Ref(), ref selfReference.Ref(), _allowAllOperations); + outFile.Reset(adapter); + + return Result.Success; + } + + public Result OpenDirectory(ref SharedRef outDirectory, in PathSf path, uint mode) + { + const int maxTryCount = 2; + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + using var directory = new UniqueRef(); + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = _baseFileSystem.Get.OpenDirectory(ref directory.Ref(), in pathNormalized, + (OpenDirectoryMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + using SharedRef selfReference = + SharedRef.Create(in _selfReference); + + var adapter = new DirectoryInterfaceAdapter(ref directory.Ref(), ref selfReference.Ref()); + outDirectory.Reset(adapter); + + return Result.Success; + } + + public Result Commit() + { + return _baseFileSystem.Get.Commit(); + } + + public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in PathSf path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.GetFileTimeStampRaw(out FileTimeStampRaw tempTimeStamp, in pathNormalized); + if (rc.IsFailure()) return rc; + + timeStamp = tempTimeStamp; + return Result.Success; + } + + public Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in PathSf path) + { + static Result PermissionCheck(QueryId queryId, FileSystemInterfaceAdapter fsAdapter) + { + if (queryId == QueryId.SetConcatenationFileAttribute || + queryId == QueryId.IsSignedSystemPartitionOnSdCardValid || + queryId == QueryId.QueryUnpreparedFileInformation) + { + return Result.Success; + } + + if (!fsAdapter._allowAllOperations) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + Result rc = PermissionCheck((QueryId)queryId, this); + if (rc.IsFailure()) return rc; + + using var pathNormalized = new Path(); + rc = SetUpPath(ref pathNormalized.Ref(), in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.Get.QueryEntry(outBuffer.Buffer, inBuffer.Buffer, (QueryId)queryId, + in pathNormalized); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result GetImpl(ref SharedRef fileSystem) + { + fileSystem.SetByCopy(in _baseFileSystem); + return Result.Success; } } diff --git a/src/LibHac/FsSrv/Impl/FileSystemProxyServiceObject.cs b/src/LibHac/FsSrv/Impl/FileSystemProxyServiceObject.cs index 96a26480..7c5f4933 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemProxyServiceObject.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemProxyServiceObject.cs @@ -4,82 +4,81 @@ using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public static class FileSystemProxyServiceObject { - public static class FileSystemProxyServiceObject + public static SharedRef GetFileSystemProxyServiceObject(this FileSystemServerImpl fsSrv) { - public static SharedRef GetFileSystemProxyServiceObject(this FileSystemServerImpl fsSrv) + return new SharedRef(new FileSystemProxyImpl(fsSrv.FsSrv)); + } + + public static SharedRef GetFileSystemProxyForLoaderServiceObject( + this FileSystemServerImpl fsSrv) + { + return new SharedRef(new FileSystemProxyImpl(fsSrv.FsSrv)); + } + + public static SharedRef GetInvalidFileSystemProxyForLoaderServiceObject( + this FileSystemServerImpl fsSrv) + { + return new SharedRef(new InvalidFileSystemProxyImplForLoader()); + } + + public static SharedRef GetProgramRegistryServiceObject(this FileSystemServerImpl fsSrv) + { + return new SharedRef(new ProgramRegistryImpl(fsSrv.FsSrv)); + } + + public static SharedRef GetInvalidProgramRegistryServiceObject( + this FileSystemServerImpl fsSrv) + { + return new SharedRef(new InvalidProgramRegistryImpl()); + } + + private class InvalidFileSystemProxyImplForLoader : IFileSystemProxyForLoader + { + public void Dispose() { } + + public Result IsArchivedProgram(out bool isArchived, ulong processId) { - return new SharedRef(new FileSystemProxyImpl(fsSrv.FsSrv)); + UnsafeHelpers.SkipParamInit(out isArchived); + + return ResultFs.PortAcceptableCountLimited.Log(); } - public static SharedRef GetFileSystemProxyForLoaderServiceObject( - this FileSystemServerImpl fsSrv) + public Result OpenCodeFileSystem(ref SharedRef fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId) { - return new SharedRef(new FileSystemProxyImpl(fsSrv.FsSrv)); + UnsafeHelpers.SkipParamInit(out verificationData); + + return ResultFs.PortAcceptableCountLimited.Log(); } - public static SharedRef GetInvalidFileSystemProxyForLoaderServiceObject( - this FileSystemServerImpl fsSrv) + public Result SetCurrentProcess(ulong processId) { - return new SharedRef(new InvalidFileSystemProxyImplForLoader()); + return ResultFs.PortAcceptableCountLimited.Log(); + } + } + + private class InvalidProgramRegistryImpl : IProgramRegistry + { + public void Dispose() { } + + public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + InBuffer accessControlData, InBuffer accessControlDescriptor) + { + return ResultFs.PortAcceptableCountLimited.Log(); } - public static SharedRef GetProgramRegistryServiceObject(this FileSystemServerImpl fsSrv) + public Result SetCurrentProcess(ulong processId) { - return new SharedRef(new ProgramRegistryImpl(fsSrv.FsSrv)); + return ResultFs.PortAcceptableCountLimited.Log(); } - public static SharedRef GetInvalidProgramRegistryServiceObject( - this FileSystemServerImpl fsSrv) + public Result UnregisterProgram(ulong processId) { - return new SharedRef(new InvalidProgramRegistryImpl()); - } - - private class InvalidFileSystemProxyImplForLoader : IFileSystemProxyForLoader - { - public void Dispose() { } - - public Result IsArchivedProgram(out bool isArchived, ulong processId) - { - UnsafeHelpers.SkipParamInit(out isArchived); - - return ResultFs.PortAcceptableCountLimited.Log(); - } - - public Result OpenCodeFileSystem(ref SharedRef fileSystem, - out CodeVerificationData verificationData, in FspPath path, ProgramId programId) - { - UnsafeHelpers.SkipParamInit(out verificationData); - - return ResultFs.PortAcceptableCountLimited.Log(); - } - - public Result SetCurrentProcess(ulong processId) - { - return ResultFs.PortAcceptableCountLimited.Log(); - } - } - - private class InvalidProgramRegistryImpl : IProgramRegistry - { - public void Dispose() { } - - public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - InBuffer accessControlData, InBuffer accessControlDescriptor) - { - return ResultFs.PortAcceptableCountLimited.Log(); - } - - public Result SetCurrentProcess(ulong processId) - { - return ResultFs.PortAcceptableCountLimited.Log(); - } - - public Result UnregisterProgram(ulong processId) - { - return ResultFs.PortAcceptableCountLimited.Log(); - } + return ResultFs.PortAcceptableCountLimited.Log(); } } } diff --git a/src/LibHac/FsSrv/Impl/IDeviceEventSimulator.cs b/src/LibHac/FsSrv/Impl/IDeviceEventSimulator.cs index affe66fa..6c5a505d 100644 --- a/src/LibHac/FsSrv/Impl/IDeviceEventSimulator.cs +++ b/src/LibHac/FsSrv/Impl/IDeviceEventSimulator.cs @@ -2,185 +2,184 @@ using LibHac.Fs; using LibHac.Os; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +internal struct DeviceEventSimulatorGlobals { - internal struct DeviceEventSimulatorGlobals + public GameCardEventSimulator GameCardEventSimulator; + public SdCardEventSimulator SdCardEventSimulator; + public nint GameCardEventSimulatorInit; + public nint SdCardEventSimulatorInit; +} + +internal static class DeviceEventSimulatorGlobalMethods +{ + public static SdCardEventSimulator GetSdCardEventSimulator(this FileSystemServerImpl fs) { - public GameCardEventSimulator GameCardEventSimulator; - public SdCardEventSimulator SdCardEventSimulator; - public nint GameCardEventSimulatorInit; - public nint SdCardEventSimulatorInit; - } + ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator; + using var guard = new InitializationGuard(ref g.SdCardEventSimulatorInit, fs.Globals.InitMutex); - internal static class DeviceEventSimulatorGlobalMethods - { - public static SdCardEventSimulator GetSdCardEventSimulator(this FileSystemServerImpl fs) - { - ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator; - using var guard = new InitializationGuard(ref g.SdCardEventSimulatorInit, fs.Globals.InitMutex); - - if (guard.IsInitialized) - return g.SdCardEventSimulator; - - g.SdCardEventSimulator = new SdCardEventSimulator(fs.Hos.Os); + if (guard.IsInitialized) return g.SdCardEventSimulator; - } - public static GameCardEventSimulator GetGameCardEventSimulator(this FileSystemServerImpl fs) - { - ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator; - using var guard = new InitializationGuard(ref g.GameCardEventSimulatorInit, fs.Globals.InitMutex); + g.SdCardEventSimulator = new SdCardEventSimulator(fs.Hos.Os); + return g.SdCardEventSimulator; + } - if (guard.IsInitialized) - return g.GameCardEventSimulator; + public static GameCardEventSimulator GetGameCardEventSimulator(this FileSystemServerImpl fs) + { + ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator; + using var guard = new InitializationGuard(ref g.GameCardEventSimulatorInit, fs.Globals.InitMutex); - g.GameCardEventSimulator = new GameCardEventSimulator(fs.Hos.Os); + if (guard.IsInitialized) return g.GameCardEventSimulator; - } + + g.GameCardEventSimulator = new GameCardEventSimulator(fs.Hos.Os); + return g.GameCardEventSimulator; + } +} + +// ReSharper disable once InconsistentNaming +public abstract class IDeviceEventSimulator +{ + private bool _isEventSet; + private bool _isDetectionSimulationEnabled; + private SdkRecursiveMutex _mutex; + private SimulatingDeviceDetectionMode _detectionSimulationMode; + private SimulatingDeviceAccessFailureEventType _simulatedFailureType; + private SimulatingDeviceTargetOperation _simulatedOperation; + private Result _failureResult; + private bool _isRecurringEvent; + private int _timeoutLengthMs; + + private OsState _os; + + public IDeviceEventSimulator(OsState os, int timeoutMs) + { + _os = os; + _timeoutLengthMs = timeoutMs; + _mutex = new SdkRecursiveMutex(); } - // ReSharper disable once InconsistentNaming - public abstract class IDeviceEventSimulator + public virtual Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) { - private bool _isEventSet; - private bool _isDetectionSimulationEnabled; - private SdkRecursiveMutex _mutex; - private SimulatingDeviceDetectionMode _detectionSimulationMode; - private SimulatingDeviceAccessFailureEventType _simulatedFailureType; - private SimulatingDeviceTargetOperation _simulatedOperation; - private Result _failureResult; - private bool _isRecurringEvent; - private int _timeoutLengthMs; + return Result.Success; + } - private OsState _os; + public void SetDeviceEvent(SimulatingDeviceTargetOperation operation, + SimulatingDeviceAccessFailureEventType failureType, Result failureResult, bool isRecurringEvent) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - public IDeviceEventSimulator(OsState os, int timeoutMs) - { - _os = os; - _timeoutLengthMs = timeoutMs; - _mutex = new SdkRecursiveMutex(); - } + if (failureResult.IsFailure()) + _failureResult = failureResult; - public virtual Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) - { + _isEventSet = true; + _simulatedFailureType = failureType; + _simulatedOperation = operation; + _isRecurringEvent = isRecurringEvent; + } + + public void ClearDeviceEvent() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _isEventSet = false; + _simulatedFailureType = SimulatingDeviceAccessFailureEventType.None; + _simulatedOperation = SimulatingDeviceTargetOperation.None; + _failureResult = Result.Success; + _isRecurringEvent = false; + } + + public void SetDetectionSimulationMode(SimulatingDeviceDetectionMode mode) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _isDetectionSimulationEnabled = mode != SimulatingDeviceDetectionMode.NoSimulation; + _detectionSimulationMode = mode; + } + + public void ClearDetectionSimulationMode() + { + SetDetectionSimulationMode(SimulatingDeviceDetectionMode.NoSimulation); + } + + public Result CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation operation) + { + if (_isEventSet) return Result.Success; - } - public void SetDeviceEvent(SimulatingDeviceTargetOperation operation, - SimulatingDeviceAccessFailureEventType failureType, Result failureResult, bool isRecurringEvent) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - if (failureResult.IsFailure()) - _failureResult = failureResult; + if ((_simulatedOperation & operation) == 0) + return Result.Success; - _isEventSet = true; - _simulatedFailureType = failureType; - _simulatedOperation = operation; - _isRecurringEvent = isRecurringEvent; - } + Result result = GetCorrespondingResult(_simulatedFailureType); - public void ClearDeviceEvent() - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + if (result.IsFailure() && _failureResult.IsFailure()) + result = _failureResult; - _isEventSet = false; - _simulatedFailureType = SimulatingDeviceAccessFailureEventType.None; - _simulatedOperation = SimulatingDeviceTargetOperation.None; - _failureResult = Result.Success; - _isRecurringEvent = false; - } + if (_simulatedFailureType == SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure) + SimulateTimeout(); - public void SetDetectionSimulationMode(SimulatingDeviceDetectionMode mode) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + if (!_isRecurringEvent) + ClearDeviceEvent(); - _isDetectionSimulationEnabled = mode != SimulatingDeviceDetectionMode.NoSimulation; - _detectionSimulationMode = mode; - } - - public void ClearDetectionSimulationMode() - { - SetDetectionSimulationMode(SimulatingDeviceDetectionMode.NoSimulation); - } - - public Result CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation operation) - { - if (_isEventSet) - return Result.Success; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - if ((_simulatedOperation & operation) == 0) - return Result.Success; - - Result result = GetCorrespondingResult(_simulatedFailureType); - - if (result.IsFailure() && _failureResult.IsFailure()) - result = _failureResult; - - if (_simulatedFailureType == SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure) - SimulateTimeout(); - - if (!_isRecurringEvent) - ClearDeviceEvent(); - - return result; - } - - public bool FilterDetectionState(bool actualState) - { - if (!_isDetectionSimulationEnabled) - return actualState; - - bool simulatedState = _detectionSimulationMode switch - { - SimulatingDeviceDetectionMode.NoSimulation => actualState, - SimulatingDeviceDetectionMode.DeviceAttached => true, - SimulatingDeviceDetectionMode.DeviceRemoved => false, - _ => actualState - }; - - return simulatedState; - } - - protected virtual void SimulateTimeout() - { - _os.SleepThread(TimeSpan.FromMilliSeconds(_timeoutLengthMs)); - } + return result; } - public class GameCardEventSimulator : IDeviceEventSimulator + public bool FilterDetectionState(bool actualState) { - public GameCardEventSimulator(OsState os) : base(os, 2000) { } + if (!_isDetectionSimulationEnabled) + return actualState; - public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) + bool simulatedState = _detectionSimulationMode switch { - return eventType switch - { - SimulatingDeviceAccessFailureEventType.None => Result.Success, - SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.GameCardCardAccessTimeout.Log(), - SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.GameCardAccessFailed.Log(), - SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(), - _ => ResultFs.InvalidArgument.Log() - }; - } + SimulatingDeviceDetectionMode.NoSimulation => actualState, + SimulatingDeviceDetectionMode.DeviceAttached => true, + SimulatingDeviceDetectionMode.DeviceRemoved => false, + _ => actualState + }; + + return simulatedState; } - public class SdCardEventSimulator : IDeviceEventSimulator + protected virtual void SimulateTimeout() { - public SdCardEventSimulator(OsState os) : base(os, 2000) { } - - public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) - { - return eventType switch - { - SimulatingDeviceAccessFailureEventType.None => Result.Success, - SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.PortSdCardResponseTimeoutError.Log(), - SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.SdCardAccessFailed.Log(), - SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(), - _ => ResultFs.InvalidArgument.Log() - }; - } + _os.SleepThread(TimeSpan.FromMilliSeconds(_timeoutLengthMs)); } -} \ No newline at end of file +} + +public class GameCardEventSimulator : IDeviceEventSimulator +{ + public GameCardEventSimulator(OsState os) : base(os, 2000) { } + + public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) + { + return eventType switch + { + SimulatingDeviceAccessFailureEventType.None => Result.Success, + SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.GameCardCardAccessTimeout.Log(), + SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.GameCardAccessFailed.Log(), + SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(), + _ => ResultFs.InvalidArgument.Log() + }; + } +} + +public class SdCardEventSimulator : IDeviceEventSimulator +{ + public SdCardEventSimulator(OsState os) : base(os, 2000) { } + + public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType) + { + return eventType switch + { + SimulatingDeviceAccessFailureEventType.None => Result.Success, + SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.PortSdCardResponseTimeoutError.Log(), + SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.SdCardAccessFailed.Log(), + SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(), + _ => ResultFs.InvalidArgument.Log() + }; + } +} diff --git a/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs b/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs index e013b71c..7af66f13 100644 --- a/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs +++ b/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs @@ -2,10 +2,9 @@ using LibHac.Common; using LibHac.FsSystem; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public interface IEntryOpenCountSemaphoreManager : IDisposable { - public interface IEntryOpenCountSemaphoreManager : IDisposable - { - Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore); - } -} \ No newline at end of file + Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore); +} diff --git a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs index 24e5d5f6..989eca42 100644 --- a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs @@ -3,194 +3,193 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +// ReSharper disable once InconsistentNaming +public abstract class IResultConvertFile : IFile { - // ReSharper disable once InconsistentNaming - public abstract class IResultConvertFile : IFile + protected UniqueRef BaseFile; + + protected IResultConvertFile(ref UniqueRef baseFile) { - protected UniqueRef BaseFile; - - protected IResultConvertFile(ref UniqueRef baseFile) - { - BaseFile = new UniqueRef(ref baseFile); - } - - public override void Dispose() - { - BaseFile.Destroy(); - base.Dispose(); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) - { - return ConvertResult(BaseFile.Get.Read(out bytesRead, offset, destination, option)); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return ConvertResult(BaseFile.Get.Write(offset, source, option)); - } - - protected override Result DoFlush() - { - return ConvertResult(BaseFile.Get.Flush()); - } - - protected override Result DoSetSize(long size) - { - return ConvertResult(BaseFile.Get.SetSize(size)); - } - - protected override Result DoGetSize(out long size) - { - return ConvertResult(BaseFile.Get.GetSize(out size)); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return ConvertResult(BaseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer)); - } - - protected abstract Result ConvertResult(Result result); + BaseFile = new UniqueRef(ref baseFile); } - // ReSharper disable once InconsistentNaming - public abstract class IResultConvertDirectory : IDirectory + public override void Dispose() { - protected UniqueRef BaseDirectory; - - protected IResultConvertDirectory(ref UniqueRef baseDirectory) - { - BaseDirectory = new UniqueRef(ref baseDirectory); - } - - public override void Dispose() - { - BaseDirectory.Destroy(); - base.Dispose(); - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - return ConvertResult(BaseDirectory.Get.Read(out entriesRead, entryBuffer)); - } - - protected override Result DoGetEntryCount(out long entryCount) - { - return ConvertResult(BaseDirectory.Get.GetEntryCount(out entryCount)); - } - - protected abstract Result ConvertResult(Result result); + BaseFile.Destroy(); + base.Dispose(); } - // ReSharper disable once InconsistentNaming - public abstract class IResultConvertFileSystem : IFileSystem + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) { - protected SharedRef BaseFileSystem; - - protected IResultConvertFileSystem(ref SharedRef baseFileSystem) - { - BaseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - } - - public override void Dispose() - { - BaseFileSystem.Destroy(); - base.Dispose(); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - return ConvertResult(BaseFileSystem.Get.CreateFile(path, size, option)); - } - - protected override Result DoDeleteFile(in Path path) - { - return ConvertResult(BaseFileSystem.Get.DeleteFile(path)); - } - - protected override Result DoCreateDirectory(in Path path) - { - return ConvertResult(BaseFileSystem.Get.CreateDirectory(path)); - } - - protected override Result DoDeleteDirectory(in Path path) - { - return ConvertResult(BaseFileSystem.Get.DeleteDirectory(path)); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - return ConvertResult(BaseFileSystem.Get.DeleteDirectoryRecursively(path)); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - return ConvertResult(BaseFileSystem.Get.CleanDirectoryRecursively(path)); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - return ConvertResult(BaseFileSystem.Get.RenameFile(currentPath, newPath)); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - return ConvertResult(BaseFileSystem.Get.RenameDirectory(currentPath, newPath)); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - return ConvertResult(BaseFileSystem.Get.GetEntryType(out entryType, path)); - } - - protected abstract override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); - - protected abstract override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode); - - protected override Result DoCommit() - { - return ConvertResult(BaseFileSystem.Get.Commit()); - } - - protected override Result DoCommitProvisionally(long counter) - { - return ConvertResult(BaseFileSystem.Get.CommitProvisionally(counter)); - } - - protected override Result DoRollback() - { - return ConvertResult(BaseFileSystem.Get.Rollback()); - } - - protected override Result DoFlush() - { - return ConvertResult(BaseFileSystem.Get.Flush()); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - return ConvertResult(BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path)); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - return ConvertResult(BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, path)); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return ConvertResult(BaseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path)); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return ConvertResult(BaseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path)); - } - - protected abstract Result ConvertResult(Result result); + return ConvertResult(BaseFile.Get.Read(out bytesRead, offset, destination, option)); } -} \ No newline at end of file + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return ConvertResult(BaseFile.Get.Write(offset, source, option)); + } + + protected override Result DoFlush() + { + return ConvertResult(BaseFile.Get.Flush()); + } + + protected override Result DoSetSize(long size) + { + return ConvertResult(BaseFile.Get.SetSize(size)); + } + + protected override Result DoGetSize(out long size) + { + return ConvertResult(BaseFile.Get.GetSize(out size)); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ConvertResult(BaseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer)); + } + + protected abstract Result ConvertResult(Result result); +} + +// ReSharper disable once InconsistentNaming +public abstract class IResultConvertDirectory : IDirectory +{ + protected UniqueRef BaseDirectory; + + protected IResultConvertDirectory(ref UniqueRef baseDirectory) + { + BaseDirectory = new UniqueRef(ref baseDirectory); + } + + public override void Dispose() + { + BaseDirectory.Destroy(); + base.Dispose(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + return ConvertResult(BaseDirectory.Get.Read(out entriesRead, entryBuffer)); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + return ConvertResult(BaseDirectory.Get.GetEntryCount(out entryCount)); + } + + protected abstract Result ConvertResult(Result result); +} + +// ReSharper disable once InconsistentNaming +public abstract class IResultConvertFileSystem : IFileSystem +{ + protected SharedRef BaseFileSystem; + + protected IResultConvertFileSystem(ref SharedRef baseFileSystem) + { + BaseFileSystem = SharedRef.CreateMove(ref baseFileSystem); + } + + public override void Dispose() + { + BaseFileSystem.Destroy(); + base.Dispose(); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + return ConvertResult(BaseFileSystem.Get.CreateFile(path, size, option)); + } + + protected override Result DoDeleteFile(in Path path) + { + return ConvertResult(BaseFileSystem.Get.DeleteFile(path)); + } + + protected override Result DoCreateDirectory(in Path path) + { + return ConvertResult(BaseFileSystem.Get.CreateDirectory(path)); + } + + protected override Result DoDeleteDirectory(in Path path) + { + return ConvertResult(BaseFileSystem.Get.DeleteDirectory(path)); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + return ConvertResult(BaseFileSystem.Get.DeleteDirectoryRecursively(path)); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + return ConvertResult(BaseFileSystem.Get.CleanDirectoryRecursively(path)); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + return ConvertResult(BaseFileSystem.Get.RenameFile(currentPath, newPath)); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + return ConvertResult(BaseFileSystem.Get.RenameDirectory(currentPath, newPath)); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + return ConvertResult(BaseFileSystem.Get.GetEntryType(out entryType, path)); + } + + protected abstract override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); + + protected abstract override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode); + + protected override Result DoCommit() + { + return ConvertResult(BaseFileSystem.Get.Commit()); + } + + protected override Result DoCommitProvisionally(long counter) + { + return ConvertResult(BaseFileSystem.Get.CommitProvisionally(counter)); + } + + protected override Result DoRollback() + { + return ConvertResult(BaseFileSystem.Get.Rollback()); + } + + protected override Result DoFlush() + { + return ConvertResult(BaseFileSystem.Get.Flush()); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return ConvertResult(BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path)); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + return ConvertResult(BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, path)); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return ConvertResult(BaseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path)); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return ConvertResult(BaseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path)); + } + + protected abstract Result ConvertResult(Result result); +} diff --git a/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs b/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs index 5f23ab7d..0b875686 100644 --- a/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs +++ b/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs @@ -4,14 +4,13 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Ncm; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public interface IRomFileSystemAccessFailureManager : IDisposable { - public interface IRomFileSystemAccessFailureManager : IDisposable - { - Result OpenDataStorageCore(ref SharedRef outStorage, out Hash ncaHeaderDigest, ulong id, StorageId storageId); - Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected); - void IncrementRomFsRemountForDataCorruptionCount(); - void IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); - void IncrementRomFsRecoveredByInvalidateCacheCount(); - } -} \ No newline at end of file + Result OpenDataStorageCore(ref SharedRef outStorage, out Hash ncaHeaderDigest, ulong id, StorageId storageId); + Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected); + void IncrementRomFsRemountForDataCorruptionCount(); + void IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); + void IncrementRomFsRecoveredByInvalidateCacheCount(); +} diff --git a/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs index 29e13660..1bc6e6ad 100644 --- a/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs +++ b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs @@ -3,13 +3,12 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public interface ISaveDataMultiCommitCoreInterface : IDisposable { - public interface ISaveDataMultiCommitCoreInterface : IDisposable - { - Result RecoverMultiCommit(); - Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo); - Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback); - Result OpenMultiCommitContext(ref SharedRef contextFileSystem); - } + Result RecoverMultiCommit(); + Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo); + Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback); + Result OpenMultiCommitContext(ref SharedRef contextFileSystem); } diff --git a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs index e5f9532d..2e0a35a0 100644 --- a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs +++ b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs @@ -5,27 +5,26 @@ using LibHac.Fs.Fsa; using LibHac.Util; using IFileSf = LibHac.FsSrv.Sf.IFile; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public interface ISaveDataTransferCoreInterface : IDisposable { - public interface ISaveDataTransferCoreInterface : IDisposable - { - Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); - Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); - Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId); - Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized); - Result GetSaveDataInfo(out SaveDataInfo saveInfo, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData); - Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp); - Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); - Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); - Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType metaType); - Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode); - Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey); - Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); - Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); - Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2); - Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state); - Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank); - Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, SaveDataSpaceId spaceId); - } + Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); + Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); + Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId); + Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized); + Result GetSaveDataInfo(out SaveDataInfo saveInfo, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData); + Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp); + Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); + Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); + Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType metaType); + Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode); + Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey); + Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); + Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); + Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2); + Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state); + Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank); + Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, SaveDataSpaceId spaceId); } diff --git a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs index 64429597..ca8b88f2 100644 --- a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs +++ b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs @@ -8,288 +8,287 @@ using LibHac.Ncm; using LibHac.Os; using LibHac.Util; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public static class LocationResolverSetGlobalMethods { - public static class LocationResolverSetGlobalMethods + public static void InitializeLocationResolverSet(this FileSystemServer fsSrv) { - public static void InitializeLocationResolverSet(this FileSystemServer fsSrv) + ref LocationResolverSetGlobals globals = ref fsSrv.Globals.LocationResolverSet; + using ScopedLock scopedLock = ScopedLock.Lock(ref globals.Mutex); + + if (!globals.IsLrInitialized) { - ref LocationResolverSetGlobals globals = ref fsSrv.Globals.LocationResolverSet; - using ScopedLock scopedLock = ScopedLock.Lock(ref globals.Mutex); - - if (!globals.IsLrInitialized) - { - fsSrv.Hos.Lr.Initialize(); - globals.IsLrInitialized = true; - } - } - } - - internal struct LocationResolverSetGlobals - { - public SdkMutexType Mutex; - public bool IsLrInitialized; - - public void Initialize() - { - Mutex.Initialize(); - } - } - - /// - /// Manages resolving the location of NCAs via the lr service. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal class LocationResolverSet : IDisposable - { - private Array5> _resolvers; - private Optional _aocResolver; - private SdkMutexType _mutex; - - // LibHac addition - private FileSystemServer _fsServer; - private HorizonClient Hos => _fsServer.Hos; - - public LocationResolverSet(FileSystemServer fsServer) - { - _mutex.Initialize(); - _fsServer = fsServer; - } - - public void Dispose() - { - for (int i = 0; i < Array5>.Length; i++) - { - if (_resolvers[i].HasValue) - { - _resolvers[i].Value.Dispose(); - _resolvers[i].Clear(); - } - } - - if (_aocResolver.HasValue) - { - _aocResolver.Value.Dispose(); - _aocResolver.Clear(); - } - } - - private static Result SetUpFsPath(ref Fs.Path outPath, in Lr.Path lrPath) - { - var pathFlags = new PathFlags(); - pathFlags.AllowMountName(); - - if (Utility.IsHostFsMountName(lrPath.Str)) - pathFlags.AllowWindowsPath(); - - Result rc = outPath.InitializeWithReplaceUnc(lrPath.Str); - if (rc.IsFailure()) return rc; - - rc = outPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private Result GetLocationResolver(out LocationResolver resolver, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out resolver); - - _fsServer.InitializeLocationResolverSet(); - - int index = GetResolverIndexFromStorageId(storageId); - if (index == -1) - return ResultLr.ApplicationNotFound.Log(); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - // Open the location resolver if it hasn't been already - if (!_resolvers[index].HasValue) - { - _resolvers[index].Set(null); - Result rc = Hos.Lr.OpenLocationResolver(out _resolvers[index].Value, storageId); - - if (rc.IsFailure()) - { - _resolvers[index].Clear(); - return ResultLr.ApplicationNotFound.Log(); - } - } - - resolver = _resolvers[index].Value; - return Result.Success; - } - - private Result GetRegisteredLocationResolver(out RegisteredLocationResolver resolver) - { - _fsServer.InitializeLocationResolverSet(); - - return Hos.Lr.OpenRegisteredLocationResolver(out resolver); - } - - private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) - { - _fsServer.InitializeLocationResolverSet(); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - if (!_aocResolver.HasValue) - { - Result rc = Hos.Lr.OpenAddOnContentLocationResolver(out AddOnContentLocationResolver lr); - if (rc.IsFailure()) - { - UnsafeHelpers.SkipParamInit(out resolver); - return rc; - } - - _aocResolver = lr; - } - - resolver = _aocResolver.Value; - return Result.Success; - } - - public Result ResolveApplicationControlPath(ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) - { - Result rc = GetLocationResolver(out LocationResolver resolver, storageId); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveApplicationControlPath(out Lr.Path path, applicationId); - if (rc.IsFailure()) return rc; - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out isDirectory); - - Result rc = GetLocationResolver(out LocationResolver resolver, storageId); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, applicationId); - if (rc.IsFailure()) return rc; - - isDirectory = PathUtility.IsDirectoryPath(path.Str); - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveProgramPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out isDirectory); - - Result rc = GetLocationResolver(out LocationResolver resolver, storageId); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveProgramPath(out Lr.Path path, programId); - if (rc.IsFailure()) return rc; - - isDirectory = PathUtility.IsDirectoryPath(path.Str); - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveRomPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out isDirectory); - - Result rc = GetLocationResolver(out LocationResolver resolver, storageId); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveProgramPathForDebug(out Lr.Path path, programId); - if (rc.IsFailure()) return rc; - - isDirectory = PathUtility.IsDirectoryPath(path.Str); - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveAddOnContentPath(ref Fs.Path outPath, DataId dataId) - { - Result rc = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveAddOnContentPath(out Lr.Path path, dataId); - if (rc.IsFailure()) return rc; - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveDataPath(ref Fs.Path outPath, DataId dataId, StorageId storageId) - { - if (storageId == StorageId.None) - return ResultFs.InvalidAlignment.Log(); - - Result rc = GetLocationResolver(out LocationResolver resolver, storageId); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveDataPath(out Lr.Path path, dataId); - if (rc.IsFailure()) return rc; - - return SetUpFsPath(ref outPath, in path); - } - - public Result ResolveRegisteredProgramPath(ref Fs.Path outPath, ulong id) - { - RegisteredLocationResolver resolver = null; - try - { - Result rc = GetRegisteredLocationResolver(out resolver); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveProgramPath(out Lr.Path path, new ProgramId(id)); - if (rc.IsFailure()) return rc; - - return SetUpFsPath(ref outPath, in path); - } - finally - { - resolver?.Dispose(); - } - } - - public Result ResolveRegisteredHtmlDocumentPath(ref Fs.Path outPath, ulong id) - { - RegisteredLocationResolver resolver = null; - try - { - Result rc = GetRegisteredLocationResolver(out resolver); - if (rc.IsFailure()) return rc; - - rc = resolver.ResolveHtmlDocumentPath(out Lr.Path path, new ProgramId(id)); - if (rc.IsFailure()) return rc; - - return SetUpFsPath(ref outPath, in path); - } - finally - { - resolver?.Dispose(); - } - } - - private static bool IsValidStorageId(StorageId id) - { - return id == StorageId.Host || - id == StorageId.GameCard || - id == StorageId.BuiltInSystem || - id == StorageId.BuiltInUser || - id == StorageId.SdCard; - } - - private static int GetResolverIndexFromStorageId(StorageId id) - { - Assert.SdkRequires(IsValidStorageId(id)); - - return id switch - { - StorageId.Host => 2, - StorageId.GameCard => 4, - StorageId.BuiltInSystem => 0, - StorageId.BuiltInUser => 1, - StorageId.SdCard => 3, - _ => -1 - }; + fsSrv.Hos.Lr.Initialize(); + globals.IsLrInitialized = true; } } } + +internal struct LocationResolverSetGlobals +{ + public SdkMutexType Mutex; + public bool IsLrInitialized; + + public void Initialize() + { + Mutex.Initialize(); + } +} + +/// +/// Manages resolving the location of NCAs via the lr service. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal class LocationResolverSet : IDisposable +{ + private Array5> _resolvers; + private Optional _aocResolver; + private SdkMutexType _mutex; + + // LibHac addition + private FileSystemServer _fsServer; + private HorizonClient Hos => _fsServer.Hos; + + public LocationResolverSet(FileSystemServer fsServer) + { + _mutex.Initialize(); + _fsServer = fsServer; + } + + public void Dispose() + { + for (int i = 0; i < Array5>.Length; i++) + { + if (_resolvers[i].HasValue) + { + _resolvers[i].Value.Dispose(); + _resolvers[i].Clear(); + } + } + + if (_aocResolver.HasValue) + { + _aocResolver.Value.Dispose(); + _aocResolver.Clear(); + } + } + + private static Result SetUpFsPath(ref Fs.Path outPath, in Lr.Path lrPath) + { + var pathFlags = new PathFlags(); + pathFlags.AllowMountName(); + + if (Utility.IsHostFsMountName(lrPath.Str)) + pathFlags.AllowWindowsPath(); + + Result rc = outPath.InitializeWithReplaceUnc(lrPath.Str); + if (rc.IsFailure()) return rc; + + rc = outPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result GetLocationResolver(out LocationResolver resolver, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out resolver); + + _fsServer.InitializeLocationResolverSet(); + + int index = GetResolverIndexFromStorageId(storageId); + if (index == -1) + return ResultLr.ApplicationNotFound.Log(); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + // Open the location resolver if it hasn't been already + if (!_resolvers[index].HasValue) + { + _resolvers[index].Set(null); + Result rc = Hos.Lr.OpenLocationResolver(out _resolvers[index].Value, storageId); + + if (rc.IsFailure()) + { + _resolvers[index].Clear(); + return ResultLr.ApplicationNotFound.Log(); + } + } + + resolver = _resolvers[index].Value; + return Result.Success; + } + + private Result GetRegisteredLocationResolver(out RegisteredLocationResolver resolver) + { + _fsServer.InitializeLocationResolverSet(); + + return Hos.Lr.OpenRegisteredLocationResolver(out resolver); + } + + private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) + { + _fsServer.InitializeLocationResolverSet(); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (!_aocResolver.HasValue) + { + Result rc = Hos.Lr.OpenAddOnContentLocationResolver(out AddOnContentLocationResolver lr); + if (rc.IsFailure()) + { + UnsafeHelpers.SkipParamInit(out resolver); + return rc; + } + + _aocResolver = lr; + } + + resolver = _aocResolver.Value; + return Result.Success; + } + + public Result ResolveApplicationControlPath(ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) + { + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveApplicationControlPath(out Lr.Path path, applicationId); + if (rc.IsFailure()) return rc; + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Fs.Path outPath, Ncm.ApplicationId applicationId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out isDirectory); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveApplicationHtmlDocumentPath(out Lr.Path path, applicationId); + if (rc.IsFailure()) return rc; + + isDirectory = PathUtility.IsDirectoryPath(path.Str); + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveProgramPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out isDirectory); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveProgramPath(out Lr.Path path, programId); + if (rc.IsFailure()) return rc; + + isDirectory = PathUtility.IsDirectoryPath(path.Str); + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveRomPath(out bool isDirectory, ref Fs.Path outPath, ProgramId programId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out isDirectory); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveProgramPathForDebug(out Lr.Path path, programId); + if (rc.IsFailure()) return rc; + + isDirectory = PathUtility.IsDirectoryPath(path.Str); + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveAddOnContentPath(ref Fs.Path outPath, DataId dataId) + { + Result rc = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveAddOnContentPath(out Lr.Path path, dataId); + if (rc.IsFailure()) return rc; + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveDataPath(ref Fs.Path outPath, DataId dataId, StorageId storageId) + { + if (storageId == StorageId.None) + return ResultFs.InvalidAlignment.Log(); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveDataPath(out Lr.Path path, dataId); + if (rc.IsFailure()) return rc; + + return SetUpFsPath(ref outPath, in path); + } + + public Result ResolveRegisteredProgramPath(ref Fs.Path outPath, ulong id) + { + RegisteredLocationResolver resolver = null; + try + { + Result rc = GetRegisteredLocationResolver(out resolver); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveProgramPath(out Lr.Path path, new ProgramId(id)); + if (rc.IsFailure()) return rc; + + return SetUpFsPath(ref outPath, in path); + } + finally + { + resolver?.Dispose(); + } + } + + public Result ResolveRegisteredHtmlDocumentPath(ref Fs.Path outPath, ulong id) + { + RegisteredLocationResolver resolver = null; + try + { + Result rc = GetRegisteredLocationResolver(out resolver); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveHtmlDocumentPath(out Lr.Path path, new ProgramId(id)); + if (rc.IsFailure()) return rc; + + return SetUpFsPath(ref outPath, in path); + } + finally + { + resolver?.Dispose(); + } + } + + private static bool IsValidStorageId(StorageId id) + { + return id == StorageId.Host || + id == StorageId.GameCard || + id == StorageId.BuiltInSystem || + id == StorageId.BuiltInUser || + id == StorageId.SdCard; + } + + private static int GetResolverIndexFromStorageId(StorageId id) + { + Assert.SdkRequires(IsValidStorageId(id)); + + return id switch + { + StorageId.Host => 2, + StorageId.GameCard => 4, + StorageId.BuiltInSystem => 0, + StorageId.BuiltInUser => 1, + StorageId.SdCard => 3, + _ => -1 + }; + } +} diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index 2fd7e879..4184869f 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -13,288 +13,369 @@ using IFile = LibHac.Fs.Fsa.IFile; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.FsSrv.Impl -{ - internal struct MultiCommitManagerGlobals - { - public SdkMutexType MultiCommitMutex; +namespace LibHac.FsSrv.Impl; - public void Initialize() +internal struct MultiCommitManagerGlobals +{ + public SdkMutexType MultiCommitMutex; + + public void Initialize() + { + MultiCommitMutex.Initialize(); + } +} + +/// +/// Manages atomically committing a group of file systems. +/// +/// +/// The commit process is as follows:
+/// 1. Create a commit context file that tracks the progress of the commit in case it is interrupted.
+/// 2. Provisionally commit each file system individually. If any fail, rollback the file systems that were provisionally committed.
+/// 3. Update the commit context file to note that the file systems have been provisionally committed. +/// If the multi-commit is interrupted past this point, the file systems will be fully committed during recovery.
+/// 4. Fully commit each file system individually.
+/// 5. Delete the commit context file.
+///
+/// Even though multi-commits are supposed to be atomic, issues can arise from errors during the process of fully committing the save data. +/// Save data image files are designed so that minimal changes are made when fully committing a provisionally committed save. +/// However if any commit fails for any reason, all other saves in the multi-commit will still be committed. +/// This can especially cause issues with directory save data where finishing a commit is much more involved. +/// Based on FS 12.1.0 (nnSdk 12.3.1) +///
+internal class MultiCommitManager : IMultiCommitManager +{ + private const int MaxFileSystemCount = 10; + + public const ulong ProgramId = 0x0100000000000000; + public const ulong SaveDataId = 0x8000000000000001; + private const long SaveDataSize = 0xC000; + private const long SaveJournalSize = 0xC000; + + private const int CurrentCommitContextVersion = 0x10000; + private const long CommitContextFileSize = 0x200; + + // /commitinfo + private static ReadOnlySpan CommitContextFileName => + new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' }; + + private SharedRef _multiCommitInterface; + private readonly SharedRef[] _fileSystems; + private int _fileSystemCount; + private long _counter; + + // LibHac additions + private readonly FileSystemServer _fsServer; + private ref MultiCommitManagerGlobals Globals => ref _fsServer.Globals.MultiCommitManager; + + public MultiCommitManager(FileSystemServer fsServer, ref SharedRef multiCommitInterface) + { + _fsServer = fsServer; + + _multiCommitInterface = SharedRef.CreateMove(ref multiCommitInterface); + _fileSystems = new SharedRef[MaxFileSystemCount]; + _fileSystemCount = 0; + _counter = 0; + } + + public static SharedRef CreateShared(FileSystemServer fsServer, + ref SharedRef multiCommitInterface) + { + return new SharedRef(new MultiCommitManager(fsServer, ref multiCommitInterface)); + } + + public void Dispose() + { + for (int i = 0; i < _fileSystems.Length; i++) { - MultiCommitMutex.Initialize(); + _fileSystems[i].Destroy(); } + + _multiCommitInterface.Destroy(); } /// - /// Manages atomically committing a group of file systems. + /// Ensures the save data used to store the commit context exists. /// - /// - /// The commit process is as follows:
- /// 1. Create a commit context file that tracks the progress of the commit in case it is interrupted.
- /// 2. Provisionally commit each file system individually. If any fail, rollback the file systems that were provisionally committed.
- /// 3. Update the commit context file to note that the file systems have been provisionally committed. - /// If the multi-commit is interrupted past this point, the file systems will be fully committed during recovery.
- /// 4. Fully commit each file system individually.
- /// 5. Delete the commit context file.
- ///
- /// Even though multi-commits are supposed to be atomic, issues can arise from errors during the process of fully committing the save data. - /// Save data image files are designed so that minimal changes are made when fully committing a provisionally committed save. - /// However if any commit fails for any reason, all other saves in the multi-commit will still be committed. - /// This can especially cause issues with directory save data where finishing a commit is much more involved. - /// Based on FS 12.1.0 (nnSdk 12.3.1) - ///
- internal class MultiCommitManager : IMultiCommitManager + /// The of the operation. + private Result EnsureSaveDataForContext() { - private const int MaxFileSystemCount = 10; + using var contextFileSystem = new SharedRef(); + Result rc = _multiCommitInterface.Get.OpenMultiCommitContext(ref contextFileSystem.Ref()); - public const ulong ProgramId = 0x0100000000000000; - public const ulong SaveDataId = 0x8000000000000001; - private const long SaveDataSize = 0xC000; - private const long SaveJournalSize = 0xC000; - - private const int CurrentCommitContextVersion = 0x10000; - private const long CommitContextFileSize = 0x200; - - // /commitinfo - private static ReadOnlySpan CommitContextFileName => - new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' }; - - private SharedRef _multiCommitInterface; - private readonly SharedRef[] _fileSystems; - private int _fileSystemCount; - private long _counter; - - // LibHac additions - private readonly FileSystemServer _fsServer; - private ref MultiCommitManagerGlobals Globals => ref _fsServer.Globals.MultiCommitManager; - - public MultiCommitManager(FileSystemServer fsServer, ref SharedRef multiCommitInterface) + if (rc.IsFailure()) { - _fsServer = fsServer; + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; - _multiCommitInterface = SharedRef.CreateMove(ref multiCommitInterface); - _fileSystems = new SharedRef[MaxFileSystemCount]; - _fileSystemCount = 0; - _counter = 0; + rc = _fsServer.Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None); + if (rc.IsFailure()) return rc; } - public static SharedRef CreateShared(FileSystemServer fsServer, - ref SharedRef multiCommitInterface) + return Result.Success; + } + + /// + /// Adds a file system to the list of file systems to be committed. + /// + /// The file system to be committed. + /// : The operation was successful.
+ /// : The maximum number of file systems have been added. + /// file systems may be added to a single multi-commit.
+ /// : The provided file system has already been added.
+ public Result Add(ref SharedRef fileSystem) + { + if (_fileSystemCount >= MaxFileSystemCount) + return ResultFs.MultiCommitFileSystemLimit.Log(); + + using var fsaFileSystem = new SharedRef(); + Result rc = fileSystem.Get.GetImpl(ref fsaFileSystem.Ref()); + if (rc.IsFailure()) return rc; + + // Check that the file system hasn't already been added + for (int i = 0; i < _fileSystemCount; i++) { - return new SharedRef(new MultiCommitManager(fsServer, ref multiCommitInterface)); + if (ReferenceEquals(fsaFileSystem.Get, _fileSystems[i].Get)) + return ResultFs.MultiCommitFileSystemAlreadyAdded.Log(); } - public void Dispose() - { - for (int i = 0; i < _fileSystems.Length; i++) - { - _fileSystems[i].Destroy(); - } + _fileSystems[_fileSystemCount].SetByMove(ref fsaFileSystem.Ref()); + _fileSystemCount++; - _multiCommitInterface.Destroy(); - } + return Result.Success; + } - /// - /// Ensures the save data used to store the commit context exists. - /// - /// The of the operation. - private Result EnsureSaveDataForContext() + /// + /// Commits all added file systems using to + /// store the . + /// + /// The file system where the commit context will be stored. + /// The of the operation. + private Result Commit(IFileSystem contextFileSystem) + { + _counter = 1; + + using var contextUpdater = new ContextUpdater(contextFileSystem); + Result rc = contextUpdater.Create(_counter, _fileSystemCount); + if (rc.IsFailure()) return rc; + + rc = CommitProvisionallyFileSystem(_counter); + if (rc.IsFailure()) return rc; + + rc = contextUpdater.CommitProvisionallyDone(); + if (rc.IsFailure()) return rc; + + rc = CommitFileSystem(); + if (rc.IsFailure()) return rc; + + rc = contextUpdater.CommitDone(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + /// + /// Commits all added file systems. + /// + /// The of the operation. + public Result Commit() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.MultiCommitMutex); + + using var contextFileSystem = new SharedRef(); + Result rc = EnsureSaveDataForContext(); + if (rc.IsFailure()) return rc; + + rc = _multiCommitInterface.Get.OpenMultiCommitContext(ref contextFileSystem.Ref()); + if (rc.IsFailure()) return rc; + + return Commit(contextFileSystem.Get); + } + + /// + /// Tries to provisionally commit all the added file systems. + /// + /// The provisional commit counter. + /// The of the operation. + private Result CommitProvisionallyFileSystem(long counter) + { + Result rc = Result.Success; + int i; + + for (i = 0; i < _fileSystemCount; i++) { - using var contextFileSystem = new SharedRef(); - Result rc = _multiCommitInterface.Get.OpenMultiCommitContext(ref contextFileSystem.Ref()); + Assert.SdkNotNull(_fileSystems[i].Get); + + rc = _fileSystems[i].Get.CommitProvisionally(counter); if (rc.IsFailure()) - { - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; + break; + } - rc = _fsServer.Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None); + if (rc.IsFailure()) + { + // Rollback all provisional commits including the failed commit + for (int j = 0; j <= i; j++) + { + Assert.SdkNotNull(_fileSystems[j].Get); + + _fileSystems[j].Get.Rollback().IgnoreResult(); + } + } + + return rc; + } + + /// + /// Tries to fully commit all the added file systems. + /// + /// The of the operation. + private Result CommitFileSystem() + { + // Try to commit all file systems even if one fails. + // If any commits fail, the result from the first failed recovery will be returned. + Result result = Result.Success; + + for (int i = 0; i < _fileSystemCount; i++) + { + Assert.SdkNotNull(_fileSystems[i].Get); + + Result resultLast = _fileSystems[i].Get.Commit(); + + // If the commit failed, set the overall result if it hasn't been set yet. + if (result.IsSuccess() && resultLast.IsFailure()) + { + result = resultLast; + } + } + + if (result.IsFailure()) return result.Miss(); + + return Result.Success; + } + + /// + /// Recovers a multi-commit that was interrupted after all file systems had been provisionally committed. + /// The recovery will finish committing any file systems that are still provisionally committed. + /// + /// The core interface used for multi-commits. + /// The file system containing the multi-commit context file. + /// The save data service. + /// : The operation was successful.
+ /// : The version of the commit context + /// file isn't supported.
+ /// : The multi-commit hadn't finished + /// provisionally committing all the file systems.
+ private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface, + IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService) + { + using var contextFilePath = new Fs.Path(); + Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); + if (rc.IsFailure()) return rc; + + // Read the multi-commit context + using var contextFile = new UniqueRef(); + rc = contextFs.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc; + + Unsafe.SkipInit(out Context context); + rc = contextFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref context), ReadOption.None); + if (rc.IsFailure()) return rc; + + // Note: Nintendo doesn't check if the proper amount of bytes were read, but it + // doesn't really matter since the context is validated. + if (context.Version > CurrentCommitContextVersion) + return ResultFs.InvalidMultiCommitContextVersion.Log(); + + // All the file systems in the multi-commit must have been at least provisionally committed + // before we can try to recover the commit. + if (context.State != CommitState.ProvisionallyCommitted) + return ResultFs.InvalidMultiCommitContextState.Log(); + + // Keep track of the first error that occurs during the recovery + Result recoveryResult = Result.Success; + + int saveCount = 0; + Span savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; + + { + using var reader = new SharedRef(); + using var accessor = new UniqueRef(); + + rc = saveService.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out _, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; + + // Iterate through all the saves to find any provisionally committed save data + while (true) + { + Unsafe.SkipInit(out SaveDataInfo info); + + rc = reader.Get.Read(out long readCount, OutBuffer.FromStruct(ref info)); if (rc.IsFailure()) return rc; - } - return Result.Success; - } - - /// - /// Adds a file system to the list of file systems to be committed. - /// - /// The file system to be committed. - /// : The operation was successful.
- /// : The maximum number of file systems have been added. - /// file systems may be added to a single multi-commit.
- /// : The provided file system has already been added.
- public Result Add(ref SharedRef fileSystem) - { - if (_fileSystemCount >= MaxFileSystemCount) - return ResultFs.MultiCommitFileSystemLimit.Log(); - - using var fsaFileSystem = new SharedRef(); - Result rc = fileSystem.Get.GetImpl(ref fsaFileSystem.Ref()); - if (rc.IsFailure()) return rc; - - // Check that the file system hasn't already been added - for (int i = 0; i < _fileSystemCount; i++) - { - if (ReferenceEquals(fsaFileSystem.Get, _fileSystems[i].Get)) - return ResultFs.MultiCommitFileSystemAlreadyAdded.Log(); - } - - _fileSystems[_fileSystemCount].SetByMove(ref fsaFileSystem.Ref()); - _fileSystemCount++; - - return Result.Success; - } - - /// - /// Commits all added file systems using to - /// store the . - /// - /// The file system where the commit context will be stored. - /// The of the operation. - private Result Commit(IFileSystem contextFileSystem) - { - _counter = 1; - - using var contextUpdater = new ContextUpdater(contextFileSystem); - Result rc = contextUpdater.Create(_counter, _fileSystemCount); - if (rc.IsFailure()) return rc; - - rc = CommitProvisionallyFileSystem(_counter); - if (rc.IsFailure()) return rc; - - rc = contextUpdater.CommitProvisionallyDone(); - if (rc.IsFailure()) return rc; - - rc = CommitFileSystem(); - if (rc.IsFailure()) return rc; - - rc = contextUpdater.CommitDone(); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - /// - /// Commits all added file systems. - /// - /// The of the operation. - public Result Commit() - { - using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.MultiCommitMutex); - - using var contextFileSystem = new SharedRef(); - Result rc = EnsureSaveDataForContext(); - if (rc.IsFailure()) return rc; - - rc = _multiCommitInterface.Get.OpenMultiCommitContext(ref contextFileSystem.Ref()); - if (rc.IsFailure()) return rc; - - return Commit(contextFileSystem.Get); - } - - /// - /// Tries to provisionally commit all the added file systems. - /// - /// The provisional commit counter. - /// The of the operation. - private Result CommitProvisionallyFileSystem(long counter) - { - Result rc = Result.Success; - int i; - - for (i = 0; i < _fileSystemCount; i++) - { - Assert.SdkNotNull(_fileSystems[i].Get); - - rc = _fileSystems[i].Get.CommitProvisionally(counter); - - if (rc.IsFailure()) + // Break once we're done iterating all save data + if (readCount == 0) break; - } - if (rc.IsFailure()) - { - // Rollback all provisional commits including the failed commit - for (int j = 0; j <= i; j++) + rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, + in info); + + // Note: Multi-commits are only recovered at boot time, so some saves could be missed if there + // are more than MaxFileSystemCount provisionally committed saves. + // In theory this shouldn't happen because a multi-commit should only be interrupted if the + // entire OS is brought down. + if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount) { - Assert.SdkNotNull(_fileSystems[j].Get); - - _fileSystems[j].Get.Rollback().IgnoreResult(); + savesToRecover[saveCount] = info; + saveCount++; } } - - return rc; } - /// - /// Tries to fully commit all the added file systems. - /// - /// The of the operation. - private Result CommitFileSystem() + // Recover the saves by finishing their commits. + // All file systems will try to be recovered, even if one fails. + // If any commits fail, the result from the first failed recovery will be returned. + for (int i = 0; i < saveCount; i++) { - // Try to commit all file systems even if one fails. - // If any commits fail, the result from the first failed recovery will be returned. - Result result = Result.Success; + rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], false); - for (int i = 0; i < _fileSystemCount; i++) + if (rc.IsFailure() && !recoveryResult.IsFailure()) { - Assert.SdkNotNull(_fileSystems[i].Get); - - Result resultLast = _fileSystems[i].Get.Commit(); - - // If the commit failed, set the overall result if it hasn't been set yet. - if (result.IsSuccess() && resultLast.IsFailure()) - { - result = resultLast; - } + recoveryResult = rc; } - - if (result.IsFailure()) return result.Miss(); - - return Result.Success; } - /// - /// Recovers a multi-commit that was interrupted after all file systems had been provisionally committed. - /// The recovery will finish committing any file systems that are still provisionally committed. - /// - /// The core interface used for multi-commits. - /// The file system containing the multi-commit context file. - /// The save data service. - /// : The operation was successful.
- /// : The version of the commit context - /// file isn't supported.
- /// : The multi-commit hadn't finished - /// provisionally committing all the file systems.
- private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface, - IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService) + return recoveryResult; + } + + /// + /// Tries to recover a multi-commit using the context in the provided file system. + /// + /// The core interface used for multi-commits. + /// The file system containing the multi-commit context file. + /// The save data service. + /// + private static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs, + SaveDataFileSystemServiceImpl saveService) + { + if (multiCommitInterface is null) + return ResultFs.InvalidArgument.Log(); + + if (contextFs is null) + return ResultFs.InvalidArgument.Log(); + + // Keep track of the first error that occurs during the recovery + Result recoveryResult = Result.Success; + + Result rc = RecoverCommit(multiCommitInterface, contextFs, saveService); + + if (rc.IsFailure()) { - using var contextFilePath = new Fs.Path(); - Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); - if (rc.IsFailure()) return rc; - - // Read the multi-commit context - using var contextFile = new UniqueRef(); - rc = contextFs.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - - Unsafe.SkipInit(out Context context); - rc = contextFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref context), ReadOption.None); - if (rc.IsFailure()) return rc; - - // Note: Nintendo doesn't check if the proper amount of bytes were read, but it - // doesn't really matter since the context is validated. - if (context.Version > CurrentCommitContextVersion) - return ResultFs.InvalidMultiCommitContextVersion.Log(); - - // All the file systems in the multi-commit must have been at least provisionally committed - // before we can try to recover the commit. - if (context.State != CommitState.ProvisionallyCommitted) - return ResultFs.InvalidMultiCommitContextState.Log(); - - // Keep track of the first error that occurs during the recovery - Result recoveryResult = Result.Success; - + // Note: Yes, the next ~50 lines are exactly the same as the code in RecoverCommit except + // for a single bool value. No, Nintendo doesn't split it out into its own function. int saveCount = 0; Span savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; @@ -335,310 +416,228 @@ namespace LibHac.FsSrv.Impl } } - // Recover the saves by finishing their commits. + // Recover the saves by rolling them back to the previous commit. // All file systems will try to be recovered, even if one fails. // If any commits fail, the result from the first failed recovery will be returned. for (int i = 0; i < saveCount; i++) { - rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], false); + rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], true); if (rc.IsFailure() && !recoveryResult.IsFailure()) { recoveryResult = rc; } } - - return recoveryResult; } - /// - /// Tries to recover a multi-commit using the context in the provided file system. - /// - /// The core interface used for multi-commits. - /// The file system containing the multi-commit context file. - /// The save data service. - /// - private static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs, - SaveDataFileSystemServiceImpl saveService) + using var contextFilePath = new Fs.Path(); + rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); + if (rc.IsFailure()) return rc; + + // Delete the commit context file + rc = contextFs.DeleteFile(in contextFilePath); + if (rc.IsFailure()) return rc; + + rc = contextFs.Commit(); + if (rc.IsFailure()) return rc; + + return recoveryResult; + } + + /// + /// Recovers an interrupted multi-commit. The commit will either be completed or rolled back depending on + /// where in the commit process it was interrupted. Does nothing if there is no commit to recover. + /// + /// The that contains the save data to recover. + /// The core interface used for multi-commits. + /// The save data service. + /// The of the operation.
+ /// : The recovery was successful or there was no multi-commit to recover.
+ public static Result Recover(FileSystemServer fsServer, ISaveDataMultiCommitCoreInterface multiCommitInterface, + SaveDataFileSystemServiceImpl saveService) + { + using ScopedLock scopedLock = + ScopedLock.Lock(ref fsServer.Globals.MultiCommitManager.MultiCommitMutex); + + bool needsRecovery = true; + using var fileSystem = new SharedRef(); + + // Check if a multi-commit was interrupted by checking if there's a commit context file. + Result rc = multiCommitInterface.OpenMultiCommitContext(ref fileSystem.Ref()); + + if (rc.IsFailure()) { - if (multiCommitInterface is null) - return ResultFs.InvalidArgument.Log(); + if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc)) + return rc; - if (contextFs is null) - return ResultFs.InvalidArgument.Log(); - - // Keep track of the first error that occurs during the recovery - Result recoveryResult = Result.Success; - - Result rc = RecoverCommit(multiCommitInterface, contextFs, saveService); - - if (rc.IsFailure()) - { - // Note: Yes, the next ~50 lines are exactly the same as the code in RecoverCommit except - // for a single bool value. No, Nintendo doesn't split it out into its own function. - int saveCount = 0; - Span savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; - - { - using var reader = new SharedRef(); - using var accessor = new UniqueRef(); - - rc = saveService.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out _, SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - // Iterate through all the saves to find any provisionally committed save data - while (true) - { - Unsafe.SkipInit(out SaveDataInfo info); - - rc = reader.Get.Read(out long readCount, OutBuffer.FromStruct(ref info)); - if (rc.IsFailure()) return rc; - - // Break once we're done iterating all save data - if (readCount == 0) - break; - - rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, - in info); - - // Note: Multi-commits are only recovered at boot time, so some saves could be missed if there - // are more than MaxFileSystemCount provisionally committed saves. - // In theory this shouldn't happen because a multi-commit should only be interrupted if the - // entire OS is brought down. - if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount) - { - savesToRecover[saveCount] = info; - saveCount++; - } - } - } - - // Recover the saves by rolling them back to the previous commit. - // All file systems will try to be recovered, even if one fails. - // If any commits fail, the result from the first failed recovery will be returned. - for (int i = 0; i < saveCount; i++) - { - rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], true); - - if (rc.IsFailure() && !recoveryResult.IsFailure()) - { - recoveryResult = rc; - } - } - } + // Unable to open the multi-commit context file system, so there's nothing to recover + needsRecovery = false; + } + if (needsRecovery) + { using var contextFilePath = new Fs.Path(); rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); if (rc.IsFailure()) return rc; - // Delete the commit context file - rc = contextFs.DeleteFile(in contextFilePath); - if (rc.IsFailure()) return rc; - - rc = contextFs.Commit(); - if (rc.IsFailure()) return rc; - - return recoveryResult; - } - - /// - /// Recovers an interrupted multi-commit. The commit will either be completed or rolled back depending on - /// where in the commit process it was interrupted. Does nothing if there is no commit to recover. - /// - /// The that contains the save data to recover. - /// The core interface used for multi-commits. - /// The save data service. - /// The of the operation.
- /// : The recovery was successful or there was no multi-commit to recover.
- public static Result Recover(FileSystemServer fsServer, ISaveDataMultiCommitCoreInterface multiCommitInterface, - SaveDataFileSystemServiceImpl saveService) - { - using ScopedLock scopedLock = - ScopedLock.Lock(ref fsServer.Globals.MultiCommitManager.MultiCommitMutex); - - bool needsRecovery = true; - using var fileSystem = new SharedRef(); - - // Check if a multi-commit was interrupted by checking if there's a commit context file. - Result rc = multiCommitInterface.OpenMultiCommitContext(ref fileSystem.Ref()); + using var file = new UniqueRef(); + rc = fileSystem.Get.OpenFile(ref file.Ref(), in contextFilePath, OpenMode.Read); if (rc.IsFailure()) { - if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc)) - return rc; - - // Unable to open the multi-commit context file system, so there's nothing to recover - needsRecovery = false; + // Unable to open the context file. No multi-commit to recover. + if (ResultFs.PathNotFound.Includes(rc)) + needsRecovery = false; } + } - if (needsRecovery) + if (!needsRecovery) + return Result.Success; + + // There was a context file. Recover the unfinished commit. + return Recover(multiCommitInterface, fileSystem.Get, saveService); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x18)] + private struct Context + { + [FieldOffset(0x00)] public int Version; + [FieldOffset(0x04)] public CommitState State; + [FieldOffset(0x08)] public int FileSystemCount; + [FieldOffset(0x10)] public long Counter; + } + + private enum CommitState + { + // ReSharper disable once UnusedMember.Local + None = 0, + NotCommitted = 1, + ProvisionallyCommitted = 2 + } + + private struct ContextUpdater : IDisposable + { + private Context _context; + private IFileSystem _fileSystem; + + public ContextUpdater(IFileSystem contextFileSystem) + { + _context = default; + _fileSystem = contextFileSystem; + } + + public void Dispose() + { + if (_fileSystem is null) return; + + using var contextFilePath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName).IgnoreResult(); + _fileSystem.DeleteFile(in contextFilePath).IgnoreResult(); + _fileSystem.Commit().IgnoreResult(); + + _fileSystem = null; + } + + /// + /// Creates and writes the initial commit context to a file. + /// + /// The counter. + /// The number of file systems being committed. + /// The of the operation. + public Result Create(long counter, int fileSystemCount) + { + using var contextFilePath = new Fs.Path(); + Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); + if (rc.IsFailure()) return rc; + + // Open context file and create if it doesn't exist + using (var contextFile = new UniqueRef()) { - using var contextFilePath = new Fs.Path(); - rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(); - rc = fileSystem.Get.OpenFile(ref file.Ref(), in contextFilePath, OpenMode.Read); + rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.Read); if (rc.IsFailure()) { - // Unable to open the context file. No multi-commit to recover. - if (ResultFs.PathNotFound.Includes(rc)) - needsRecovery = false; - } - } + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; - if (!needsRecovery) - return Result.Success; + rc = _fileSystem.CreateFile(in contextFilePath, CommitContextFileSize); + if (rc.IsFailure()) return rc; - // There was a context file. Recover the unfinished commit. - return Recover(multiCommitInterface, fileSystem.Get, saveService); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x18)] - private struct Context - { - [FieldOffset(0x00)] public int Version; - [FieldOffset(0x04)] public CommitState State; - [FieldOffset(0x08)] public int FileSystemCount; - [FieldOffset(0x10)] public long Counter; - } - - private enum CommitState - { - // ReSharper disable once UnusedMember.Local - None = 0, - NotCommitted = 1, - ProvisionallyCommitted = 2 - } - - private struct ContextUpdater : IDisposable - { - private Context _context; - private IFileSystem _fileSystem; - - public ContextUpdater(IFileSystem contextFileSystem) - { - _context = default; - _fileSystem = contextFileSystem; - } - - public void Dispose() - { - if (_fileSystem is null) return; - - using var contextFilePath = new Fs.Path(); - PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName).IgnoreResult(); - _fileSystem.DeleteFile(in contextFilePath).IgnoreResult(); - _fileSystem.Commit().IgnoreResult(); - - _fileSystem = null; - } - - /// - /// Creates and writes the initial commit context to a file. - /// - /// The counter. - /// The number of file systems being committed. - /// The of the operation. - public Result Create(long counter, int fileSystemCount) - { - using var contextFilePath = new Fs.Path(); - Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); - if (rc.IsFailure()) return rc; - - // Open context file and create if it doesn't exist - using (var contextFile = new UniqueRef()) - { rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.Read); - - if (rc.IsFailure()) - { - if (!ResultFs.PathNotFound.Includes(rc)) - return rc; - - rc = _fileSystem.CreateFile(in contextFilePath, CommitContextFileSize); - if (rc.IsFailure()) return rc; - - rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.Read); - if (rc.IsFailure()) return rc; - } - } - - using (var contextFile = new UniqueRef()) - { - rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - - _context.Version = CurrentCommitContextVersion; - _context.State = CommitState.NotCommitted; - _context.FileSystemCount = fileSystemCount; - _context.Counter = counter; - - // Write the initial context to the file - rc = contextFile.Get.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); - if (rc.IsFailure()) return rc; - - rc = contextFile.Get.Flush(); if (rc.IsFailure()) return rc; } + } - rc = _fileSystem.Commit(); + using (var contextFile = new UniqueRef()) + { + rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; - return Result.Success; + _context.Version = CurrentCommitContextVersion; + _context.State = CommitState.NotCommitted; + _context.FileSystemCount = fileSystemCount; + _context.Counter = counter; + + // Write the initial context to the file + rc = contextFile.Get.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); + if (rc.IsFailure()) return rc; + + rc = contextFile.Get.Flush(); + if (rc.IsFailure()) return rc; } - /// - /// Updates the commit context and writes it to a file, signifying that all - /// the file systems have been provisionally committed. - /// - /// The of the operation. - public Result CommitProvisionallyDone() + rc = _fileSystem.Commit(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + /// + /// Updates the commit context and writes it to a file, signifying that all + /// the file systems have been provisionally committed. + /// + /// The of the operation. + public Result CommitProvisionallyDone() + { + using (var contextFilePath = new Fs.Path()) { - using (var contextFilePath = new Fs.Path()) - { - Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); - if (rc.IsFailure()) return rc; - - using var contextFile = new UniqueRef(); - rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - - _context.State = CommitState.ProvisionallyCommitted; - - rc = contextFile.Get.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); - if (rc.IsFailure()) return rc; - - rc = contextFile.Get.Flush(); - if (rc.IsFailure()) return rc; - } - - return _fileSystem.Commit(); - } - - /// - /// To be called once the multi-commit has been successfully completed. Deletes the commit context file. - /// - /// The of the operation. - public Result CommitDone() - { - using var contextFilePath = new Fs.Path(); Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); if (rc.IsFailure()) return rc; - rc = _fileSystem.DeleteFile(in contextFilePath); + using var contextFile = new UniqueRef(); + rc = _fileSystem.OpenFile(ref contextFile.Ref(), in contextFilePath, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; - rc = _fileSystem.Commit(); + _context.State = CommitState.ProvisionallyCommitted; + + rc = contextFile.Get.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); if (rc.IsFailure()) return rc; - _fileSystem = null; - return Result.Success; + rc = contextFile.Get.Flush(); + if (rc.IsFailure()) return rc; } + + return _fileSystem.Commit(); + } + + /// + /// To be called once the multi-commit has been successfully completed. Deletes the commit context file. + /// + /// The of the operation. + public Result CommitDone() + { + using var contextFilePath = new Fs.Path(); + Result rc = PathFunctions.SetUpFixedPath(ref contextFilePath.Ref(), CommitContextFileName); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.DeleteFile(in contextFilePath); + if (rc.IsFailure()) return rc; + + rc = _fileSystem.Commit(); + if (rc.IsFailure()) return rc; + + _fileSystem = null; + return Result.Success; } } } diff --git a/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs index 6d03ddaa..82505a6e 100644 --- a/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs @@ -3,68 +3,67 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +internal class OpenCountFileSystem : ForwardingFileSystem { - internal class OpenCountFileSystem : ForwardingFileSystem + private SharedRef _entryCountSemaphore; + private UniqueRef _mountCountSemaphore; + + public OpenCountFileSystem(ref SharedRef baseFileSystem, + ref SharedRef entryCountSemaphore) : base(ref baseFileSystem) { - private SharedRef _entryCountSemaphore; - private UniqueRef _mountCountSemaphore; + _entryCountSemaphore = SharedRef.CreateMove(ref entryCountSemaphore); + } - public OpenCountFileSystem(ref SharedRef baseFileSystem, - ref SharedRef entryCountSemaphore) : base(ref baseFileSystem) - { - _entryCountSemaphore = SharedRef.CreateMove(ref entryCountSemaphore); - } + public OpenCountFileSystem(ref SharedRef baseFileSystem, + ref SharedRef entryCountSemaphore, + ref UniqueRef mountCountSemaphore) : base(ref baseFileSystem) + { + _entryCountSemaphore = SharedRef.CreateMove(ref entryCountSemaphore); + _mountCountSemaphore = new UniqueRef(ref mountCountSemaphore); + } - public OpenCountFileSystem(ref SharedRef baseFileSystem, - ref SharedRef entryCountSemaphore, - ref UniqueRef mountCountSemaphore) : base(ref baseFileSystem) - { - _entryCountSemaphore = SharedRef.CreateMove(ref entryCountSemaphore); - _mountCountSemaphore = new UniqueRef(ref mountCountSemaphore); - } + public static SharedRef CreateShared( + ref SharedRef baseFileSystem, + ref SharedRef entryCountSemaphore, + ref UniqueRef mountCountSemaphore) + { + var filesystem = + new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore, ref mountCountSemaphore); - public static SharedRef CreateShared( - ref SharedRef baseFileSystem, - ref SharedRef entryCountSemaphore, - ref UniqueRef mountCountSemaphore) - { - var filesystem = - new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore, ref mountCountSemaphore); + return new SharedRef(filesystem); + } - return new SharedRef(filesystem); - } + public static SharedRef CreateShared( + ref SharedRef baseFileSystem, + ref SharedRef entryCountSemaphore) + { + var filesystem = + new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore); - public static SharedRef CreateShared( - ref SharedRef baseFileSystem, - ref SharedRef entryCountSemaphore) - { - var filesystem = - new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore); + return new SharedRef(filesystem); + } - return new SharedRef(filesystem); - } + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(ref outFile, path, mode); + } - // ReSharper disable once RedundantOverriddenMember - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - // Todo: Implement - return base.DoOpenFile(ref outFile, path, mode); - } + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + // Todo: Implement + return base.DoOpenDirectory(ref outDirectory, path, mode); + } - // ReSharper disable once RedundantOverriddenMember - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - // Todo: Implement - return base.DoOpenDirectory(ref outDirectory, path, mode); - } - - public override void Dispose() - { - _entryCountSemaphore.Destroy(); - _mountCountSemaphore.Destroy(); - base.Dispose(); - } + public override void Dispose() + { + _entryCountSemaphore.Destroy(); + _mountCountSemaphore.Destroy(); + base.Dispose(); } } diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs index 2e4806e6..6e722f71 100644 --- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs @@ -4,209 +4,208 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Ncm; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +/// +/// Handles adding, removing, and accessing from the . +/// +/// Based on FS 10.0.0 (nnSdk 10.4.0) +internal class ProgramRegistryManager { - /// - /// Handles adding, removing, and accessing from the . - /// - /// Based on FS 10.0.0 (nnSdk 10.4.0) - internal class ProgramRegistryManager + // Note: FS keeps each ProgramInfo in a shared_ptr, but there aren't any non-memory resources + // that need to be freed, so we use plain ProgramInfos + private LinkedList ProgramInfoList { get; } + private FileSystemServer FsServer { get; } + + // Note: This variable is global in FS. It's moved to ProgramRegistryManager here because it + // relies on some state kept in FileSystemServer, and it's only used by ProgramRegistryManager + private ProgramInfo _programInfoForInitialProcess; + private readonly object _programInfoForInitialProcessGuard = new object(); + + public ProgramRegistryManager(FileSystemServer fsServer) { - // Note: FS keeps each ProgramInfo in a shared_ptr, but there aren't any non-memory resources - // that need to be freed, so we use plain ProgramInfos - private LinkedList ProgramInfoList { get; } - private FileSystemServer FsServer { get; } + ProgramInfoList = new LinkedList(); + FsServer = fsServer; + } - // Note: This variable is global in FS. It's moved to ProgramRegistryManager here because it - // relies on some state kept in FileSystemServer, and it's only used by ProgramRegistryManager - private ProgramInfo _programInfoForInitialProcess; - private readonly object _programInfoForInitialProcessGuard = new object(); - - public ProgramRegistryManager(FileSystemServer fsServer) + private ProgramInfo GetProgramInfoForInitialProcess() + { + if (_programInfoForInitialProcess == null) { - ProgramInfoList = new LinkedList(); - FsServer = fsServer; - } - - private ProgramInfo GetProgramInfoForInitialProcess() - { - if (_programInfoForInitialProcess == null) + lock (_programInfoForInitialProcessGuard) { - lock (_programInfoForInitialProcessGuard) - { - _programInfoForInitialProcess ??= ProgramInfo.CreateProgramInfoForInitialProcess(FsServer); - } - } - - return _programInfoForInitialProcess; - } - - /// - /// Registers a program with information about that program in the program registry. - /// - /// The process ID of the program. - /// The of the program. - /// The where the program is located. - /// The FS access control data header located in the program's ACI. - /// The FS access control descriptor located in the program's ACID. - /// : The operation was successful.
- /// : The process ID is already registered.
- public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) - { - var programInfo = new ProgramInfo(FsServer, processId, programId, storageId, accessControlData, - accessControlDescriptor); - - lock (ProgramInfoList) - { - foreach (ProgramInfo info in ProgramInfoList) - { - if (info.Contains(processId)) - return ResultFs.InvalidArgument.Log(); - } - - ProgramInfoList.AddLast(programInfo); - return Result.Success; + _programInfoForInitialProcess ??= ProgramInfo.CreateProgramInfoForInitialProcess(FsServer); } } - /// - /// Unregisters the program with the specified process ID. - /// - /// The process ID of the program to unregister. - /// : The operation was successful.
- /// : The process ID is not registered.
- public Result UnregisterProgram(ulong processId) - { - lock (ProgramInfoList) - { - for (LinkedListNode node = ProgramInfoList.First; node != null; node = node.Next) - { - if (node.Value.Contains(processId)) - { - ProgramInfoList.Remove(node); - return Result.Success; - } - } + return _programInfoForInitialProcess; + } - return ResultFs.InvalidArgument.Log(); + /// + /// Registers a program with information about that program in the program registry. + /// + /// The process ID of the program. + /// The of the program. + /// The where the program is located. + /// The FS access control data header located in the program's ACI. + /// The FS access control descriptor located in the program's ACID. + /// : The operation was successful.
+ /// : The process ID is already registered.
+ public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + { + var programInfo = new ProgramInfo(FsServer, processId, programId, storageId, accessControlData, + accessControlDescriptor); + + lock (ProgramInfoList) + { + foreach (ProgramInfo info in ProgramInfoList) + { + if (info.Contains(processId)) + return ResultFs.InvalidArgument.Log(); } - } - /// - /// Gets the associated with the specified process ID. - /// - /// If the method returns successfully, contains the - /// associated with the specified process ID. - /// The process ID of the to get. - /// : The operation was successful.
- /// : The was not found.
- public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + ProgramInfoList.AddLast(programInfo); + return Result.Success; + } + } + + /// + /// Unregisters the program with the specified process ID. + /// + /// The process ID of the program to unregister. + /// : The operation was successful.
+ /// : The process ID is not registered.
+ public Result UnregisterProgram(ulong processId) + { + lock (ProgramInfoList) { - lock (ProgramInfoList) + for (LinkedListNode node = ProgramInfoList.First; node != null; node = node.Next) { - if (ProgramInfo.IsInitialProgram(processId)) + if (node.Value.Contains(processId)) { - programInfo = GetProgramInfoForInitialProcess(); + ProgramInfoList.Remove(node); return Result.Success; } - - foreach (ProgramInfo info in ProgramInfoList) - { - if (info.Contains(processId)) - { - programInfo = info; - return Result.Success; - } - } - - UnsafeHelpers.SkipParamInit(out programInfo); - return ResultFs.ProgramInfoNotFound.Log(); } - } - /// - /// Gets the associated with the specified program ID. - /// - /// If the method returns successfully, contains the - /// associated with the specified program ID. - /// The program ID of the to get. - /// : The operation was successful.
- /// : The was not found.
- public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - lock (ProgramInfoList) - { - foreach (ProgramInfo info in ProgramInfoList) - { - if (info.ProgramId.Value == programId) - { - programInfo = info; - return Result.Success; - } - } - - UnsafeHelpers.SkipParamInit(out programInfo); - return ResultFs.ProgramInfoNotFound.Log(); - } + return ResultFs.InvalidArgument.Log(); } } - public class ProgramInfo + /// + /// Gets the associated with the specified process ID. + /// + /// If the method returns successfully, contains the + /// associated with the specified process ID. + /// The process ID of the to get. + /// : The operation was successful.
+ /// : The was not found.
+ public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) { - private ulong ProcessId { get; } - public ProgramId ProgramId { get; } - public StorageId StorageId { get; } - public AccessControl AccessControl { get; } - - public ulong ProgramIdValue => ProgramId.Value; - - public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + lock (ProgramInfoList) { - ProcessId = processId; - AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor); - ProgramId = programId; - StorageId = storageId; + if (ProgramInfo.IsInitialProgram(processId)) + { + programInfo = GetProgramInfoForInitialProcess(); + return Result.Success; + } + + foreach (ProgramInfo info in ProgramInfoList) + { + if (info.Contains(processId)) + { + programInfo = info; + return Result.Success; + } + } + + UnsafeHelpers.SkipParamInit(out programInfo); + return ResultFs.ProgramInfoNotFound.Log(); } + } - private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData, - ReadOnlySpan accessControlDescriptor) + /// + /// Gets the associated with the specified program ID. + /// + /// If the method returns successfully, contains the + /// associated with the specified program ID. + /// The program ID of the to get. + /// : The operation was successful.
+ /// : The was not found.
+ public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + lock (ProgramInfoList) { - ProcessId = 0; - AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue); - ProgramId = default; - StorageId = 0; + foreach (ProgramInfo info in ProgramInfoList) + { + if (info.ProgramId.Value == programId) + { + programInfo = info; + return Result.Success; + } + } + + UnsafeHelpers.SkipParamInit(out programInfo); + return ResultFs.ProgramInfoNotFound.Log(); } - - public bool Contains(ulong processId) => ProcessId == processId; - - public static bool IsInitialProgram(ulong processId) - { - // Todo: We have no kernel to call into, so use hardcoded values for now - const int initialProcessIdLowerBound = 1; - const int initialProcessIdUpperBound = 0x50; - - return initialProcessIdLowerBound <= processId && processId <= initialProcessIdUpperBound; - } - - internal static ProgramInfo CreateProgramInfoForInitialProcess(FileSystemServer fsServer) - { - return new ProgramInfo(fsServer, InitialProcessAccessControlDataHeader, - InitialProcessAccessControlDescriptor); - } - - private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[] - { - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[] - { - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; } } + +public class ProgramInfo +{ + private ulong ProcessId { get; } + public ProgramId ProgramId { get; } + public StorageId StorageId { get; } + public AccessControl AccessControl { get; } + + public ulong ProgramIdValue => ProgramId.Value; + + public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId, + ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + { + ProcessId = processId; + AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor); + ProgramId = programId; + StorageId = storageId; + } + + private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData, + ReadOnlySpan accessControlDescriptor) + { + ProcessId = 0; + AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue); + ProgramId = default; + StorageId = 0; + } + + public bool Contains(ulong processId) => ProcessId == processId; + + public static bool IsInitialProgram(ulong processId) + { + // Todo: We have no kernel to call into, so use hardcoded values for now + const int initialProcessIdLowerBound = 1; + const int initialProcessIdUpperBound = 0x50; + + return initialProcessIdLowerBound <= processId && processId <= initialProcessIdUpperBound; + } + + internal static ProgramInfo CreateProgramInfoForInitialProcess(FileSystemServer fsServer) + { + return new ProgramInfo(fsServer, InitialProcessAccessControlDataHeader, + InitialProcessAccessControlDescriptor); + } + + private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; +} diff --git a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs index 9db3f512..aa378dfe 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs @@ -5,138 +5,137 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Os; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +/// +/// Holds the s for opened save data file systems. +/// +public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorCacheObserver { - /// - /// Holds the s for opened save data file systems. - /// - public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorCacheObserver + [NonCopyable] + private struct Cache : IDisposable { - [NonCopyable] - private struct Cache : IDisposable + private WeakRef _accessor; + private readonly SaveDataSpaceId _spaceId; + private readonly ulong _saveDataId; + + public Cache(in SharedRef accessor, SaveDataSpaceId spaceId, + ulong saveDataId) { - private WeakRef _accessor; - private readonly SaveDataSpaceId _spaceId; - private readonly ulong _saveDataId; - - public Cache(in SharedRef accessor, SaveDataSpaceId spaceId, - ulong saveDataId) - { - _accessor = new WeakRef(in accessor); - _spaceId = spaceId; - _saveDataId = saveDataId; - } - - public void Dispose() - { - _accessor.Destroy(); - } - - public bool Contains(SaveDataSpaceId spaceId, ulong saveDataId) - { - return _spaceId == spaceId && _saveDataId == saveDataId; - } - - public SharedRef Lock() - { - return _accessor.Lock(); - } - } - - private LinkedList _accessorList; - private SdkRecursiveMutexType _mutex; - - public SaveDataExtraDataAccessorCacheManager() - { - _accessorList = new LinkedList(); - _mutex.Initialize(); + _accessor = new WeakRef(in accessor); + _spaceId = spaceId; + _saveDataId = saveDataId; } public void Dispose() { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - LinkedListNode currentEntry = _accessorList.First; - - while (currentEntry is not null) - { - ref Cache entry = ref currentEntry.ValueRef; - _accessorList.Remove(currentEntry); - entry.Dispose(); - - currentEntry = _accessorList.First; - } - - _accessorList.Clear(); + _accessor.Destroy(); } - public Result Register(in SharedRef accessor, SaveDataSpaceId spaceId, - ulong saveDataId) + public bool Contains(SaveDataSpaceId spaceId, ulong saveDataId) { - var node = new LinkedListNode(new Cache(in accessor, spaceId, saveDataId)); - - using (ScopedLock.Lock(ref _mutex)) - { - UnregisterImpl(spaceId, saveDataId); - _accessorList.AddLast(node); - } - - return Result.Success; + return _spaceId == spaceId && _saveDataId == saveDataId; } - public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + public SharedRef Lock() { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - UnregisterImpl(spaceId, saveDataId); - } - - private void UnregisterImpl(SaveDataSpaceId spaceId, ulong saveDataId) - { - LinkedListNode currentNode = _accessorList.First; - - while (currentNode is not null) - { - if (currentNode.ValueRef.Contains(spaceId, saveDataId)) - { - _accessorList.Remove(currentNode); - return; - } - - currentNode = currentNode.Next; - } - } - - public Result GetCache(ref SharedRef outAccessor, SaveDataSpaceId spaceId, - ulong saveDataId) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - LinkedListNode currentNode = _accessorList.First; - - while (true) - { - if (currentNode is null) - return ResultFs.TargetNotFound.Log(); - - if (currentNode.ValueRef.Contains(spaceId, saveDataId)) - break; - - currentNode = currentNode.Next; - } - - using SharedRef accessor = currentNode.ValueRef.Lock(); - - if (!accessor.HasValue) - return ResultFs.TargetNotFound.Log(); - - outAccessor.Reset(new SaveDataExtraDataResultConvertAccessor(ref accessor.Ref())); - return Result.Success; - } - - public UniqueLockRef GetScopedLock() - { - return new UniqueLockRef(ref _mutex); + return _accessor.Lock(); } } -} \ No newline at end of file + + private LinkedList _accessorList; + private SdkRecursiveMutexType _mutex; + + public SaveDataExtraDataAccessorCacheManager() + { + _accessorList = new LinkedList(); + _mutex.Initialize(); + } + + public void Dispose() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + LinkedListNode currentEntry = _accessorList.First; + + while (currentEntry is not null) + { + ref Cache entry = ref currentEntry.ValueRef; + _accessorList.Remove(currentEntry); + entry.Dispose(); + + currentEntry = _accessorList.First; + } + + _accessorList.Clear(); + } + + public Result Register(in SharedRef accessor, SaveDataSpaceId spaceId, + ulong saveDataId) + { + var node = new LinkedListNode(new Cache(in accessor, spaceId, saveDataId)); + + using (ScopedLock.Lock(ref _mutex)) + { + UnregisterImpl(spaceId, saveDataId); + _accessorList.AddLast(node); + } + + return Result.Success; + } + + public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + UnregisterImpl(spaceId, saveDataId); + } + + private void UnregisterImpl(SaveDataSpaceId spaceId, ulong saveDataId) + { + LinkedListNode currentNode = _accessorList.First; + + while (currentNode is not null) + { + if (currentNode.ValueRef.Contains(spaceId, saveDataId)) + { + _accessorList.Remove(currentNode); + return; + } + + currentNode = currentNode.Next; + } + } + + public Result GetCache(ref SharedRef outAccessor, SaveDataSpaceId spaceId, + ulong saveDataId) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + LinkedListNode currentNode = _accessorList.First; + + while (true) + { + if (currentNode is null) + return ResultFs.TargetNotFound.Log(); + + if (currentNode.ValueRef.Contains(spaceId, saveDataId)) + break; + + currentNode = currentNode.Next; + } + + using SharedRef accessor = currentNode.ValueRef.Lock(); + + if (!accessor.HasValue) + return ResultFs.TargetNotFound.Log(); + + outAccessor.Reset(new SaveDataExtraDataResultConvertAccessor(ref accessor.Ref())); + return Result.Success; + } + + public UniqueLockRef GetScopedLock() + { + return new UniqueLockRef(ref _mutex); + } +} diff --git a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs index 528f05c2..82070e27 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs @@ -1,103 +1,102 @@ using LibHac.Diag; using LibHac.Fs; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public static class SaveDataProperties { - public static class SaveDataProperties + public const long DefaultSaveDataBlockSize = 0x4000; + public const long BcatSaveDataJournalSize = 0x200000; + + public static bool IsJournalingSupported(SaveDataType type) { - public const long DefaultSaveDataBlockSize = 0x4000; - public const long BcatSaveDataJournalSize = 0x200000; - - public static bool IsJournalingSupported(SaveDataType type) + switch (type) { - switch (type) - { - case SaveDataType.System: - case SaveDataType.Account: - case SaveDataType.Bcat: - case SaveDataType.Device: - case SaveDataType.Cache: - return true; - case SaveDataType.Temporary: - return false; - default: - Abort.UnexpectedDefault(); - return default; - } + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Cache: + return true; + case SaveDataType.Temporary: + return false; + default: + Abort.UnexpectedDefault(); + return default; } + } - public static bool IsMultiCommitSupported(SaveDataType type) + public static bool IsMultiCommitSupported(SaveDataType type) + { + switch (type) { - switch (type) - { - case SaveDataType.System: - case SaveDataType.Account: - case SaveDataType.Device: - return true; - case SaveDataType.Bcat: - case SaveDataType.Temporary: - case SaveDataType.Cache: - return false; - default: - Abort.UnexpectedDefault(); - return default; - } + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Device: + return true; + case SaveDataType.Bcat: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; } + } - public static bool IsSharedOpenNeeded(SaveDataType type) + public static bool IsSharedOpenNeeded(SaveDataType type) + { + switch (type) { - switch (type) - { - case SaveDataType.System: - case SaveDataType.Bcat: - case SaveDataType.Temporary: - case SaveDataType.Cache: - return false; - case SaveDataType.Account: - case SaveDataType.Device: - return true; - default: - Abort.UnexpectedDefault(); - return default; - } + case SaveDataType.System: + case SaveDataType.Bcat: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + case SaveDataType.Account: + case SaveDataType.Device: + return true; + default: + Abort.UnexpectedDefault(); + return default; } + } - public static bool CanUseIndexerReservedArea(SaveDataType type) + public static bool CanUseIndexerReservedArea(SaveDataType type) + { + switch (type) { - switch (type) - { - case SaveDataType.System: - case SaveDataType.SystemBcat: - return true; - case SaveDataType.Account: - case SaveDataType.Bcat: - case SaveDataType.Device: - case SaveDataType.Temporary: - case SaveDataType.Cache: - return false; - default: - Abort.UnexpectedDefault(); - return default; - } + case SaveDataType.System: + case SaveDataType.SystemBcat: + return true; + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; } + } - public static bool IsSystemSaveData(SaveDataType type) + public static bool IsSystemSaveData(SaveDataType type) + { + switch (type) { - switch (type) - { - case SaveDataType.System: - case SaveDataType.SystemBcat: - return true; - case SaveDataType.Account: - case SaveDataType.Bcat: - case SaveDataType.Device: - case SaveDataType.Temporary: - case SaveDataType.Cache: - return false; - default: - Abort.UnexpectedDefault(); - return default; - } + case SaveDataType.System: + case SaveDataType.SystemBcat: + return true; + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; } } } diff --git a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs index 0644cd9e..89bc0e38 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs @@ -4,238 +4,237 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public static class SaveDataResultConvert { - public static class SaveDataResultConvert + private static Result ConvertCorruptedResult(Result result) { - private static Result ConvertCorruptedResult(Result result) + if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) { - if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) - { - if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) - return ResultFs.IncorrectSaveDataIntegrityVerificationMagic.Value; + if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) + return ResultFs.IncorrectSaveDataIntegrityVerificationMagic.Value; - if (ResultFs.InvalidZeroHash.Includes(result)) - return ResultFs.InvalidSaveDataZeroHash.Value; + if (ResultFs.InvalidZeroHash.Includes(result)) + return ResultFs.InvalidSaveDataZeroHash.Value; - if (ResultFs.NonRealDataVerificationFailed.Includes(result)) - return ResultFs.SaveDataNonRealDataVerificationFailed.Value; + if (ResultFs.NonRealDataVerificationFailed.Includes(result)) + return ResultFs.SaveDataNonRealDataVerificationFailed.Value; - if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) - return ResultFs.ClearedSaveDataRealDataVerificationFailed.Value; + if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) + return ResultFs.ClearedSaveDataRealDataVerificationFailed.Value; - if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) - return ResultFs.UnclearedSaveDataRealDataVerificationFailed.Value; + if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) + return ResultFs.UnclearedSaveDataRealDataVerificationFailed.Value; - Assert.SdkAssert(false); - } + Assert.SdkAssert(false); + } - if (ResultFs.HostFileSystemCorrupted.Includes(result)) - { - if (ResultFs.HostEntryCorrupted.Includes(result)) - return ResultFs.SaveDataHostEntryCorrupted.Value; + if (ResultFs.HostFileSystemCorrupted.Includes(result)) + { + if (ResultFs.HostEntryCorrupted.Includes(result)) + return ResultFs.SaveDataHostEntryCorrupted.Value; - if (ResultFs.HostFileDataCorrupted.Includes(result)) - return ResultFs.SaveDataHostFileDataCorrupted.Value; + if (ResultFs.HostFileDataCorrupted.Includes(result)) + return ResultFs.SaveDataHostFileDataCorrupted.Value; - if (ResultFs.HostFileCorrupted.Includes(result)) - return ResultFs.SaveDataHostFileCorrupted.Value; + if (ResultFs.HostFileCorrupted.Includes(result)) + return ResultFs.SaveDataHostFileCorrupted.Value; - if (ResultFs.InvalidHostHandle.Includes(result)) - return ResultFs.InvalidSaveDataHostHandle.Value; + if (ResultFs.InvalidHostHandle.Includes(result)) + return ResultFs.InvalidSaveDataHostHandle.Value; - Assert.SdkAssert(false); - } + Assert.SdkAssert(false); + } - if (ResultFs.DatabaseCorrupted.Includes(result)) - { - if (ResultFs.InvalidAllocationTableBlock.Includes(result)) - return ResultFs.InvalidSaveDataAllocationTableBlock.Value; + if (ResultFs.DatabaseCorrupted.Includes(result)) + { + if (ResultFs.InvalidAllocationTableBlock.Includes(result)) + return ResultFs.InvalidSaveDataAllocationTableBlock.Value; - if (ResultFs.InvalidKeyValueListElementIndex.Includes(result)) - return ResultFs.InvalidSaveDataKeyValueListElementIndex.Value; + if (ResultFs.InvalidKeyValueListElementIndex.Includes(result)) + return ResultFs.InvalidSaveDataKeyValueListElementIndex.Value; - if (ResultFs.AllocationTableIteratedRangeEntry.Includes(result)) - return ResultFs.SaveDataAllocationTableIteratedRangeEntry.Value; + if (ResultFs.AllocationTableIteratedRangeEntry.Includes(result)) + return ResultFs.SaveDataAllocationTableIteratedRangeEntry.Value; - if (ResultFs.InvalidAllocationTableOffset.Includes(result)) - return ResultFs.InvalidSaveDataAllocationTableOffset.Value; + if (ResultFs.InvalidAllocationTableOffset.Includes(result)) + return ResultFs.InvalidSaveDataAllocationTableOffset.Value; - if (ResultFs.InvalidAllocationTableBlockCount.Includes(result)) - return ResultFs.InvalidSaveDataAllocationTableBlockCount.Value; + if (ResultFs.InvalidAllocationTableBlockCount.Includes(result)) + return ResultFs.InvalidSaveDataAllocationTableBlockCount.Value; - if (ResultFs.InvalidKeyValueListEntryIndex.Includes(result)) - return ResultFs.InvalidSaveDataKeyValueListEntryIndex.Value; + if (ResultFs.InvalidKeyValueListEntryIndex.Includes(result)) + return ResultFs.InvalidSaveDataKeyValueListEntryIndex.Value; - if (ResultFs.InvalidBitmapIndex.Includes(result)) - return ResultFs.InvalidSaveDataBitmapIndex.Value; + if (ResultFs.InvalidBitmapIndex.Includes(result)) + return ResultFs.InvalidSaveDataBitmapIndex.Value; - Assert.SdkAssert(false); - } + Assert.SdkAssert(false); + } - if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) - { - if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result)) - return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.Value; + if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) + { + if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result)) + return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.Value; - Assert.SdkAssert(false); - } + Assert.SdkAssert(false); + } + return result; + } + + public static Result ConvertSaveFsDriverPublicResult(Result result) + { + if (result.IsSuccess()) return result; + + if (ResultFs.UnsupportedVersion.Includes(result)) + return ResultFs.UnsupportedSaveDataVersion.Value; + + if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) || + ResultFs.BuiltInStorageCorrupted.Includes(result) || + ResultFs.HostFileSystemCorrupted.Includes(result) || + ResultFs.DatabaseCorrupted.Includes(result) || + ResultFs.ZeroBitmapFileCorrupted.Includes(result)) + { + return ConvertCorruptedResult(result); } - public static Result ConvertSaveFsDriverPublicResult(Result result) - { - if (result.IsSuccess()) - return result; - - if (ResultFs.UnsupportedVersion.Includes(result)) - return ResultFs.UnsupportedSaveDataVersion.Value; - - if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) || - ResultFs.BuiltInStorageCorrupted.Includes(result) || - ResultFs.HostFileSystemCorrupted.Includes(result) || - ResultFs.DatabaseCorrupted.Includes(result) || - ResultFs.ZeroBitmapFileCorrupted.Includes(result)) - { - return ConvertCorruptedResult(result); - } - - if (ResultFs.FatFileSystemCorrupted.Includes(result)) - return result; - - if (ResultFs.NotFound.Includes(result)) - return ResultFs.PathNotFound.Value; - - if (ResultFs.AllocationTableFull.Includes(result)) - return ResultFs.UsableSpaceNotEnough.Value; - - if (ResultFs.AlreadyExists.Includes(result)) - return ResultFs.PathAlreadyExists.Value; - - if (ResultFs.InvalidOffset.Includes(result)) - return ResultFs.OutOfRange.Value; - - if (ResultFs.IncompatiblePath.Includes(result) || - ResultFs.FileNotFound.Includes(result)) - { - return ResultFs.PathNotFound.Value; - } - + if (ResultFs.FatFileSystemCorrupted.Includes(result)) return result; - } - } - /// - /// Wraps an , converting its returned s - /// to save-data-specific s. - /// - public class SaveDataResultConvertFile : IResultConvertFile + if (ResultFs.NotFound.Includes(result)) + return ResultFs.PathNotFound.Value; + + if (ResultFs.AllocationTableFull.Includes(result)) + return ResultFs.UsableSpaceNotEnough.Value; + + if (ResultFs.AlreadyExists.Includes(result)) + return ResultFs.PathAlreadyExists.Value; + + if (ResultFs.InvalidOffset.Includes(result)) + return ResultFs.OutOfRange.Value; + + if (ResultFs.IncompatiblePath.Includes(result) || + ResultFs.FileNotFound.Includes(result)) + { + return ResultFs.PathNotFound.Value; + } + + return result; + } +} + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +public class SaveDataResultConvertFile : IResultConvertFile +{ + public SaveDataResultConvertFile(ref UniqueRef baseFile) : base(ref baseFile) { - public SaveDataResultConvertFile(ref UniqueRef baseFile) : base(ref baseFile) - { - } - - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); - } } - /// - /// Wraps an , converting its returned s - /// to save-data-specific s. - /// - public class SaveDataResultConvertDirectory : IResultConvertDirectory + protected override Result ConvertResult(Result result) { - public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory) : base(ref baseDirectory) - { - } - - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); - } + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); } +} - /// - /// Wraps an , converting its returned s - /// to save-data-specific s. - /// - public class SaveDataResultConvertFileSystem : IResultConvertFileSystem +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +public class SaveDataResultConvertDirectory : IResultConvertDirectory +{ + public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory) : base(ref baseDirectory) { - public SaveDataResultConvertFileSystem(ref SharedRef baseFileSystem) - : base(ref baseFileSystem) - { - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var file = new UniqueRef(); - Result rc = ConvertResult(BaseFileSystem.Get.OpenFile(ref file.Ref(), path, mode)); - if (rc.IsFailure()) return rc; - - outFile.Reset(new SaveDataResultConvertFile(ref file.Ref())); - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var directory = new UniqueRef(); - Result rc = ConvertResult(BaseFileSystem.Get.OpenDirectory(ref directory.Ref(), path, mode)); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new SaveDataResultConvertDirectory(ref directory.Ref())); - return Result.Success; - } - - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); - } } - /// - /// Wraps an , converting its returned s - /// to save-data-specific s. - /// - public class SaveDataExtraDataResultConvertAccessor : ISaveDataExtraDataAccessor + protected override Result ConvertResult(Result result) { - private SharedRef _accessor; - - public SaveDataExtraDataResultConvertAccessor(ref SharedRef accessor) - { - _accessor = SharedRef.CreateMove(ref accessor); - } - - public void Dispose() - { - _accessor.Destroy(); - } - - public Result WriteExtraData(in SaveDataExtraData extraData) - { - Result rc = _accessor.Get.WriteExtraData(in extraData); - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); - } - - public Result CommitExtraData(bool updateTimeStamp) - { - Result rc = _accessor.Get.CommitExtraData(updateTimeStamp); - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); - } - - public Result ReadExtraData(out SaveDataExtraData extraData) - { - Result rc = _accessor.Get.ReadExtraData(out extraData); - return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); - } - - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, - ulong saveDataId) - { - _accessor.Get.RegisterCacheObserver(observer, spaceId, saveDataId); - } + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); } -} \ No newline at end of file +} + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +public class SaveDataResultConvertFileSystem : IResultConvertFileSystem +{ + public SaveDataResultConvertFileSystem(ref SharedRef baseFileSystem) + : base(ref baseFileSystem) + { + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var file = new UniqueRef(); + Result rc = ConvertResult(BaseFileSystem.Get.OpenFile(ref file.Ref(), path, mode)); + if (rc.IsFailure()) return rc; + + outFile.Reset(new SaveDataResultConvertFile(ref file.Ref())); + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var directory = new UniqueRef(); + Result rc = ConvertResult(BaseFileSystem.Get.OpenDirectory(ref directory.Ref(), path, mode)); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(new SaveDataResultConvertDirectory(ref directory.Ref())); + return Result.Success; + } + + protected override Result ConvertResult(Result result) + { + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(result); + } +} + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +public class SaveDataExtraDataResultConvertAccessor : ISaveDataExtraDataAccessor +{ + private SharedRef _accessor; + + public SaveDataExtraDataResultConvertAccessor(ref SharedRef accessor) + { + _accessor = SharedRef.CreateMove(ref accessor); + } + + public void Dispose() + { + _accessor.Destroy(); + } + + public Result WriteExtraData(in SaveDataExtraData extraData) + { + Result rc = _accessor.Get.WriteExtraData(in extraData); + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); + } + + public Result CommitExtraData(bool updateTimeStamp) + { + Result rc = _accessor.Get.CommitExtraData(updateTimeStamp); + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); + } + + public Result ReadExtraData(out SaveDataExtraData extraData) + { + Result rc = _accessor.Get.ReadExtraData(out extraData); + return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc); + } + + public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, + ulong saveDataId) + { + _accessor.Get.RegisterCacheObserver(observer, spaceId, saveDataId); + } +} diff --git a/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs index ab900abc..79b6f4ff 100644 --- a/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs @@ -6,98 +6,97 @@ using LibHac.FsSystem; using LibHac.Sf; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +public class StorageInterfaceAdapter : IStorageSf { - public class StorageInterfaceAdapter : IStorageSf + private SharedRef _baseStorage; + + public StorageInterfaceAdapter(ref SharedRef baseStorage) { - private SharedRef _baseStorage; + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } - public StorageInterfaceAdapter(ref SharedRef baseStorage) + public void Dispose() + { + _baseStorage.Destroy(); + } + + public Result Read(long offset, OutBuffer destination, long size) + { + const int maxTryCount = 2; + + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (destination.Size < 0) + return ResultFs.InvalidSize.Log(); + + Result rc = Result.Success; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) { - _baseStorage = SharedRef.CreateMove(ref baseStorage); + rc = _baseStorage.Get.Read(offset, destination.Buffer.Slice(0, (int)size)); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; } - public void Dispose() + return rc; + } + + public Result Write(long offset, InBuffer source, long size) + { + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (source.Size < 0) + return ResultFs.InvalidSize.Log(); + + using var scopedPriorityChanger = new ScopedThreadPriorityChangerByAccessPriority( + ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write); + + return _baseStorage.Get.Write(offset, source.Buffer.Slice(0, (int)size)); + } + + public Result Flush() + { + return _baseStorage.Get.Flush(); + } + + public Result SetSize(long size) + { + return _baseStorage.Get.SetSize(size); + } + + public Result GetSize(out long size) + { + return _baseStorage.Get.GetSize(out size); + } + + public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + UnsafeHelpers.SkipParamInit(out rangeInfo); + rangeInfo.Clear(); + + if (operationId == (int)OperationId.QueryRange) { - _baseStorage.Destroy(); + Unsafe.SkipInit(out QueryRangeInfo info); + + Result rc = _baseStorage.Get.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, + offset, size, ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + + rangeInfo.Merge(in info); + } + else if (operationId == (int)OperationId.InvalidateCache) + { + Result rc = _baseStorage.Get.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; } - public Result Read(long offset, OutBuffer destination, long size) - { - const int maxTryCount = 2; - - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (destination.Size < 0) - return ResultFs.InvalidSize.Log(); - - Result rc = Result.Success; - - for (int tryNum = 0; tryNum < maxTryCount; tryNum++) - { - rc = _baseStorage.Get.Read(offset, destination.Buffer.Slice(0, (int)size)); - - // Retry on ResultDataCorrupted - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - } - - return rc; - } - - public Result Write(long offset, InBuffer source, long size) - { - if (offset < 0) - return ResultFs.InvalidOffset.Log(); - - if (source.Size < 0) - return ResultFs.InvalidSize.Log(); - - using var scopedPriorityChanger = new ScopedThreadPriorityChangerByAccessPriority( - ScopedThreadPriorityChangerByAccessPriority.AccessMode.Write); - - return _baseStorage.Get.Write(offset, source.Buffer.Slice(0, (int)size)); - } - - public Result Flush() - { - return _baseStorage.Get.Flush(); - } - - public Result SetSize(long size) - { - return _baseStorage.Get.SetSize(size); - } - - public Result GetSize(out long size) - { - return _baseStorage.Get.GetSize(out size); - } - - public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) - { - UnsafeHelpers.SkipParamInit(out rangeInfo); - rangeInfo.Clear(); - - if (operationId == (int)OperationId.QueryRange) - { - Unsafe.SkipInit(out QueryRangeInfo info); - - Result rc = _baseStorage.Get.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, - offset, size, ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - - rangeInfo.Merge(in info); - } - else if (operationId == (int)OperationId.InvalidateCache) - { - Result rc = _baseStorage.Get.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, - ReadOnlySpan.Empty); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } + return Result.Success; } } diff --git a/src/LibHac/FsSrv/Impl/Utility.cs b/src/LibHac/FsSrv/Impl/Utility.cs index 2cb71cb8..75894983 100644 --- a/src/LibHac/FsSrv/Impl/Utility.cs +++ b/src/LibHac/FsSrv/Impl/Utility.cs @@ -6,69 +6,68 @@ using LibHac.Fs.Impl; using LibHac.FsSystem; using LibHac.Util; -namespace LibHac.FsSrv.Impl +namespace LibHac.FsSrv.Impl; + +internal static class Utility { - internal static class Utility + public static bool IsHostFsMountName(ReadOnlySpan name) { - public static bool IsHostFsMountName(ReadOnlySpan name) + return StringUtils.Compare(name, CommonMountNames.HostRootFileSystemMountName) == 0; + } + + public static Result CreateSubDirectoryFileSystem(ref SharedRef outSubDirFileSystem, + ref SharedRef baseFileSystem, in Path rootPath) + { + if (rootPath.IsEmpty()) { - return StringUtils.Compare(name, CommonMountNames.HostRootFileSystemMountName) == 0; - } - - public static Result CreateSubDirectoryFileSystem(ref SharedRef outSubDirFileSystem, - ref SharedRef baseFileSystem, in Path rootPath) - { - if (rootPath.IsEmpty()) - { - outSubDirFileSystem.SetByMove(ref baseFileSystem); - return Result.Success; - } - - // Check if the directory exists - using var dir = new UniqueRef(); - Result rc = baseFileSystem.Get.OpenDirectory(ref dir.Ref(), rootPath, OpenDirectoryMode.Directory); - if (rc.IsFailure()) return rc; - - dir.Reset(); - - using var fs = new SharedRef(new SubdirectoryFileSystem(ref baseFileSystem)); - if (!fs.HasValue) - return ResultFs.AllocationMemoryFailedInSubDirectoryFileSystemCreatorA.Log(); - - rc = fs.Get.Initialize(in rootPath); - if (rc.IsFailure()) return rc; - - outSubDirFileSystem.SetByMove(ref fs.Ref()); - + outSubDirFileSystem.SetByMove(ref baseFileSystem); return Result.Success; } - public static Result WrapSubDirectory(ref SharedRef outFileSystem, - ref SharedRef baseFileSystem, in Path rootPath, bool createIfMissing) + // Check if the directory exists + using var dir = new UniqueRef(); + Result rc = baseFileSystem.Get.OpenDirectory(ref dir.Ref(), rootPath, OpenDirectoryMode.Directory); + if (rc.IsFailure()) return rc; + + dir.Reset(); + + using var fs = new SharedRef(new SubdirectoryFileSystem(ref baseFileSystem)); + if (!fs.HasValue) + return ResultFs.AllocationMemoryFailedInSubDirectoryFileSystemCreatorA.Log(); + + rc = fs.Get.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + outSubDirFileSystem.SetByMove(ref fs.Ref()); + + return Result.Success; + } + + public static Result WrapSubDirectory(ref SharedRef outFileSystem, + ref SharedRef baseFileSystem, in Path rootPath, bool createIfMissing) + { + // The path must already exist if we're not automatically creating it + if (!createIfMissing) { - // The path must already exist if we're not automatically creating it - if (!createIfMissing) - { - Result result = baseFileSystem.Get.GetEntryType(out _, in rootPath); - if (result.IsFailure()) return result; - } - - // Ensure the path exists or check if it's a directory - Result rc = FsSystem.Utility.EnsureDirectory(baseFileSystem.Get, in rootPath); - if (rc.IsFailure()) return rc; - - return CreateSubDirectoryFileSystem(ref outFileSystem, ref baseFileSystem, rootPath); + Result result = baseFileSystem.Get.GetEntryType(out _, in rootPath); + if (result.IsFailure()) return result; } - public static long ConvertZeroCommitId(in SaveDataExtraData extraData) - { - if (extraData.CommitId != 0) - return extraData.CommitId; + // Ensure the path exists or check if it's a directory + Result rc = FsSystem.Utility.EnsureDirectory(baseFileSystem.Get, in rootPath); + if (rc.IsFailure()) return rc; - Span hash = stackalloc byte[Crypto.Sha256.DigestSize]; + return CreateSubDirectoryFileSystem(ref outFileSystem, ref baseFileSystem, rootPath); + } - Crypto.Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in extraData), hash); - return BitConverter.ToInt64(hash); - } + public static long ConvertZeroCommitId(in SaveDataExtraData extraData) + { + if (extraData.CommitId != 0) + return extraData.CommitId; + + Span hash = stackalloc byte[Crypto.Sha256.DigestSize]; + + Crypto.Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in extraData), hash); + return BitConverter.ToInt64(hash); } } diff --git a/src/LibHac/FsSrv/MemoryReport.cs b/src/LibHac/FsSrv/MemoryReport.cs index 36002780..58c15ac0 100644 --- a/src/LibHac/FsSrv/MemoryReport.cs +++ b/src/LibHac/FsSrv/MemoryReport.cs @@ -1,11 +1,10 @@ -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public abstract class MemoryReport { - public abstract class MemoryReport - { - public abstract long GetFreeSizePeak(); - public abstract long GetTotalAllocatableSizePeak(); - public abstract long GetRetriedCount(); - public abstract long GetAllocateSizeMax(); - public abstract void Clear(); - } + public abstract long GetFreeSizePeak(); + public abstract long GetTotalAllocatableSizePeak(); + public abstract long GetRetriedCount(); + public abstract long GetAllocateSizeMax(); + public abstract void Clear(); } diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 05f8164f..fd765879 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -14,585 +14,584 @@ using IStorageSf = LibHac.FsSrv.Sf.IStorage; using Path = LibHac.Fs.Path; using Utility = LibHac.FsSrv.Impl.Utility; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +internal class NcaFileSystemService : IRomFileSystemAccessFailureManager { - internal class NcaFileSystemService : IRomFileSystemAccessFailureManager + private const int AocSemaphoreCount = 128; + private const int RomSemaphoreCount = 10; + + private WeakRef _selfReference; + private NcaFileSystemServiceImpl _serviceImpl; + private ulong _processId; + private SemaphoreAdapter _aocMountCountSemaphore; + private SemaphoreAdapter _romMountCountSemaphore; + + private NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId) { - private const int AocSemaphoreCount = 128; - private const int RomSemaphoreCount = 10; + _serviceImpl = serviceImpl; + _processId = processId; + _aocMountCountSemaphore = new SemaphoreAdapter(AocSemaphoreCount, AocSemaphoreCount); + _romMountCountSemaphore = new SemaphoreAdapter(RomSemaphoreCount, RomSemaphoreCount); + } - private WeakRef _selfReference; - private NcaFileSystemServiceImpl _serviceImpl; - private ulong _processId; - private SemaphoreAdapter _aocMountCountSemaphore; - private SemaphoreAdapter _romMountCountSemaphore; + public static SharedRef CreateShared(NcaFileSystemServiceImpl serviceImpl, + ulong processId) + { + // Create the service + var ncaService = new NcaFileSystemService(serviceImpl, processId); - private NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId) + // Wrap the service in a ref-counter and give the service a weak self-reference + using var sharedService = new SharedRef(ncaService); + ncaService._selfReference.Set(in sharedService); + + return SharedRef.CreateMove(ref sharedService.Ref()); + } + + public void Dispose() + { + _aocMountCountSemaphore?.Dispose(); + _romMountCountSemaphore?.Dispose(); + _selfReference.Destroy(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfoByProcessId(out programInfo, _processId); + } + + private Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) + { + return _serviceImpl.GetProgramInfoByProcessId(out programInfo, processId); + } + + private Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + return _serviceImpl.GetProgramInfoByProgramId(out programInfo, programId); + } + + public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, ProgramId programId, + FileSystemProxyType fsType) + { + const StorageType storageFlag = StorageType.All; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + // Get the program info for the caller and verify permissions + Result rc = GetProgramInfo(out ProgramInfo callerProgramInfo); + if (rc.IsFailure()) return rc; + + if (fsType != FileSystemProxyType.Manual) { - _serviceImpl = serviceImpl; - _processId = processId; - _aocMountCountSemaphore = new SemaphoreAdapter(AocSemaphoreCount, AocSemaphoreCount); - _romMountCountSemaphore = new SemaphoreAdapter(RomSemaphoreCount, RomSemaphoreCount); - } - - public static SharedRef CreateShared(NcaFileSystemServiceImpl serviceImpl, - ulong processId) - { - // Create the service - var ncaService = new NcaFileSystemService(serviceImpl, processId); - - // Wrap the service in a ref-counter and give the service a weak self-reference - using var sharedService = new SharedRef(ncaService); - ncaService._selfReference.Set(in sharedService); - - return SharedRef.CreateMove(ref sharedService.Ref()); - } - - public void Dispose() - { - _aocMountCountSemaphore?.Dispose(); - _romMountCountSemaphore?.Dispose(); - _selfReference.Destroy(); - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfoByProcessId(out programInfo, _processId); - } - - private Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) - { - return _serviceImpl.GetProgramInfoByProcessId(out programInfo, processId); - } - - private Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - return _serviceImpl.GetProgramInfoByProgramId(out programInfo, programId); - } - - public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, ProgramId programId, - FileSystemProxyType fsType) - { - const StorageType storageFlag = StorageType.All; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - // Get the program info for the caller and verify permissions - Result rc = GetProgramInfo(out ProgramInfo callerProgramInfo); - if (rc.IsFailure()) return rc; - - if (fsType != FileSystemProxyType.Manual) - { - switch (fsType) - { - case FileSystemProxyType.Logo: - case FileSystemProxyType.Control: - case FileSystemProxyType.Meta: - case FileSystemProxyType.Data: - case FileSystemProxyType.Package: - return ResultFs.NotImplemented.Log(); - default: - return ResultFs.InvalidArgument.Log(); - } - } - - Accessibility accessibility = - callerProgramInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentManual); - - if (!accessibility.CanRead) - return ResultFs.PermissionDenied.Log(); - - // Get the program info for the owner of the file system being opened - rc = GetProgramInfoByProgramId(out ProgramInfo ownerProgramInfo, programId.Value); - if (rc.IsFailure()) return rc; - - // Try to find the path to the original version of the file system - using var originalPath = new Path(); - Result originalResult = _serviceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory, - ref originalPath.Ref(), new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId); - - // The file system might have a patch version with no original version, so continue if not found - if (originalResult.IsFailure() && !ResultLr.HtmlDocumentNotFound.Includes(originalResult)) - return originalResult; - - // Try to find the path to the patch file system - using var patchPath = new Path(); - Result patchResult = _serviceImpl.ResolveRegisteredHtmlDocumentPath(ref patchPath.Ref(), programId.Value); - - using var fileSystem = new SharedRef(); - - if (ResultLr.HtmlDocumentNotFound.Includes(patchResult)) - { - // There must either be an original version or patch version of the file system being opened - if (originalResult.IsFailure()) - return originalResult; - - // There is an original version and no patch version. Open the original directly - rc = _serviceImpl.OpenFileSystem(ref fileSystem.Ref(), in originalPath, fsType, programId.Value, - isDirectory); - if (rc.IsFailure()) return rc; - } - else - { - if (patchResult.IsFailure()) - return patchResult; - - ref readonly Path originalNcaPath = ref originalResult.IsSuccess() - ? ref originalPath - : ref PathExtensions.GetNullRef(); - - // Open the file system using both the original and patch versions - rc = _serviceImpl.OpenFileSystemWithPatch(ref fileSystem.Ref(), in originalNcaPath, in patchPath, - fsType, programId.Value); - if (rc.IsFailure()) return rc; - } - - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - - using SharedRef accessFailureManager = - SharedRef.Create(in _selfReference); - - using SharedRef retryFileSystem = - DeepRetryFileSystem.CreateShared(ref asyncFileSystem.Ref(), ref accessFailureManager.Ref()); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref retryFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - public Result OpenCodeFileSystem(ref SharedRef outFileSystem, - out CodeVerificationData verificationData, in FspPath path, ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem) - { - throw new NotImplementedException(); - } - - private Result TryAcquireAddOnContentOpenCountSemaphore(ref UniqueRef outSemaphoreLock) - { - throw new NotImplementedException(); - } - - private Result TryAcquireRomMountCountSemaphore(ref UniqueRef outSemaphoreLock) - { - throw new NotImplementedException(); - } - - public void IncrementRomFsRemountForDataCorruptionCount() - { - _serviceImpl.IncrementRomFsRemountForDataCorruptionCount(); - } - - public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() - { - _serviceImpl.IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); - } - - public void IncrementRomFsRecoveredByInvalidateCacheCount() - { - _serviceImpl.IncrementRomFsRecoveredByInvalidateCacheCount(); - } - - private Result OpenDataStorageCore(ref SharedRef outStorage, out Hash ncaHeaderDigest, - ulong id, StorageId storageId) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageByProgramId(ref SharedRef outStorage, ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, - ulong id, FileSystemProxyType fsType) - { - const StorageType storageFlag = StorageType.All; - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - AccessControl ac = programInfo.AccessControl; - switch (fsType) { case FileSystemProxyType.Logo: - if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead) - return ResultFs.PermissionDenied.Log(); - break; case FileSystemProxyType.Control: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Manual: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead) - return ResultFs.PermissionDenied.Log(); - break; case FileSystemProxyType.Meta: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead) - return ResultFs.PermissionDenied.Log(); - break; case FileSystemProxyType.Data: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead) - return ResultFs.PermissionDenied.Log(); - break; case FileSystemProxyType.Package: - if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead) - return ResultFs.PermissionDenied.Log(); - break; + return ResultFs.NotImplemented.Log(); default: return ResultFs.InvalidArgument.Log(); } + } - if (fsType == FileSystemProxyType.Meta) - { - id = ulong.MaxValue; - } - else if (id == ulong.MaxValue) - { + Accessibility accessibility = + callerProgramInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentManual); + + if (!accessibility.CanRead) + return ResultFs.PermissionDenied.Log(); + + // Get the program info for the owner of the file system being opened + rc = GetProgramInfoByProgramId(out ProgramInfo ownerProgramInfo, programId.Value); + if (rc.IsFailure()) return rc; + + // Try to find the path to the original version of the file system + using var originalPath = new Path(); + Result originalResult = _serviceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory, + ref originalPath.Ref(), new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId); + + // The file system might have a patch version with no original version, so continue if not found + if (originalResult.IsFailure() && !ResultLr.HtmlDocumentNotFound.Includes(originalResult)) + return originalResult; + + // Try to find the path to the patch file system + using var patchPath = new Path(); + Result patchResult = _serviceImpl.ResolveRegisteredHtmlDocumentPath(ref patchPath.Ref(), programId.Value); + + using var fileSystem = new SharedRef(); + + if (ResultLr.HtmlDocumentNotFound.Includes(patchResult)) + { + // There must either be an original version or patch version of the file system being opened + if (originalResult.IsFailure()) + return originalResult; + + // There is an original version and no patch version. Open the original directly + rc = _serviceImpl.OpenFileSystem(ref fileSystem.Ref(), in originalPath, fsType, programId.Value, + isDirectory); + if (rc.IsFailure()) return rc; + } + else + { + if (patchResult.IsFailure()) + return patchResult; + + ref readonly Path originalNcaPath = ref originalResult.IsSuccess() + ? ref originalPath + : ref PathExtensions.GetNullRef(); + + // Open the file system using both the original and patch versions + rc = _serviceImpl.OpenFileSystemWithPatch(ref fileSystem.Ref(), in originalNcaPath, in patchPath, + fsType, programId.Value); + if (rc.IsFailure()) return rc; + } + + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + + using SharedRef accessFailureManager = + SharedRef.Create(in _selfReference); + + using SharedRef retryFileSystem = + DeepRetryFileSystem.CreateShared(ref asyncFileSystem.Ref(), ref accessFailureManager.Ref()); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref retryFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenCodeFileSystem(ref SharedRef outFileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem) + { + throw new NotImplementedException(); + } + + private Result TryAcquireAddOnContentOpenCountSemaphore(ref UniqueRef outSemaphoreLock) + { + throw new NotImplementedException(); + } + + private Result TryAcquireRomMountCountSemaphore(ref UniqueRef outSemaphoreLock) + { + throw new NotImplementedException(); + } + + public void IncrementRomFsRemountForDataCorruptionCount() + { + _serviceImpl.IncrementRomFsRemountForDataCorruptionCount(); + } + + public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() + { + _serviceImpl.IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); + } + + public void IncrementRomFsRecoveredByInvalidateCacheCount() + { + _serviceImpl.IncrementRomFsRecoveredByInvalidateCacheCount(); + } + + private Result OpenDataStorageCore(ref SharedRef outStorage, out Hash ncaHeaderDigest, + ulong id, StorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByProgramId(ref SharedRef outStorage, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, + ulong id, FileSystemProxyType fsType) + { + const StorageType storageFlag = StorageType.All; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + AccessControl ac = programInfo.AccessControl; + + switch (fsType) + { + case FileSystemProxyType.Logo: + if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Control: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Manual: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Meta: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Data: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Package: + if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + default: return ResultFs.InvalidArgument.Log(); - } - - bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; - - using var pathNormalized = new Path(); - rc = pathNormalized.InitializeWithReplaceUnc(path.Str); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowWindowsPath(); - pathFlags.AllowMountName(); - rc = pathNormalized.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - bool isDirectory = PathUtility.IsDirectoryPath(in path); - - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenFileSystem(ref fileSystem.Ref(), in pathNormalized, fsType, canMountSystemDataPrivate, - id, isDirectory); - if (rc.IsFailure()) return rc; - - // Add all the wrappers for the file system - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; } - public Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, ProgramId programId) + if (fsType == FileSystemProxyType.Meta) { - throw new NotImplementedException(); + id = ulong.MaxValue; } - - public Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, StorageId storageId) + else if (id == ulong.MaxValue) { - throw new NotImplementedException(); + return ResultFs.InvalidArgument.Log(); } - public Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, byte programIndex) + bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; + + using var pathNormalized = new Path(); + rc = pathNormalized.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowMountName(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + bool isDirectory = PathUtility.IsDirectoryPath(in path); + + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenFileSystem(ref fileSystem.Ref(), in pathNormalized, fsType, canMountSystemDataPrivate, + id, isDirectory); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + public Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, StorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, byte programIndex) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + // Get the program ID of the program with the specified index if in a multi-program application + rc = _serviceImpl.ResolveRomReferenceProgramId(out ProgramId targetProgramId, programInfo.ProgramId, + programIndex); + if (rc.IsFailure()) return rc; + + using var fileSystem = new SharedRef(); + rc = OpenDataFileSystemCore(ref fileSystem.Ref(), out _, targetProgramId.Value, programInfo.StorageId); + if (rc.IsFailure()) return rc; + + // Verify the caller has access to the file system + if (targetProgramId != programInfo.ProgramId && + !programInfo.AccessControl.HasContentOwnerId(targetProgramId.Value)) { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - // Get the program ID of the program with the specified index if in a multi-program application - rc = _serviceImpl.ResolveRomReferenceProgramId(out ProgramId targetProgramId, programInfo.ProgramId, - programIndex); - if (rc.IsFailure()) return rc; - - using var fileSystem = new SharedRef(); - rc = OpenDataFileSystemCore(ref fileSystem.Ref(), out _, targetProgramId.Value, programInfo.StorageId); - if (rc.IsFailure()) return rc; - - // Verify the caller has access to the file system - if (targetProgramId != programInfo.ProgramId && - !programInfo.AccessControl.HasContentOwnerId(targetProgramId.Value)) - { - return ResultFs.PermissionDenied.Log(); - } - - // Add all the file system wrappers - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; + return ResultFs.PermissionDenied.Log(); } - public Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, byte programIndex) - { - throw new NotImplementedException(); - } + // Add all the file system wrappers + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref fileSystem.Ref())); - public Result GetRightsId(out RightsId outRightsId, ProgramId programId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out outRightsId); + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + return Result.Success; + } - if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) - return ResultFs.PermissionDenied.Log(); + public Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, byte programIndex) + { + throw new NotImplementedException(); + } - using var programPath = new Path(); - rc = _serviceImpl.ResolveProgramPath(out bool isDirectory, ref programPath.Ref(), programId, storageId); - if (rc.IsFailure()) return rc; + public Result GetRightsId(out RightsId outRightsId, ProgramId programId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out outRightsId); - if (isDirectory) - return ResultFs.TargetNotFound.Log(); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); - rc = _serviceImpl.GetRightsId(out RightsId rightsId, out _, in programPath, programId); - if (rc.IsFailure()) return rc; + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - outRightsId = rightsId; + if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) + return ResultFs.PermissionDenied.Log(); - return Result.Success; - } + using var programPath = new Path(); + rc = _serviceImpl.ResolveProgramPath(out bool isDirectory, ref programPath.Ref(), programId, storageId); + if (rc.IsFailure()) return rc; - public Result GetRightsIdAndKeyGenerationByPath(out RightsId outRightsId, out byte outKeyGeneration, in FspPath path) - { - const ulong checkThroughProgramId = ulong.MaxValue; - UnsafeHelpers.SkipParamInit(out outRightsId, out outKeyGeneration); + if (isDirectory) + return ResultFs.TargetNotFound.Log(); - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); + rc = _serviceImpl.GetRightsId(out RightsId rightsId, out _, in programPath, programId); + if (rc.IsFailure()) return rc; - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + outRightsId = rightsId; - if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) - return ResultFs.PermissionDenied.Log(); + return Result.Success; + } - using var pathNormalized = new Path(); - rc = pathNormalized.Initialize(path.Str); - if (rc.IsFailure()) return rc; + public Result GetRightsIdAndKeyGenerationByPath(out RightsId outRightsId, out byte outKeyGeneration, in FspPath path) + { + const ulong checkThroughProgramId = ulong.MaxValue; + UnsafeHelpers.SkipParamInit(out outRightsId, out outKeyGeneration); - var pathFlags = new PathFlags(); - pathFlags.AllowWindowsPath(); - pathFlags.AllowMountName(); - rc = pathNormalized.Normalize(pathFlags); - if (rc.IsFailure()) return rc; + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); - if (PathUtility.IsDirectoryPath(in path)) - return ResultFs.TargetNotFound.Log(); + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - rc = _serviceImpl.GetRightsId(out RightsId rightsId, out byte keyGeneration, in pathNormalized, - new ProgramId(checkThroughProgramId)); - if (rc.IsFailure()) return rc; + if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) + return ResultFs.PermissionDenied.Log(); - outRightsId = rightsId; - outKeyGeneration = keyGeneration; + using var pathNormalized = new Path(); + rc = pathNormalized.Initialize(path.Str); + if (rc.IsFailure()) return rc; - return Result.Success; - } + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowMountName(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; - private Result OpenDataFileSystemCore(ref SharedRef outFileSystem, out bool isHostFs, - ulong programId, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out isHostFs); + if (PathUtility.IsDirectoryPath(in path)) + return ResultFs.TargetNotFound.Log(); - StorageType storageFlag = _serviceImpl.GetStorageFlag(programId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + rc = _serviceImpl.GetRightsId(out RightsId rightsId, out byte keyGeneration, in pathNormalized, + new ProgramId(checkThroughProgramId)); + if (rc.IsFailure()) return rc; - using var programPath = new Path(); - Result rc = _serviceImpl.ResolveRomPath(out bool isDirectory, ref programPath.Ref(), programId, storageId); - if (rc.IsFailure()) return rc; + outRightsId = rightsId; + outKeyGeneration = keyGeneration; - isHostFs = Utility.IsHostFsMountName(programPath.GetString()); + return Result.Success; + } - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenDataFileSystem(ref fileSystem.Ref(), in programPath, FileSystemProxyType.Rom, - programId, isDirectory); - if (rc.IsFailure()) return rc; + private Result OpenDataFileSystemCore(ref SharedRef outFileSystem, out bool isHostFs, + ulong programId, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out isHostFs); - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + StorageType storageFlag = _serviceImpl.GetStorageFlag(programId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - outFileSystem.SetByMove(ref typeSetFileSystem.Ref()); + using var programPath = new Path(); + Result rc = _serviceImpl.ResolveRomPath(out bool isDirectory, ref programPath.Ref(), programId, storageId); + if (rc.IsFailure()) return rc; - return Result.Success; - } + isHostFs = Utility.IsHostFsMountName(programPath.GetString()); - public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, - ContentStorageId contentStorageId) - { - StorageType storageFlag = contentStorageId == ContentStorageId.System ? StorageType.Bis : StorageType.All; - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenDataFileSystem(ref fileSystem.Ref(), in programPath, FileSystemProxyType.Rom, + programId, isDirectory); + if (rc.IsFailure()) return rc; - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage); + outFileSystem.SetByMove(ref typeSetFileSystem.Ref()); - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); + return Result.Success; + } - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenContentStorageFileSystem(ref fileSystem.Ref(), contentStorageId); - if (rc.IsFailure()) return rc; + public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, + ContentStorageId contentStorageId) + { + StorageType storageFlag = contentStorageId == ContentStorageId.System ? StorageType.Bis : StorageType.All; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage); - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenContentStorageFileSystem(ref fileSystem.Ref(), contentStorageId); + if (rc.IsFailure()) return rc; - return Result.Success; - } + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) - return ResultFs.PermissionDenied.Log(); + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - return _serviceImpl.RegisterExternalKey(in rightsId, in accessKey); - } + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - public Result UnregisterExternalKey(in RightsId rightsId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + return Result.Success; + } - if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) - return ResultFs.PermissionDenied.Log(); + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - return _serviceImpl.UnregisterExternalKey(in rightsId); - } + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); - public Result UnregisterAllExternalKey() - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + return _serviceImpl.RegisterExternalKey(in rightsId, in accessKey); + } - if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) - return ResultFs.PermissionDenied.Log(); + public Result UnregisterExternalKey(in RightsId rightsId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - return _serviceImpl.UnregisterAllExternalKey(); - } + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); - public Result RegisterUpdatePartition() - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + return _serviceImpl.UnregisterExternalKey(in rightsId); + } - if (!programInfo.AccessControl.CanCall(OperationType.RegisterUpdatePartition)) - return ResultFs.PermissionDenied.Log(); + public Result UnregisterAllExternalKey() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - ulong targetProgramId = programInfo.ProgramIdValue; + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); - using var programPath = new Path(); - rc = _serviceImpl.ResolveRomPath(out _, ref programPath.Ref(), targetProgramId, programInfo.StorageId); - if (rc.IsFailure()) return rc; + return _serviceImpl.UnregisterAllExternalKey(); + } - return _serviceImpl.RegisterUpdatePartition(targetProgramId, in programPath); - } + public Result RegisterUpdatePartition() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) - { - var storageFlag = StorageType.All; - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + if (!programInfo.AccessControl.CanCall(OperationType.RegisterUpdatePartition)) + return ResultFs.PermissionDenied.Log(); - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + ulong targetProgramId = programInfo.ProgramIdValue; - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountRegisteredUpdatePartition); - if (!accessibility.CanRead) - return ResultFs.PermissionDenied.Log(); + using var programPath = new Path(); + rc = _serviceImpl.ResolveRomPath(out _, ref programPath.Ref(), targetProgramId, programInfo.StorageId); + if (rc.IsFailure()) return rc; - using var fileSystem = new SharedRef(); - rc = _serviceImpl.OpenRegisteredUpdatePartition(ref fileSystem.Ref()); - if (rc.IsFailure()) return rc; + return _serviceImpl.RegisterUpdatePartition(targetProgramId, in programPath); + } - // Add all the file system wrappers - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) + { + var storageFlag = StorageType.All; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); - using var asyncFileSystem = - new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountRegisteredUpdatePartition); + if (!accessibility.CanRead) + return ResultFs.PermissionDenied.Log(); - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + using var fileSystem = new SharedRef(); + rc = _serviceImpl.OpenRegisteredUpdatePartition(ref fileSystem.Ref()); + if (rc.IsFailure()) return rc; - return Result.Success; - } + // Add all the file system wrappers + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - public Result IsArchivedProgram(out bool isArchived, ulong processId) - { - throw new NotImplementedException(); - } + using var asyncFileSystem = + new SharedRef(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref asyncFileSystem.Ref(), false); - if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) - return ResultFs.PermissionDenied.Log(); + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - return _serviceImpl.SetSdCardEncryptionSeed(in encryptionSeed); - } + return Result.Success; + } - public Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier) - { - throw new NotImplementedException(); - } + public Result IsArchivedProgram(out bool isArchived, ulong processId) + { + throw new NotImplementedException(); + } - public Result NotifySystemDataUpdateEvent() - { - throw new NotImplementedException(); - } + public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected) - { - return _serviceImpl.HandleResolubleAccessFailure(out wasDeferred, resultForNoFailureDetected, _processId); - } + if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) + return ResultFs.PermissionDenied.Log(); - Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(ref SharedRef outStorage, - out Hash ncaHeaderDigest, ulong id, StorageId storageId) - { - return OpenDataStorageCore(ref outStorage, out ncaHeaderDigest, id, storageId); - } + return _serviceImpl.SetSdCardEncryptionSeed(in encryptionSeed); + } + + public Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier) + { + throw new NotImplementedException(); + } + + public Result NotifySystemDataUpdateEvent() + { + throw new NotImplementedException(); + } + + public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected) + { + return _serviceImpl.HandleResolubleAccessFailure(out wasDeferred, resultForNoFailureDetected, _processId); + } + + Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(ref SharedRef outStorage, + out Hash ncaHeaderDigest, ulong id, StorageId storageId) + { + return OpenDataStorageCore(ref outStorage, out ncaHeaderDigest, id, storageId); } } diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs index 163167f9..c71825e1 100644 --- a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -16,922 +16,921 @@ using LibHac.Util; using RightsId = LibHac.Fs.RightsId; using Utility = LibHac.FsSystem.Utility; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class NcaFileSystemServiceImpl { - public class NcaFileSystemServiceImpl + private Configuration _config; + // UpdatePartitionPath + private ExternalKeySet _externalKeyManager; + private LocationResolverSet _locationResolverSet; + // SystemDataUpdateEventManager + private EncryptionSeed _encryptionSeed; + private int _romFsRemountForDataCorruptionCount; + private int _romfsUnrecoverableDataCorruptionByRemountCount; + private int _romFsRecoveredByInvalidateCacheCount; + private SdkMutexType _romfsCountMutex; + + public NcaFileSystemServiceImpl(in Configuration configuration, ExternalKeySet externalKeySet) { - private Configuration _config; - // UpdatePartitionPath - private ExternalKeySet _externalKeyManager; - private LocationResolverSet _locationResolverSet; - // SystemDataUpdateEventManager - private EncryptionSeed _encryptionSeed; - private int _romFsRemountForDataCorruptionCount; - private int _romfsUnrecoverableDataCorruptionByRemountCount; - private int _romFsRecoveredByInvalidateCacheCount; - private SdkMutexType _romfsCountMutex; + _config = configuration; + _externalKeyManager = externalKeySet; + _locationResolverSet = new LocationResolverSet(_config.FsServer); + _romfsCountMutex.Initialize(); + } - public NcaFileSystemServiceImpl(in Configuration configuration, ExternalKeySet externalKeySet) + public struct Configuration + { + public BaseFileSystemServiceImpl BaseFsService; + public ILocalFileSystemCreator LocalFsCreator; + public ITargetManagerFileSystemCreator TargetManagerFsCreator; + public IPartitionFileSystemCreator PartitionFsCreator; + public IRomFileSystemCreator RomFsCreator; + public IStorageOnNcaCreator StorageOnNcaCreator; + public ISubDirectoryFileSystemCreator SubDirectoryFsCreator; + public IEncryptedFileSystemCreator EncryptedFsCreator; + public ProgramRegistryServiceImpl ProgramRegistryService; + public AccessFailureManagementServiceImpl AccessFailureManagementService; + public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange; + + // LibHac additions + public FileSystemServer FsServer; + } + + private struct MountInfo + { + public bool IsGameCard; + public int GcHandle; + public bool IsHostFs; + public bool CanMountNca; + } + + public Result OpenFileSystem(ref SharedRef outFileSystem, in Path path, FileSystemProxyType type, + ulong id, bool isDirectory) + { + return OpenFileSystem(ref outFileSystem, out Unsafe.NullRef(), in path, type, false, + id, isDirectory); + } + + public Result OpenFileSystem(ref SharedRef outFileSystem, in Path path, FileSystemProxyType type, + bool canMountSystemDataPrivate, ulong id, bool isDirectory) + { + return OpenFileSystem(ref outFileSystem, out Unsafe.NullRef(), in path, type, + canMountSystemDataPrivate, id, isDirectory); + } + + public Result OpenFileSystem(ref SharedRef outFileSystem, + out CodeVerificationData verificationData, in Path path, FileSystemProxyType type, + bool canMountSystemDataPrivate, ulong id, bool isDirectory) + { + UnsafeHelpers.SkipParamInit(out verificationData); + + if (!Unsafe.IsNullRef(ref verificationData)) + verificationData.IsValid = false; + + // Get a reference to the path that will be advanced as each part of the path is parsed + var currentPath = new U8Span(path.GetString()); + + // Open the root filesystem based on the path's mount name + using var baseFileSystem = new SharedRef(); + Result rc = ParseMountName(ref currentPath, ref baseFileSystem.Ref(), out bool shouldContinue, + out MountInfo mountNameInfo); + if (rc.IsFailure()) return rc; + + // Don't continue if the rest of the path is empty + if (!shouldContinue) + return ResultFs.InvalidArgument.Log(); + + if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard) { - _config = configuration; - _externalKeyManager = externalKeySet; - _locationResolverSet = new LocationResolverSet(_config.FsServer); - _romfsCountMutex.Initialize(); - } - - public struct Configuration - { - public BaseFileSystemServiceImpl BaseFsService; - public ILocalFileSystemCreator LocalFsCreator; - public ITargetManagerFileSystemCreator TargetManagerFsCreator; - public IPartitionFileSystemCreator PartitionFsCreator; - public IRomFileSystemCreator RomFsCreator; - public IStorageOnNcaCreator StorageOnNcaCreator; - public ISubDirectoryFileSystemCreator SubDirectoryFsCreator; - public IEncryptedFileSystemCreator EncryptedFsCreator; - public ProgramRegistryServiceImpl ProgramRegistryService; - public AccessFailureManagementServiceImpl AccessFailureManagementService; - public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange; - - // LibHac additions - public FileSystemServer FsServer; - } - - private struct MountInfo - { - public bool IsGameCard; - public int GcHandle; - public bool IsHostFs; - public bool CanMountNca; - } - - public Result OpenFileSystem(ref SharedRef outFileSystem, in Path path, FileSystemProxyType type, - ulong id, bool isDirectory) - { - return OpenFileSystem(ref outFileSystem, out Unsafe.NullRef(), in path, type, false, - id, isDirectory); - } - - public Result OpenFileSystem(ref SharedRef outFileSystem, in Path path, FileSystemProxyType type, - bool canMountSystemDataPrivate, ulong id, bool isDirectory) - { - return OpenFileSystem(ref outFileSystem, out Unsafe.NullRef(), in path, type, - canMountSystemDataPrivate, id, isDirectory); - } - - public Result OpenFileSystem(ref SharedRef outFileSystem, - out CodeVerificationData verificationData, in Path path, FileSystemProxyType type, - bool canMountSystemDataPrivate, ulong id, bool isDirectory) - { - UnsafeHelpers.SkipParamInit(out verificationData); - - if (!Unsafe.IsNullRef(ref verificationData)) - verificationData.IsValid = false; - - // Get a reference to the path that will be advanced as each part of the path is parsed - var currentPath = new U8Span(path.GetString()); - - // Open the root filesystem based on the path's mount name - using var baseFileSystem = new SharedRef(); - Result rc = ParseMountName(ref currentPath, ref baseFileSystem.Ref(), out bool shouldContinue, - out MountInfo mountNameInfo); - if (rc.IsFailure()) return rc; - - // Don't continue if the rest of the path is empty - if (!shouldContinue) - return ResultFs.InvalidArgument.Log(); - - if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard) - { - rc = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, - new GameCardHandle(mountNameInfo.GcHandle), - GameCardPartition.Logo); - - if (rc.IsSuccess()) - return Result.Success; - - if (!ResultFs.PartitionNotFound.Includes(rc)) - return rc; - } - - rc = CheckDirOrNcaOrNsp(ref currentPath, out isDirectory); - if (rc.IsFailure()) return rc; - - if (isDirectory) - { - if (!mountNameInfo.IsHostFs) - return ResultFs.PermissionDenied.Log(); - - using var directoryPath = new Path(); - rc = directoryPath.InitializeWithNormalization(currentPath.Value); - if (rc.IsFailure()) return rc; - - if (type == FileSystemProxyType.Manual) - { - using var hostFileSystem = new SharedRef(); - using var readOnlyFileSystem = new SharedRef(); - - rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(ref hostFileSystem.Ref(), - in directoryPath); - if (rc.IsFailure()) return rc; - - readOnlyFileSystem.Reset(new ReadOnlyFileSystem(ref hostFileSystem.Ref())); - outFileSystem.SetByMove(ref readOnlyFileSystem.Ref()); - - return Result.Success; - } - - return ParseDir(in directoryPath, ref outFileSystem, ref baseFileSystem.Ref(), type, true); - } - - using var nspFileSystem = new SharedRef(); - using SharedRef tempFileSystem = SharedRef.CreateCopy(in baseFileSystem); - rc = ParseNsp(ref currentPath, ref nspFileSystem.Ref(), ref baseFileSystem.Ref()); + rc = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, + new GameCardHandle(mountNameInfo.GcHandle), + GameCardPartition.Logo); if (rc.IsSuccess()) - { - // Must be the end of the path to open Application Package FS type - if (currentPath.Value.At(0) == 0) - { - if (type == FileSystemProxyType.Package) - { - outFileSystem.SetByMove(ref nspFileSystem.Ref()); - return Result.Success; - } + return Result.Success; - return ResultFs.InvalidArgument.Log(); + if (!ResultFs.PartitionNotFound.Includes(rc)) + return rc; + } + + rc = CheckDirOrNcaOrNsp(ref currentPath, out isDirectory); + if (rc.IsFailure()) return rc; + + if (isDirectory) + { + if (!mountNameInfo.IsHostFs) + return ResultFs.PermissionDenied.Log(); + + using var directoryPath = new Path(); + rc = directoryPath.InitializeWithNormalization(currentPath.Value); + if (rc.IsFailure()) return rc; + + if (type == FileSystemProxyType.Manual) + { + using var hostFileSystem = new SharedRef(); + using var readOnlyFileSystem = new SharedRef(); + + rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(ref hostFileSystem.Ref(), + in directoryPath); + if (rc.IsFailure()) return rc; + + readOnlyFileSystem.Reset(new ReadOnlyFileSystem(ref hostFileSystem.Ref())); + outFileSystem.SetByMove(ref readOnlyFileSystem.Ref()); + + return Result.Success; + } + + return ParseDir(in directoryPath, ref outFileSystem, ref baseFileSystem.Ref(), type, true); + } + + using var nspFileSystem = new SharedRef(); + using SharedRef tempFileSystem = SharedRef.CreateCopy(in baseFileSystem); + rc = ParseNsp(ref currentPath, ref nspFileSystem.Ref(), ref baseFileSystem.Ref()); + + if (rc.IsSuccess()) + { + // Must be the end of the path to open Application Package FS type + if (currentPath.Value.At(0) == 0) + { + if (type == FileSystemProxyType.Package) + { + outFileSystem.SetByMove(ref nspFileSystem.Ref()); + return Result.Success; } - baseFileSystem.SetByMove(ref nspFileSystem.Ref()); + return ResultFs.InvalidArgument.Log(); } - if (!mountNameInfo.CanMountNca) - { - return ResultFs.UnexpectedInNcaFileSystemServiceImplA.Log(); - } + baseFileSystem.SetByMove(ref nspFileSystem.Ref()); + } - ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : id; + if (!mountNameInfo.CanMountNca) + { + return ResultFs.UnexpectedInNcaFileSystemServiceImplA.Log(); + } - rc = ParseNca(ref currentPath, out Nca nca, ref baseFileSystem.Ref(), openProgramId); + ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : id; + + rc = ParseNca(ref currentPath, out Nca nca, ref baseFileSystem.Ref(), openProgramId); + if (rc.IsFailure()) return rc; + + using var ncaSectionStorage = new SharedRef(); + rc = OpenStorageByContentType(ref ncaSectionStorage.Ref(), nca, out NcaFormatType fsType, type, + mountNameInfo.IsGameCard, canMountSystemDataPrivate); + if (rc.IsFailure()) return rc; + + switch (fsType) + { + case NcaFormatType.Romfs: + return _config.RomFsCreator.Create(ref outFileSystem, ref ncaSectionStorage.Ref()); + case NcaFormatType.Pfs0: + return _config.PartitionFsCreator.Create(ref outFileSystem, ref ncaSectionStorage.Ref()); + default: + return ResultFs.InvalidNcaFileSystemType.Log(); + } + } + + public Result OpenDataFileSystem(ref SharedRef outFileSystem, in Path path, + FileSystemProxyType fsType, ulong programId, bool isDirectory) + { + throw new NotImplementedException(); + } + + public Result OpenStorageWithPatch(ref SharedRef outStorage, out Hash ncaHeaderDigest, + in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) + { + throw new NotImplementedException(); + } + + public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, + in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) + { + using var romFsStorage = new SharedRef(); + Result rc = OpenStorageWithPatch(ref romFsStorage.Ref(), out Unsafe.NullRef(), in originalNcaPath, + in currentNcaPath, fsType, id); + if (rc.IsFailure()) return rc; + + return _config.RomFsCreator.Create(ref outFileSystem, ref romFsStorage.Ref()); + } + + public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, + ContentStorageId contentStorageId) + { + const int pathBufferLength = 0x40; + + using var fileSystem = new SharedRef(); + Result rc; + + // Open the appropriate base file system for the content storage ID + switch (contentStorageId) + { + case ContentStorageId.System: + rc = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.System); + if (rc.IsFailure()) return rc; + break; + case ContentStorageId.User: + rc = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.User); + if (rc.IsFailure()) return rc; + break; + case ContentStorageId.SdCard: + rc = _config.BaseFsService.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); + if (rc.IsFailure()) return rc; + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + // Hack around error CS8350. + Span buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span contentStoragePathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); + + // Build the appropriate path for the content storage ID + if (contentStorageId == ContentStorageId.SdCard) + { + var sb = new U8StringBuilder(contentStoragePathBuffer); + sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName); + sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); + } + else + { + var sb = new U8StringBuilder(contentStoragePathBuffer); + sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); + } + + using var contentStoragePath = new Path(); + rc = PathFunctions.SetUpFixedPath(ref contentStoragePath.Ref(), contentStoragePathBuffer); + if (rc.IsFailure()) return rc; + + // Make sure the content storage path exists + rc = Utility.EnsureDirectory(fileSystem.Get, in contentStoragePath); + if (rc.IsFailure()) return rc; + + using var subDirFs = new SharedRef(); + rc = _config.SubDirectoryFsCreator.Create(ref subDirFs.Ref(), ref fileSystem.Ref(), in contentStoragePath); + if (rc.IsFailure()) return rc; + + // Only content on the SD card is encrypted + if (contentStorageId == ContentStorageId.SdCard) + { + using SharedRef tempFileSystem = SharedRef.CreateMove(ref subDirFs.Ref()); + rc = _config.EncryptedFsCreator.Create(ref subDirFs.Ref(), ref tempFileSystem.Ref(), + IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed); if (rc.IsFailure()) return rc; + } + outFileSystem.SetByMove(ref subDirFs.Ref()); - using var ncaSectionStorage = new SharedRef(); - rc = OpenStorageByContentType(ref ncaSectionStorage.Ref(), nca, out NcaFormatType fsType, type, - mountNameInfo.IsGameCard, canMountSystemDataPrivate); - if (rc.IsFailure()) return rc; + return Result.Success; + } - switch (fsType) + public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, in Path path, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) + { + return _externalKeyManager.Add(rightsId, accessKey); + } + + public Result UnregisterExternalKey(in RightsId rightsId) + { + _externalKeyManager.Remove(rightsId); + + return Result.Success; + } + + public Result UnregisterAllExternalKey() + { + _externalKeyManager.Clear(); + + return Result.Success; + } + + public Result RegisterUpdatePartition(ulong programId, in Path path) + { + throw new NotImplementedException(); + } + + public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) + { + throw new NotImplementedException(); + } + + private Result ParseMountName(ref U8Span path, ref SharedRef outFileSystem, + out bool shouldContinue, out MountInfo info) + { + info = new MountInfo(); + shouldContinue = true; + + if (StringUtils.Compare(path, CommonPaths.GameCardFileSystemMountName, + CommonPaths.GameCardFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.GameCardFileSystemMountName.Length); + + if (StringUtils.GetLength(path.Value, 9) < 9) + return ResultFs.InvalidPath.Log(); + + GameCardPartition partition; + switch ((char)path[0]) { - case NcaFormatType.Romfs: - return _config.RomFsCreator.Create(ref outFileSystem, ref ncaSectionStorage.Ref()); - case NcaFormatType.Pfs0: - return _config.PartitionFsCreator.Create(ref outFileSystem, ref ncaSectionStorage.Ref()); - default: - return ResultFs.InvalidNcaFileSystemType.Log(); - } - } - - public Result OpenDataFileSystem(ref SharedRef outFileSystem, in Path path, - FileSystemProxyType fsType, ulong programId, bool isDirectory) - { - throw new NotImplementedException(); - } - - public Result OpenStorageWithPatch(ref SharedRef outStorage, out Hash ncaHeaderDigest, - in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) - { - throw new NotImplementedException(); - } - - public Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, - in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) - { - using var romFsStorage = new SharedRef(); - Result rc = OpenStorageWithPatch(ref romFsStorage.Ref(), out Unsafe.NullRef(), in originalNcaPath, - in currentNcaPath, fsType, id); - if (rc.IsFailure()) return rc; - - return _config.RomFsCreator.Create(ref outFileSystem, ref romFsStorage.Ref()); - } - - public Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, - ContentStorageId contentStorageId) - { - const int pathBufferLength = 0x40; - - using var fileSystem = new SharedRef(); - Result rc; - - // Open the appropriate base file system for the content storage ID - switch (contentStorageId) - { - case ContentStorageId.System: - rc = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.System); - if (rc.IsFailure()) return rc; + case CommonPaths.GameCardFileSystemMountNameUpdateSuffix: + partition = GameCardPartition.Update; break; - case ContentStorageId.User: - rc = _config.BaseFsService.OpenBisFileSystem(ref fileSystem.Ref(), BisPartitionId.User); - if (rc.IsFailure()) return rc; + case CommonPaths.GameCardFileSystemMountNameNormalSuffix: + partition = GameCardPartition.Normal; break; - case ContentStorageId.SdCard: - rc = _config.BaseFsService.OpenSdCardProxyFileSystem(ref fileSystem.Ref()); - if (rc.IsFailure()) return rc; + case CommonPaths.GameCardFileSystemMountNameSecureSuffix: + partition = GameCardPartition.Secure; break; default: - return ResultFs.InvalidArgument.Log(); - } - - // Hack around error CS8350. - Span buffer = stackalloc byte[pathBufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span contentStoragePathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); - - // Build the appropriate path for the content storage ID - if (contentStorageId == ContentStorageId.SdCard) - { - var sb = new U8StringBuilder(contentStoragePathBuffer); - sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName); - sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); - } - else - { - var sb = new U8StringBuilder(contentStoragePathBuffer); - sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); - } - - using var contentStoragePath = new Path(); - rc = PathFunctions.SetUpFixedPath(ref contentStoragePath.Ref(), contentStoragePathBuffer); - if (rc.IsFailure()) return rc; - - // Make sure the content storage path exists - rc = Utility.EnsureDirectory(fileSystem.Get, in contentStoragePath); - if (rc.IsFailure()) return rc; - - using var subDirFs = new SharedRef(); - rc = _config.SubDirectoryFsCreator.Create(ref subDirFs.Ref(), ref fileSystem.Ref(), in contentStoragePath); - if (rc.IsFailure()) return rc; - - // Only content on the SD card is encrypted - if (contentStorageId == ContentStorageId.SdCard) - { - using SharedRef tempFileSystem = SharedRef.CreateMove(ref subDirFs.Ref()); - rc = _config.EncryptedFsCreator.Create(ref subDirFs.Ref(), ref tempFileSystem.Ref(), - IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed); - if (rc.IsFailure()) return rc; - } - outFileSystem.SetByMove(ref subDirFs.Ref()); - - return Result.Success; - } - - public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, in Path path, ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) - { - return _externalKeyManager.Add(rightsId, accessKey); - } - - public Result UnregisterExternalKey(in RightsId rightsId) - { - _externalKeyManager.Remove(rightsId); - - return Result.Success; - } - - public Result UnregisterAllExternalKey() - { - _externalKeyManager.Clear(); - - return Result.Success; - } - - public Result RegisterUpdatePartition(ulong programId, in Path path) - { - throw new NotImplementedException(); - } - - public Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem) - { - throw new NotImplementedException(); - } - - private Result ParseMountName(ref U8Span path, ref SharedRef outFileSystem, - out bool shouldContinue, out MountInfo info) - { - info = new MountInfo(); - shouldContinue = true; - - if (StringUtils.Compare(path, CommonPaths.GameCardFileSystemMountName, - CommonPaths.GameCardFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonPaths.GameCardFileSystemMountName.Length); - - if (StringUtils.GetLength(path.Value, 9) < 9) return ResultFs.InvalidPath.Log(); - - GameCardPartition partition; - switch ((char)path[0]) - { - case CommonPaths.GameCardFileSystemMountNameUpdateSuffix: - partition = GameCardPartition.Update; - break; - case CommonPaths.GameCardFileSystemMountNameNormalSuffix: - partition = GameCardPartition.Normal; - break; - case CommonPaths.GameCardFileSystemMountNameSecureSuffix: - partition = GameCardPartition.Secure; - break; - default: - return ResultFs.InvalidPath.Log(); - } - - path = path.Slice(1); - bool handleParsed = Utf8Parser.TryParse(path, out int handle, out int bytesConsumed); - - if (!handleParsed || handle == -1 || bytesConsumed != 8) - return ResultFs.InvalidPath.Log(); - - path = path.Slice(8); - - Result rc = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, new GameCardHandle(handle), - partition); - if (rc.IsFailure()) return rc; - - info.GcHandle = handle; - info.IsGameCard = true; - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonPaths.ContentStorageSystemMountName, - CommonPaths.ContentStorageSystemMountName.Length) == 0) - { - path = path.Slice(CommonPaths.ContentStorageSystemMountName.Length); - - Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.System); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonPaths.ContentStorageUserMountName, - CommonPaths.ContentStorageUserMountName.Length) == 0) - { - path = path.Slice(CommonPaths.ContentStorageUserMountName.Length); - - Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.User); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonPaths.ContentStorageSdCardMountName, - CommonPaths.ContentStorageSdCardMountName.Length) == 0) - { - path = path.Slice(CommonPaths.ContentStorageSdCardMountName.Length); - - Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.SdCard); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonPaths.BisCalibrationFilePartitionMountName, - CommonPaths.BisCalibrationFilePartitionMountName.Length) == 0) - { - path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length); - - Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.CalibrationFile); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.BisSafeModePartitionMountName, - CommonPaths.BisSafeModePartitionMountName.Length) == 0) - { - path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length); - - Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.SafeMode); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.BisUserPartitionMountName, - CommonPaths.BisUserPartitionMountName.Length) == 0) - { - path = path.Slice(CommonPaths.BisUserPartitionMountName.Length); - - Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.User); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.BisSystemPartitionMountName, - CommonPaths.BisSystemPartitionMountName.Length) == 0) - { - path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length); - - Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.System); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.SdCardFileSystemMountName, - CommonPaths.SdCardFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonPaths.SdCardFileSystemMountName.Length); - - Result rc = _config.BaseFsService.OpenSdCardProxyFileSystem(ref outFileSystem); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, - CommonPaths.HostRootFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length); - - using var rootPathEmpty = new Path(); - Result rc = rootPathEmpty.InitializeAsEmpty(); - if (rc.IsFailure()) return rc; - - info.IsHostFs = true; - info.CanMountNca = true; - - rc = OpenHostFileSystem(ref outFileSystem, in rootPathEmpty, openCaseSensitive: false); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonPaths.RegisteredUpdatePartitionMountName, - CommonPaths.RegisteredUpdatePartitionMountName.Length) == 0) - { - path = path.Slice(CommonPaths.RegisteredUpdatePartitionMountName.Length); - - info.CanMountNca = true; - - throw new NotImplementedException(); - } - - else - { - return ResultFs.PathNotFound.Log(); - } - - if (StringUtils.GetLength(path, FsPath.MaxLength) == 0) - { - shouldContinue = false; - } - - return Result.Success; - } - - private Result CheckDirOrNcaOrNsp(ref U8Span path, out bool isDirectory) - { - UnsafeHelpers.SkipParamInit(out isDirectory); - - ReadOnlySpan mountSeparator = new[] { (byte)':', (byte)'/' }; - - if (StringUtils.Compare(mountSeparator, path, mountSeparator.Length) != 0) - { - return ResultFs.PathNotFound.Log(); } path = path.Slice(1); - int pathLen = StringUtils.GetLength(path); + bool handleParsed = Utf8Parser.TryParse(path, out int handle, out int bytesConsumed); - if (path[pathLen - 1] == '/') - { - isDirectory = true; - return Result.Success; - } + if (!handleParsed || handle == -1 || bytesConsumed != 8) + return ResultFs.InvalidPath.Log(); - // Now make sure the path has a content file extension - if (pathLen < 5) - return ResultFs.PathNotFound.Log(); + path = path.Slice(8); - ReadOnlySpan fileExtension = path.Value.Slice(pathLen - 4); + Result rc = _config.BaseFsService.OpenGameCardFileSystem(ref outFileSystem, new GameCardHandle(handle), + partition); + if (rc.IsFailure()) return rc; - ReadOnlySpan ncaExtension = new[] { (byte)'.', (byte)'n', (byte)'c', (byte)'a' }; - ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; - - if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0 || - StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0) - { - isDirectory = false; - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); + info.GcHandle = handle; + info.IsGameCard = true; + info.CanMountNca = true; } - private Result ParseDir(in Path path, ref SharedRef outContentFileSystem, - ref SharedRef baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) + else if (StringUtils.Compare(path, CommonPaths.ContentStorageSystemMountName, + CommonPaths.ContentStorageSystemMountName.Length) == 0) { - using var fileSystem = new SharedRef(); - Result rc = _config.SubDirectoryFsCreator.Create(ref fileSystem.Ref(), ref baseFileSystem, in path); + path = path.Slice(CommonPaths.ContentStorageSystemMountName.Length); + + Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.System); if (rc.IsFailure()) return rc; - return ParseContentTypeForDirectory(ref outContentFileSystem, ref fileSystem.Ref(), fsType); + info.CanMountNca = true; } - private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(ref SharedRef outFileSystem, - in Path path) + else if (StringUtils.Compare(path, CommonPaths.ContentStorageUserMountName, + CommonPaths.ContentStorageUserMountName.Length) == 0) { - using var pathRoot = new Path(); - using var pathData = new Path(); + path = path.Slice(CommonPaths.ContentStorageUserMountName.Length); - Result rc = PathFunctions.SetUpFixedPath(ref pathData.Ref(), - new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a' }); + Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.User); if (rc.IsFailure()) return rc; - rc = pathRoot.Combine(in path, in pathData); - if (rc.IsFailure()) return rc; - - rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isSupported, ref pathRoot.Ref()); - if (rc.IsFailure()) return rc; - - rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in pathRoot, isSupported, false, - Result.Success); - if (rc.IsFailure()) return rc; - - return Result.Success; + info.CanMountNca = true; } - private Result ParseNsp(ref U8Span path, ref SharedRef outFileSystem, - ref SharedRef baseFileSystem) + else if (StringUtils.Compare(path, CommonPaths.ContentStorageSdCardMountName, + CommonPaths.ContentStorageSdCardMountName.Length) == 0) { - ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; + path = path.Slice(CommonPaths.ContentStorageSdCardMountName.Length); - // Search for the end of the nsp part of the path - int nspPathLen = 0; - - while (true) - { - U8Span currentSpan; - - while (true) - { - currentSpan = path.Slice(nspPathLen); - if (StringUtils.CompareCaseInsensitive(nspExtension, currentSpan, 4) == 0) - break; - - if (currentSpan.Length == 0 || currentSpan[0] == 0) - { - return ResultFs.PathNotFound.Log(); - } - - nspPathLen++; - } - - // The nsp filename must be the end of the entire path or the end of a path segment - if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/') - break; - - nspPathLen += 4; - } - - nspPathLen += 4; - - using var pathNsp = new Path(); - Result rc = pathNsp.InitializeWithNormalization(path, nspPathLen); + Result rc = OpenContentStorageFileSystem(ref outFileSystem, ContentStorageId.SdCard); if (rc.IsFailure()) return rc; - using var nspFileStorage = new SharedRef(new FileStorageBasedFileSystem()); + info.CanMountNca = true; + } - rc = nspFileStorage.Get.Initialize(ref baseFileSystem, in pathNsp, OpenMode.Read); + else if (StringUtils.Compare(path, CommonPaths.BisCalibrationFilePartitionMountName, + CommonPaths.BisCalibrationFilePartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.CalibrationFile); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisSafeModePartitionMountName, + CommonPaths.BisSafeModePartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.SafeMode); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisUserPartitionMountName, + CommonPaths.BisUserPartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisUserPartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.User); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisSystemPartitionMountName, + CommonPaths.BisSystemPartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(ref outFileSystem, BisPartitionId.System); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.SdCardFileSystemMountName, + CommonPaths.SdCardFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.SdCardFileSystemMountName.Length); + + Result rc = _config.BaseFsService.OpenSdCardProxyFileSystem(ref outFileSystem); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, + CommonPaths.HostRootFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length); + + using var rootPathEmpty = new Path(); + Result rc = rootPathEmpty.InitializeAsEmpty(); if (rc.IsFailure()) return rc; - using SharedRef tempStorage = SharedRef.CreateMove(ref nspFileStorage.Ref()); - rc = _config.PartitionFsCreator.Create(ref outFileSystem, ref tempStorage.Ref()); + info.IsHostFs = true; + info.CanMountNca = true; - if (rc.IsSuccess()) - { - path = path.Slice(nspPathLen); - } - - return rc; - } - - private Result ParseNca(ref U8Span path, out Nca nca, ref SharedRef baseFileSystem, ulong ncaId) - { - UnsafeHelpers.SkipParamInit(out nca); - - // Todo: Create ref-counted storage - var ncaFileStorage = new FileStorageBasedFileSystem(); - - using var pathNca = new Path(); - Result rc = pathNca.InitializeWithNormalization(path); + rc = OpenHostFileSystem(ref outFileSystem, in rootPathEmpty, openCaseSensitive: false); if (rc.IsFailure()) return rc; - - rc = ncaFileStorage.Initialize(ref baseFileSystem, in pathNca, OpenMode.Read); - if (rc.IsFailure()) return rc; - - rc = _config.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage); - if (rc.IsFailure()) return rc; - - if (ncaId == ulong.MaxValue) - { - ulong ncaProgramId = ncaTemp.Header.TitleId; - - if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId) - { - return ResultFs.InvalidNcaId.Log(); - } - } - - nca = ncaTemp; - return Result.Success; } - private Result ParseContentTypeForDirectory(ref SharedRef outFileSystem, - ref SharedRef baseFileSystem, FileSystemProxyType fsType) + else if (StringUtils.Compare(path, CommonPaths.RegisteredUpdatePartitionMountName, + CommonPaths.RegisteredUpdatePartitionMountName.Length) == 0) { - ReadOnlySpan dirName; + path = path.Slice(CommonPaths.RegisteredUpdatePartitionMountName.Length); - // Get the name of the subdirectory for the filesystem type - switch (fsType) - { - case FileSystemProxyType.Package: - outFileSystem.SetByMove(ref baseFileSystem); - return Result.Success; + info.CanMountNca = true; - case FileSystemProxyType.Code: - dirName = new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'d', (byte)'e', (byte)'/' }; - break; - case FileSystemProxyType.Rom: - case FileSystemProxyType.Control: - case FileSystemProxyType.Manual: - case FileSystemProxyType.Meta: - case FileSystemProxyType.RegisteredUpdate: - dirName = new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }; - break; - case FileSystemProxyType.Logo: - dirName = new[] { (byte)'/', (byte)'l', (byte)'o', (byte)'g', (byte)'o', (byte)'/' }; - break; - - default: - return ResultFs.InvalidArgument.Log(); - } - - using var subDirFs = new SharedRef(); - - using var directoryPath = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref directoryPath.Ref(), dirName); - if (rc.IsFailure()) return rc; - - if (directoryPath.IsEmpty()) - return ResultFs.InvalidArgument.Log(); - - // Open the subdirectory filesystem - rc = _config.SubDirectoryFsCreator.Create(ref subDirFs.Ref(), ref baseFileSystem, in directoryPath); - if (rc.IsFailure()) return rc; - - outFileSystem.SetByMove(ref subDirFs.Ref()); - return Result.Success; - } - - private Result SetExternalKeyForRightsId(Nca nca) - { - var rightsId = new RightsId(nca.Header.RightsId); - var zero = new RightsId(0, 0); - - if (Crypto.CryptoUtil.IsSameBytes(rightsId.AsBytes(), zero.AsBytes(), Unsafe.SizeOf())) - return Result.Success; - - // ReSharper disable once UnusedVariable - Result rc = _externalKeyManager.Get(rightsId, out AccessKey accessKey); - if (rc.IsFailure()) return rc; - - // todo: Set key in nca reader - - return Result.Success; - } - - private Result OpenStorageByContentType(ref SharedRef outNcaStorage, Nca nca, - out NcaFormatType fsType, FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate) - { - UnsafeHelpers.SkipParamInit(out fsType); - - NcaContentType contentType = nca.Header.ContentType; - - switch (fsProxyType) - { - case FileSystemProxyType.Code: - case FileSystemProxyType.Rom: - case FileSystemProxyType.Logo: - case FileSystemProxyType.RegisteredUpdate: - if (contentType != NcaContentType.Program) - return ResultFs.PreconditionViolation.Log(); - - break; - - case FileSystemProxyType.Control: - if (contentType != NcaContentType.Control) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Manual: - if (contentType != NcaContentType.Manual) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Meta: - if (contentType != NcaContentType.Meta) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Data: - if (contentType != NcaContentType.Data && contentType != NcaContentType.PublicData) - return ResultFs.PreconditionViolation.Log(); - - if (contentType == NcaContentType.Data && !canMountSystemDataPrivate) - return ResultFs.PermissionDenied.Log(); - - break; - default: - return ResultFs.InvalidArgument.Log(); - } - - if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard) - return ResultFs.PermissionDenied.Log(); - - Result rc = SetExternalKeyForRightsId(nca); - if (rc.IsFailure()) return rc; - - rc = GetPartitionIndex(out int sectionIndex, fsProxyType); - if (rc.IsFailure()) return rc; - - rc = _config.StorageOnNcaCreator.Create(ref outNcaStorage, out NcaFsHeader fsHeader, nca, - sectionIndex, fsProxyType == FileSystemProxyType.Code); - if (rc.IsFailure()) return rc; - - fsType = fsHeader.FormatType; - return Result.Success; - } - - public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) - { - _encryptionSeed = encryptionSeed; - - return Result.Success; - } - - public Result ResolveRomReferenceProgramId(out ProgramId targetProgramId, ProgramId programId, - byte programIndex) - { - UnsafeHelpers.SkipParamInit(out targetProgramId); - - ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, programIndex); - if (mainProgramId == ProgramId.InvalidId) - return ResultFs.ProgramIndexNotFound.Log(); - - targetProgramId = mainProgramId; - return Result.Success; - } - - public Result ResolveProgramPath(out bool isDirectory, ref Path path, ProgramId programId, StorageId storageId) - { - Result rc = _locationResolverSet.ResolveProgramPath(out isDirectory, ref path, programId, storageId); - if (rc.IsSuccess()) - return Result.Success; - - isDirectory = false; - - rc = _locationResolverSet.ResolveDataPath(ref path, new DataId(programId.Value), storageId); - if (rc.IsSuccess()) - return Result.Success; - - return ResultFs.TargetNotFound.Log(); - } - - public Result ResolveRomPath(out bool isDirectory, ref Path path, ulong id, StorageId storageId) - { - return _locationResolverSet.ResolveRomPath(out isDirectory, ref path, new ProgramId(id), storageId); - } - - public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Path path, - Ncm.ApplicationId applicationId, StorageId storageId) - { - return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out isDirectory, ref path, applicationId, - storageId); - } - - public Result ResolveRegisteredHtmlDocumentPath(ref Path path, ulong id) - { - return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(ref path, id); - } - - internal StorageType GetStorageFlag(ulong programId) - { - if (programId >= _config.SpeedEmulationRange.ProgramIdMin && - programId <= _config.SpeedEmulationRange.ProgramIdMax) - return StorageType.Bis; - else - return StorageType.All; - } - - public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected, - ulong processId) - { throw new NotImplementedException(); } - public void IncrementRomFsRemountForDataCorruptionCount() + else { - using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); - - _romFsRemountForDataCorruptionCount++; + return ResultFs.PathNotFound.Log(); } - public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() + if (StringUtils.GetLength(path, FsPath.MaxLength) == 0) { - using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); - - _romfsUnrecoverableDataCorruptionByRemountCount++; + shouldContinue = false; } - public void IncrementRomFsRecoveredByInvalidateCacheCount() - { - using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); + return Result.Success; + } - _romFsRecoveredByInvalidateCacheCount++; + private Result CheckDirOrNcaOrNsp(ref U8Span path, out bool isDirectory) + { + UnsafeHelpers.SkipParamInit(out isDirectory); + + ReadOnlySpan mountSeparator = new[] { (byte)':', (byte)'/' }; + + if (StringUtils.Compare(mountSeparator, path, mountSeparator.Length) != 0) + { + return ResultFs.PathNotFound.Log(); } - public void GetAndClearRomFsErrorInfo(out int recoveredByRemountCount, out int unrecoverableCount, - out int recoveredByCacheInvalidationCount) + path = path.Slice(1); + int pathLen = StringUtils.GetLength(path); + + if (path[pathLen - 1] == '/') { - using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); - - recoveredByRemountCount = _romFsRemountForDataCorruptionCount; - unrecoverableCount = _romfsUnrecoverableDataCorruptionByRemountCount; - recoveredByCacheInvalidationCount = _romFsRecoveredByInvalidateCacheCount; - - _romFsRemountForDataCorruptionCount = 0; - _romfsUnrecoverableDataCorruptionByRemountCount = 0; - _romFsRecoveredByInvalidateCacheCount = 0; + isDirectory = true; + return Result.Success; } - public Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive) + // Now make sure the path has a content file extension + if (pathLen < 5) + return ResultFs.PathNotFound.Log(); + + ReadOnlySpan fileExtension = path.Value.Slice(pathLen - 4); + + ReadOnlySpan ncaExtension = new[] { (byte)'.', (byte)'n', (byte)'c', (byte)'a' }; + ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; + + if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0 || + StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0) { - return _config.TargetManagerFsCreator.Create(ref outFileSystem, in rootPath, openCaseSensitive, false, - Result.Success); + isDirectory = false; + return Result.Success; } - internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + return ResultFs.PathNotFound.Log(); + } - internal Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfoByProgramId(out programInfo, programId); - } + private Result ParseDir(in Path path, ref SharedRef outContentFileSystem, + ref SharedRef baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) + { + using var fileSystem = new SharedRef(); + Result rc = _config.SubDirectoryFsCreator.Create(ref fileSystem.Ref(), ref baseFileSystem, in path); + if (rc.IsFailure()) return rc; - private Result GetPartitionIndex(out int index, FileSystemProxyType fspType) + return ParseContentTypeForDirectory(ref outContentFileSystem, ref fileSystem.Ref(), fsType); + } + + private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(ref SharedRef outFileSystem, + in Path path) + { + using var pathRoot = new Path(); + using var pathData = new Path(); + + Result rc = PathFunctions.SetUpFixedPath(ref pathData.Ref(), + new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a' }); + if (rc.IsFailure()) return rc; + + rc = pathRoot.Combine(in path, in pathData); + if (rc.IsFailure()) return rc; + + rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isSupported, ref pathRoot.Ref()); + if (rc.IsFailure()) return rc; + + rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in pathRoot, isSupported, false, + Result.Success); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result ParseNsp(ref U8Span path, ref SharedRef outFileSystem, + ref SharedRef baseFileSystem) + { + ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; + + // Search for the end of the nsp part of the path + int nspPathLen = 0; + + while (true) { - switch (fspType) + U8Span currentSpan; + + while (true) { - case FileSystemProxyType.Code: - case FileSystemProxyType.Control: - case FileSystemProxyType.Manual: - case FileSystemProxyType.Meta: - case FileSystemProxyType.Data: - index = 0; - return Result.Success; - case FileSystemProxyType.Rom: - case FileSystemProxyType.RegisteredUpdate: - index = 1; - return Result.Success; - case FileSystemProxyType.Logo: - index = 2; - return Result.Success; - default: - UnsafeHelpers.SkipParamInit(out index); - return ResultFs.InvalidArgument.Log(); + currentSpan = path.Slice(nspPathLen); + if (StringUtils.CompareCaseInsensitive(nspExtension, currentSpan, 4) == 0) + break; + + if (currentSpan.Length == 0 || currentSpan[0] == 0) + { + return ResultFs.PathNotFound.Log(); + } + + nspPathLen++; + } + + // The nsp filename must be the end of the entire path or the end of a path segment + if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/') + break; + + nspPathLen += 4; + } + + nspPathLen += 4; + + using var pathNsp = new Path(); + Result rc = pathNsp.InitializeWithNormalization(path, nspPathLen); + if (rc.IsFailure()) return rc; + + using var nspFileStorage = new SharedRef(new FileStorageBasedFileSystem()); + + rc = nspFileStorage.Get.Initialize(ref baseFileSystem, in pathNsp, OpenMode.Read); + if (rc.IsFailure()) return rc; + + using SharedRef tempStorage = SharedRef.CreateMove(ref nspFileStorage.Ref()); + rc = _config.PartitionFsCreator.Create(ref outFileSystem, ref tempStorage.Ref()); + + if (rc.IsSuccess()) + { + path = path.Slice(nspPathLen); + } + + return rc; + } + + private Result ParseNca(ref U8Span path, out Nca nca, ref SharedRef baseFileSystem, ulong ncaId) + { + UnsafeHelpers.SkipParamInit(out nca); + + // Todo: Create ref-counted storage + var ncaFileStorage = new FileStorageBasedFileSystem(); + + using var pathNca = new Path(); + Result rc = pathNca.InitializeWithNormalization(path); + if (rc.IsFailure()) return rc; + + rc = ncaFileStorage.Initialize(ref baseFileSystem, in pathNca, OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = _config.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage); + if (rc.IsFailure()) return rc; + + if (ncaId == ulong.MaxValue) + { + ulong ncaProgramId = ncaTemp.Header.TitleId; + + if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId) + { + return ResultFs.InvalidNcaId.Log(); } } - private static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo - new[] - { - (byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o' - }; - - private static ReadOnlySpan ContentStorageDirectoryName => // Contents - new[] - { - (byte) 'C', (byte) 'o', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 't', (byte) 's' - }; + nca = ncaTemp; + return Result.Success; } - public readonly struct InternalProgramIdRangeForSpeedEmulation + private Result ParseContentTypeForDirectory(ref SharedRef outFileSystem, + ref SharedRef baseFileSystem, FileSystemProxyType fsType) { - public readonly ulong ProgramIdMin; - public readonly ulong ProgramIdMax; + ReadOnlySpan dirName; - public InternalProgramIdRangeForSpeedEmulation(ulong min, ulong max) + // Get the name of the subdirectory for the filesystem type + switch (fsType) { - ProgramIdMin = min; - ProgramIdMax = max; + case FileSystemProxyType.Package: + outFileSystem.SetByMove(ref baseFileSystem); + return Result.Success; + + case FileSystemProxyType.Code: + dirName = new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'d', (byte)'e', (byte)'/' }; + break; + case FileSystemProxyType.Rom: + case FileSystemProxyType.Control: + case FileSystemProxyType.Manual: + case FileSystemProxyType.Meta: + case FileSystemProxyType.RegisteredUpdate: + dirName = new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }; + break; + case FileSystemProxyType.Logo: + dirName = new[] { (byte)'/', (byte)'l', (byte)'o', (byte)'g', (byte)'o', (byte)'/' }; + break; + + default: + return ResultFs.InvalidArgument.Log(); + } + + using var subDirFs = new SharedRef(); + + using var directoryPath = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref directoryPath.Ref(), dirName); + if (rc.IsFailure()) return rc; + + if (directoryPath.IsEmpty()) + return ResultFs.InvalidArgument.Log(); + + // Open the subdirectory filesystem + rc = _config.SubDirectoryFsCreator.Create(ref subDirFs.Ref(), ref baseFileSystem, in directoryPath); + if (rc.IsFailure()) return rc; + + outFileSystem.SetByMove(ref subDirFs.Ref()); + return Result.Success; + } + + private Result SetExternalKeyForRightsId(Nca nca) + { + var rightsId = new RightsId(nca.Header.RightsId); + var zero = new RightsId(0, 0); + + if (Crypto.CryptoUtil.IsSameBytes(rightsId.AsBytes(), zero.AsBytes(), Unsafe.SizeOf())) + return Result.Success; + + // ReSharper disable once UnusedVariable + Result rc = _externalKeyManager.Get(rightsId, out AccessKey accessKey); + if (rc.IsFailure()) return rc; + + // todo: Set key in nca reader + + return Result.Success; + } + + private Result OpenStorageByContentType(ref SharedRef outNcaStorage, Nca nca, + out NcaFormatType fsType, FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate) + { + UnsafeHelpers.SkipParamInit(out fsType); + + NcaContentType contentType = nca.Header.ContentType; + + switch (fsProxyType) + { + case FileSystemProxyType.Code: + case FileSystemProxyType.Rom: + case FileSystemProxyType.Logo: + case FileSystemProxyType.RegisteredUpdate: + if (contentType != NcaContentType.Program) + return ResultFs.PreconditionViolation.Log(); + + break; + + case FileSystemProxyType.Control: + if (contentType != NcaContentType.Control) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Manual: + if (contentType != NcaContentType.Manual) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Meta: + if (contentType != NcaContentType.Meta) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Data: + if (contentType != NcaContentType.Data && contentType != NcaContentType.PublicData) + return ResultFs.PreconditionViolation.Log(); + + if (contentType == NcaContentType.Data && !canMountSystemDataPrivate) + return ResultFs.PermissionDenied.Log(); + + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard) + return ResultFs.PermissionDenied.Log(); + + Result rc = SetExternalKeyForRightsId(nca); + if (rc.IsFailure()) return rc; + + rc = GetPartitionIndex(out int sectionIndex, fsProxyType); + if (rc.IsFailure()) return rc; + + rc = _config.StorageOnNcaCreator.Create(ref outNcaStorage, out NcaFsHeader fsHeader, nca, + sectionIndex, fsProxyType == FileSystemProxyType.Code); + if (rc.IsFailure()) return rc; + + fsType = fsHeader.FormatType; + return Result.Success; + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) + { + _encryptionSeed = encryptionSeed; + + return Result.Success; + } + + public Result ResolveRomReferenceProgramId(out ProgramId targetProgramId, ProgramId programId, + byte programIndex) + { + UnsafeHelpers.SkipParamInit(out targetProgramId); + + ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, programIndex); + if (mainProgramId == ProgramId.InvalidId) + return ResultFs.ProgramIndexNotFound.Log(); + + targetProgramId = mainProgramId; + return Result.Success; + } + + public Result ResolveProgramPath(out bool isDirectory, ref Path path, ProgramId programId, StorageId storageId) + { + Result rc = _locationResolverSet.ResolveProgramPath(out isDirectory, ref path, programId, storageId); + if (rc.IsSuccess()) + return Result.Success; + + isDirectory = false; + + rc = _locationResolverSet.ResolveDataPath(ref path, new DataId(programId.Value), storageId); + if (rc.IsSuccess()) + return Result.Success; + + return ResultFs.TargetNotFound.Log(); + } + + public Result ResolveRomPath(out bool isDirectory, ref Path path, ulong id, StorageId storageId) + { + return _locationResolverSet.ResolveRomPath(out isDirectory, ref path, new ProgramId(id), storageId); + } + + public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Path path, + Ncm.ApplicationId applicationId, StorageId storageId) + { + return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out isDirectory, ref path, applicationId, + storageId); + } + + public Result ResolveRegisteredHtmlDocumentPath(ref Path path, ulong id) + { + return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(ref path, id); + } + + internal StorageType GetStorageFlag(ulong programId) + { + if (programId >= _config.SpeedEmulationRange.ProgramIdMin && + programId <= _config.SpeedEmulationRange.ProgramIdMax) + return StorageType.Bis; + else + return StorageType.All; + } + + public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected, + ulong processId) + { + throw new NotImplementedException(); + } + + public void IncrementRomFsRemountForDataCorruptionCount() + { + using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); + + _romFsRemountForDataCorruptionCount++; + } + + public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() + { + using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); + + _romfsUnrecoverableDataCorruptionByRemountCount++; + } + + public void IncrementRomFsRecoveredByInvalidateCacheCount() + { + using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); + + _romFsRecoveredByInvalidateCacheCount++; + } + + public void GetAndClearRomFsErrorInfo(out int recoveredByRemountCount, out int unrecoverableCount, + out int recoveredByCacheInvalidationCount) + { + using ScopedLock lk = ScopedLock.Lock(ref _romfsCountMutex); + + recoveredByRemountCount = _romFsRemountForDataCorruptionCount; + unrecoverableCount = _romfsUnrecoverableDataCorruptionByRemountCount; + recoveredByCacheInvalidationCount = _romFsRecoveredByInvalidateCacheCount; + + _romFsRemountForDataCorruptionCount = 0; + _romfsUnrecoverableDataCorruptionByRemountCount = 0; + _romFsRecoveredByInvalidateCacheCount = 0; + } + + public Result OpenHostFileSystem(ref SharedRef outFileSystem, in Path rootPath, bool openCaseSensitive) + { + return _config.TargetManagerFsCreator.Create(ref outFileSystem, in rootPath, openCaseSensitive, false, + Result.Success); + } + + internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); + } + + internal Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfoByProgramId(out programInfo, programId); + } + + private Result GetPartitionIndex(out int index, FileSystemProxyType fspType) + { + switch (fspType) + { + case FileSystemProxyType.Code: + case FileSystemProxyType.Control: + case FileSystemProxyType.Manual: + case FileSystemProxyType.Meta: + case FileSystemProxyType.Data: + index = 0; + return Result.Success; + case FileSystemProxyType.Rom: + case FileSystemProxyType.RegisteredUpdate: + index = 1; + return Result.Success; + case FileSystemProxyType.Logo: + index = 2; + return Result.Success; + default: + UnsafeHelpers.SkipParamInit(out index); + return ResultFs.InvalidArgument.Log(); } } + + private static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo + new[] + { + (byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o' + }; + + private static ReadOnlySpan ContentStorageDirectoryName => // Contents + new[] + { + (byte) 'C', (byte) 'o', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 't', (byte) 's' + }; +} + +public readonly struct InternalProgramIdRangeForSpeedEmulation +{ + public readonly ulong ProgramIdMin; + public readonly ulong ProgramIdMax; + + public InternalProgramIdRangeForSpeedEmulation(ulong min, ulong max) + { + ProgramIdMin = min; + ProgramIdMax = max; + } } diff --git a/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs b/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs index d334e864..2015b761 100644 --- a/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs +++ b/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs @@ -4,119 +4,118 @@ using LibHac.Fs; using LibHac.Ncm; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Keeps track of the program IDs and program indexes of each program in a multi-program application. +/// +/// Based on FS 10.0.0 (nnSdk 10.4.0) +public class ProgramIndexMapInfoManager { + private LinkedList MapEntries { get; } = new LinkedList(); + /// - /// Keeps track of the program IDs and program indexes of each program in a multi-program application. + /// Unregisters any previously registered program index map info and registers the provided map info. /// - /// Based on FS 10.0.0 (nnSdk 10.4.0) - public class ProgramIndexMapInfoManager + /// The program map info entries to register. + /// : The operation was successful. + public Result Reset(ReadOnlySpan programIndexMapInfo) { - private LinkedList MapEntries { get; } = new LinkedList(); - - /// - /// Unregisters any previously registered program index map info and registers the provided map info. - /// - /// The program map info entries to register. - /// : The operation was successful. - public Result Reset(ReadOnlySpan programIndexMapInfo) + lock (MapEntries) { - lock (MapEntries) + ClearImpl(); + + for (int i = 0; i < programIndexMapInfo.Length; i++) { - ClearImpl(); - - for (int i = 0; i < programIndexMapInfo.Length; i++) - { - MapEntries.AddLast(programIndexMapInfo[i]); - } - - return Result.Success; - } - } - - /// - /// Unregisters all currently registered program index map info. - /// - public void Clear() - { - lock (MapEntries) - { - ClearImpl(); - } - } - - /// - /// Gets the associated with the specified program ID. - /// - /// The program ID of the map info to get. - /// If the program ID was found, the associated - /// with that ID; otherwise, . - public Optional Get(ProgramId programId) - { - lock (MapEntries) - { - return GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); - } - } - - /// - /// Gets the of the program with index in the - /// multi-program app is part of. - /// - /// A program ID in the multi-program app to query. - /// The index of the program to get. - /// If the program exists, the ID of the program with the specified index, - /// otherwise - public ProgramId GetProgramId(ProgramId programId, byte programIndex) - { - lock (MapEntries) - { - Optional mainProgramMapInfo = - GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); - - if(!mainProgramMapInfo.HasValue) - return ProgramId.InvalidId; - - Optional requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) => - x.MainProgramId == mainProgramMapInfo.Value.MainProgramId && x.ProgramIndex == programIndex); - - if (!requestedMapInfo.HasValue) - return ProgramId.InvalidId; - - return requestedMapInfo.Value.ProgramId; - } - } - - /// - /// Gets the number of currently registered programs, - /// - /// The number of registered programs. - public int GetProgramCount() - { - lock (MapEntries) - { - return MapEntries.Count; - } - } - - private delegate bool EntrySelector(in ProgramIndexMapInfo candidate); - - private Optional GetImpl(EntrySelector selector) - { - foreach (ProgramIndexMapInfo entry in MapEntries) - { - if (selector(in entry)) - { - return new Optional(entry); - } + MapEntries.AddLast(programIndexMapInfo[i]); } - return new Optional(); - } - - private void ClearImpl() - { - MapEntries.Clear(); + return Result.Success; } } + + /// + /// Unregisters all currently registered program index map info. + /// + public void Clear() + { + lock (MapEntries) + { + ClearImpl(); + } + } + + /// + /// Gets the associated with the specified program ID. + /// + /// The program ID of the map info to get. + /// If the program ID was found, the associated + /// with that ID; otherwise, . + public Optional Get(ProgramId programId) + { + lock (MapEntries) + { + return GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); + } + } + + /// + /// Gets the of the program with index in the + /// multi-program app is part of. + /// + /// A program ID in the multi-program app to query. + /// The index of the program to get. + /// If the program exists, the ID of the program with the specified index, + /// otherwise + public ProgramId GetProgramId(ProgramId programId, byte programIndex) + { + lock (MapEntries) + { + Optional mainProgramMapInfo = + GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); + + if (!mainProgramMapInfo.HasValue) + return ProgramId.InvalidId; + + Optional requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) => + x.MainProgramId == mainProgramMapInfo.Value.MainProgramId && x.ProgramIndex == programIndex); + + if (!requestedMapInfo.HasValue) + return ProgramId.InvalidId; + + return requestedMapInfo.Value.ProgramId; + } + } + + /// + /// Gets the number of currently registered programs, + /// + /// The number of registered programs. + public int GetProgramCount() + { + lock (MapEntries) + { + return MapEntries.Count; + } + } + + private delegate bool EntrySelector(in ProgramIndexMapInfo candidate); + + private Optional GetImpl(EntrySelector selector) + { + foreach (ProgramIndexMapInfo entry in MapEntries) + { + if (selector(in entry)) + { + return new Optional(entry); + } + } + + return new Optional(); + } + + private void ClearImpl() + { + MapEntries.Clear(); + } } diff --git a/src/LibHac/FsSrv/ProgramIndexRegistryService.cs b/src/LibHac/FsSrv/ProgramIndexRegistryService.cs index fc800000..5bba7d95 100644 --- a/src/LibHac/FsSrv/ProgramIndexRegistryService.cs +++ b/src/LibHac/FsSrv/ProgramIndexRegistryService.cs @@ -6,95 +6,94 @@ using LibHac.FsSrv.Impl; using LibHac.Sf; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Used to perform operations on the program index registry. +/// +/// Appropriate methods calls on IFileSystemProxy are forwarded to this class +/// which then checks the calling process' permissions and performs the requested operation. +///
Based on FS 10.0.0 (nnSdk 10.4.0)
+internal readonly struct ProgramIndexRegistryService { - /// - /// Used to perform operations on the program index registry. - /// - /// Appropriate methods calls on IFileSystemProxy are forwarded to this class - /// which then checks the calling process' permissions and performs the requested operation. - ///
Based on FS 10.0.0 (nnSdk 10.4.0)
- internal readonly struct ProgramIndexRegistryService + private ProgramRegistryServiceImpl ServiceImpl { get; } + private ulong ProcessId { get; } + + public ProgramIndexRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId) { - private ProgramRegistryServiceImpl ServiceImpl { get; } - private ulong ProcessId { get; } + ServiceImpl = serviceImpl; + ProcessId = processId; + } - public ProgramIndexRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId) - { - ServiceImpl = serviceImpl; - ProcessId = processId; - } + /// + /// Unregisters any previously registered program index map info and registers the provided map info. + /// + /// A buffer containing the program map info to register. + /// The number of programs to register. The provided buffer must be + /// large enough to hold this many entries. + /// : The operation was successful.
+ /// : Insufficient permissions.
+ /// : The buffer was too small to hold the specified + /// number of entries.
+ public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfo, int programCount) + { + // Verify the caller's permissions + Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId); + if (rc.IsFailure()) return rc; - /// - /// Unregisters any previously registered program index map info and registers the provided map info. - /// - /// A buffer containing the program map info to register. - /// The number of programs to register. The provided buffer must be - /// large enough to hold this many entries. - /// : The operation was successful.
- /// : Insufficient permissions.
- /// : The buffer was too small to hold the specified - /// number of entries.
- public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfo, int programCount) - { - // Verify the caller's permissions - Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.RegisterProgramIndexMapInfo)) - return ResultFs.PermissionDenied.Log(); - - // Return early if the program count is 0 so we leave any previously - // registered entries as they were - if (programCount == 0) - return Result.Success; - - // Verify that the provided buffer is large enough to hold "programCount" entries - ReadOnlySpan - mapInfo = MemoryMarshal.Cast(programIndexMapInfo.Buffer); - - if (mapInfo.Length < programCount) - return ResultFs.InvalidSize.Log(); - - // Register the map info - return ServiceImpl.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); - } - - /// - /// Gets the multi-program index of the calling process and the number of programs - /// in the current application. - /// - /// When this method returns successfully, contains the - /// program index of the calling process. - /// When this method returns successfully, contains the - /// number of programs in the current application. - /// : The operation was successful.
- /// : The calling program was not found - /// in the program registry. Something's wrong with Loader if this happens.
- public Result GetProgramIndex(out int programIndex, out int programCount) - { - UnsafeHelpers.SkipParamInit(out programIndex, out programCount); - - // No permissions are needed to call this method - Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId); - if (rc.IsFailure()) return rc; - - // Try to get map info for this process - Optional mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); - - // Set the output program index if map info was found - programIndex = mapInfo.HasValue ? mapInfo.ValueRo.ProgramIndex : 0; - - // Set the number of programs in the current application - programCount = ServiceImpl.GetProgramIndexMapInfoCount(); + if (!programInfo.AccessControl.CanCall(OperationType.RegisterProgramIndexMapInfo)) + return ResultFs.PermissionDenied.Log(); + // Return early if the program count is 0 so we leave any previously + // registered entries as they were + if (programCount == 0) return Result.Success; - } - /// - private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - return ServiceImpl.GetProgramInfo(out programInfo, processId); - } + // Verify that the provided buffer is large enough to hold "programCount" entries + ReadOnlySpan + mapInfo = MemoryMarshal.Cast(programIndexMapInfo.Buffer); + + if (mapInfo.Length < programCount) + return ResultFs.InvalidSize.Log(); + + // Register the map info + return ServiceImpl.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); + } + + /// + /// Gets the multi-program index of the calling process and the number of programs + /// in the current application. + /// + /// When this method returns successfully, contains the + /// program index of the calling process. + /// When this method returns successfully, contains the + /// number of programs in the current application. + /// : The operation was successful.
+ /// : The calling program was not found + /// in the program registry. Something's wrong with Loader if this happens.
+ public Result GetProgramIndex(out int programIndex, out int programCount) + { + UnsafeHelpers.SkipParamInit(out programIndex, out programCount); + + // No permissions are needed to call this method + Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId); + if (rc.IsFailure()) return rc; + + // Try to get map info for this process + Optional mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); + + // Set the output program index if map info was found + programIndex = mapInfo.HasValue ? mapInfo.ValueRo.ProgramIndex : 0; + + // Set the number of programs in the current application + programCount = ServiceImpl.GetProgramIndexMapInfoCount(); + + return Result.Success; + } + + /// + private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return ServiceImpl.GetProgramInfo(out programInfo, processId); } } diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs index 605975b1..973714ce 100644 --- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs @@ -4,92 +4,91 @@ using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public static class ProgramRegistryImplGlobalMethods { - public static class ProgramRegistryImplGlobalMethods + public static void InitializeProgramRegistryImpl(this FileSystemServer fsSrv, + ProgramRegistryServiceImpl serviceImpl) { - public static void InitializeProgramRegistryImpl(this FileSystemServer fsSrv, - ProgramRegistryServiceImpl serviceImpl) - { - fsSrv.Globals.ProgramRegistryImpl.ServiceImpl = serviceImpl; - } + fsSrv.Globals.ProgramRegistryImpl.ServiceImpl = serviceImpl; + } +} + +internal struct ProgramRegistryImplGlobals +{ + public ProgramRegistryServiceImpl ServiceImpl; +} + +/// +/// Used to add, remove or access the Program Registry. +/// +/// Every process that is launched has information registered with FS. This information +/// is stored in a and includes the process' process ID, program ID, +/// storage location and file system permissions. This allows FS to resolve the program ID and +/// verify the permissions of any process calling it. +///
Based on FS 10.0.0 (nnSdk 10.4.0)
+public class ProgramRegistryImpl : IProgramRegistry +{ + private FileSystemServer _fsServer; + private ulong _processId; + + private ref ProgramRegistryImplGlobals Globals => ref _fsServer.Globals.ProgramRegistryImpl; + + public ProgramRegistryImpl(FileSystemServer server) + { + _fsServer = server; + _processId = ulong.MaxValue; } - internal struct ProgramRegistryImplGlobals + public void Dispose() { } + + /// : The operation was successful.
+ /// : The process ID is already registered.
+ /// : Insufficient permissions.
+ /// + public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + InBuffer accessControlData, InBuffer accessControlDescriptor) { - public ProgramRegistryServiceImpl ServiceImpl; + if (!ProgramInfo.IsInitialProgram(_processId)) + return ResultFs.PermissionDenied.Log(); + + return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer, + accessControlDescriptor.Buffer); + } + + /// : The operation was successful.
+ /// : The process ID is not registered.
+ /// : Insufficient permissions.
+ /// + public Result UnregisterProgram(ulong processId) + { + if (!ProgramInfo.IsInitialProgram(_processId)) + return ResultFs.PermissionDenied.Log(); + + return Globals.ServiceImpl.UnregisterProgramInfo(processId); + } + + /// + public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return Globals.ServiceImpl.GetProgramInfo(out programInfo, processId); + } + + /// + public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + return Globals.ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId); } /// - /// Used to add, remove or access the Program Registry. + /// Sets the process ID of the process that will use this service via IPC. /// - /// Every process that is launched has information registered with FS. This information - /// is stored in a and includes the process' process ID, program ID, - /// storage location and file system permissions. This allows FS to resolve the program ID and - /// verify the permissions of any process calling it. - ///
Based on FS 10.0.0 (nnSdk 10.4.0)
- public class ProgramRegistryImpl : IProgramRegistry + /// The process ID to set. + /// : The operation was successful. + public Result SetCurrentProcess(ulong processId) { - private FileSystemServer _fsServer; - private ulong _processId; - - private ref ProgramRegistryImplGlobals Globals => ref _fsServer.Globals.ProgramRegistryImpl; - - public ProgramRegistryImpl(FileSystemServer server) - { - _fsServer = server; - _processId = ulong.MaxValue; - } - - public void Dispose() { } - - /// : The operation was successful.
- /// : The process ID is already registered.
- /// : Insufficient permissions.
- /// - public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - InBuffer accessControlData, InBuffer accessControlDescriptor) - { - if (!ProgramInfo.IsInitialProgram(_processId)) - return ResultFs.PermissionDenied.Log(); - - return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer, - accessControlDescriptor.Buffer); - } - - /// : The operation was successful.
- /// : The process ID is not registered.
- /// : Insufficient permissions.
- /// - public Result UnregisterProgram(ulong processId) - { - if (!ProgramInfo.IsInitialProgram(_processId)) - return ResultFs.PermissionDenied.Log(); - - return Globals.ServiceImpl.UnregisterProgramInfo(processId); - } - - /// - public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - return Globals.ServiceImpl.GetProgramInfo(out programInfo, processId); - } - - /// - public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - return Globals.ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId); - } - - /// - /// Sets the process ID of the process that will use this service via IPC. - /// - /// The process ID to set. - /// : The operation was successful. - public Result SetCurrentProcess(ulong processId) - { - _processId = processId; - return Result.Success; - } + _processId = processId; + return Result.Success; } } diff --git a/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs b/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs index f8ab978d..9d5a52b7 100644 --- a/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs @@ -4,83 +4,82 @@ using LibHac.FsSrv.Impl; using LibHac.Ncm; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Manages the main program registry and the multi-program registry. +/// +/// Based on FS 10.0.0 (nnSdk 10.4.0) +public class ProgramRegistryServiceImpl { - /// - /// Manages the main program registry and the multi-program registry. - /// - /// Based on FS 10.0.0 (nnSdk 10.4.0) - public class ProgramRegistryServiceImpl + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private Configuration _config; + private ProgramRegistryManager _registryManager; + private ProgramIndexMapInfoManager _programIndexManager; + + public ProgramRegistryServiceImpl(in Configuration config) { - // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable - private Configuration _config; - private ProgramRegistryManager _registryManager; - private ProgramIndexMapInfoManager _programIndexManager; + _config = config; + _registryManager = new ProgramRegistryManager(_config.FsServer); + _programIndexManager = new ProgramIndexMapInfoManager(); + } - public ProgramRegistryServiceImpl(in Configuration config) - { - _config = config; - _registryManager = new ProgramRegistryManager(_config.FsServer); - _programIndexManager = new ProgramIndexMapInfoManager(); - } + public struct Configuration + { + // LibHac addition + public FileSystemServer FsServer; + } - public struct Configuration - { - // LibHac addition - public FileSystemServer FsServer; - } + /// + public Result RegisterProgramInfo(ulong processId, ProgramId programId, StorageId storageId, + ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + { + return _registryManager.RegisterProgram(processId, programId, storageId, accessControlData, + accessControlDescriptor); + } - /// - public Result RegisterProgramInfo(ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) - { - return _registryManager.RegisterProgram(processId, programId, storageId, accessControlData, - accessControlDescriptor); - } + /// + public Result UnregisterProgramInfo(ulong processId) + { + return _registryManager.UnregisterProgram(processId); + } - /// - public Result UnregisterProgramInfo(ulong processId) - { - return _registryManager.UnregisterProgram(processId); - } + /// + public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _registryManager.GetProgramInfo(out programInfo, processId); + } - /// - public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - return _registryManager.GetProgramInfo(out programInfo, processId); - } + /// + public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + return _registryManager.GetProgramInfoByProgramId(out programInfo, programId); + } - /// - public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) - { - return _registryManager.GetProgramInfoByProgramId(out programInfo, programId); - } + /// + public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex) + { + return _programIndexManager.GetProgramId(programId, programIndex); + } - /// - public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex) - { - return _programIndexManager.GetProgramId(programId, programIndex); - } + /// + public Optional GetProgramIndexMapInfo(ProgramId programId) + { + return _programIndexManager.Get(programId); + } - /// - public Optional GetProgramIndexMapInfo(ProgramId programId) - { - return _programIndexManager.Get(programId); - } + /// + /// Gets the number of programs in the currently registered application. + /// + /// The number of programs. + public int GetProgramIndexMapInfoCount() + { + return _programIndexManager.GetProgramCount(); + } - /// - /// Gets the number of programs in the currently registered application. - /// - /// The number of programs. - public int GetProgramIndexMapInfoCount() - { - return _programIndexManager.GetProgramCount(); - } - - /// - public Result RegisterProgramIndexMapInfo(ReadOnlySpan programIndexMapInfo) - { - return _programIndexManager.Reset(programIndexMapInfo); - } + /// + public Result RegisterProgramIndexMapInfo(ReadOnlySpan programIndexMapInfo) + { + return _programIndexManager.Reset(programIndexMapInfo); } } diff --git a/src/LibHac/FsSrv/ResultSdmmc.cs b/src/LibHac/FsSrv/ResultSdmmc.cs index 73d2eb96..e4269f54 100644 --- a/src/LibHac/FsSrv/ResultSdmmc.cs +++ b/src/LibHac/FsSrv/ResultSdmmc.cs @@ -11,175 +11,174 @@ using System.Runtime.CompilerServices; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public static class ResultSdmmc { - public static class ResultSdmmc - { - public const int ModuleSdmmc = 24; + public const int ModuleSdmmc = 24; - /// Error code: 2024-0001; Inner value: 0x218 - public static Result.Base NoDevice => new Result.Base(ModuleSdmmc, 1); - /// Error code: 2024-0002; Inner value: 0x418 - public static Result.Base NotActivated => new Result.Base(ModuleSdmmc, 2); - /// Error code: 2024-0003; Inner value: 0x618 - public static Result.Base DeviceRemoved => new Result.Base(ModuleSdmmc, 3); - /// Error code: 2024-0004; Inner value: 0x818 - public static Result.Base NotAwakened => new Result.Base(ModuleSdmmc, 4); + /// Error code: 2024-0001; Inner value: 0x218 + public static Result.Base NoDevice => new Result.Base(ModuleSdmmc, 1); + /// Error code: 2024-0002; Inner value: 0x418 + public static Result.Base NotActivated => new Result.Base(ModuleSdmmc, 2); + /// Error code: 2024-0003; Inner value: 0x618 + public static Result.Base DeviceRemoved => new Result.Base(ModuleSdmmc, 3); + /// Error code: 2024-0004; Inner value: 0x818 + public static Result.Base NotAwakened => new Result.Base(ModuleSdmmc, 4); - /// Error code: 2024-0032; Range: 32-126; Inner value: 0x4018 - public static Result.Base CommunicationError => new Result.Base(ModuleSdmmc, 32, 126); - /// Error code: 2024-0033; Range: 33-46; Inner value: 0x4218 - public static Result.Base CommunicationNotAttained => new Result.Base(ModuleSdmmc, 33, 46); - /// Error code: 2024-0034; Inner value: 0x4418 - public static Result.Base ResponseIndexError => new Result.Base(ModuleSdmmc, 34); - /// Error code: 2024-0035; Inner value: 0x4618 - public static Result.Base ResponseEndBitError => new Result.Base(ModuleSdmmc, 35); - /// Error code: 2024-0036; Inner value: 0x4818 - public static Result.Base ResponseCrcError => new Result.Base(ModuleSdmmc, 36); - /// Error code: 2024-0037; Inner value: 0x4a18 - public static Result.Base ResponseTimeoutError => new Result.Base(ModuleSdmmc, 37); - /// Error code: 2024-0038; Inner value: 0x4c18 - public static Result.Base DataEndBitError => new Result.Base(ModuleSdmmc, 38); - /// Error code: 2024-0039; Inner value: 0x4e18 - public static Result.Base DataCrcError => new Result.Base(ModuleSdmmc, 39); - /// Error code: 2024-0040; Inner value: 0x5018 - public static Result.Base DataTimeoutError => new Result.Base(ModuleSdmmc, 40); - /// Error code: 2024-0041; Inner value: 0x5218 - public static Result.Base AutoCommandResponseIndexError => new Result.Base(ModuleSdmmc, 41); - /// Error code: 2024-0042; Inner value: 0x5418 - public static Result.Base AutoCommandResponseEndBitError => new Result.Base(ModuleSdmmc, 42); - /// Error code: 2024-0043; Inner value: 0x5618 - public static Result.Base AutoCommandResponseCrcError => new Result.Base(ModuleSdmmc, 43); - /// Error code: 2024-0044; Inner value: 0x5818 - public static Result.Base AutoCommandResponseTimeoutError => new Result.Base(ModuleSdmmc, 44); - /// Error code: 2024-0045; Inner value: 0x5a18 - public static Result.Base CommandCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 45); - /// Error code: 2024-0046; Inner value: 0x5c18 - public static Result.Base TransferCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 46); + /// Error code: 2024-0032; Range: 32-126; Inner value: 0x4018 + public static Result.Base CommunicationError => new Result.Base(ModuleSdmmc, 32, 126); + /// Error code: 2024-0033; Range: 33-46; Inner value: 0x4218 + public static Result.Base CommunicationNotAttained => new Result.Base(ModuleSdmmc, 33, 46); + /// Error code: 2024-0034; Inner value: 0x4418 + public static Result.Base ResponseIndexError => new Result.Base(ModuleSdmmc, 34); + /// Error code: 2024-0035; Inner value: 0x4618 + public static Result.Base ResponseEndBitError => new Result.Base(ModuleSdmmc, 35); + /// Error code: 2024-0036; Inner value: 0x4818 + public static Result.Base ResponseCrcError => new Result.Base(ModuleSdmmc, 36); + /// Error code: 2024-0037; Inner value: 0x4a18 + public static Result.Base ResponseTimeoutError => new Result.Base(ModuleSdmmc, 37); + /// Error code: 2024-0038; Inner value: 0x4c18 + public static Result.Base DataEndBitError => new Result.Base(ModuleSdmmc, 38); + /// Error code: 2024-0039; Inner value: 0x4e18 + public static Result.Base DataCrcError => new Result.Base(ModuleSdmmc, 39); + /// Error code: 2024-0040; Inner value: 0x5018 + public static Result.Base DataTimeoutError => new Result.Base(ModuleSdmmc, 40); + /// Error code: 2024-0041; Inner value: 0x5218 + public static Result.Base AutoCommandResponseIndexError => new Result.Base(ModuleSdmmc, 41); + /// Error code: 2024-0042; Inner value: 0x5418 + public static Result.Base AutoCommandResponseEndBitError => new Result.Base(ModuleSdmmc, 42); + /// Error code: 2024-0043; Inner value: 0x5618 + public static Result.Base AutoCommandResponseCrcError => new Result.Base(ModuleSdmmc, 43); + /// Error code: 2024-0044; Inner value: 0x5818 + public static Result.Base AutoCommandResponseTimeoutError => new Result.Base(ModuleSdmmc, 44); + /// Error code: 2024-0045; Inner value: 0x5a18 + public static Result.Base CommandCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 45); + /// Error code: 2024-0046; Inner value: 0x5c18 + public static Result.Base TransferCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 46); - /// Error code: 2024-0048; Range: 48-70; Inner value: 0x6018 - public static Result.Base DeviceStatusHasError => new Result.Base(ModuleSdmmc, 48, 70); - /// Error code: 2024-0049; Inner value: 0x6218 - public static Result.Base DeviceStatusAddressOutOfRange => new Result.Base(ModuleSdmmc, 49); - /// Error code: 2024-0050; Inner value: 0x6418 - public static Result.Base DeviceStatusAddressMisaligned => new Result.Base(ModuleSdmmc, 50); - /// Error code: 2024-0051; Inner value: 0x6618 - public static Result.Base DeviceStatusBlockLenError => new Result.Base(ModuleSdmmc, 51); - /// Error code: 2024-0052; Inner value: 0x6818 - public static Result.Base DeviceStatusEraseSeqError => new Result.Base(ModuleSdmmc, 52); - /// Error code: 2024-0053; Inner value: 0x6a18 - public static Result.Base DeviceStatusEraseParam => new Result.Base(ModuleSdmmc, 53); - /// Error code: 2024-0054; Inner value: 0x6c18 - public static Result.Base DeviceStatusWpViolation => new Result.Base(ModuleSdmmc, 54); - /// Error code: 2024-0055; Inner value: 0x6e18 - public static Result.Base DeviceStatusLockUnlockFailed => new Result.Base(ModuleSdmmc, 55); - /// Error code: 2024-0056; Inner value: 0x7018 - public static Result.Base DeviceStatusComCrcError => new Result.Base(ModuleSdmmc, 56); - /// Error code: 2024-0057; Inner value: 0x7218 - public static Result.Base DeviceStatusIllegalCommand => new Result.Base(ModuleSdmmc, 57); - /// Error code: 2024-0058; Inner value: 0x7418 - public static Result.Base DeviceStatusDeviceEccFailed => new Result.Base(ModuleSdmmc, 58); - /// Error code: 2024-0059; Inner value: 0x7618 - public static Result.Base DeviceStatusCcError => new Result.Base(ModuleSdmmc, 59); - /// Error code: 2024-0060; Inner value: 0x7818 - public static Result.Base DeviceStatusError => new Result.Base(ModuleSdmmc, 60); - /// Error code: 2024-0061; Inner value: 0x7a18 - public static Result.Base DeviceStatusCidCsdOverwrite => new Result.Base(ModuleSdmmc, 61); - /// Error code: 2024-0062; Inner value: 0x7c18 - public static Result.Base DeviceStatusWpEraseSkip => new Result.Base(ModuleSdmmc, 62); - /// Error code: 2024-0063; Inner value: 0x7e18 - public static Result.Base DeviceStatusEraseReset => new Result.Base(ModuleSdmmc, 63); - /// Error code: 2024-0064; Inner value: 0x8018 - public static Result.Base DeviceStatusSwitchError => new Result.Base(ModuleSdmmc, 64); + /// Error code: 2024-0048; Range: 48-70; Inner value: 0x6018 + public static Result.Base DeviceStatusHasError => new Result.Base(ModuleSdmmc, 48, 70); + /// Error code: 2024-0049; Inner value: 0x6218 + public static Result.Base DeviceStatusAddressOutOfRange => new Result.Base(ModuleSdmmc, 49); + /// Error code: 2024-0050; Inner value: 0x6418 + public static Result.Base DeviceStatusAddressMisaligned => new Result.Base(ModuleSdmmc, 50); + /// Error code: 2024-0051; Inner value: 0x6618 + public static Result.Base DeviceStatusBlockLenError => new Result.Base(ModuleSdmmc, 51); + /// Error code: 2024-0052; Inner value: 0x6818 + public static Result.Base DeviceStatusEraseSeqError => new Result.Base(ModuleSdmmc, 52); + /// Error code: 2024-0053; Inner value: 0x6a18 + public static Result.Base DeviceStatusEraseParam => new Result.Base(ModuleSdmmc, 53); + /// Error code: 2024-0054; Inner value: 0x6c18 + public static Result.Base DeviceStatusWpViolation => new Result.Base(ModuleSdmmc, 54); + /// Error code: 2024-0055; Inner value: 0x6e18 + public static Result.Base DeviceStatusLockUnlockFailed => new Result.Base(ModuleSdmmc, 55); + /// Error code: 2024-0056; Inner value: 0x7018 + public static Result.Base DeviceStatusComCrcError => new Result.Base(ModuleSdmmc, 56); + /// Error code: 2024-0057; Inner value: 0x7218 + public static Result.Base DeviceStatusIllegalCommand => new Result.Base(ModuleSdmmc, 57); + /// Error code: 2024-0058; Inner value: 0x7418 + public static Result.Base DeviceStatusDeviceEccFailed => new Result.Base(ModuleSdmmc, 58); + /// Error code: 2024-0059; Inner value: 0x7618 + public static Result.Base DeviceStatusCcError => new Result.Base(ModuleSdmmc, 59); + /// Error code: 2024-0060; Inner value: 0x7818 + public static Result.Base DeviceStatusError => new Result.Base(ModuleSdmmc, 60); + /// Error code: 2024-0061; Inner value: 0x7a18 + public static Result.Base DeviceStatusCidCsdOverwrite => new Result.Base(ModuleSdmmc, 61); + /// Error code: 2024-0062; Inner value: 0x7c18 + public static Result.Base DeviceStatusWpEraseSkip => new Result.Base(ModuleSdmmc, 62); + /// Error code: 2024-0063; Inner value: 0x7e18 + public static Result.Base DeviceStatusEraseReset => new Result.Base(ModuleSdmmc, 63); + /// Error code: 2024-0064; Inner value: 0x8018 + public static Result.Base DeviceStatusSwitchError => new Result.Base(ModuleSdmmc, 64); - /// Error code: 2024-0072; Inner value: 0x9018 - public static Result.Base UnexpectedDeviceState => new Result.Base(ModuleSdmmc, 72); - /// Error code: 2024-0073; Inner value: 0x9218 - public static Result.Base UnexpectedDeviceCsdValue => new Result.Base(ModuleSdmmc, 73); - /// Error code: 2024-0074; Inner value: 0x9418 - public static Result.Base AbortTransactionSoftwareTimeout => new Result.Base(ModuleSdmmc, 74); - /// Error code: 2024-0075; Inner value: 0x9618 - public static Result.Base CommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleSdmmc, 75); - /// Error code: 2024-0076; Inner value: 0x9818 - public static Result.Base CommandInhibitDatSoftwareTimeout => new Result.Base(ModuleSdmmc, 76); - /// Error code: 2024-0077; Inner value: 0x9a18 - public static Result.Base BusySoftwareTimeout => new Result.Base(ModuleSdmmc, 77); - /// Error code: 2024-0078; Inner value: 0x9c18 - public static Result.Base IssueTuningCommandSoftwareTimeout => new Result.Base(ModuleSdmmc, 78); - /// Error code: 2024-0079; Inner value: 0x9e18 - public static Result.Base TuningFailed => new Result.Base(ModuleSdmmc, 79); - /// Error code: 2024-0080; Inner value: 0xa018 - public static Result.Base MmcInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 80); - /// Error code: 2024-0081; Inner value: 0xa218 - public static Result.Base MmcNotSupportExtendedCsd => new Result.Base(ModuleSdmmc, 81); - /// Error code: 2024-0082; Inner value: 0xa418 - public static Result.Base UnexpectedMmcExtendedCsdValue => new Result.Base(ModuleSdmmc, 82); - /// Error code: 2024-0083; Inner value: 0xa618 - public static Result.Base MmcEraseSoftwareTimeout => new Result.Base(ModuleSdmmc, 83); - /// Error code: 2024-0084; Inner value: 0xa818 - public static Result.Base SdCardValidationError => new Result.Base(ModuleSdmmc, 84); - /// Error code: 2024-0085; Inner value: 0xaa18 - public static Result.Base SdCardInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 85); - /// Error code: 2024-0086; Inner value: 0xac18 - public static Result.Base SdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleSdmmc, 86); - /// Error code: 2024-0087; Inner value: 0xae18 - public static Result.Base UnexpectedSdCardAcmdDisabled => new Result.Base(ModuleSdmmc, 87); - /// Error code: 2024-0088; Inner value: 0xb018 - public static Result.Base SdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 88); - /// Error code: 2024-0089; Inner value: 0xb218 - public static Result.Base UnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 89); - /// Error code: 2024-0090; Inner value: 0xb418 - public static Result.Base SdCardNotSupportAccessMode => new Result.Base(ModuleSdmmc, 90); - /// Error code: 2024-0091; Inner value: 0xb618 - public static Result.Base SdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleSdmmc, 91); - /// Error code: 2024-0092; Inner value: 0xb818 - public static Result.Base SdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleSdmmc, 92); - /// Error code: 2024-0093; Inner value: 0xba18 - public static Result.Base SdCardCannotSwitchAccessMode => new Result.Base(ModuleSdmmc, 93); - /// Error code: 2024-0094; Inner value: 0xbc18 - public static Result.Base SdCardFailedSwitchAccessMode => new Result.Base(ModuleSdmmc, 94); - /// Error code: 2024-0095; Inner value: 0xbe18 - public static Result.Base SdCardUnacceptableCurrentConsumption => new Result.Base(ModuleSdmmc, 95); - /// Error code: 2024-0096; Inner value: 0xc018 - public static Result.Base SdCardNotReadyToVoltageSwitch => new Result.Base(ModuleSdmmc, 96); - /// Error code: 2024-0097; Inner value: 0xc218 - public static Result.Base SdCardNotCompleteVoltageSwitch => new Result.Base(ModuleSdmmc, 97); + /// Error code: 2024-0072; Inner value: 0x9018 + public static Result.Base UnexpectedDeviceState => new Result.Base(ModuleSdmmc, 72); + /// Error code: 2024-0073; Inner value: 0x9218 + public static Result.Base UnexpectedDeviceCsdValue => new Result.Base(ModuleSdmmc, 73); + /// Error code: 2024-0074; Inner value: 0x9418 + public static Result.Base AbortTransactionSoftwareTimeout => new Result.Base(ModuleSdmmc, 74); + /// Error code: 2024-0075; Inner value: 0x9618 + public static Result.Base CommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleSdmmc, 75); + /// Error code: 2024-0076; Inner value: 0x9818 + public static Result.Base CommandInhibitDatSoftwareTimeout => new Result.Base(ModuleSdmmc, 76); + /// Error code: 2024-0077; Inner value: 0x9a18 + public static Result.Base BusySoftwareTimeout => new Result.Base(ModuleSdmmc, 77); + /// Error code: 2024-0078; Inner value: 0x9c18 + public static Result.Base IssueTuningCommandSoftwareTimeout => new Result.Base(ModuleSdmmc, 78); + /// Error code: 2024-0079; Inner value: 0x9e18 + public static Result.Base TuningFailed => new Result.Base(ModuleSdmmc, 79); + /// Error code: 2024-0080; Inner value: 0xa018 + public static Result.Base MmcInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 80); + /// Error code: 2024-0081; Inner value: 0xa218 + public static Result.Base MmcNotSupportExtendedCsd => new Result.Base(ModuleSdmmc, 81); + /// Error code: 2024-0082; Inner value: 0xa418 + public static Result.Base UnexpectedMmcExtendedCsdValue => new Result.Base(ModuleSdmmc, 82); + /// Error code: 2024-0083; Inner value: 0xa618 + public static Result.Base MmcEraseSoftwareTimeout => new Result.Base(ModuleSdmmc, 83); + /// Error code: 2024-0084; Inner value: 0xa818 + public static Result.Base SdCardValidationError => new Result.Base(ModuleSdmmc, 84); + /// Error code: 2024-0085; Inner value: 0xaa18 + public static Result.Base SdCardInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 85); + /// Error code: 2024-0086; Inner value: 0xac18 + public static Result.Base SdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleSdmmc, 86); + /// Error code: 2024-0087; Inner value: 0xae18 + public static Result.Base UnexpectedSdCardAcmdDisabled => new Result.Base(ModuleSdmmc, 87); + /// Error code: 2024-0088; Inner value: 0xb018 + public static Result.Base SdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 88); + /// Error code: 2024-0089; Inner value: 0xb218 + public static Result.Base UnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 89); + /// Error code: 2024-0090; Inner value: 0xb418 + public static Result.Base SdCardNotSupportAccessMode => new Result.Base(ModuleSdmmc, 90); + /// Error code: 2024-0091; Inner value: 0xb618 + public static Result.Base SdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleSdmmc, 91); + /// Error code: 2024-0092; Inner value: 0xb818 + public static Result.Base SdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleSdmmc, 92); + /// Error code: 2024-0093; Inner value: 0xba18 + public static Result.Base SdCardCannotSwitchAccessMode => new Result.Base(ModuleSdmmc, 93); + /// Error code: 2024-0094; Inner value: 0xbc18 + public static Result.Base SdCardFailedSwitchAccessMode => new Result.Base(ModuleSdmmc, 94); + /// Error code: 2024-0095; Inner value: 0xbe18 + public static Result.Base SdCardUnacceptableCurrentConsumption => new Result.Base(ModuleSdmmc, 95); + /// Error code: 2024-0096; Inner value: 0xc018 + public static Result.Base SdCardNotReadyToVoltageSwitch => new Result.Base(ModuleSdmmc, 96); + /// Error code: 2024-0097; Inner value: 0xc218 + public static Result.Base SdCardNotCompleteVoltageSwitch => new Result.Base(ModuleSdmmc, 97); - /// Error code: 2024-0128; Range: 128-158; Inner value: 0x10018 - public static Result.Base HostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 128, 158); } - /// Error code: 2024-0129; Inner value: 0x10218 - public static Result.Base InternalClockStableSoftwareTimeout => new Result.Base(ModuleSdmmc, 129); - /// Error code: 2024-0130; Inner value: 0x10418 - public static Result.Base SdHostStandardUnknownAutoCmdError => new Result.Base(ModuleSdmmc, 130); - /// Error code: 2024-0131; Inner value: 0x10618 - public static Result.Base SdHostStandardUnknownError => new Result.Base(ModuleSdmmc, 131); - /// Error code: 2024-0132; Inner value: 0x10818 - public static Result.Base SdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 132); - /// Error code: 2024-0133; Inner value: 0x10a18 - public static Result.Base SdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleSdmmc, 133); - /// Error code: 2024-0134; Inner value: 0x10c18 - public static Result.Base SdHostStandardFailSwitchTo18V => new Result.Base(ModuleSdmmc, 134); - /// Error code: 2024-0135; Inner value: 0x10e18 - public static Result.Base DriveStrengthCalibrationNotCompleted => new Result.Base(ModuleSdmmc, 135); - /// Error code: 2024-0136; Inner value: 0x11018 - public static Result.Base DriveStrengthCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 136); - /// Error code: 2024-0137; Inner value: 0x11218 - public static Result.Base SdmmcCompShortToGnd => new Result.Base(ModuleSdmmc, 137); - /// Error code: 2024-0138; Inner value: 0x11418 - public static Result.Base SdmmcCompOpen => new Result.Base(ModuleSdmmc, 138); + /// Error code: 2024-0128; Range: 128-158; Inner value: 0x10018 + public static Result.Base HostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 128, 158); } + /// Error code: 2024-0129; Inner value: 0x10218 + public static Result.Base InternalClockStableSoftwareTimeout => new Result.Base(ModuleSdmmc, 129); + /// Error code: 2024-0130; Inner value: 0x10418 + public static Result.Base SdHostStandardUnknownAutoCmdError => new Result.Base(ModuleSdmmc, 130); + /// Error code: 2024-0131; Inner value: 0x10618 + public static Result.Base SdHostStandardUnknownError => new Result.Base(ModuleSdmmc, 131); + /// Error code: 2024-0132; Inner value: 0x10818 + public static Result.Base SdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 132); + /// Error code: 2024-0133; Inner value: 0x10a18 + public static Result.Base SdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleSdmmc, 133); + /// Error code: 2024-0134; Inner value: 0x10c18 + public static Result.Base SdHostStandardFailSwitchTo18V => new Result.Base(ModuleSdmmc, 134); + /// Error code: 2024-0135; Inner value: 0x10e18 + public static Result.Base DriveStrengthCalibrationNotCompleted => new Result.Base(ModuleSdmmc, 135); + /// Error code: 2024-0136; Inner value: 0x11018 + public static Result.Base DriveStrengthCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 136); + /// Error code: 2024-0137; Inner value: 0x11218 + public static Result.Base SdmmcCompShortToGnd => new Result.Base(ModuleSdmmc, 137); + /// Error code: 2024-0138; Inner value: 0x11418 + public static Result.Base SdmmcCompOpen => new Result.Base(ModuleSdmmc, 138); - /// Error code: 2024-0160; Range: 160-190; Inner value: 0x14018 - public static Result.Base InternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 160, 190); } - /// Error code: 2024-0161; Inner value: 0x14218 - public static Result.Base NoWaitedInterrupt => new Result.Base(ModuleSdmmc, 161); - /// Error code: 2024-0162; Inner value: 0x14418 - public static Result.Base WaitInterruptSoftwareTimeout => new Result.Base(ModuleSdmmc, 162); + /// Error code: 2024-0160; Range: 160-190; Inner value: 0x14018 + public static Result.Base InternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 160, 190); } + /// Error code: 2024-0161; Inner value: 0x14218 + public static Result.Base NoWaitedInterrupt => new Result.Base(ModuleSdmmc, 161); + /// Error code: 2024-0162; Inner value: 0x14418 + public static Result.Base WaitInterruptSoftwareTimeout => new Result.Base(ModuleSdmmc, 162); - /// Error code: 2024-0192; Inner value: 0x18018 - public static Result.Base AbortCommandIssued => new Result.Base(ModuleSdmmc, 192); - /// Error code: 2024-0200; Inner value: 0x19018 - public static Result.Base NotSupported => new Result.Base(ModuleSdmmc, 200); - /// Error code: 2024-0201; Inner value: 0x19218 - public static Result.Base NotImplemented => new Result.Base(ModuleSdmmc, 201); - } + /// Error code: 2024-0192; Inner value: 0x18018 + public static Result.Base AbortCommandIssued => new Result.Base(ModuleSdmmc, 192); + /// Error code: 2024-0200; Inner value: 0x19018 + public static Result.Base NotSupported => new Result.Base(ModuleSdmmc, 200); + /// Error code: 2024-0201; Inner value: 0x19218 + public static Result.Base NotImplemented => new Result.Base(ModuleSdmmc, 201); } diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index 207ddefa..a2759fad 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -19,2243 +19,2242 @@ using SaveData = LibHac.Fs.SaveData; using static LibHac.Fs.StringTraits; using Utility = LibHac.FsSystem.Utility; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Handles save-data-related calls for . +/// +/// FS will have one instance of this class for every connected process. +/// The FS permissions of the calling process are checked on every function call. +///
Based on FS 10.2.0 (nnSdk 10.6.0)
+internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface { - /// - /// Handles save-data-related calls for . - /// - /// FS will have one instance of this class for every connected process. - /// The FS permissions of the calling process are checked on every function call. - ///
Based on FS 10.2.0 (nnSdk 10.6.0)
- internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface + private const int OpenEntrySemaphoreCount = 256; + private const int SaveMountSemaphoreCount = 10; + + private const int SaveDataBlockSize = 0x4000; + + private WeakRef _selfReference; + private SaveDataFileSystemServiceImpl _serviceImpl; + private ulong _processId; + private Path.Stored _saveDataRootPath; + private SemaphoreAdapter _openEntryCountSemaphore; + private SemaphoreAdapter _saveDataMountCountSemaphore; + + private HorizonClient Hos => _serviceImpl.Hos; + + private SharedRef GetSharedFromThis() => + SharedRef.Create(in _selfReference); + + private SharedRef GetSharedMultiCommitInterfaceFromThis() => + SharedRef.Create(in _selfReference); + + public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) { - private const int OpenEntrySemaphoreCount = 256; - private const int SaveMountSemaphoreCount = 10; + _serviceImpl = serviceImpl; + _processId = processId; + _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); + _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + } - private const int SaveDataBlockSize = 0x4000; + public static SharedRef CreateShared(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + { + // Create the service + var saveService = new SaveDataFileSystemService(serviceImpl, processId); - private WeakRef _selfReference; - private SaveDataFileSystemServiceImpl _serviceImpl; - private ulong _processId; - private Path.Stored _saveDataRootPath; - private SemaphoreAdapter _openEntryCountSemaphore; - private SemaphoreAdapter _saveDataMountCountSemaphore; + // Wrap the service in a ref-counter and give the service a weak self-reference + using var sharedService = new SharedRef(saveService); + saveService._selfReference.Set(in sharedService); - private HorizonClient Hos => _serviceImpl.Hos; + return SharedRef.CreateMove(ref sharedService.Ref()); + } - private SharedRef GetSharedFromThis() => - SharedRef.Create(in _selfReference); + private class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager + { + private SharedRef _saveService; - private SharedRef GetSharedMultiCommitInterfaceFromThis() => - SharedRef.Create(in _selfReference); - - public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + public SaveDataOpenCountAdapter(ref SharedRef saveService) { - _serviceImpl = serviceImpl; - _processId = processId; - _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); - _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + _saveService = SharedRef.CreateMove(ref saveService); } - public static SharedRef CreateShared(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) { - // Create the service - var saveService = new SaveDataFileSystemService(serviceImpl, processId); - - // Wrap the service in a ref-counter and give the service a weak self-reference - using var sharedService = new SharedRef(saveService); - saveService._selfReference.Set(in sharedService); - - return SharedRef.CreateMove(ref sharedService.Ref()); + return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore); } - private class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager + public void Dispose() { - private SharedRef _saveService; + _saveService.Destroy(); + } + } - public SaveDataOpenCountAdapter(ref SharedRef saveService) - { - _saveService = SharedRef.CreateMove(ref saveService); - } - - public Result TryAcquireEntryOpenCountSemaphore(ref UniqueRef outSemaphore) - { - return _saveService.Get.TryAcquireSaveDataEntryOpenCountSemaphore(ref outSemaphore); - } - - public void Dispose() - { - _saveService.Destroy(); - } + private Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + return ResultFs.PermissionDenied.Log(); + break; + case SaveDataSpaceId.User: + case SaveDataSpaceId.Temporary: + case SaveDataSpaceId.SdCache: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) + return ResultFs.PermissionDenied.Log(); + break; + default: + return ResultFs.InvalidSaveDataSpaceId.Log(); } - private Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + return Result.Success; + } + + private static class SaveDataAccessibilityChecker + { + public delegate Result ExtraDataGetter(out SaveDataExtraData extraData); + + public static Result CheckCreate(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + ProgramInfo programInfo, ProgramId programId) { - switch (spaceId) + AccessControl accessControl = programInfo.AccessControl; + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) { - case SaveDataSpaceId.System: - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.ProperSystem: - case SaveDataSpaceId.SafeMode: - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + if (creationInfo.OwnerId == programInfo.ProgramIdValue) + { + bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); + + if (!canAccess) return ResultFs.PermissionDenied.Log(); - break; - case SaveDataSpaceId.User: - case SaveDataSpaceId.Temporary: - case SaveDataSpaceId.SdCache: - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) + } + else + { + // If the program doesn't own the created save data it needs either the permission to create + // any system save data or it needs explicit access to the owner's save data. + Accessibility accessibility = + accessControl.GetAccessibilitySaveDataOwnedBy(creationInfo.OwnerId); + + bool canAccess = + accessControl.CanCall(OperationType.CreateSystemSaveData) && + accessControl.CanCall(OperationType.CreateOthersSystemSaveData) || accessibility.CanWrite; + + if (!canAccess) return ResultFs.PermissionDenied.Log(); - break; - default: - return ResultFs.InvalidSaveDataSpaceId.Log(); + } + } + else if (attribute.Type == SaveDataType.Account && attribute.UserId == UserId.InvalidId) + { + bool canAccess = + accessControl.CanCall(OperationType.CreateSaveData) || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else + { + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + creationInfo.OwnerId); + if (rc.IsFailure()) return rc; + + // If none of the above conditions apply, the program needs write access to the owner's save data. + // The program also needs either permission to create any save data, or it must be creating its own + // save data and have the permission to do so. + bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); + + if (accessibility.CanWrite && + attribute.ProgramId == programId || attribute.ProgramId.Value == creationInfo.OwnerId) + { + canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); } return Result.Success; } - private static class SaveDataAccessibilityChecker + public static Result CheckOpenPre(in SaveDataAttribute attribute, ProgramInfo programInfo) { - public delegate Result ExtraDataGetter(out SaveDataExtraData extraData); + AccessControl accessControl = programInfo.AccessControl; - public static Result CheckCreate(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, - ProgramInfo programInfo, ProgramId programId) + if (attribute.Type == SaveDataType.Device) { - AccessControl accessControl = programInfo.AccessControl; + Accessibility accessibility = + accessControl.GetAccessibilityFor(AccessibilityType.MountDeviceSaveData); + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else if (attribute.Type == SaveDataType.Account) + { + bool canAccess = attribute.UserId != UserId.InvalidId || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckOpen(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); + if (rc.IsFailure()) return rc; + + // Note: This is correct. Even if a program only has read accessibility to another program's save data, + // Nintendo gives it full read/write accessibility as of FS 12.0.0 + if (accessibility.CanRead || accessibility.CanWrite) + return Result.Success; + + // The program doesn't have permissions for this specific save data. Check if it has overall + // permissions for other programs' save data. + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSystemSaveData); + } + else + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSaveData); + } + + if (accessibility.CanRead && accessibility.CanWrite) + return Result.Success; + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckDelete(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + // DeleteSystemSaveData permission is needed to delete system save data + if (SaveDataProperties.IsSystemSaveData(attribute.Type) && + !accessControl.CanCall(OperationType.DeleteSystemSaveData)) + { + return ResultFs.PermissionDenied.Log(); + } + + // The DeleteSaveData permission allows deleting any non-system save data + if (accessControl.CanCall(OperationType.DeleteSaveData)) + { + return Result.Success; + } + + // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); + if (rc.IsFailure()) return rc; + + if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) + { + return Result.Success; + } + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckReadExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, + ProgramInfo programInfo, ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); + + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + extraDataGetter); + if (rc.IsFailure()) return rc; + + SaveDataExtraData emptyMask = default; + SaveDataExtraData maskWithoutRestoreFlag = mask; + maskWithoutRestoreFlag.Flags &= ~SaveDataFlags.Restore; + + // Only read access to the save is needed to read the restore flag + if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) + .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutRestoreFlag))) + { + canAccess |= accessibility.CanRead; + } + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + canAccess |= accessibility.CanRead; + } + else if (attribute.ProgramId == programInfo.ProgramId) + { + canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + public static Result CheckWriteExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, + ProgramInfo programInfo, ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + if (mask.Flags != SaveDataFlags.None) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataFlags); if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - if (creationInfo.OwnerId == programInfo.ProgramIdValue) - { - bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - else - { - // If the program doesn't own the created save data it needs either the permission to create - // any system save data or it needs explicit access to the owner's save data. - Accessibility accessibility = - accessControl.GetAccessibilitySaveDataOwnedBy(creationInfo.OwnerId); - - bool canAccess = - accessControl.CanCall(OperationType.CreateSystemSaveData) && - accessControl.CanCall(OperationType.CreateOthersSystemSaveData) || accessibility.CanWrite; - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - } - else if (attribute.Type == SaveDataType.Account && attribute.UserId == UserId.InvalidId) - { - bool canAccess = - accessControl.CanCall(OperationType.CreateSaveData) || - accessControl.CanCall(OperationType.DebugSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - else { Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - creationInfo.OwnerId); + extraDataGetter); if (rc.IsFailure()) return rc; - // If none of the above conditions apply, the program needs write access to the owner's save data. - // The program also needs either permission to create any save data, or it must be creating its own - // save data and have the permission to do so. - bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); - - if (accessibility.CanWrite && - attribute.ProgramId == programId || attribute.ProgramId.Value == creationInfo.OwnerId) - { - canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); + canAccess |= accessibility.CanWrite; } - return Result.Success; - } - - public static Result CheckOpenPre(in SaveDataAttribute attribute, ProgramInfo programInfo) - { - AccessControl accessControl = programInfo.AccessControl; - - if (attribute.Type == SaveDataType.Device) + if ((mask.Flags & ~SaveDataFlags.Restore) == 0) { - Accessibility accessibility = - accessControl.GetAccessibilityFor(AccessibilityType.MountDeviceSaveData); + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + extraDataGetter); + if (rc.IsFailure()) return rc; - bool canAccess = accessibility.CanRead && accessibility.CanWrite; - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - else if (attribute.Type == SaveDataType.Account) - { - bool canAccess = attribute.UserId != UserId.InvalidId || - accessControl.CanCall(OperationType.DebugSaveData); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public static Result CheckOpen(in SaveDataAttribute attribute, ProgramInfo programInfo, - ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; - - // Note: This is correct. Even if a program only has read accessibility to another program's save data, - // Nintendo gives it full read/write accessibility as of FS 12.0.0 - if (accessibility.CanRead || accessibility.CanWrite) - return Result.Success; - - // The program doesn't have permissions for this specific save data. Check if it has overall - // permissions for other programs' save data. - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSystemSaveData); - } - else - { - accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSaveData); - } - - if (accessibility.CanRead && accessibility.CanWrite) - return Result.Success; - - return ResultFs.PermissionDenied.Log(); - } - - public static Result CheckDelete(in SaveDataAttribute attribute, ProgramInfo programInfo, - ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - // DeleteSystemSaveData permission is needed to delete system save data - if (SaveDataProperties.IsSystemSaveData(attribute.Type) && - !accessControl.CanCall(OperationType.DeleteSystemSaveData)) - { - return ResultFs.PermissionDenied.Log(); - } - - // The DeleteSaveData permission allows deleting any non-system save data - if (accessControl.CanCall(OperationType.DeleteSaveData)) - { - return Result.Success; - } - - // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); - if (rc.IsFailure()) return rc; - - if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) - { - return Result.Success; - } - - return ResultFs.PermissionDenied.Log(); - } - - public static Result CheckReadExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, - ProgramInfo programInfo, ExtraDataGetter extraDataGetter) - { - AccessControl accessControl = programInfo.AccessControl; - - bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); - - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (rc.IsFailure()) return rc; - - SaveDataExtraData emptyMask = default; - SaveDataExtraData maskWithoutRestoreFlag = mask; - maskWithoutRestoreFlag.Flags &= ~SaveDataFlags.Restore; - - // Only read access to the save is needed to read the restore flag - if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) - .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutRestoreFlag))) - { - canAccess |= accessibility.CanRead; - } - - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - canAccess |= accessibility.CanRead; - } - else if (attribute.ProgramId == programInfo.ProgramId) - { - canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); + canAccess |= accessibility.CanWrite; } if (!canAccess) return ResultFs.PermissionDenied.Log(); - - return Result.Success; } - public static Result CheckWriteExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, - ProgramInfo programInfo, ExtraDataGetter extraDataGetter) + if (mask.TimeStamp != 0) { - AccessControl accessControl = programInfo.AccessControl; - - if (mask.Flags != SaveDataFlags.None) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataFlags); - - if (SaveDataProperties.IsSystemSaveData(attribute.Type)) - { - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (rc.IsFailure()) return rc; - - canAccess |= accessibility.CanWrite; - } - - if ((mask.Flags & ~SaveDataFlags.Restore) == 0) - { - Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, - extraDataGetter); - if (rc.IsFailure()) return rc; - - canAccess |= accessibility.CanWrite; - } - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - if (mask.TimeStamp != 0) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataTimeStamp); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - if (mask.CommitId != 0) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || - accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataCommitId); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - SaveDataExtraData emptyMask = default; - SaveDataExtraData maskWithoutFlags = mask; - maskWithoutFlags.Flags = SaveDataFlags.None; - maskWithoutFlags.TimeStamp = 0; - maskWithoutFlags.CommitId = 0; - - // Full write access is needed for writing anything other than flags, timestamp or commit ID - if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) - .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutFlags))) - { - bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll); - - if (!canAccess) - return ResultFs.PermissionDenied.Log(); - } - - return Result.Success; - } - - public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo) - { - bool canAccess; - - if (programInfo.ProgramId == filter.Attribute.ProgramId) - { - canAccess = programInfo.AccessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); - } - else - { - canAccess = true; - } + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataTimeStamp); if (!canAccess) return ResultFs.PermissionDenied.Log(); + } + if (mask.CommitId != 0) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll) || + accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataCommitId); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + SaveDataExtraData emptyMask = default; + SaveDataExtraData maskWithoutFlags = mask; + maskWithoutFlags.Flags = SaveDataFlags.None; + maskWithoutFlags.TimeStamp = 0; + maskWithoutFlags.CommitId = 0; + + // Full write access is needed for writing anything other than flags, timestamp or commit ID + if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) + .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutFlags))) + { + bool canAccess = accessControl.CanCall(OperationType.WriteSaveDataFileSystemExtraDataAll); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo) + { + bool canAccess; + + if (programInfo.ProgramId == filter.Attribute.ProgramId) + { + canAccess = programInfo.AccessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); + } + else + { + canAccess = true; + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + + private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + UnsafeHelpers.SkipParamInit(out accessibility); + + Result rc = extraDataGetter(out SaveDataExtraData extraData); + if (rc.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(rc)) + { + accessibility = new Accessibility(false, false); + return Result.Success; + } + + return rc; + } + + // Allow access when opening a directory save FS on a dev console + if (extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0 && + programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + { + accessibility = new Accessibility(true, true); return Result.Success; } + return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId); + } - private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, - ExtraDataGetter extraDataGetter) + private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, + ulong ownerId) + { + if (ownerId == programInfo.ProgramIdValue) { - UnsafeHelpers.SkipParamInit(out accessibility); + // A program always has full access to its own save data + accessibility = new Accessibility(true, true); + } + else + { + accessibility = programInfo.AccessControl.GetAccessibilitySaveDataOwnedBy(ownerId); + } + + return Result.Success; + } + } + + public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) + { + throw new NotImplementedException(); + } + + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId); + } + + private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) + { + // Delete the save data's meta files + Result rc = _serviceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId); + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) + return rc; + + // Delete the actual save data. + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, in saveDataRootPath); + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) + return rc; + + return Result.Success; + } + + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, saveDataId); + } + + private Result DeleteSaveDataFileSystemBySaveDataSpaceIdCore(SaveDataSpaceId spaceId, ulong saveDataId) + { + if (saveDataId != SaveData.SaveIndexerId) + { + using var accessor = new UniqueRef(); + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsFailure()) return rc; + + if (value.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + } + + return DeleteSaveDataFileSystemCommon(spaceId, saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, in attribute); + if (rs.IsFailure()) return rs; + + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, info.SaveDataId); + } + + private Result DeleteSaveDataFileSystemCommon(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + using var accessor = new UniqueRef(); + + SaveDataSpaceId actualSpaceId; + + // Only the FS process may delete the save indexer's save data. + if (saveDataId == SaveData.SaveIndexerId) + { + if (!IsCurrentProcess(_processId)) + return ResultFs.PermissionDenied.Log(); + + actualSpaceId = spaceId; + } + else + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + // Get the actual space ID of this save. + if (spaceId == SaveDataSpaceId.ProperSystem && spaceId == SaveDataSpaceId.SafeMode) + { + actualSpaceId = spaceId; + } + else + { + rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsFailure()) return rc; + + actualSpaceId = value.SpaceId; + } + + // Check if the caller has permission to delete this save. + rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + if (rc.IsFailure()) return rc; + + Result GetExtraData(out SaveDataExtraData data) => + _serviceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, + _saveDataRootPath.DangerousGetPath()); + + rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); + if (rc.IsFailure()) return rc; + + // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. + rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + // Do the actual deletion. + rc = DeleteSaveDataFileSystemCore(actualSpaceId, saveDataId, false); + if (rc.IsFailure()) return rc; + + // Remove the save data from the indexer. + // The indexer doesn't track itself, so skip if deleting its save data. + if (saveDataId != SaveData.SaveIndexerId) + { + rc = accessor.Get.Indexer.Delete(saveDataId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank) + { + throw new NotImplementedException(); + } + + public Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataRootPath(in FspPath path) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + return ResultFs.PermissionDenied.Log(); + + using var saveDataRootPath = new Path(); + + if (path.Str[0] == NullTerminator) + { + rc = saveDataRootPath.Initialize(new[] { (byte)'/' }); + if (rc.IsFailure()) return rc; + } + else + { + rc = saveDataRootPath.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; + } + + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowRelativePath(); + pathFlags.AllowEmptyPath(); + + rc = saveDataRootPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + _saveDataRootPath.Initialize(in saveDataRootPath); + + return Result.Success; + } + + public Result UnsetSaveDataRootPath() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + return ResultFs.PermissionDenied.Log(); + + using var saveDataRootPath = new Path(); + rc = saveDataRootPath.InitializeAsEmpty(); + if (rc.IsFailure()) return rc; + + _saveDataRootPath.Initialize(in saveDataRootPath); + + return Result.Success; + } + + // ReSharper disable once UnusedParameter.Global + public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) + { + if (saveDataId == SaveData.SaveIndexerId) + return ResultFs.InvalidArgument.Log(); + + return ResultFs.NotImplemented.Log(); + } + + public Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType metaType) + { + throw new NotImplementedException(); + } + + public Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo, in Optional hashSalt) + { + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, false); + } + + private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) + { + ulong saveDataId = 0; + bool creating = false; + bool accessorInitialized = false; + Result rc; + + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + using var accessor = new UniqueRef(); + + try + { + // Add the new save data to the save indexer + if (attribute.StaticSaveDataId == SaveData.SaveIndexerId) + { + // The save indexer doesn't index itself + saveDataId = SaveData.SaveIndexerId; + rc = _serviceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId); + + if (rc.IsSuccess() && saveExists) + { + return ResultFs.PathAlreadyExists.Log(); + } + + creating = true; + } + else + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), creationInfo.SpaceId); + if (rc.IsFailure()) return rc; + + accessorInitialized = true; + + SaveDataAttribute indexerKey = attribute; + + // Add the new value to the indexer + if (attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId) + { + // If a static save data ID is specified that ID is always used + saveDataId = attribute.StaticSaveDataId; + + rc = accessor.Get.Indexer.PutStaticSaveDataIdIndex(in indexerKey); + } + else + { + // The save indexer has an upper limit on the number of entries it can hold. + // A few of those entries are reserved for system saves so the system doesn't + // end up in a situation where it can't create a required system save. + if (!SaveDataProperties.CanUseIndexerReservedArea(attribute.Type)) + { + if (accessor.Get.Indexer.IsRemainedReservedOnly()) + { + return ResultKvdb.OutOfKeyResource.Log(); + } + } + + // If a static save data ID is no specified we're assigned a new save ID + rc = accessor.Get.Indexer.Publish(out saveDataId, in indexerKey); + } - Result rc = extraDataGetter(out SaveDataExtraData extraData); if (rc.IsFailure()) { - if (ResultFs.TargetNotFound.Includes(rc)) + if (ResultFs.AlreadyExists.Includes(rc)) { - accessibility = new Accessibility(false, false); - return Result.Success; + return ResultFs.PathAlreadyExists.LogConverted(rc); } return rc; } - // Allow access when opening a directory save FS on a dev console - if (extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0 && - programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) - { - accessibility = new Accessibility(true, true); - return Result.Success; - } + creating = true; - return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId); + // Set the state, space ID and size on the new save indexer entry. + rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); + if (rc.IsFailure()) return rc; + + rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.SetSize(saveDataId, saveDataSize); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Commit(); + if (rc.IsFailure()) return rc; } - private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, - ulong ownerId) - { - if (ownerId == programInfo.ProgramIdValue) - { - // A program always has full access to its own save data - accessibility = new Accessibility(true, true); - } - else - { - accessibility = programInfo.AccessControl.GetAccessibilitySaveDataOwnedBy(ownerId); - } + // After the new save was added to the save indexer, create the save data file or directory. + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, + in saveDataRootPath, in hashSalt, false); + if (rc.IsFailure()) + { + if (!ResultFs.PathAlreadyExists.Includes(rc)) return rc; + + // Handle the situation where a save exists on disk but not in the save indexer. + // Delete the save data and try creating it again. + rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); + if (rc.IsFailure()) return rc; + + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, + in saveDataRootPath, in hashSalt, false); + if (rc.IsFailure()) return rc; + } + + if (metaInfo.Type != SaveDataMetaType.None) + { + // Create the requested save data meta file. + rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type, + metaInfo.Size); + if (rc.IsFailure()) return rc; + + if (metaInfo.Type == SaveDataMetaType.Thumbnail) + { + using var metaFile = new UniqueRef(); + rc = _serviceImpl.OpenSaveDataMeta(ref metaFile.Ref(), saveDataId, creationInfo.SpaceId, + metaInfo.Type); + + if (rc.IsFailure()) return rc; + + // The first 0x20 bytes of thumbnail meta files is an SHA-256 hash. + // Zero the hash to indicate that it's currently unused. + ReadOnlySpan metaFileHash = stackalloc byte[0x20]; + + rc = metaFile.Get.Write(0, metaFileHash, WriteOption.Flush); + if (rc.IsFailure()) return rc; + } + } + + if (leaveUnfinalized) + { + creating = false; return Result.Success; } + + // The indexer's save data isn't tracked, so we don't need to update its state. + if (attribute.StaticSaveDataId != SaveData.SaveIndexerId) + { + // Mark the save data as being successfully created + rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Normal); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + creating = false; + return Result.Success; + } + finally + { + // Revert changes if an error happened in the middle of creation + if (creating) + { + DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false).IgnoreResult(); + + if (accessorInitialized && saveDataId != SaveData.SaveIndexerId) + { + rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + + if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) + { + accessor.Get.Indexer.Delete(saveDataId).IgnoreResult(); + accessor.Get.Indexer.Commit().IgnoreResult(); + } + } + } + } + } + + public Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + UnsafeHelpers.SkipParamInit(out info); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + using var accessor = new UniqueRef(); + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in attribute); + if (rc.IsFailure()) return rc; + + SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); + return Result.Success; + } + + public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + { + UnsafeHelpers.SkipParamInit(out totalSize); + + if (dataSize < 0 || journalSize < 0) + return ResultFs.InvalidSize.Log(); + + return _serviceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); + } + + public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo) + { + var hashSalt = new Optional(); + + return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, in hashSalt); + } + + public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) + { + var optionalHashSalt = new Optional(in hashSalt); + + return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, + in optionalHashSalt); + } + + private Result CreateSaveDataFileSystemWithHashSaltImpl(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt) + { + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute = attribute; + SaveDataCreationInfo tempCreationInfo = creationInfo; + + if (hashSalt.HasValue && !programInfo.AccessControl.CanCall(OperationType.CreateSaveDataWithHashSalt)) + { + return ResultFs.PermissionDenied.Log(); } - public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in creationInfo, programInfo, programId); + if (rc.IsFailure()) return rc; + + if (tempAttribute.Type == SaveDataType.Account && tempAttribute.UserId == UserId.InvalidId) { - throw new NotImplementedException(); + if (tempAttribute.ProgramId == ProgramId.InvalidId) + { + tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + } + + if (tempCreationInfo.OwnerId == 0) + { + tempCreationInfo.OwnerId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + } } - public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) + return CreateSaveDataFileSystemCore(in tempAttribute, in tempCreationInfo, in metaInfo, in hashSalt); + } + + public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo) + { + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) + return ResultFs.InvalidArgument.Log(); + + SaveDataCreationInfo tempCreationInfo = creationInfo; + + if (tempCreationInfo.OwnerId == 0) { - throw new NotImplementedException(); + tempCreationInfo.OwnerId = programInfo.ProgramIdValue; } - public Result DeleteSaveDataFileSystem(ulong saveDataId) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in tempCreationInfo, programInfo, + programInfo.ProgramId); + if (rc.IsFailure()) return rc; - return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId); + // Static system saves don't usually have meta files + SaveDataMetaInfo metaInfo = default; + Optional hashSalt = default; + + return CreateSaveDataFileSystemCore(in attribute, in tempCreationInfo, in metaInfo, in hashSalt); + } + + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, + long journalSize) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, false); + } + + public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, true); + } + + private Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, + out ulong saveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, + bool cacheExtraData) + { + UnsafeHelpers.SkipParamInit(out saveDataId); + + using var accessor = new UniqueRef(); + + ulong tempSaveDataId; + bool isStaticSaveDataId = attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId; + + // Get the ID of the save data + if (isStaticSaveDataId) + { + tempSaveDataId = attribute.StaticSaveDataId; + } + else + { + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue indexerValue, in attribute); + if (rc.IsFailure()) return rc; + + if (indexerValue.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + + if (indexerValue.State == SaveDataState.Extending) + return ResultFs.SaveDataExtending.Log(); + + tempSaveDataId = indexerValue.SaveDataId; } - private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) + // Open the save data using its ID + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(ref outFileSystem, spaceId, tempSaveDataId, + in saveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); + + if (saveFsResult.IsSuccess()) { - // Delete the save data's meta files - Result rc = _serviceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId); - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; - - // Delete the actual save data. - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, in saveDataRootPath); - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; - + saveDataId = tempSaveDataId; return Result.Success; } - public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + // Copy the key so we can use it in a local function + SaveDataAttribute key = attribute; - return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, saveDataId); + // Remove the save from the indexer if the save is missing from the disk. + if (ResultFs.PathNotFound.Includes(saveFsResult)) + { + Result rc = RemoveSaveIndexerEntry(); + if (rc.IsFailure()) return rc; + + return ResultFs.TargetNotFound.LogConverted(saveFsResult); } - private Result DeleteSaveDataFileSystemBySaveDataSpaceIdCore(SaveDataSpaceId spaceId, ulong saveDataId) + if (ResultFs.TargetNotFound.Includes(saveFsResult)) { - if (saveDataId != SaveData.SaveIndexerId) + Result rc = RemoveSaveIndexerEntry(); + if (rc.IsFailure()) return rc; + } + + return saveFsResult; + + Result RemoveSaveIndexerEntry() + { + if (tempSaveDataId == SaveData.SaveIndexerId) + return Result.Success; + + if (isStaticSaveDataId) { - using var accessor = new UniqueRef(); + // The accessor won't be open yet if the save has a static ID Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + // Check the space ID of the save data + rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in key); if (rc.IsFailure()) return rc; if (value.SpaceId != ConvertToRealSpaceId(spaceId)) return ResultFs.TargetNotFound.Log(); } - return DeleteSaveDataFileSystemCommon(spaceId, saveDataId); - } - - public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, - in SaveDataAttribute attribute) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, in attribute); - if (rs.IsFailure()) return rs; - - return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, info.SaveDataId); - } - - private Result DeleteSaveDataFileSystemCommon(SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - using var accessor = new UniqueRef(); - - SaveDataSpaceId actualSpaceId; - - // Only the FS process may delete the save indexer's save data. - if (saveDataId == SaveData.SaveIndexerId) - { - if (!IsCurrentProcess(_processId)) - return ResultFs.PermissionDenied.Log(); - - actualSpaceId = spaceId; - } - else - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - // Get the actual space ID of this save. - if (spaceId == SaveDataSpaceId.ProperSystem && spaceId == SaveDataSpaceId.SafeMode) - { - actualSpaceId = spaceId; - } - else - { - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; - - actualSpaceId = value.SpaceId; - } - - // Check if the caller has permission to delete this save. - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; - - Result GetExtraData(out SaveDataExtraData data) => - _serviceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, - _saveDataRootPath.DangerousGetPath()); - - rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); - if (rc.IsFailure()) return rc; - - // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - // Do the actual deletion. - rc = DeleteSaveDataFileSystemCore(actualSpaceId, saveDataId, false); - if (rc.IsFailure()) return rc; - - // Remove the save data from the indexer. - // The indexer doesn't track itself, so skip if deleting its save data. - if (saveDataId != SaveData.SaveIndexerId) - { - rc = accessor.Get.Indexer.Delete(saveDataId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } + // Remove the indexer entry. Nintendo ignores these results + accessor.Get.Indexer.Delete(tempSaveDataId).IgnoreResult(); + accessor.Get.Indexer.Commit().IgnoreResult(); return Result.Success; } + } - public Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2) + private Result OpenUserSaveDataFileSystemCore(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, ProgramInfo programInfo, bool openReadOnly) + { + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + // Try grabbing the mount count semaphore + using var mountCountSemaphore = new UniqueRef(); + Result rc = TryAcquireSaveDataMountCountSemaphore(ref mountCountSemaphore.Ref()); + if (rc.IsFailure()) return rc; + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); + + using var fileSystem = new SharedRef(); + + // Open the file system + rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, + openReadOnly, true); + if (rc.IsFailure()) return rc; + + // Can't use attribute in a closure, so copy the needed field + SaveDataType type = attribute.Type; + + Result ReadExtraData(out SaveDataExtraData data) { - throw new NotImplementedException(); + using Path savePath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, + in savePath); } - public Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state) + // Check if we have permissions to open this save data + rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = new SharedRef(); + + if (useAsyncFileSystem) { - throw new NotImplementedException(); + asyncFileSystem.Reset(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + } + else + { + asyncFileSystem.SetByMove(ref typeSetFileSystem.Ref()); } - public Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank) + using SharedRef saveService = GetSharedFromThis(); + using var openEntryCountAdapter = + new SharedRef(new SaveDataOpenCountAdapter(ref saveService.Ref())); + + using var openCountFileSystem = new SharedRef( + new OpenCountFileSystem(ref asyncFileSystem.Ref(), ref openEntryCountAdapter.Ref(), + ref mountCountSemaphore.Ref())); + + var pathFlags = new PathFlags(); + pathFlags.AllowBackslash(); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + private Result OpenUserSaveDataFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = SaveDataAccessibilityChecker.CheckOpenPre(in attribute, programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute; + + if (attribute.ProgramId.Value == 0) { - throw new NotImplementedException(); - } - - public Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) - { - throw new NotImplementedException(); - } - - public Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) - { - throw new NotImplementedException(); - } - - public Result SetSaveDataRootPath(in FspPath path) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) - return ResultFs.PermissionDenied.Log(); - - using var saveDataRootPath = new Path(); - - if (path.Str[0] == NullTerminator) - { - rc = saveDataRootPath.Initialize(new[] { (byte)'/' }); - if (rc.IsFailure()) return rc; - } - else - { - rc = saveDataRootPath.InitializeWithReplaceUnc(path.Str); - if (rc.IsFailure()) return rc; - } - - var pathFlags = new PathFlags(); - pathFlags.AllowWindowsPath(); - pathFlags.AllowRelativePath(); - pathFlags.AllowEmptyPath(); - - rc = saveDataRootPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - _saveDataRootPath.Initialize(in saveDataRootPath); - - return Result.Success; - } - - public Result UnsetSaveDataRootPath() - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) - return ResultFs.PermissionDenied.Log(); - - using var saveDataRootPath = new Path(); - rc = saveDataRootPath.InitializeAsEmpty(); - if (rc.IsFailure()) return rc; - - _saveDataRootPath.Initialize(in saveDataRootPath); - - return Result.Success; - } - - // ReSharper disable once UnusedParameter.Global - public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) - { - if (saveDataId == SaveData.SaveIndexerId) - return ResultFs.InvalidArgument.Log(); - - return ResultFs.NotImplemented.Log(); - } - - public Result OpenSaveDataFile(ref SharedRef file, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute, SaveDataMetaType metaType) - { - throw new NotImplementedException(); - } - - public Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId) - { - throw new NotImplementedException(); - } - - private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo, in Optional hashSalt) - { - return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, false); - } - - private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) - { - ulong saveDataId = 0; - bool creating = false; - bool accessorInitialized = false; - Result rc; - - StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - using var accessor = new UniqueRef(); - - try - { - // Add the new save data to the save indexer - if (attribute.StaticSaveDataId == SaveData.SaveIndexerId) - { - // The save indexer doesn't index itself - saveDataId = SaveData.SaveIndexerId; - rc = _serviceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId); - - if (rc.IsSuccess() && saveExists) - { - return ResultFs.PathAlreadyExists.Log(); - } - - creating = true; - } - else - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), creationInfo.SpaceId); - if (rc.IsFailure()) return rc; - - accessorInitialized = true; - - SaveDataAttribute indexerKey = attribute; - - // Add the new value to the indexer - if (attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId) - { - // If a static save data ID is specified that ID is always used - saveDataId = attribute.StaticSaveDataId; - - rc = accessor.Get.Indexer.PutStaticSaveDataIdIndex(in indexerKey); - } - else - { - // The save indexer has an upper limit on the number of entries it can hold. - // A few of those entries are reserved for system saves so the system doesn't - // end up in a situation where it can't create a required system save. - if (!SaveDataProperties.CanUseIndexerReservedArea(attribute.Type)) - { - if (accessor.Get.Indexer.IsRemainedReservedOnly()) - { - return ResultKvdb.OutOfKeyResource.Log(); - } - } - - // If a static save data ID is no specified we're assigned a new save ID - rc = accessor.Get.Indexer.Publish(out saveDataId, in indexerKey); - } - - if (rc.IsFailure()) - { - if (ResultFs.AlreadyExists.Includes(rc)) - { - return ResultFs.PathAlreadyExists.LogConverted(rc); - } - - return rc; - } - - creating = true; - - // Set the state, space ID and size on the new save indexer entry. - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); - if (rc.IsFailure()) return rc; - - rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.SetSize(saveDataId, saveDataSize); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - // After the new save was added to the save indexer, create the save data file or directory. - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, - in saveDataRootPath, in hashSalt, false); - - if (rc.IsFailure()) - { - if (!ResultFs.PathAlreadyExists.Includes(rc)) return rc; - - // Handle the situation where a save exists on disk but not in the save indexer. - // Delete the save data and try creating it again. - rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); - if (rc.IsFailure()) return rc; - - rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, - in saveDataRootPath, in hashSalt, false); - if (rc.IsFailure()) return rc; - } - - if (metaInfo.Type != SaveDataMetaType.None) - { - // Create the requested save data meta file. - rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type, - metaInfo.Size); - if (rc.IsFailure()) return rc; - - if (metaInfo.Type == SaveDataMetaType.Thumbnail) - { - using var metaFile = new UniqueRef(); - rc = _serviceImpl.OpenSaveDataMeta(ref metaFile.Ref(), saveDataId, creationInfo.SpaceId, - metaInfo.Type); - - if (rc.IsFailure()) return rc; - - // The first 0x20 bytes of thumbnail meta files is an SHA-256 hash. - // Zero the hash to indicate that it's currently unused. - ReadOnlySpan metaFileHash = stackalloc byte[0x20]; - - rc = metaFile.Get.Write(0, metaFileHash, WriteOption.Flush); - if (rc.IsFailure()) return rc; - } - } - - if (leaveUnfinalized) - { - creating = false; - return Result.Success; - } - - // The indexer's save data isn't tracked, so we don't need to update its state. - if (attribute.StaticSaveDataId != SaveData.SaveIndexerId) - { - // Mark the save data as being successfully created - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Normal); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - creating = false; - return Result.Success; - } - finally - { - // Revert changes if an error happened in the middle of creation - if (creating) - { - DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false).IgnoreResult(); - - if (accessorInitialized && saveDataId != SaveData.SaveIndexerId) - { - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - - if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) - { - accessor.Get.Indexer.Delete(saveDataId).IgnoreResult(); - accessor.Get.Indexer.Commit().IgnoreResult(); - } - } - } - } - } - - public Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - UnsafeHelpers.SkipParamInit(out info); - - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - using var accessor = new UniqueRef(); - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in attribute); - if (rc.IsFailure()) return rc; - - SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); - return Result.Success; - } - - public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) - { - UnsafeHelpers.SkipParamInit(out totalSize); - - if (dataSize < 0 || journalSize < 0) - return ResultFs.InvalidSize.Log(); - - return _serviceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); - } - - public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, - in SaveDataMetaInfo metaInfo) - { - var hashSalt = new Optional(); - - return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, in hashSalt); - } - - public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) - { - var optionalHashSalt = new Optional(in hashSalt); - - return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, - in optionalHashSalt); - } - - private Result CreateSaveDataFileSystemWithHashSaltImpl(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt) - { - StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataAttribute tempAttribute = attribute; - SaveDataCreationInfo tempCreationInfo = creationInfo; - - if (hashSalt.HasValue && !programInfo.AccessControl.CanCall(OperationType.CreateSaveDataWithHashSalt)) - { - return ResultFs.PermissionDenied.Log(); - } - ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in creationInfo, programInfo, programId); + + rc = SaveDataAttribute.Make(out tempAttribute, programId, attribute.Type, attribute.UserId, + attribute.StaticSaveDataId, attribute.Index); if (rc.IsFailure()) return rc; - - if (tempAttribute.Type == SaveDataType.Account && tempAttribute.UserId == UserId.InvalidId) - { - if (tempAttribute.ProgramId == ProgramId.InvalidId) - { - tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - } - - if (tempCreationInfo.OwnerId == 0) - { - tempCreationInfo.OwnerId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; - } - } - - return CreateSaveDataFileSystemCore(in tempAttribute, in tempCreationInfo, in metaInfo, in hashSalt); + } + else + { + tempAttribute = attribute; } - public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo) - { - StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + SaveDataSpaceId actualSpaceId; - Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (tempAttribute.Type == SaveDataType.Cache) + { + // Check whether the save is on the SD card or the BIS + rc = GetCacheStorageSpaceId(out actualSpaceId, tempAttribute.ProgramId.Value); if (rc.IsFailure()) return rc; - - if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) - return ResultFs.InvalidArgument.Log(); - - SaveDataCreationInfo tempCreationInfo = creationInfo; - - if (tempCreationInfo.OwnerId == 0) - { - tempCreationInfo.OwnerId = programInfo.ProgramIdValue; - } - - rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in tempCreationInfo, programInfo, - programInfo.ProgramId); - if (rc.IsFailure()) return rc; - - // Static system saves don't usually have meta files - SaveDataMetaInfo metaInfo = default; - Optional hashSalt = default; - - return CreateSaveDataFileSystemCore(in attribute, in tempCreationInfo, in metaInfo, in hashSalt); + } + else + { + actualSpaceId = spaceId; } - public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, - long journalSize) + return OpenUserSaveDataFileSystemCore(ref outFileSystem, actualSpaceId, in tempAttribute, programInfo, + openReadOnly); + } + + public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) + return ResultFs.InvalidArgument.Log(); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSystemSaveData); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); + + using var fileSystem = new SharedRef(); + + // Open the file system + rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, + false, true); + if (rc.IsFailure()) return rc; + + // Can't use attribute in a closure, so copy the needed field + SaveDataType type = attribute.Type; + + Result ReadExtraData(out SaveDataExtraData data) { - throw new NotImplementedException(); + using Path savePath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, + in savePath); } - public Result OpenSaveDataFileSystem(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + // Check if we have permissions to open this save data + rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + using var typeSetFileSystem = + new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); + + using var asyncFileSystem = new SharedRef(); + + if (useAsyncFileSystem) { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, false); + asyncFileSystem.Reset(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); + } + else + { + asyncFileSystem.SetByMove(ref typeSetFileSystem.Ref()); } - public Result OpenReadOnlySaveDataFileSystem(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - return OpenUserSaveDataFileSystem(ref fileSystem, spaceId, in attribute, true); - } + using SharedRef saveService = GetSharedFromThis(); + using var openEntryCountAdapter = + new SharedRef(new SaveDataOpenCountAdapter(ref saveService.Ref())); - private Result OpenSaveDataFileSystemCore(ref SharedRef outFileSystem, - out ulong saveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, - bool cacheExtraData) - { - UnsafeHelpers.SkipParamInit(out saveDataId); + using var openCountFileSystem = new SharedRef( + new OpenCountFileSystem(ref asyncFileSystem.Ref(), ref openEntryCountAdapter.Ref())); + var pathFlags = new PathFlags(); + pathFlags.AllowBackslash(); + + using SharedRef fileSystemAdapter = + FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); + + outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); + + return Result.Success; + } + + // ReSharper disable once UnusedParameter.Local + // Nintendo used isTemporarySaveData in older FS versions, but never removed the parameter. + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, bool isTemporarySaveData) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + using var accessor = new UniqueRef(); + + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + if (rc.IsFailure()) return rc; + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, + in saveDataRootPath); + } + + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraDataMask) + { + UnsafeHelpers.SkipParamInit(out extraData); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataSpaceId resolvedSpaceId; + SaveDataAttribute key; + + if (spaceId == SaveDataSpaceId.BisAuto) + { using var accessor = new UniqueRef(); - ulong tempSaveDataId; - bool isStaticSaveDataId = attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId; - - // Get the ID of the save data - if (isStaticSaveDataId) + if (IsStaticSaveDataIdValueRange(saveDataId)) { - tempSaveDataId = attribute.StaticSaveDataId; - } - else - { - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue indexerValue, in attribute); - if (rc.IsFailure()) return rc; - - if (indexerValue.SpaceId != ConvertToRealSpaceId(spaceId)) - return ResultFs.TargetNotFound.Log(); - - if (indexerValue.State == SaveDataState.Extending) - return ResultFs.SaveDataExtending.Log(); - - tempSaveDataId = indexerValue.SaveDataId; - } - - // Open the save data using its ID - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(ref outFileSystem, spaceId, tempSaveDataId, - in saveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); - - if (saveFsResult.IsSuccess()) - { - saveDataId = tempSaveDataId; - return Result.Success; - } - - // Copy the key so we can use it in a local function - SaveDataAttribute key = attribute; - - // Remove the save from the indexer if the save is missing from the disk. - if (ResultFs.PathNotFound.Includes(saveFsResult)) - { - Result rc = RemoveSaveIndexerEntry(); - if (rc.IsFailure()) return rc; - - return ResultFs.TargetNotFound.LogConverted(saveFsResult); - } - - if (ResultFs.TargetNotFound.Includes(saveFsResult)) - { - Result rc = RemoveSaveIndexerEntry(); - if (rc.IsFailure()) return rc; - } - - return saveFsResult; - - Result RemoveSaveIndexerEntry() - { - if (tempSaveDataId == SaveData.SaveIndexerId) - return Result.Success; - - if (isStaticSaveDataId) - { - // The accessor won't be open yet if the save has a static ID - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - // Check the space ID of the save data - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in key); - if (rc.IsFailure()) return rc; - - if (value.SpaceId != ConvertToRealSpaceId(spaceId)) - return ResultFs.TargetNotFound.Log(); - } - - // Remove the indexer entry. Nintendo ignores these results - accessor.Get.Indexer.Delete(tempSaveDataId).IgnoreResult(); - accessor.Get.Indexer.Commit().IgnoreResult(); - - return Result.Success; - } - } - - private Result OpenUserSaveDataFileSystemCore(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, ProgramInfo programInfo, bool openReadOnly) - { - StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - // Try grabbing the mount count semaphore - using var mountCountSemaphore = new UniqueRef(); - Result rc = TryAcquireSaveDataMountCountSemaphore(ref mountCountSemaphore.Ref()); - if (rc.IsFailure()) return rc; - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); - - using var fileSystem = new SharedRef(); - - // Open the file system - rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, - openReadOnly, true); - if (rc.IsFailure()) return rc; - - // Can't use attribute in a closure, so copy the needed field - SaveDataType type = attribute.Type; - - Result ReadExtraData(out SaveDataExtraData data) - { - using Path savePath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, - in savePath); - } - - // Check if we have permissions to open this save data - rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); - if (rc.IsFailure()) return rc; - - // Add all the wrappers for the file system - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = new SharedRef(); - - if (useAsyncFileSystem) - { - asyncFileSystem.Reset(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - } - else - { - asyncFileSystem.SetByMove(ref typeSetFileSystem.Ref()); - } - - using SharedRef saveService = GetSharedFromThis(); - using var openEntryCountAdapter = - new SharedRef(new SaveDataOpenCountAdapter(ref saveService.Ref())); - - using var openCountFileSystem = new SharedRef( - new OpenCountFileSystem(ref asyncFileSystem.Ref(), ref openEntryCountAdapter.Ref(), - ref mountCountSemaphore.Ref())); - - var pathFlags = new PathFlags(); - pathFlags.AllowBackslash(); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - private Result OpenUserSaveDataFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - rc = SaveDataAccessibilityChecker.CheckOpenPre(in attribute, programInfo); - if (rc.IsFailure()) return rc; - - SaveDataAttribute tempAttribute; - - if (attribute.ProgramId.Value == 0) - { - ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - - rc = SaveDataAttribute.Make(out tempAttribute, programId, attribute.Type, attribute.UserId, - attribute.StaticSaveDataId, attribute.Index); + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); if (rc.IsFailure()) return rc; } else { - tempAttribute = attribute; - } - - SaveDataSpaceId actualSpaceId; - - if (tempAttribute.Type == SaveDataType.Cache) - { - // Check whether the save is on the SD card or the BIS - rc = GetCacheStorageSpaceId(out actualSpaceId, tempAttribute.ProgramId.Value); - if (rc.IsFailure()) return rc; - } - else - { - actualSpaceId = spaceId; - } - - return OpenUserSaveDataFileSystemCore(ref outFileSystem, actualSpaceId, in tempAttribute, programInfo, - openReadOnly); - } - - public Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) - return ResultFs.InvalidArgument.Log(); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); - using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - - Accessibility accessibility = - programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSystemSaveData); - - if (!accessibility.CanRead || !accessibility.CanWrite) - return ResultFs.PermissionDenied.Log(); - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); - - using var fileSystem = new SharedRef(); - - // Open the file system - rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out ulong saveDataId, spaceId, in attribute, - false, true); - if (rc.IsFailure()) return rc; - - // Can't use attribute in a closure, so copy the needed field - SaveDataType type = attribute.Type; - - Result ReadExtraData(out SaveDataExtraData data) - { - using Path savePath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, - in savePath); - } - - // Check if we have permissions to open this save data - rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); - if (rc.IsFailure()) return rc; - - // Add all the wrappers for the file system - using var typeSetFileSystem = - new SharedRef(new StorageLayoutTypeSetFileSystem(ref fileSystem.Ref(), storageFlag)); - - using var asyncFileSystem = new SharedRef(); - - if (useAsyncFileSystem) - { - asyncFileSystem.Reset(new AsynchronousAccessFileSystem(ref typeSetFileSystem.Ref())); - } - else - { - asyncFileSystem.SetByMove(ref typeSetFileSystem.Ref()); - } - - using SharedRef saveService = GetSharedFromThis(); - using var openEntryCountAdapter = - new SharedRef(new SaveDataOpenCountAdapter(ref saveService.Ref())); - - using var openCountFileSystem = new SharedRef( - new OpenCountFileSystem(ref asyncFileSystem.Ref(), ref openEntryCountAdapter.Ref())); - - var pathFlags = new PathFlags(); - pathFlags.AllowBackslash(); - - using SharedRef fileSystemAdapter = - FileSystemInterfaceAdapter.CreateShared(ref openCountFileSystem.Ref(), pathFlags, false); - - outFileSystem.SetByMove(ref fileSystemAdapter.Ref()); - - return Result.Success; - } - - // ReSharper disable once UnusedParameter.Local - // Nintendo used isTemporarySaveData in older FS versions, but never removed the parameter. - private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, - ulong saveDataId, bool isTemporarySaveData) - { - UnsafeHelpers.SkipParamInit(out extraData); - - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - using var accessor = new UniqueRef(); - - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, - in saveDataRootPath); - } - - private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, - ulong saveDataId, in SaveDataExtraData extraDataMask) - { - UnsafeHelpers.SkipParamInit(out extraData); - - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataSpaceId resolvedSpaceId; - SaveDataAttribute key; - - if (spaceId == SaveDataSpaceId.BisAuto) - { - using var accessor = new UniqueRef(); - - if (IsStaticSaveDataIdValueRange(saveDataId)) - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; - } - else - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - } - - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; - - resolvedSpaceId = value.SpaceId; - - rc = accessor.Get.Indexer.GetKey(out key, saveDataId); - if (rc.IsFailure()) return rc; - } - else - { - using var accessor = new UniqueRef(); - - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; - - resolvedSpaceId = value.SpaceId; - - rc = accessor.Get.Indexer.GetKey(out key, saveDataId); + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.User); if (rc.IsFailure()) return rc; } - Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, - resolvedSpaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); - - rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo, - ReadExtraData); + rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId, - saveDataId, key.Type, in saveDataRootPath); + resolvedSpaceId = value.SpaceId; + + rc = accessor.Get.Indexer.GetKey(out key, saveDataId); if (rc.IsFailure()) return rc; - - MaskExtraData(ref tempExtraData, in extraDataMask); - extraData = tempExtraData; - - return Result.Success; } - - public Result ReadSaveDataFileSystemExtraData(OutBuffer extraData, ulong saveDataId) + else { - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - // Make a mask for reading the entire extra data - Unsafe.SkipInit(out SaveDataExtraData extraDataMask); - SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - - return ReadSaveDataFileSystemExtraDataCore(out SpanHelpers.AsStruct(extraData.Buffer), - SaveDataSpaceId.BisAuto, saveDataId, in extraDataMask); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataAttribute tempAttribute = attribute; - - if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) - { - tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - } - - rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; - - // Make a mask for reading the entire extra data - Unsafe.SkipInit(out SaveDataExtraData extraDataMask); - SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in extraDataMask); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData, - SaveDataSpaceId spaceId, ulong saveDataId) - { - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); - - // Make a mask for reading the entire extra data - Unsafe.SkipInit(out SaveDataExtraData extraDataMask); - SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); - - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, saveDataId, in extraDataMask); - } - - public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData, - SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer extraDataMask) - { - if (extraDataMask.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - ref readonly SaveDataExtraData maskRef = - ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); - - ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataAttribute tempAttribute = attribute; - - if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) - { - tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - } - - rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; - - return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in maskRef); - } - - private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, - in SaveDataExtraData extraData, SaveDataType saveType, bool updateTimeStamp) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, - saveType, updateTimeStamp); - } - - private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId, - in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - using var accessor = new UniqueRef(); rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; - Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, - spaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); + resolvedSpaceId = value.SpaceId; - rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo, - ReadExtraData); + rc = accessor.Get.Indexer.GetKey(out key, saveDataId); if (rc.IsFailure()) return rc; - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, - saveDataId, key.Type, in saveDataRootPath); - if (rc.IsFailure()) return rc; - - ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); - - return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, - in saveDataRootPath, key.Type, false); } - public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) + Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, + resolvedSpaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); + + rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo, + ReadExtraData); + if (rc.IsFailure()) return rc; + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId, + saveDataId, key.Type, in saveDataRootPath); + if (rc.IsFailure()) return rc; + + MaskExtraData(ref tempExtraData, in extraDataMask); + extraData = tempExtraData; + + return Result.Success; + } + + public Result ReadSaveDataFileSystemExtraData(OutBuffer extraData, ulong saveDataId) + { + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + // Make a mask for reading the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return ReadSaveDataFileSystemExtraDataCore(out SpanHelpers.AsStruct(extraData.Buffer), + SaveDataSpaceId.BisAuto, saveDataId, in extraDataMask); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute = attribute; + + if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) { - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - ref readonly SaveDataExtraData extraDataRef = - ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); - - var extraDataMask = new SaveDataExtraData(); - extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); - - return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in extraDataMask); + tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); } - public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, - SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask) + rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); + if (rc.IsFailure()) return rc; + + // Make a mask for reading the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in extraDataMask); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData, + SaveDataSpaceId spaceId, ulong saveDataId) + { + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + + // Make a mask for reading the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, saveDataId, in extraDataMask); + } + + public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer extraDataMask) + { + if (extraDataMask.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + ref readonly SaveDataExtraData maskRef = + ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); + + ref SaveDataExtraData extraDataRef = ref SpanHelpers.AsStruct(extraData.Buffer); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute = attribute; + + if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataAttribute tempAttribute = attribute; - - if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) - { - tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - } - - rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); - if (rc.IsFailure()) return rc; - - return WriteSaveDataFileSystemExtraDataWithMask(info.SaveDataId, spaceId, extraData, extraDataMask); + tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); } - public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, - InBuffer extraData, InBuffer extraDataMask) + rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); + if (rc.IsFailure()) return rc; + + return ReadSaveDataFileSystemExtraDataCore(out extraDataRef, spaceId, info.SaveDataId, in maskRef); + } + + private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, + in SaveDataExtraData extraData, SaveDataType saveType, bool updateTimeStamp) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, + saveType, updateTimeStamp); + } + + private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId, + in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + using var accessor = new UniqueRef(); + + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + if (rc.IsFailure()) return rc; + + Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, + spaceId, saveDataId, key.Type, _saveDataRootPath.DangerousGetPath()); + + rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo, + ReadExtraData); + if (rc.IsFailure()) return rc; + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, + saveDataId, key.Type, in saveDataRootPath); + if (rc.IsFailure()) return rc; + + ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); + + return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, + in saveDataRootPath, key.Type, false); + } + + public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) + { + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + ref readonly SaveDataExtraData extraDataRef = + ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); + + var extraDataMask = new SaveDataExtraData(); + extraDataMask.Flags = unchecked((SaveDataFlags)0xFFFFFFFF); + + return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in extraDataMask); + } + + public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, + SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute = attribute; + + if (tempAttribute.ProgramId == SaveData.AutoResolveCallerProgramId) { - if (extraDataMask.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - if (extraData.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - ref readonly SaveDataExtraData maskRef = - ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); - - ref readonly SaveDataExtraData extraDataRef = - ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); - - return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef); + tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); } - public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + rc = GetSaveDataInfo(out SaveDataInfo info, spaceId, in tempAttribute); + if (rc.IsFailure()) return rc; - Result rc = GetProgramInfo(out ProgramInfo programInfo); + return WriteSaveDataFileSystemExtraDataWithMask(info.SaveDataId, spaceId, extraData, extraDataMask); + } + + public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraData, InBuffer extraDataMask) + { + if (extraDataMask.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + if (extraData.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + ref readonly SaveDataExtraData maskRef = + ref SpanHelpers.AsReadOnlyStruct(extraDataMask.Buffer); + + ref readonly SaveDataExtraData extraDataRef = + ref SpanHelpers.AsReadOnlyStruct(extraData.Buffer); + + return WriteSaveDataFileSystemExtraDataWithMaskCore(saveDataId, spaceId, in extraDataRef, in maskRef); + } + + public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader) || + !programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + { + return ResultFs.PermissionDenied.Log(); + } + + using var reader = new SharedRef(); + + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); if (rc.IsFailure()) return rc; - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader) || - !programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) - { - return ResultFs.PermissionDenied.Log(); - } + rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; + } + + outInfoReader.SetByMove(ref reader.Ref()); + + return Result.Success; + } + + public Result OpenSaveDataInfoReaderBySaveDataSpaceId( + ref SharedRef outInfoReader, SaveDataSpaceId spaceId) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + if (rc.IsFailure()) return rc; + + using var filterReader = new UniqueRef(); + + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; using var reader = new SharedRef(); - using (var accessor = new UniqueRef()) - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; + rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - } + var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), programId: default, + saveDataType: default, userId: default, saveDataId: default, index: default, rank: 0); - outInfoReader.SetByMove(ref reader.Ref()); - - return Result.Success; + filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); } - public Result OpenSaveDataInfoReaderBySaveDataSpaceId( - ref SharedRef outInfoReader, SaveDataSpaceId spaceId) + outInfoReader.Set(ref filterReader.Ref()); + + return Result.Success; + } + + public Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) + return ResultFs.PermissionDenied.Log(); + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + if (rc.IsFailure()) return rc; + + using var filterReader = new UniqueRef(); + + using (var accessor = new UniqueRef()) { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); - if (rc.IsFailure()) return rc; - - using var filterReader = new UniqueRef(); - - using (var accessor = new UniqueRef()) - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - using var reader = new SharedRef(); - - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), programId: default, - saveDataType: default, userId: default, saveDataId: default, index: default, rank: 0); - - filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); - } - - outInfoReader.Set(ref filterReader.Ref()); - - return Result.Success; - } - - public Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, - SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) - return ResultFs.PermissionDenied.Log(); - - rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); - if (rc.IsFailure()) return rc; - - using var filterReader = new UniqueRef(); - - using (var accessor = new UniqueRef()) - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - using var reader = new SharedRef(); - - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); - - filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in infoFilter)); - } - - outInfoReader.Set(ref filterReader.Ref()); - - return Result.Success; - } - - private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId, - in SaveDataInfoFilter infoFilter) - { - UnsafeHelpers.SkipParamInit(out count, out info); - using var reader = new SharedRef(); - using var accessor = new UniqueRef(); - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; + + var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); + + filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in infoFilter)); + } + + outInfoReader.Set(ref filterReader.Ref()); + + return Result.Success; + } + + private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId, + in SaveDataInfoFilter infoFilter) + { + UnsafeHelpers.SkipParamInit(out count, out info); + + using var reader = new SharedRef(); + using var accessor = new UniqueRef(); + + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; + + using var filterReader = + new UniqueRef(new SaveDataInfoFilterReader(ref reader.Ref(), in infoFilter)); + + return filterReader.Get.Read(out count, new OutBuffer(SpanHelpers.AsByteSpan(ref info))); + } + + public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, + in SaveDataFilter filter) + { + UnsafeHelpers.SkipParamInit(out count); + + if (saveDataInfoBuffer.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + + if (rc.IsFailure()) + { + if (!ResultFs.PermissionDenied.Includes(rc)) + return rc; + + // Don't have full info reader permissions. Check if we have find permissions. + rc = SaveDataAccessibilityChecker.CheckFind(in filter, programInfo); + if (rc.IsFailure()) return rc; + } + + var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); + + return FindSaveDataWithFilterImpl(out count, + out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), spaceId, in infoFilter); + } + + private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + private Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out commitId); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.GetSaveDataCommitId)) + return ResultFs.PermissionDenied.Log(); + + Unsafe.SkipInit(out SaveDataExtraData extraData); + rc = ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer.FromStruct(ref extraData), spaceId, + saveDataId); + if (rc.IsFailure()) return rc; + + commitId = Impl.Utility.ConvertZeroCommitId(in extraData); + return Result.Success; + } + + public Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + // Find where the current program's cache storage is located + Result rc = GetCacheStorageSpaceId(out SaveDataSpaceId spaceId); + + if (rc.IsFailure()) + { + spaceId = SaveDataSpaceId.User; + + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; + } + + return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId); + } + + private Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader, + SaveDataSpaceId spaceId) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (spaceId != SaveDataSpaceId.SdCache && spaceId != SaveDataSpaceId.User) + return ResultFs.InvalidSaveDataSpaceId.Log(); + + using var filterReader = new UniqueRef(); + + using (var reader = new SharedRef()) + using (var accessor = new UniqueRef()) + { + rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; - using var filterReader = - new UniqueRef(new SaveDataInfoFilterReader(ref reader.Ref(), in infoFilter)); - - return filterReader.Get.Read(out count, new OutBuffer(SpanHelpers.AsByteSpan(ref info))); - } - - public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, - in SaveDataFilter filter) - { - UnsafeHelpers.SkipParamInit(out count); - - if (saveDataInfoBuffer.Size != Unsafe.SizeOf()) - return ResultFs.InvalidArgument.Log(); - - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); - - if (rc.IsFailure()) - { - if (!ResultFs.PermissionDenied.Includes(rc)) - return rc; - - // Don't have full info reader permissions. Check if we have find permissions. - rc = SaveDataAccessibilityChecker.CheckFind(in filter, programInfo); - if (rc.IsFailure()) return rc; - } - - var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); - - return FindSaveDataWithFilterImpl(out count, - out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), spaceId, in infoFilter); - } - - private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - private Result OpenSaveDataInternalStorageFileSystemCore(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef fileSystem, - SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out commitId); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.GetSaveDataCommitId)) - return ResultFs.PermissionDenied.Log(); - - Unsafe.SkipInit(out SaveDataExtraData extraData); - rc = ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer.FromStruct(ref extraData), spaceId, - saveDataId); - if (rc.IsFailure()) return rc; - - commitId = Impl.Utility.ConvertZeroCommitId(in extraData); - return Result.Success; - } - - public Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - // Find where the current program's cache storage is located - Result rc = GetCacheStorageSpaceId(out SaveDataSpaceId spaceId); - - if (rc.IsFailure()) - { - spaceId = SaveDataSpaceId.User; - - if (!ResultFs.TargetNotFound.Includes(rc)) - return rc; - } - - return OpenSaveDataInfoReaderOnlyCacheStorage(ref outInfoReader, spaceId); - } - - private Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader, - SaveDataSpaceId spaceId) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (spaceId != SaveDataSpaceId.SdCache && spaceId != SaveDataSpaceId.User) - return ResultFs.InvalidSaveDataSpaceId.Log(); - - using var filterReader = new UniqueRef(); - - using (var reader = new SharedRef()) - using (var accessor = new UniqueRef()) - { - rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); - if (rc.IsFailure()) return rc; - - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - - var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, - SaveDataType.Cache, userId: default, saveDataId: default, index: default, - (int)SaveDataRank.Primary); - - filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); - } - - outInfoReader.Set(ref filterReader.Ref()); - - return Result.Success; - } - - private Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, - ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataMetaFile(ref SharedRef file, SaveDataSpaceId spaceId, - in SaveDataAttribute attribute, SaveDataMetaType metaType) - { - throw new NotImplementedException(); - } - - private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId) - { - UnsafeHelpers.SkipParamInit(out spaceId); - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - ulong programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; - return GetCacheStorageSpaceId(out spaceId, programId); - } - - private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) - { - UnsafeHelpers.SkipParamInit(out spaceId); - Result rc; - - // Cache storage on the SD card will always take priority over case storage in NAND - if (_serviceImpl.IsSdCardAccessible()) - { - rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdCache); - if (rc.IsFailure()) return rc; - - if (existsOnSdCard) - { - spaceId = SaveDataSpaceId.SdCache; - return Result.Success; - } - } - - rc = SaveExists(out bool existsOnNand, SaveDataSpaceId.User); - if (rc.IsFailure()) return rc; - - if (existsOnNand) - { - spaceId = SaveDataSpaceId.User; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); - - Result SaveExists(out bool exists, SaveDataSpaceId saveSpaceId) - { - UnsafeHelpers.SkipParamInit(out exists); - - var infoFilter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, - default, default, default, 0); - - Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in infoFilter); - if (result.IsFailure()) return result; - - exists = count != 0; - return Result.Success; - } - } - - private Result FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, ushort index) - { - UnsafeHelpers.SkipParamInit(out saveInfo, out spaceId); - - Result rc = GetCacheStorageSpaceId(out spaceId); - if (rc.IsFailure()) return rc; - - rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); - var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, SaveDataType.Cache, - userId: default, saveDataId: default, index, (int)SaveDataRank.Primary); + var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, + SaveDataType.Cache, userId: default, saveDataId: default, index: default, + (int)SaveDataRank.Primary); - rc = FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, spaceId, in filter); + filterReader.Reset(new SaveDataInfoFilterReader(ref reader.Ref(), in filter)); + } + + outInfoReader.Set(ref filterReader.Ref()); + + return Result.Success; + } + + private Result OpenSaveDataMetaFileRaw(ref SharedRef file, SaveDataSpaceId spaceId, + ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMetaFile(ref SharedRef file, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType metaType) + { + throw new NotImplementedException(); + } + + private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId) + { + UnsafeHelpers.SkipParamInit(out spaceId); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + ulong programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + return GetCacheStorageSpaceId(out spaceId, programId); + } + + private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) + { + UnsafeHelpers.SkipParamInit(out spaceId); + Result rc; + + // Cache storage on the SD card will always take priority over case storage in NAND + if (_serviceImpl.IsSdCardAccessible()) + { + rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdCache); if (rc.IsFailure()) return rc; - if (count == 0) - return ResultFs.TargetNotFound.Log(); - - saveInfo = info; - return Result.Success; - } - - public Result DeleteCacheStorage(ushort index) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); - if (rc.IsFailure()) return rc; - - rc = Hos.Fs.DeleteSaveData(spaceId, saveInfo.SaveDataId); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result GetCacheStorageSize(out long usableDataSize, out long journalSize, ushort index) - { - UnsafeHelpers.SkipParamInit(out usableDataSize, out journalSize); - - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - - Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); - if (rc.IsFailure()) return rc; - - using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); - rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveInfo.SaveDataId, saveInfo.Type, in saveDataRootPath); - if (rc.IsFailure()) return rc; - - usableDataSize = extraData.DataSize; - journalSize = extraData.JournalSize; - - return Result.Success; - } - - public Result OpenSaveDataTransferManager(ref SharedRef manager) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataTransferManagerVersion2( - ref SharedRef manager) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataTransferManagerForSaveDataRepair( - ref SharedRef manager) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataTransferManagerForRepair( - ref SharedRef manager) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataTransferProhibiter( - ref SharedRef prohibiter, Ncm.ApplicationId applicationId) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataMover(ref SharedRef saveMover, - SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, - ulong workBufferSize) - { - throw new NotImplementedException(); - } - - public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) - { - return _serviceImpl.SetSdCardEncryptionSeed(in seed); - } - - public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, - int startIndex, int bufferIdCount) - { - throw new NotImplementedException(); - } - - private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) - { - return _serviceImpl.ResolveDefaultSaveDataReferenceProgramId(programId); - } - - public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, - OutBuffer workBuffer) - { - throw new NotImplementedException(); - } - - public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) - { - throw new NotImplementedException(); - } - - public Result CleanUpSaveData() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); - using var accessor = new UniqueRef(); - - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; - - return CleanUpSaveData(accessor.Get); - } - - private Result CleanUpSaveData(SaveDataIndexerAccessor accessor) - { - // Todo: Implement - return Result.Success; - } - - public Result CompleteSaveDataExtension() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); - using var accessor = new UniqueRef(); - - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; - - return CompleteSaveDataExtension(accessor.Get); - } - - private Result CompleteSaveDataExtension(SaveDataIndexerAccessor accessor) - { - // Todo: Implement - return Result.Success; - } - - public Result CleanUpTemporaryStorage() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); - using var fileSystem = new SharedRef(); - - Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), SaveDataSpaceId.Temporary); - if (rc.IsFailure()) return rc; - - using var pathRoot = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathRoot.Ref(), new[] { (byte)'/' }); - if (rc.IsFailure()) return rc; - - rc = fileSystem.Get.CleanDirectoryRecursively(in pathRoot); - if (rc.IsFailure()) return rc; - - _serviceImpl.ResetTemporaryStorageIndexer(); - return Result.Success; - } - - public Result FixSaveData() - { - // Todo: Implement - return Result.Success; - } - - public Result OpenMultiCommitManager(ref SharedRef outCommitManager) - { - using SharedRef - commitInterface = GetSharedMultiCommitInterfaceFromThis(); - - outCommitManager.Reset(new MultiCommitManager(_serviceImpl.FsServer, ref commitInterface.Ref())); - - return Result.Success; - } - - public Result OpenMultiCommitContext(ref SharedRef contextFileSystem) - { - var attribute = new SaveDataAttribute + if (existsOnSdCard) { - Index = 0, - Type = SaveDataType.System, - UserId = UserId.InvalidId, - StaticSaveDataId = MultiCommitManager.SaveDataId, - ProgramId = new ProgramId(MultiCommitManager.ProgramId) - }; - - return OpenSaveDataFileSystemCore(ref contextFileSystem, out _, SaveDataSpaceId.System, in attribute, false, - true); - } - - public Result RecoverMultiCommit() - { - return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl); - } - - public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) - { - return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); - } - - public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) - { - var attribute = new SaveDataAttribute - { - Index = saveInfo.Index, - Type = saveInfo.Type, - UserId = UserId.InvalidId, - StaticSaveDataId = saveInfo.StaticSaveDataId, - ProgramId = saveInfo.ProgramId - }; - - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out _, saveInfo.SpaceId, in attribute, false, - false); - if (rc.IsFailure()) return rc; - - if (doRollback) - { - rc = fileSystem.Get.Rollback(); - } - else - { - rc = fileSystem.Get.Commit(); - } - - return rc; - } - - private Result TryAcquireSaveDataEntryOpenCountSemaphore(ref UniqueRef outSemaphoreLock) - { - using SharedRef saveService = GetSharedFromThis(); - - Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, - ref saveService.Ref()); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private Result TryAcquireSaveDataMountCountSemaphore(ref UniqueRef outSemaphoreLock) - { - using SharedRef saveService = GetSharedFromThis(); - - Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, - ref saveService.Ref()); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key) - { - throw new NotImplementedException(); - } - - public Result SetSdCardAccessibility(bool isAccessible) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility)) - return ResultFs.PermissionDenied.Log(); - - _serviceImpl.SetSdCardAccessibility(isAccessible); - return Result.Success; - } - - public Result IsSdCardAccessible(out bool isAccessible) - { - isAccessible = _serviceImpl.IsSdCardAccessible(); - return Result.Success; - } - - private Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, - SaveDataSpaceId spaceId) - { - using var accessor = new UniqueRef(); - Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool neededInit, spaceId); - if (rc.IsFailure()) return rc; - - if (neededInit) - { - // todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore - // nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore - } - - outAccessor.Set(ref accessor.Ref()); - return Result.Success; - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } - - private bool IsCurrentProcess(ulong processId) - { - ulong currentId = Hos.Os.GetCurrentProcessId().Value; - - return processId == currentId; - } - - private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) - { - return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode - ? SaveDataSpaceId.System - : spaceId; - } - - private bool IsStaticSaveDataIdValueRange(ulong id) - { - return (long)id < 0; - } - - private void ModifySaveDataExtraData(ref SaveDataExtraData currentExtraData, in SaveDataExtraData extraData, - in SaveDataExtraData extraDataMask) - { - Span currentExtraDataBytes = SpanHelpers.AsByteSpan(ref currentExtraData); - ReadOnlySpan extraDataBytes = SpanHelpers.AsReadOnlyByteSpan(in extraData); - ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); - - for (int i = 0; i < Unsafe.SizeOf(); i++) - { - currentExtraDataBytes[i] = (byte)(extraDataBytes[i] & extraDataMaskBytes[i] | - currentExtraDataBytes[i] & ~extraDataMaskBytes[i]); + spaceId = SaveDataSpaceId.SdCache; + return Result.Success; } } - private void MaskExtraData(ref SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) - { - Span extraDataBytes = SpanHelpers.AsByteSpan(ref extraData); - ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); + rc = SaveExists(out bool existsOnNand, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; - for (int i = 0; i < Unsafe.SizeOf(); i++) - { - extraDataBytes[i] &= extraDataMaskBytes[i]; - } + if (existsOnNand) + { + spaceId = SaveDataSpaceId.User; + return Result.Success; } - private StorageType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId) + return ResultFs.TargetNotFound.Log(); + + Result SaveExists(out bool exists, SaveDataSpaceId saveSpaceId) { - if (type == SaveDataType.Cache || type == SaveDataType.Bcat) - return StorageType.Bis | StorageType.SdCard | StorageType.Usb; + UnsafeHelpers.SkipParamInit(out exists); - if (type == SaveDataType.System || - spaceId != SaveDataSpaceId.SdSystem && spaceId != SaveDataSpaceId.SdCache) - return StorageType.Bis; + var infoFilter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, + default, default, default, 0); - return StorageType.SdCard | StorageType.Usb; - } + Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in infoFilter); + if (result.IsFailure()) return result; - Result ISaveDataTransferCoreInterface.CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, - bool leaveUnfinalized) - { - return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, - leaveUnfinalized); - } - - Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, - SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) - { - return ReadSaveDataFileSystemExtraDataCore(out extraData, spaceId, saveDataId, isTemporarySaveData); - } - - Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, - ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp) - { - return WriteSaveDataFileSystemExtraDataCore(spaceId, saveDataId, in extraData, type, updateTimeStamp); - } - - Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(ref SharedRef file, - SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) - { - return OpenSaveDataMetaFileRaw(ref file, spaceId, saveDataId, metaType, mode); - } - - Result ISaveDataTransferCoreInterface.OpenSaveDataInternalStorageFileSystemCore( - ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, - bool useSecondMacKey) - { - return OpenSaveDataInternalStorageFileSystemCore(ref fileSystem, spaceId, saveDataId, useSecondMacKey); - } - - Result ISaveDataTransferCoreInterface.OpenSaveDataIndexerAccessor( - ref UniqueRef outAccessor, SaveDataSpaceId spaceId) - { - return OpenSaveDataIndexerAccessor(ref outAccessor, spaceId); - } - - public void Dispose() - { - _openEntryCountSemaphore.Dispose(); - _saveDataMountCountSemaphore.Dispose(); + exists = count != 0; + return Result.Success; } } + + private Result FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, ushort index) + { + UnsafeHelpers.SkipParamInit(out saveInfo, out spaceId); + + Result rc = GetCacheStorageSpaceId(out spaceId); + if (rc.IsFailure()) return rc; + + rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + + var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), resolvedProgramId, SaveDataType.Cache, + userId: default, saveDataId: default, index, (int)SaveDataRank.Primary); + + rc = FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, spaceId, in filter); + if (rc.IsFailure()) return rc; + + if (count == 0) + return ResultFs.TargetNotFound.Log(); + + saveInfo = info; + return Result.Success; + } + + public Result DeleteCacheStorage(ushort index) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); + if (rc.IsFailure()) return rc; + + rc = Hos.Fs.DeleteSaveData(spaceId, saveInfo.SaveDataId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result GetCacheStorageSize(out long usableDataSize, out long journalSize, ushort index) + { + UnsafeHelpers.SkipParamInit(out usableDataSize, out journalSize); + + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); + if (rc.IsFailure()) return rc; + + using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveInfo.SaveDataId, saveInfo.Type, in saveDataRootPath); + if (rc.IsFailure()) return rc; + + usableDataSize = extraData.DataSize; + journalSize = extraData.JournalSize; + + return Result.Success; + } + + public Result OpenSaveDataTransferManager(ref SharedRef manager) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataTransferManagerVersion2( + ref SharedRef manager) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataTransferManagerForSaveDataRepair( + ref SharedRef manager) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataTransferManagerForRepair( + ref SharedRef manager) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataTransferProhibiter( + ref SharedRef prohibiter, Ncm.ApplicationId applicationId) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMover(ref SharedRef saveMover, + SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, + ulong workBufferSize) + { + throw new NotImplementedException(); + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + return _serviceImpl.SetSdCardEncryptionSeed(in seed); + } + + public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, + int startIndex, int bufferIdCount) + { + throw new NotImplementedException(); + } + + private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) + { + return _serviceImpl.ResolveDefaultSaveDataReferenceProgramId(programId); + } + + public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, + OutBuffer workBuffer) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) + { + throw new NotImplementedException(); + } + + public Result CleanUpSaveData() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + using var accessor = new UniqueRef(); + + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + return CleanUpSaveData(accessor.Get); + } + + private Result CleanUpSaveData(SaveDataIndexerAccessor accessor) + { + // Todo: Implement + return Result.Success; + } + + public Result CompleteSaveDataExtension() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + using var accessor = new UniqueRef(); + + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + return CompleteSaveDataExtension(accessor.Get); + } + + private Result CompleteSaveDataExtension(SaveDataIndexerAccessor accessor) + { + // Todo: Implement + return Result.Success; + } + + public Result CleanUpTemporaryStorage() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + using var fileSystem = new SharedRef(); + + Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), SaveDataSpaceId.Temporary); + if (rc.IsFailure()) return rc; + + using var pathRoot = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathRoot.Ref(), new[] { (byte)'/' }); + if (rc.IsFailure()) return rc; + + rc = fileSystem.Get.CleanDirectoryRecursively(in pathRoot); + if (rc.IsFailure()) return rc; + + _serviceImpl.ResetTemporaryStorageIndexer(); + return Result.Success; + } + + public Result FixSaveData() + { + // Todo: Implement + return Result.Success; + } + + public Result OpenMultiCommitManager(ref SharedRef outCommitManager) + { + using SharedRef + commitInterface = GetSharedMultiCommitInterfaceFromThis(); + + outCommitManager.Reset(new MultiCommitManager(_serviceImpl.FsServer, ref commitInterface.Ref())); + + return Result.Success; + } + + public Result OpenMultiCommitContext(ref SharedRef contextFileSystem) + { + var attribute = new SaveDataAttribute + { + Index = 0, + Type = SaveDataType.System, + UserId = UserId.InvalidId, + StaticSaveDataId = MultiCommitManager.SaveDataId, + ProgramId = new ProgramId(MultiCommitManager.ProgramId) + }; + + return OpenSaveDataFileSystemCore(ref contextFileSystem, out _, SaveDataSpaceId.System, in attribute, false, + true); + } + + public Result RecoverMultiCommit() + { + return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl); + } + + public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) + { + return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); + } + + public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) + { + var attribute = new SaveDataAttribute + { + Index = saveInfo.Index, + Type = saveInfo.Type, + UserId = UserId.InvalidId, + StaticSaveDataId = saveInfo.StaticSaveDataId, + ProgramId = saveInfo.ProgramId + }; + + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataFileSystemCore(ref fileSystem.Ref(), out _, saveInfo.SpaceId, in attribute, false, + false); + if (rc.IsFailure()) return rc; + + if (doRollback) + { + rc = fileSystem.Get.Rollback(); + } + else + { + rc = fileSystem.Get.Commit(); + } + + return rc; + } + + private Result TryAcquireSaveDataEntryOpenCountSemaphore(ref UniqueRef outSemaphoreLock) + { + using SharedRef saveService = GetSharedFromThis(); + + Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _openEntryCountSemaphore, + ref saveService.Ref()); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result TryAcquireSaveDataMountCountSemaphore(ref UniqueRef outSemaphoreLock) + { + using SharedRef saveService = GetSharedFromThis(); + + Result rc = Utility.MakeUniqueLockWithPin(ref outSemaphoreLock, _saveDataMountCountSemaphore, + ref saveService.Ref()); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key) + { + throw new NotImplementedException(); + } + + public Result SetSdCardAccessibility(bool isAccessible) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility)) + return ResultFs.PermissionDenied.Log(); + + _serviceImpl.SetSdCardAccessibility(isAccessible); + return Result.Success; + } + + public Result IsSdCardAccessible(out bool isAccessible) + { + isAccessible = _serviceImpl.IsSdCardAccessible(); + return Result.Success; + } + + private Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, + SaveDataSpaceId spaceId) + { + using var accessor = new UniqueRef(); + Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool neededInit, spaceId); + if (rc.IsFailure()) return rc; + + if (neededInit) + { + // todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore + // nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore + } + + outAccessor.Set(ref accessor.Ref()); + return Result.Success; + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + + private bool IsCurrentProcess(ulong processId) + { + ulong currentId = Hos.Os.GetCurrentProcessId().Value; + + return processId == currentId; + } + + private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode + ? SaveDataSpaceId.System + : spaceId; + } + + private bool IsStaticSaveDataIdValueRange(ulong id) + { + return (long)id < 0; + } + + private void ModifySaveDataExtraData(ref SaveDataExtraData currentExtraData, in SaveDataExtraData extraData, + in SaveDataExtraData extraDataMask) + { + Span currentExtraDataBytes = SpanHelpers.AsByteSpan(ref currentExtraData); + ReadOnlySpan extraDataBytes = SpanHelpers.AsReadOnlyByteSpan(in extraData); + ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); + + for (int i = 0; i < Unsafe.SizeOf(); i++) + { + currentExtraDataBytes[i] = (byte)(extraDataBytes[i] & extraDataMaskBytes[i] | + currentExtraDataBytes[i] & ~extraDataMaskBytes[i]); + } + } + + private void MaskExtraData(ref SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + Span extraDataBytes = SpanHelpers.AsByteSpan(ref extraData); + ReadOnlySpan extraDataMaskBytes = SpanHelpers.AsReadOnlyByteSpan(in extraDataMask); + + for (int i = 0; i < Unsafe.SizeOf(); i++) + { + extraDataBytes[i] &= extraDataMaskBytes[i]; + } + } + + private StorageType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId) + { + if (type == SaveDataType.Cache || type == SaveDataType.Bcat) + return StorageType.Bis | StorageType.SdCard | StorageType.Usb; + + if (type == SaveDataType.System || + spaceId != SaveDataSpaceId.SdSystem && spaceId != SaveDataSpaceId.SdCache) + return StorageType.Bis; + + return StorageType.SdCard | StorageType.Usb; + } + + Result ISaveDataTransferCoreInterface.CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, + bool leaveUnfinalized) + { + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, + leaveUnfinalized); + } + + Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, + SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) + { + return ReadSaveDataFileSystemExtraDataCore(out extraData, spaceId, saveDataId, isTemporarySaveData); + } + + Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp) + { + return WriteSaveDataFileSystemExtraDataCore(spaceId, saveDataId, in extraData, type, updateTimeStamp); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(ref SharedRef file, + SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + { + return OpenSaveDataMetaFileRaw(ref file, spaceId, saveDataId, metaType, mode); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataInternalStorageFileSystemCore( + ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, + bool useSecondMacKey) + { + return OpenSaveDataInternalStorageFileSystemCore(ref fileSystem, spaceId, saveDataId, useSecondMacKey); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataIndexerAccessor( + ref UniqueRef outAccessor, SaveDataSpaceId spaceId) + { + return OpenSaveDataIndexerAccessor(ref outAccessor, spaceId); + } + + public void Dispose() + { + _openEntryCountSemaphore.Dispose(); + _saveDataMountCountSemaphore.Dispose(); + } } diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index e381dab1..8db8ea91 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -12,478 +12,520 @@ using LibHac.Os; using LibHac.Util; using Utility = LibHac.FsSrv.Impl.Utility; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class SaveDataFileSystemServiceImpl { - public class SaveDataFileSystemServiceImpl + private Configuration _config; + private EncryptionSeed _encryptionSeed; + + private SaveDataFileSystemCacheManager _saveDataFsCacheManager; + private SaveDataExtraDataAccessorCacheManager _extraDataCacheManager; + // Save data porter manager + private bool _isSdCardAccessible; + private TimeStampGetter _timeStampGetter; + + internal HorizonClient Hos => _config.FsServer.Hos; + internal FileSystemServer FsServer => _config.FsServer; + + private class TimeStampGetter : ISaveDataCommitTimeStampGetter { - private Configuration _config; - private EncryptionSeed _encryptionSeed; + private SaveDataFileSystemServiceImpl _saveService; - private SaveDataFileSystemCacheManager _saveDataFsCacheManager; - private SaveDataExtraDataAccessorCacheManager _extraDataCacheManager; - // Save data porter manager - private bool _isSdCardAccessible; - private TimeStampGetter _timeStampGetter; - - internal HorizonClient Hos => _config.FsServer.Hos; - internal FileSystemServer FsServer => _config.FsServer; - - private class TimeStampGetter : ISaveDataCommitTimeStampGetter + public TimeStampGetter(SaveDataFileSystemServiceImpl saveService) { - private SaveDataFileSystemServiceImpl _saveService; - - public TimeStampGetter(SaveDataFileSystemServiceImpl saveService) - { - _saveService = saveService; - } - - public Result Get(out long timeStamp) - { - return _saveService.GetSaveDataCommitTimeStamp(out timeStamp); - } + _saveService = saveService; } - public SaveDataFileSystemServiceImpl(in Configuration configuration) + public Result Get(out long timeStamp) { - _config = configuration; - _saveDataFsCacheManager = new SaveDataFileSystemCacheManager(); - _extraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); - - _timeStampGetter = new TimeStampGetter(this); - - Result rc = _saveDataFsCacheManager.Initialize(_config.MaxSaveFsCacheCount); - Abort.DoAbortUnless(rc.IsSuccess()); + return _saveService.GetSaveDataCommitTimeStamp(out timeStamp); } + } - public struct Configuration + public SaveDataFileSystemServiceImpl(in Configuration configuration) + { + _config = configuration; + _saveDataFsCacheManager = new SaveDataFileSystemCacheManager(); + _extraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); + + _timeStampGetter = new TimeStampGetter(this); + + Result rc = _saveDataFsCacheManager.Initialize(_config.MaxSaveFsCacheCount); + Abort.DoAbortUnless(rc.IsSuccess()); + } + + public struct Configuration + { + public BaseFileSystemServiceImpl BaseFsService; + public TimeServiceImpl TimeService; + public ILocalFileSystemCreator LocalFsCreator; + public ITargetManagerFileSystemCreator TargetManagerFsCreator; + public ISaveDataFileSystemCreator SaveFsCreator; + public IEncryptedFileSystemCreator EncryptedFsCreator; + public ProgramRegistryServiceImpl ProgramRegistryService; + public IBufferManager BufferManager; + public RandomDataGenerator GenerateRandomData; + public SaveDataTransferCryptoConfiguration SaveTransferCryptoConfig; + public int MaxSaveFsCacheCount; + public Func IsPseudoSaveData; + public ISaveDataIndexerManager SaveIndexerManager; + + // LibHac additions + public FileSystemServer FsServer; + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_config.FsServer); + return registry.GetProgramInfo(out programInfo, processId); + } + + public Result DoesSaveDataEntityExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out exists); + + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId); + if (rc.IsFailure()) return rc.Miss(); + + // Get the path of the save data + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + rc = fileSystem.Get.GetEntryType(out _, in saveImageName); + + if (rc.IsSuccess()) { - public BaseFileSystemServiceImpl BaseFsService; - public TimeServiceImpl TimeService; - public ILocalFileSystemCreator LocalFsCreator; - public ITargetManagerFileSystemCreator TargetManagerFsCreator; - public ISaveDataFileSystemCreator SaveFsCreator; - public IEncryptedFileSystemCreator EncryptedFsCreator; - public ProgramRegistryServiceImpl ProgramRegistryService; - public IBufferManager BufferManager; - public RandomDataGenerator GenerateRandomData; - public SaveDataTransferCryptoConfiguration SaveTransferCryptoConfig; - public int MaxSaveFsCacheCount; - public Func IsPseudoSaveData; - public ISaveDataIndexerManager SaveIndexerManager; - - // LibHac additions - public FileSystemServer FsServer; - } - - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_config.FsServer); - return registry.GetProgramInfo(out programInfo, processId); - } - - public Result DoesSaveDataEntityExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out exists); - - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId); - if (rc.IsFailure()) return rc.Miss(); - - // Get the path of the save data - // Hack around error CS8350. - const int bufferLength = 0x12; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveImageName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - rc = fileSystem.Get.GetEntryType(out _, in saveImageName); - - if (rc.IsSuccess()) - { - exists = true; - return Result.Success; - } - else if (ResultFs.PathNotFound.Includes(rc)) - { - exists = false; - return Result.Success; - } - else - { - return rc.Miss(); - } - } - - public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, - ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) - { - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, true); - if (rc.IsFailure()) return rc.Miss(); - - bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, in saveDataRootPath); - - // Note: When directory save data is allowed, Nintendo creates the save directory if it doesn't exist. - // This bypasses normal save data creation, leaving the save with empty extra data. - // Instead, we return that the save doesn't exist if the directory is missing. - - using var saveDataFs = new SharedRef(); - using var cachedFs = new SharedRef(); - - // Note: Nintendo doesn't cache directory save data - // if (!allowDirectorySaveData) - { - // Check if we have the requested file system cached - if (_saveDataFsCacheManager.GetCache(ref cachedFs.Ref(), spaceId, saveDataId)) - { - using var registerBase = new SharedRef( - new SaveDataFileSystemCacheRegisterBase(ref cachedFs.Ref(), - _saveDataFsCacheManager)); - - using var resultConvertFs = new SharedRef( - new SaveDataResultConvertFileSystem(ref registerBase.Ref())); - - saveDataFs.SetByMove(ref resultConvertFs.Ref()); - } - } - - // Create a new file system if it's not in the cache - if (!saveDataFs.HasValue) - { - using UniqueLockRef scopedLock = _extraDataCacheManager.GetScopedLock(); - using var extraDataAccessor = new SharedRef(); - - bool openShared = SaveDataProperties.IsSharedOpenNeeded(type); - bool isMultiCommitSupported = SaveDataProperties.IsMultiCommitSupported(type); - bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type); - bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId); - - rc = _config.SaveFsCreator.Create(ref saveDataFs.Ref(), ref extraDataAccessor.Ref(), - _saveDataFsCacheManager, ref fileSystem.Ref(), spaceId, saveDataId, allowDirectorySaveData, - useDeviceUniqueMac, isJournalingSupported, isMultiCommitSupported, openReadOnly, openShared, - _timeStampGetter); - if (rc.IsFailure()) return rc.Miss(); - - // Cache the extra data accessor if needed - if (cacheExtraData && extraDataAccessor.HasValue) - { - extraDataAccessor.Get.RegisterCacheObserver(_extraDataCacheManager, spaceId, saveDataId); - - rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - } - } - - if (openReadOnly) - { - outFileSystem.Reset(new ReadOnlyFileSystem(ref saveDataFs.Ref())); - } - else - { - outFileSystem.SetByMove(ref saveDataFs.Ref()); - } - + exists = true; return Result.Success; } - - public Result OpenSaveDataMetaDirectoryFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId) + else if (ResultFs.PathNotFound.Includes(rc)) { - // Hack around error CS8350. - const int bufferLength = 0x1B; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveDataMetaIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveDataMetaIdDirectoryName = new Path(); - Result rc = PathFunctions.SetUpFixedPathSaveMetaDir(ref saveDataMetaIdDirectoryName.Ref(), - saveDataMetaIdDirectoryNameBuffer, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in saveDataMetaIdDirectoryName); - } - - public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey) - { - throw new NotImplementedException(); - } - - public Result QuerySaveDataTotalSize(out long totalSize, int blockSize, long dataSize, long journalSize) - { - // Todo: Implement - totalSize = 0; + exists = false; return Result.Success; } - - public Result CreateSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType, - long metaFileSize) + else { - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - // Hack around error CS8350. - const int bufferLength = 0xF; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveDataMetaName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, - (uint)metaType); - if (rc.IsFailure()) return rc.Miss(); - - rc = fileSystem.Get.CreateFile(in saveDataMetaName, metaFileSize); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; + return rc.Miss(); } + } - public Result DeleteSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType) + public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, + ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) + { + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, true); + if (rc.IsFailure()) return rc.Miss(); + + bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, in saveDataRootPath); + + // Note: When directory save data is allowed, Nintendo creates the save directory if it doesn't exist. + // This bypasses normal save data creation, leaving the save with empty extra data. + // Instead, we return that the save doesn't exist if the directory is missing. + + using var saveDataFs = new SharedRef(); + using var cachedFs = new SharedRef(); + + // Note: Nintendo doesn't cache directory save data + // if (!allowDirectorySaveData) { - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - // Hack around error CS8350. - const int bufferLength = 0xF; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveDataMetaName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, - (uint)metaType); - if (rc.IsFailure()) return rc.Miss(); - - rc = fileSystem.Get.DeleteFile(in saveDataMetaName); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId) - { - ReadOnlySpan metaDirName = // /saveMeta - new[] - { - (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', - (byte)'a' - }; - - // Hack around error CS8350. - const int bufferLength = 0x12; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveDataIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var fileSystem = new SharedRef(); - - using var saveDataMetaDirectoryName = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref saveDataMetaDirectoryName.Ref(), metaDirName); - if (rc.IsFailure()) return rc.Miss(); - - rc = OpenSaveDataDirectoryFileSystemImpl(ref fileSystem.Ref(), spaceId, in saveDataMetaDirectoryName, false); - if (rc.IsFailure()) return rc.Miss(); - - using var saveDataIdDirectoryName = new Path(); - PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName.Ref(), saveDataIdDirectoryNameBuffer, - saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - // Delete the save data's meta directory, ignoring the error if the directory is already gone - rc = fileSystem.Get.DeleteDirectoryRecursively(in saveDataIdDirectoryName); - - if (rc.IsFailure()) + // Check if we have the requested file system cached + if (_saveDataFsCacheManager.GetCache(ref cachedFs.Ref(), spaceId, saveDataId)) { - if (!ResultFs.PathNotFound.Includes(rc)) - return rc.Catch().Handle(); + using var registerBase = new SharedRef( + new SaveDataFileSystemCacheRegisterBase(ref cachedFs.Ref(), + _saveDataFsCacheManager)); - return rc.Miss(); + using var resultConvertFs = new SharedRef( + new SaveDataResultConvertFileSystem(ref registerBase.Ref())); + + saveDataFs.SetByMove(ref resultConvertFs.Ref()); } - - return Result.Success; } - public Result OpenSaveDataMeta(ref UniqueRef outMetaFile, ulong saveDataId, SaveDataSpaceId spaceId, - SaveDataMetaType metaType) + // Create a new file system if it's not in the cache + if (!saveDataFs.HasValue) { - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - // Hack around error CS8350. - const int bufferLength = 0xF; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveDataMetaName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, - (uint)metaType); - if (rc.IsFailure()) return rc.Miss(); - - rc = fileSystem.Get.OpenFile(ref outMetaFile, in saveDataMetaName, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional hashSalt, - bool skipFormat) - { - // Use directory save data for now - - // Hack around error CS8350. - const int bufferLength = 0x12; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var fileSystem = new SharedRef(); - - Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), creationInfo.SpaceId, - in saveDataRootPath, false); - if (rc.IsFailure()) return rc.Miss(); - - using var saveImageName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - if (_config.IsPseudoSaveData()) - { - rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName); - if (rc.IsFailure()) return rc.Miss(); - - using var saveFileSystem = new SharedRef(); - using var extraDataAccessor = new SharedRef(); - - bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(attribute.Type); - - rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref extraDataAccessor.Ref(), - _saveDataFsCacheManager, ref fileSystem.Ref(), creationInfo.SpaceId, saveDataId, - allowDirectorySaveData: true, useDeviceUniqueMac: false, isJournalingSupported, - isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter); - if (rc.IsFailure()) return rc.Miss(); - - var extraData = new SaveDataExtraData(); - extraData.Attribute = attribute; - extraData.OwnerId = creationInfo.OwnerId; - - rc = GetSaveDataCommitTimeStamp(out extraData.TimeStamp); - if (rc.IsFailure()) - extraData.TimeStamp = 0; - - extraData.CommitId = 0; - _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)).IgnoreResult(); - - extraData.Flags = creationInfo.Flags; - extraData.DataSize = creationInfo.Size; - extraData.JournalSize = creationInfo.JournalSize; - - rc = extraDataAccessor.Get.WriteExtraData(in extraData); - if (rc.IsFailure()) return rc.Miss(); - - rc = extraDataAccessor.Get.CommitExtraData(true); - if (rc.IsFailure()) return rc.Miss(); - } - else - { - throw new NotImplementedException(); - } - - return Result.Success; - } - - private Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random) - { - throw new NotImplementedException(); - } - - public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile, - in Path saveDataRootPath) - { - // Hack around error CS8350. - const int bufferLength = 0x12; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var fileSystem = new SharedRef(); - - _saveDataFsCacheManager.Unregister(spaceId, saveDataId); - - // Open the directory containing the save data - Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, false); - if (rc.IsFailure()) return rc.Miss(); - - using var saveImageName = new Path(); - rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); - if (rc.IsFailure()) return rc.Miss(); - - // Check if the save data is a file or a directory - rc = fileSystem.Get.GetEntryType(out DirectoryEntryType entryType, in saveImageName); - if (rc.IsFailure()) return rc.Miss(); - - // Delete the save data, wiping the file if needed - if (entryType == DirectoryEntryType.Directory) - { - rc = fileSystem.Get.DeleteDirectoryRecursively(in saveImageName); - if (rc.IsFailure()) return rc.Miss(); - } - else - { - if (wipeSaveFile) - { - WipeData(fileSystem.Get, in saveImageName, _config.GenerateRandomData).IgnoreResult(); - } - - rc = fileSystem.Get.DeleteFile(in saveImageName); - if (rc.IsFailure()) return rc.Miss(); - } - - return Result.Success; - } - - public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, - ulong saveDataId, SaveDataType type, in Path saveDataRootPath) - { - UnsafeHelpers.SkipParamInit(out extraData); - - // Nintendo returns blank extra data for directory save data. - // We've extended directory save data to store extra data so we don't need to do that. - - using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); - using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); - + using UniqueLockRef scopedLock = _extraDataCacheManager.GetScopedLock(); using var extraDataAccessor = new SharedRef(); - // Try to grab an extra data accessor for the requested save from the cache. - Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + bool openShared = SaveDataProperties.IsSharedOpenNeeded(type); + bool isMultiCommitSupported = SaveDataProperties.IsMultiCommitSupported(type); + bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type); + bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId); - if (rc.IsSuccess()) + rc = _config.SaveFsCreator.Create(ref saveDataFs.Ref(), ref extraDataAccessor.Ref(), + _saveDataFsCacheManager, ref fileSystem.Ref(), spaceId, saveDataId, allowDirectorySaveData, + useDeviceUniqueMac, isJournalingSupported, isMultiCommitSupported, openReadOnly, openShared, + _timeStampGetter); + if (rc.IsFailure()) return rc.Miss(); + + // Cache the extra data accessor if needed + if (cacheExtraData && extraDataAccessor.HasValue) { - // An extra data accessor was found in the cache. Read the extra data from it. - return extraDataAccessor.Get.ReadExtraData(out extraData); + extraDataAccessor.Get.RegisterCacheObserver(_extraDataCacheManager, spaceId, saveDataId); + + rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + } + } + + if (openReadOnly) + { + outFileSystem.Reset(new ReadOnlyFileSystem(ref saveDataFs.Ref())); + } + else + { + outFileSystem.SetByMove(ref saveDataFs.Ref()); + } + + return Result.Success; + } + + public Result OpenSaveDataMetaDirectoryFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId) + { + // Hack around error CS8350. + const int bufferLength = 0x1B; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveDataMetaIdDirectoryName = new Path(); + Result rc = PathFunctions.SetUpFixedPathSaveMetaDir(ref saveDataMetaIdDirectoryName.Ref(), + saveDataMetaIdDirectoryNameBuffer, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in saveDataMetaIdDirectoryName); + } + + public Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataTotalSize(out long totalSize, int blockSize, long dataSize, long journalSize) + { + // Todo: Implement + totalSize = 0; + return Result.Success; + } + + public Result CreateSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType, + long metaFileSize) + { + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc.Miss(); + + rc = fileSystem.Get.CreateFile(in saveDataMetaName, metaFileSize); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result DeleteSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType) + { + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc.Miss(); + + rc = fileSystem.Get.DeleteFile(in saveDataMetaName); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId) + { + ReadOnlySpan metaDirName = // /saveMeta + new[] + { + (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', + (byte)'a' + }; + + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var fileSystem = new SharedRef(); + + using var saveDataMetaDirectoryName = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref saveDataMetaDirectoryName.Ref(), metaDirName); + if (rc.IsFailure()) return rc.Miss(); + + rc = OpenSaveDataDirectoryFileSystemImpl(ref fileSystem.Ref(), spaceId, in saveDataMetaDirectoryName, false); + if (rc.IsFailure()) return rc.Miss(); + + using var saveDataIdDirectoryName = new Path(); + PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName.Ref(), saveDataIdDirectoryNameBuffer, + saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Delete the save data's meta directory, ignoring the error if the directory is already gone + rc = fileSystem.Get.DeleteDirectoryRecursively(in saveDataIdDirectoryName); + + if (rc.IsFailure()) + { + if (!ResultFs.PathNotFound.Includes(rc)) + return rc.Catch().Handle(); + + return rc.Miss(); + } + + return Result.Success; + } + + public Result OpenSaveDataMeta(ref UniqueRef outMetaFile, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataMetaType metaType) + { + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataMetaDirectoryFileSystem(ref fileSystem.Ref(), spaceId, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName.Ref(), saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc.Miss(); + + rc = fileSystem.Get.OpenFile(ref outMetaFile, in saveDataMetaName, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional hashSalt, + bool skipFormat) + { + // Use directory save data for now + + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var fileSystem = new SharedRef(); + + Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), creationInfo.SpaceId, + in saveDataRootPath, false); + if (rc.IsFailure()) return rc.Miss(); + + using var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + if (_config.IsPseudoSaveData()) + { + rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName); + if (rc.IsFailure()) return rc.Miss(); + + using var saveFileSystem = new SharedRef(); + using var extraDataAccessor = new SharedRef(); + + bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(attribute.Type); + + rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref extraDataAccessor.Ref(), + _saveDataFsCacheManager, ref fileSystem.Ref(), creationInfo.SpaceId, saveDataId, + allowDirectorySaveData: true, useDeviceUniqueMac: false, isJournalingSupported, + isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter); + if (rc.IsFailure()) return rc.Miss(); + + var extraData = new SaveDataExtraData(); + extraData.Attribute = attribute; + extraData.OwnerId = creationInfo.OwnerId; + + rc = GetSaveDataCommitTimeStamp(out extraData.TimeStamp); + if (rc.IsFailure()) + extraData.TimeStamp = 0; + + extraData.CommitId = 0; + _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)).IgnoreResult(); + + extraData.Flags = creationInfo.Flags; + extraData.DataSize = creationInfo.Size; + extraData.JournalSize = creationInfo.JournalSize; + + rc = extraDataAccessor.Get.WriteExtraData(in extraData); + if (rc.IsFailure()) return rc.Miss(); + + rc = extraDataAccessor.Get.CommitExtraData(true); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + throw new NotImplementedException(); + } + + return Result.Success; + } + + private Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random) + { + throw new NotImplementedException(); + } + + public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile, + in Path saveDataRootPath) + { + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var fileSystem = new SharedRef(); + + _saveDataFsCacheManager.Unregister(spaceId, saveDataId); + + // Open the directory containing the save data + Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, false); + if (rc.IsFailure()) return rc.Miss(); + + using var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc.Miss(); + + // Check if the save data is a file or a directory + rc = fileSystem.Get.GetEntryType(out DirectoryEntryType entryType, in saveImageName); + if (rc.IsFailure()) return rc.Miss(); + + // Delete the save data, wiping the file if needed + if (entryType == DirectoryEntryType.Directory) + { + rc = fileSystem.Get.DeleteDirectoryRecursively(in saveImageName); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + if (wipeSaveFile) + { + WipeData(fileSystem.Get, in saveImageName, _config.GenerateRandomData).IgnoreResult(); } + rc = fileSystem.Get.DeleteFile(in saveImageName); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; + } + + public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, SaveDataType type, in Path saveDataRootPath) + { + UnsafeHelpers.SkipParamInit(out extraData); + + // Nintendo returns blank extra data for directory save data. + // We've extended directory save data to store extra data so we don't need to do that. + + using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); + + using var extraDataAccessor = new SharedRef(); + + // Try to grab an extra data accessor for the requested save from the cache. + Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + + if (rc.IsSuccess()) + { + // An extra data accessor was found in the cache. Read the extra data from it. + return extraDataAccessor.Get.ReadExtraData(out extraData); + } + + using var unusedSaveDataFs = new SharedRef(); + + // We won't actually use the returned save data FS. + // Opening the FS should cache an extra data accessor for it. + rc = OpenSaveDataFileSystem(ref unusedSaveDataFs.Ref(), spaceId, saveDataId, saveDataRootPath, + openReadOnly: true, type, cacheExtraData: true); + if (rc.IsFailure()) return rc.Miss(); + + // Try to grab an accessor from the cache again. + rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + + if (rc.IsFailure()) + { + // No extra data accessor was registered for the requested save data. + // Return a blank extra data struct. + extraData = new SaveDataExtraData(); + return rc; + } + + rc = extraDataAccessor.Get.ReadExtraData(out extraData); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId, + in SaveDataExtraData extraData, in Path saveDataRootPath, SaveDataType type, bool updateTimeStamp) + { + // Nintendo does nothing when writing directory save data extra data. + // We've extended directory save data to store extra data so we don't return early. + + using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); + using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); + + using var extraDataAccessor = new SharedRef(); + + // Try to grab an extra data accessor for the requested save from the cache. + Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); + + if (rc.IsFailure()) + { + // No accessor was found in the cache. Try to open one. using var unusedSaveDataFs = new SharedRef(); // We won't actually use the returned save data FS. // Opening the FS should cache an extra data accessor for it. rc = OpenSaveDataFileSystem(ref unusedSaveDataFs.Ref(), spaceId, saveDataId, saveDataRootPath, - openReadOnly: true, type, cacheExtraData: true); + openReadOnly: false, type, cacheExtraData: true); if (rc.IsFailure()) return rc.Miss(); // Try to grab an accessor from the cache again. @@ -491,175 +533,133 @@ namespace LibHac.FsSrv if (rc.IsFailure()) { - // No extra data accessor was registered for the requested save data. - // Return a blank extra data struct. - extraData = new SaveDataExtraData(); - return rc; + // No extra data accessor was registered for the requested save data, so don't do anything. + return Result.Success; } - - rc = extraDataAccessor.Get.ReadExtraData(out extraData); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; } - public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId, - in SaveDataExtraData extraData, in Path saveDataRootPath, SaveDataType type, bool updateTimeStamp) + // We should have a valid accessor if we've reached this point. + // Write and commit the extra data. + rc = extraDataAccessor.Get.WriteExtraData(in extraData); + if (rc.IsFailure()) return rc.Miss(); + + rc = extraDataAccessor.Get.CommitExtraData(updateTimeStamp); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset, + in Path saveDataRootPath) + { + throw new NotImplementedException(); + } + + private Result GetSaveDataCommitTimeStamp(out long timeStamp) + { + return _config.TimeService.GetCurrentPosixTime(out timeStamp); + } + + private bool IsSaveEmulated(in Path saveDataRootPath) + { + return !saveDataRootPath.IsEmpty(); + } + + public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId) + { + using var rootPath = new Path(); + + return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, in rootPath, true); + } + + public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in Path saveDataRootPath, bool allowEmulatedSave) + { + Result rc; + + if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, in saveDataRootPath)) { - // Nintendo does nothing when writing directory save data extra data. - // We've extended directory save data to store extra data so we don't return early. - - using UniqueLockRef scopedLockFsCache = _saveDataFsCacheManager.GetScopedLock(); - using UniqueLockRef scopedLockExtraDataCache = _extraDataCacheManager.GetScopedLock(); - - using var extraDataAccessor = new SharedRef(); - - // Try to grab an extra data accessor for the requested save from the cache. - Result rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); - - if (rc.IsFailure()) + using (var tmFileSystem = new SharedRef()) { - // No accessor was found in the cache. Try to open one. - using var unusedSaveDataFs = new SharedRef(); - - // We won't actually use the returned save data FS. - // Opening the FS should cache an extra data accessor for it. - rc = OpenSaveDataFileSystem(ref unusedSaveDataFs.Ref(), spaceId, saveDataId, saveDataRootPath, - openReadOnly: false, type, cacheExtraData: true); - if (rc.IsFailure()) return rc.Miss(); - - // Try to grab an accessor from the cache again. - rc = _extraDataCacheManager.GetCache(ref extraDataAccessor.Ref(), spaceId, saveDataId); - - if (rc.IsFailure()) - { - // No extra data accessor was registered for the requested save data, so don't do anything. - return Result.Success; - } - } - - // We should have a valid accessor if we've reached this point. - // Write and commit the extra data. - rc = extraDataAccessor.Get.WriteExtraData(in extraData); - if (rc.IsFailure()) return rc.Miss(); - - rc = extraDataAccessor.Get.CommitExtraData(updateTimeStamp); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset, - in Path saveDataRootPath) - { - throw new NotImplementedException(); - } - - private Result GetSaveDataCommitTimeStamp(out long timeStamp) - { - return _config.TimeService.GetCurrentPosixTime(out timeStamp); - } - - private bool IsSaveEmulated(in Path saveDataRootPath) - { - return !saveDataRootPath.IsEmpty(); - } - - public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId) - { - using var rootPath = new Path(); - - return OpenSaveDataDirectoryFileSystem(ref outFileSystem, spaceId, in rootPath, true); - } - - public Result OpenSaveDataDirectoryFileSystem(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in Path saveDataRootPath, bool allowEmulatedSave) - { - Result rc; - - if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, in saveDataRootPath)) - { - using (var tmFileSystem = new SharedRef()) - { - // Ensure the target save data directory exists - rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath, false, true, - ResultFs.SaveDataRootPathUnavailable.Value); - if (rc.IsFailure()) return rc.Miss(); - } - - using var path = new Path(); - rc = path.Initialize(in saveDataRootPath); - if (rc.IsFailure()) return rc.Miss(); - - rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive, false, + // Ensure the target save data directory exists + rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath, false, true, ResultFs.SaveDataRootPathUnavailable.Value); if (rc.IsFailure()) return rc.Miss(); + } + + using var path = new Path(); + rc = path.Initialize(in saveDataRootPath); + if (rc.IsFailure()) return rc.Miss(); + + rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive, false, + ResultFs.SaveDataRootPathUnavailable.Value); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + using var saveDataDirPath = new Path(); + ReadOnlySpan saveDirName; + + if (spaceId == SaveDataSpaceId.Temporary) + { + saveDirName = new[] { (byte)'/', (byte)'t', (byte)'e', (byte)'m', (byte)'p' }; // /temp + } + else + { + saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save + } + + rc = PathFunctions.SetUpFixedPath(ref saveDataDirPath.Ref(), saveDirName); + if (rc.IsFailure()) return rc.Miss(); + + rc = OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in saveDataDirPath, true); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in Path basePath) + { + return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in basePath, true); + } + + public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, + SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing) + { + using var baseFileSystem = new SharedRef(); + + Result rc; + + switch (spaceId) + { + case SaveDataSpaceId.System: + rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.System, true); + if (rc.IsFailure()) return rc.Miss(); + + rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, + createIfMissing); + if (rc.IsFailure()) return rc.Miss(); return Result.Success; - } - using var saveDataDirPath = new Path(); - ReadOnlySpan saveDirName; + case SaveDataSpaceId.User: + case SaveDataSpaceId.Temporary: + rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.User, true); + if (rc.IsFailure()) return rc.Miss(); - if (spaceId == SaveDataSpaceId.Temporary) - { - saveDirName = new[] { (byte)'/', (byte)'t', (byte)'e', (byte)'m', (byte)'p' }; // /temp - } - else - { - saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save - } + rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); + if (rc.IsFailure()) return rc.Miss(); - rc = PathFunctions.SetUpFixedPath(ref saveDataDirPath.Ref(), saveDirName); - if (rc.IsFailure()) return rc.Miss(); + return Result.Success; - rc = OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in saveDataDirPath, true); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - } - - public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in Path basePath) - { - return OpenSaveDataDirectoryFileSystemImpl(ref outFileSystem, spaceId, in basePath, true); - } - - public Result OpenSaveDataDirectoryFileSystemImpl(ref SharedRef outFileSystem, - SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing) - { - using var baseFileSystem = new SharedRef(); - - Result rc; - - switch (spaceId) - { - case SaveDataSpaceId.System: - rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.System, true); - if (rc.IsFailure()) return rc.Miss(); - - rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, - createIfMissing); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - - case SaveDataSpaceId.User: - case SaveDataSpaceId.Temporary: - rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.User, true); - if (rc.IsFailure()) return rc.Miss(); - - rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); - if (rc.IsFailure()) return rc.Miss(); - - return Result.Success; - - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.SdCache: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: { rc = _config.BaseFsService.OpenSdCardProxyFileSystem(ref baseFileSystem.Ref(), true); if (rc.IsFailure()) return rc.Miss(); @@ -691,131 +691,130 @@ namespace LibHac.FsSrv return Result.Success; } - case SaveDataSpaceId.ProperSystem: - rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), - BisPartitionId.SystemProperPartition, true); - if (rc.IsFailure()) return rc.Miss(); + case SaveDataSpaceId.ProperSystem: + rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), + BisPartitionId.SystemProperPartition, true); + if (rc.IsFailure()) return rc.Miss(); - rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); - if (rc.IsFailure()) return rc.Miss(); + rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; + return Result.Success; - case SaveDataSpaceId.SafeMode: - rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.SafeMode, true); - if (rc.IsFailure()) return rc.Miss(); + case SaveDataSpaceId.SafeMode: + rc = _config.BaseFsService.OpenBisFileSystem(ref baseFileSystem.Ref(), BisPartitionId.SafeMode, true); + if (rc.IsFailure()) return rc.Miss(); - rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); - if (rc.IsFailure()) return rc.Miss(); + rc = Utility.WrapSubDirectory(ref outFileSystem, ref baseFileSystem.Ref(), in basePath, createIfMissing); + if (rc.IsFailure()) return rc.Miss(); - return Result.Success; + return Result.Success; - default: - return ResultFs.InvalidArgument.Log(); - } - } - - public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) - { - _encryptionSeed = seed; - - _config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value); - _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem); - _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdCache); - - return Result.Success; - } - - public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) - { - throw new NotImplementedException(); - } - - public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath) - { - return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath); - } - - // Todo: remove once file save data is supported - // Used to always allow directory save data in OpenSaveDataFileSystem - public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, in Path saveDataRootPath) - { - // Todo: remove "|| true" once file save data is supported - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath) || true; - } - - public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) - { - return spaceId == SaveDataSpaceId.System || - spaceId == SaveDataSpaceId.User || - spaceId == SaveDataSpaceId.Temporary || - spaceId == SaveDataSpaceId.ProperSystem || - spaceId == SaveDataSpaceId.SafeMode; - } - - public void SetSdCardAccessibility(bool isAccessible) - { - _isSdCardAccessible = isAccessible; - } - - public bool IsSdCardAccessible() - { - return _isSdCardAccessible; - } - - /// - /// Gets the program ID of the save data associated with the specified programID. - /// - /// In a standard application the program ID will be the same as the input program ID. - /// In multi-program applications all sub-programs use the program ID of the main program - /// for their save data. The main program always has a program index of 0. - /// The program ID to get the save data program ID for. - /// The program ID of the save data. - public ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) - { - // First check if there's an entry in the program index map with the program ID and program index 0 - ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0); - - if (mainProgramId != ProgramId.InvalidId) - { - return mainProgramId; - } - - // Check if there's an entry with the program ID, ignoring program index - Optional mapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId); - - if (mapInfo.HasValue) - { - return mapInfo.Value.MainProgramId; - } - - // The program ID isn't in the program index map. Probably running a single-program application - return programId; - } - - public Result GetSaveDataIndexCount(out int count) - { - UnsafeHelpers.SkipParamInit(out count); - - using var accessor = new UniqueRef(); - - Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool _, SaveDataSpaceId.User); - if (rc.IsFailure()) return rc.Miss(); - - count = accessor.Get.Indexer.GetIndexCount(); - return Result.Success; - } - - public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, - SaveDataSpaceId spaceId) - { - return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(ref outAccessor, out neededInit, spaceId); - } - - public void ResetTemporaryStorageIndexer() - { - _config.SaveIndexerManager.ResetIndexer(SaveDataSpaceId.Temporary); + default: + return ResultFs.InvalidArgument.Log(); } } -} \ No newline at end of file + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + _encryptionSeed = seed; + + _config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdCache); + + return Result.Success; + } + + public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) + { + throw new NotImplementedException(); + } + + public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath) + { + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath); + } + + // Todo: remove once file save data is supported + // Used to always allow directory save data in OpenSaveDataFileSystem + public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, in Path saveDataRootPath) + { + // Todo: remove "|| true" once file save data is supported + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath) || true; + } + + public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.System || + spaceId == SaveDataSpaceId.User || + spaceId == SaveDataSpaceId.Temporary || + spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.SafeMode; + } + + public void SetSdCardAccessibility(bool isAccessible) + { + _isSdCardAccessible = isAccessible; + } + + public bool IsSdCardAccessible() + { + return _isSdCardAccessible; + } + + /// + /// Gets the program ID of the save data associated with the specified programID. + /// + /// In a standard application the program ID will be the same as the input program ID. + /// In multi-program applications all sub-programs use the program ID of the main program + /// for their save data. The main program always has a program index of 0. + /// The program ID to get the save data program ID for. + /// The program ID of the save data. + public ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) + { + // First check if there's an entry in the program index map with the program ID and program index 0 + ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0); + + if (mainProgramId != ProgramId.InvalidId) + { + return mainProgramId; + } + + // Check if there's an entry with the program ID, ignoring program index + Optional mapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId); + + if (mapInfo.HasValue) + { + return mapInfo.Value.MainProgramId; + } + + // The program ID isn't in the program index map. Probably running a single-program application + return programId; + } + + public Result GetSaveDataIndexCount(out int count) + { + UnsafeHelpers.SkipParamInit(out count); + + using var accessor = new UniqueRef(); + + Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool _, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc.Miss(); + + count = accessor.Get.Indexer.GetIndexCount(); + return Result.Success; + } + + public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, out bool neededInit, + SaveDataSpaceId spaceId) + { + return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(ref outAccessor, out neededInit, spaceId); + } + + public void ResetTemporaryStorageIndexer() + { + _config.SaveIndexerManager.ResetIndexer(SaveDataSpaceId.Temporary); + } +} diff --git a/src/LibHac/FsSrv/SaveDataIndexer.cs b/src/LibHac/FsSrv/SaveDataIndexer.cs index fc93df61..a8dc3913 100644 --- a/src/LibHac/FsSrv/SaveDataIndexer.cs +++ b/src/LibHac/FsSrv/SaveDataIndexer.cs @@ -12,450 +12,321 @@ using LibHac.Fs.Shim; using LibHac.Kvdb; using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Indexes metadata for persistent save data stored on disk, holding key-value pairs of types +/// and respectively. +/// +/// +/// Each manages one to two save data spaces. +/// Each save data space is identified by a , +/// and has its own unique storage location on disk. +///
Based on FS 10.0.0 (nnSdk 10.4.0) +///
+public class SaveDataIndexer : ISaveDataIndexer { - /// - /// Indexes metadata for persistent save data stored on disk, holding key-value pairs of types - /// and respectively. - /// - /// - /// Each manages one to two save data spaces. - /// Each save data space is identified by a , - /// and has its own unique storage location on disk. - ///
Based on FS 10.0.0 (nnSdk 10.4.0) - ///
- public class SaveDataIndexer : ISaveDataIndexer + private const int KvDatabaseCapacity = 0x1080; + private const int KvDatabaseReservedEntryCount = 0x80; + + private const int SaveDataAvailableSize = 0xC0000; + private const int SaveDataJournalSize = 0xC0000; + + private const long LastPublishedIdFileSize = sizeof(long); + private const int MaxPathLength = 0x30; + + private static ReadOnlySpan LastPublishedIdFileName => // lastPublishedId + new[] + { + (byte) 'l', (byte) 'a', (byte) 's', (byte) 't', (byte) 'P', (byte) 'u', (byte) 'b', (byte) 'l', + (byte) 'i', (byte) 's', (byte) 'h', (byte) 'e', (byte) 'd', (byte) 'I', (byte) 'd' + }; + + private static ReadOnlySpan MountDelimiter => // :/ + new[] { (byte)':', (byte)'/' }; + + private delegate void SaveDataValueTransform(ref SaveDataIndexerValue value, ReadOnlySpan updateData); + + private FileSystemClient FsClient { get; } + private U8String MountName { get; } + private ulong SaveDataId { get; } + private SaveDataSpaceId SpaceId { get; } + private MemoryResource MemoryResource { get; } + private MemoryResource BufferMemoryResource { get; } + + private FlatMapKeyValueStore KvDatabase { get; } + + private object Locker { get; } = new object(); + private bool IsInitialized { get; set; } + private bool IsKvdbLoaded { get; set; } + private ulong _lastPublishedId; + private int Handle { get; set; } + + private List OpenReaders { get; } = new List(); + + public SaveDataIndexer(FileSystemClient fsClient, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, MemoryResource memoryResource) { - private const int KvDatabaseCapacity = 0x1080; - private const int KvDatabaseReservedEntryCount = 0x80; + FsClient = fsClient; + SaveDataId = saveDataId; + SpaceId = spaceId; + MemoryResource = memoryResource; - private const int SaveDataAvailableSize = 0xC0000; - private const int SaveDataJournalSize = 0xC0000; + // note: FS uses a separate PooledBufferMemoryResource here + BufferMemoryResource = memoryResource; - private const long LastPublishedIdFileSize = sizeof(long); - private const int MaxPathLength = 0x30; + KvDatabase = new FlatMapKeyValueStore(); - private static ReadOnlySpan LastPublishedIdFileName => // lastPublishedId - new[] - { - (byte) 'l', (byte) 'a', (byte) 's', (byte) 't', (byte) 'P', (byte) 'u', (byte) 'b', (byte) 'l', - (byte) 'i', (byte) 's', (byte) 'h', (byte) 'e', (byte) 'd', (byte) 'I', (byte) 'd' - }; + IsInitialized = false; + IsKvdbLoaded = false; + Handle = 1; - private static ReadOnlySpan MountDelimiter => // :/ - new[] { (byte)':', (byte)'/' }; + MountName = mountName.ToU8String(); + } - private delegate void SaveDataValueTransform(ref SaveDataIndexerValue value, ReadOnlySpan updateData); + private static void MakeLastPublishedIdSaveFilePath(Span buffer, ReadOnlySpan mountName) + { + // returns "%s:/%s", mountName, "lastPublishedId" + var sb = new U8StringBuilder(buffer); + sb.Append(mountName); + sb.Append(MountDelimiter); + sb.Append(LastPublishedIdFileName); - private FileSystemClient FsClient { get; } - private U8String MountName { get; } - private ulong SaveDataId { get; } - private SaveDataSpaceId SpaceId { get; } - private MemoryResource MemoryResource { get; } - private MemoryResource BufferMemoryResource { get; } + Debug.Assert(!sb.Overflowed); + } - private FlatMapKeyValueStore KvDatabase { get; } + private static void MakeRootPath(Span buffer, ReadOnlySpan mountName) + { + // returns "%s:/", mountName + var sb = new U8StringBuilder(buffer); + sb.Append(mountName); + sb.Append(MountDelimiter); - private object Locker { get; } = new object(); - private bool IsInitialized { get; set; } - private bool IsKvdbLoaded { get; set; } - private ulong _lastPublishedId; - private int Handle { get; set; } + Debug.Assert(!sb.Overflowed); + } - private List OpenReaders { get; } = new List(); - - public SaveDataIndexer(FileSystemClient fsClient, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, MemoryResource memoryResource) + /// + /// Generates a from the provided and . + /// + /// When this method returns, contains the generated . + /// The key used to generate the . + /// The value used to generate the . + public static void GenerateSaveDataInfo(out SaveDataInfo info, in SaveDataAttribute key, in SaveDataIndexerValue value) + { + info = new SaveDataInfo { - FsClient = fsClient; - SaveDataId = saveDataId; - SpaceId = spaceId; - MemoryResource = memoryResource; + SaveDataId = value.SaveDataId, + SpaceId = value.SpaceId, + Type = key.Type, + UserId = key.UserId, + StaticSaveDataId = key.StaticSaveDataId, + ProgramId = key.ProgramId, + Size = value.Size, + Index = key.Index, + Rank = key.Rank, + State = value.State + }; + } - // note: FS uses a separate PooledBufferMemoryResource here - BufferMemoryResource = memoryResource; - - KvDatabase = new FlatMapKeyValueStore(); - - IsInitialized = false; - IsKvdbLoaded = false; - Handle = 1; - - MountName = mountName.ToU8String(); - } - - private static void MakeLastPublishedIdSaveFilePath(Span buffer, ReadOnlySpan mountName) + public Result Commit() + { + lock (Locker) { - // returns "%s:/%s", mountName, "lastPublishedId" - var sb = new U8StringBuilder(buffer); - sb.Append(mountName); - sb.Append(MountDelimiter); - sb.Append(LastPublishedIdFileName); - - Debug.Assert(!sb.Overflowed); - } - - private static void MakeRootPath(Span buffer, ReadOnlySpan mountName) - { - // returns "%s:/", mountName - var sb = new U8StringBuilder(buffer); - sb.Append(mountName); - sb.Append(MountDelimiter); - - Debug.Assert(!sb.Overflowed); - } - - /// - /// Generates a from the provided and . - /// - /// When this method returns, contains the generated . - /// The key used to generate the . - /// The value used to generate the . - public static void GenerateSaveDataInfo(out SaveDataInfo info, in SaveDataAttribute key, in SaveDataIndexerValue value) - { - info = new SaveDataInfo - { - SaveDataId = value.SaveDataId, - SpaceId = value.SpaceId, - Type = key.Type, - UserId = key.UserId, - StaticSaveDataId = key.StaticSaveDataId, - ProgramId = key.ProgramId, - Size = value.Size, - Index = key.Index, - Rank = key.Rank, - State = value.State - }; - } - - public Result Commit() - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - var mount = new Mounter(); - - try - { - rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Save(); - if (rc.IsFailure()) return rc; - - Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; - MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); - - rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Write); - if (rc.IsFailure()) return rc; - - bool isFileClosed = false; - - try - { - rc = FsClient.WriteFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId), WriteOption.None); - if (rc.IsFailure()) return rc; - - rc = FsClient.FlushFile(handle); - if (rc.IsFailure()) return rc; - - FsClient.CloseFile(handle); - isFileClosed = true; - - return FsClient.Commit(MountName); - } - finally - { - if (!isFileClosed) - { - FsClient.CloseFile(handle); - } - } - } - finally - { - mount.Dispose(); - } - } - } - - public Result Rollback() - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(forceLoad: true); - if (rc.IsFailure()) return rc; - - UpdateHandle(); - return Result.Success; - } - } - - public Result Reset() - { - lock (Locker) - { - IsKvdbLoaded = false; - - Result rc = FsClient.DeleteSaveData(SaveDataId); - - if (rc.IsSuccess() || ResultFs.TargetNotFound.Includes(rc)) - { - UpdateHandle(); - } - - return rc; - } - } - - public Result Publish(out ulong saveDataId, in SaveDataAttribute key) - { - UnsafeHelpers.SkipParamInit(out saveDataId); - - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - Unsafe.SkipInit(out SaveDataIndexerValue value); - - // Make sure the key isn't in the database already. - rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); - - if (rc.IsSuccess()) - { - return ResultFs.AlreadyExists.Log(); - } - - _lastPublishedId++; - ulong newSaveDataId = _lastPublishedId; - - value = new SaveDataIndexerValue { SaveDataId = newSaveDataId }; - - rc = KvDatabase.Set(in key, SpanHelpers.AsByteSpan(ref value)); - - if (rc.IsFailure()) - { - _lastPublishedId--; - return rc; - } - - rc = FixReader(in key); - if (rc.IsFailure()) return rc; - - saveDataId = newSaveDataId; - return Result.Success; - } - } - - public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) - { - UnsafeHelpers.SkipParamInit(out value); - - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); - - if (rc.IsFailure()) - { - return ResultFs.TargetNotFound.LogConverted(rc); - } - - return Result.Success; - } - } - - public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - // Iterate through all existing values to check if the save ID is already in use. - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - while (!iterator.IsEnd()) - { - if (iterator.GetValue().SaveDataId == key.StaticSaveDataId) - { - return ResultFs.AlreadyExists.Log(); - } - - iterator.Next(); - } - - var newValue = new SaveDataIndexerValue - { - SaveDataId = key.StaticSaveDataId - }; - - rc = KvDatabase.Set(in key, SpanHelpers.AsReadOnlyByteSpan(in newValue)); - if (rc.IsFailure()) return rc; - - rc = FixReader(in key); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - public bool IsRemainedReservedOnly() - { - return KvDatabase.Count >= KvDatabaseCapacity - KvDatabaseReservedEntryCount; - } - - public Result Delete(ulong saveDataId) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - - while (true) - { - if (iterator.IsEnd()) - return ResultFs.TargetNotFound.Log(); - - if (iterator.GetValue().SaveDataId == saveDataId) - break; - - iterator.Next(); - } - - SaveDataAttribute key = iterator.Get().Key; - - rc = KvDatabase.Delete(in key); - if (rc.IsFailure()) return rc; - - rc = FixReader(in key); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - private Result UpdateValueBySaveDataId(ulong saveDataId, SaveDataValueTransform func, ReadOnlySpan data) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - SaveDataIndexerValue value; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - - while (true) - { - if (iterator.IsEnd()) - return ResultFs.TargetNotFound.Log(); - - ref SaveDataIndexerValue val = ref iterator.GetValue(); - - if (val.SaveDataId == saveDataId) - { - value = val; - break; - } - - iterator.Next(); - } - - func(ref value, data); - - rc = KvDatabase.Set(in iterator.Get().Key, SpanHelpers.AsReadOnlyByteSpan(in value)); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) - { - return UpdateValueBySaveDataId(saveDataId, SetSpaceIdImpl, SpanHelpers.AsReadOnlyByteSpan(in spaceId)); - - static void SetSpaceIdImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.SpaceId = (SaveDataSpaceId)updateData[0]; - } - } - - public Result SetSize(ulong saveDataId, long size) - { - return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in size)); - - static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.Size = BinaryPrimitives.ReadInt64LittleEndian(updateData); - } - } - - public Result SetState(ulong saveDataId, SaveDataState state) - { - return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in state)); - - static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.State = (SaveDataState)updateData[0]; - } - } - - public Result GetKey(out SaveDataAttribute key, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out key); - Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; rc = TryLoadDatabase(false); if (rc.IsFailure()) return rc; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + var mount = new Mounter(); + try + { + rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); + if (rc.IsFailure()) return rc; + + rc = KvDatabase.Save(); + if (rc.IsFailure()) return rc; + + Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; + MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); + + rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Write); + if (rc.IsFailure()) return rc; + + bool isFileClosed = false; + + try + { + rc = FsClient.WriteFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId), WriteOption.None); + if (rc.IsFailure()) return rc; + + rc = FsClient.FlushFile(handle); + if (rc.IsFailure()) return rc; + + FsClient.CloseFile(handle); + isFileClosed = true; + + return FsClient.Commit(MountName); + } + finally + { + if (!isFileClosed) + { + FsClient.CloseFile(handle); + } + } + } + finally + { + mount.Dispose(); + } + } + } + + public Result Rollback() + { + lock (Locker) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: true); + if (rc.IsFailure()) return rc; + + UpdateHandle(); + return Result.Success; + } + } + + public Result Reset() + { + lock (Locker) + { + IsKvdbLoaded = false; + + Result rc = FsClient.DeleteSaveData(SaveDataId); + + if (rc.IsSuccess() || ResultFs.TargetNotFound.Includes(rc)) + { + UpdateHandle(); + } + + return rc; + } + } + + public Result Publish(out ulong saveDataId, in SaveDataAttribute key) + { + UnsafeHelpers.SkipParamInit(out saveDataId); + + lock (Locker) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + Unsafe.SkipInit(out SaveDataIndexerValue value); + + // Make sure the key isn't in the database already. + rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsSuccess()) + { + return ResultFs.AlreadyExists.Log(); + } + + _lastPublishedId++; + ulong newSaveDataId = _lastPublishedId; + + value = new SaveDataIndexerValue { SaveDataId = newSaveDataId }; + + rc = KvDatabase.Set(in key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsFailure()) + { + _lastPublishedId--; + return rc; + } + + rc = FixReader(in key); + if (rc.IsFailure()) return rc; + + saveDataId = newSaveDataId; + return Result.Success; + } + } + + public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) + { + UnsafeHelpers.SkipParamInit(out value); + + lock (Locker) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsFailure()) + { + return ResultFs.TargetNotFound.LogConverted(rc); + } + + return Result.Success; + } + } + + public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) + { + lock (Locker) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + // Iterate through all existing values to check if the save ID is already in use. + FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); while (!iterator.IsEnd()) { - if (iterator.GetValue().SaveDataId == saveDataId) + if (iterator.GetValue().SaveDataId == key.StaticSaveDataId) { - key = iterator.Get().Key; - return Result.Success; + return ResultFs.AlreadyExists.Log(); } iterator.Next(); } - return ResultFs.TargetNotFound.Log(); + var newValue = new SaveDataIndexerValue + { + SaveDataId = key.StaticSaveDataId + }; + + rc = KvDatabase.Set(in key, SpanHelpers.AsReadOnlyByteSpan(in newValue)); + if (rc.IsFailure()) return rc; + + rc = FixReader(in key); + if (rc.IsFailure()) return rc; + + return Result.Success; } + } - public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId) + public bool IsRemainedReservedOnly() + { + return KvDatabase.Count >= KvDatabaseCapacity - KvDatabaseReservedEntryCount; + } + + public Result Delete(ulong saveDataId) + { + lock (Locker) { - UnsafeHelpers.SkipParamInit(out value); - Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; @@ -464,23 +335,180 @@ namespace LibHac.FsSrv FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - while (!iterator.IsEnd()) + while (true) { + if (iterator.IsEnd()) + return ResultFs.TargetNotFound.Log(); + + if (iterator.GetValue().SaveDataId == saveDataId) + break; + + iterator.Next(); + } + + SaveDataAttribute key = iterator.Get().Key; + + rc = KvDatabase.Delete(in key); + if (rc.IsFailure()) return rc; + + rc = FixReader(in key); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + } + + private Result UpdateValueBySaveDataId(ulong saveDataId, SaveDataValueTransform func, ReadOnlySpan data) + { + lock (Locker) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + SaveDataIndexerValue value; + FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + + while (true) + { + if (iterator.IsEnd()) + return ResultFs.TargetNotFound.Log(); + ref SaveDataIndexerValue val = ref iterator.GetValue(); if (val.SaveDataId == saveDataId) { value = val; - return Result.Success; + break; } iterator.Next(); } - return ResultFs.TargetNotFound.Log(); + func(ref value, data); + + rc = KvDatabase.Set(in iterator.Get().Key, SpanHelpers.AsReadOnlyByteSpan(in value)); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + } + + public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) + { + return UpdateValueBySaveDataId(saveDataId, SetSpaceIdImpl, SpanHelpers.AsReadOnlyByteSpan(in spaceId)); + + static void SetSpaceIdImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) + { + value.SpaceId = (SaveDataSpaceId)updateData[0]; + } + } + + public Result SetSize(ulong saveDataId, long size) + { + return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in size)); + + static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) + { + value.Size = BinaryPrimitives.ReadInt64LittleEndian(updateData); + } + } + + public Result SetState(ulong saveDataId, SaveDataState state) + { + return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in state)); + + static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) + { + value.State = (SaveDataState)updateData[0]; + } + } + + public Result GetKey(out SaveDataAttribute key, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out key); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + + while (!iterator.IsEnd()) + { + if (iterator.GetValue().SaveDataId == saveDataId) + { + key = iterator.Get().Key; + return Result.Success; + } + + iterator.Next(); } - public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) + return ResultFs.TargetNotFound.Log(); + } + + public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out value); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + + while (!iterator.IsEnd()) + { + ref SaveDataIndexerValue val = ref iterator.GetValue(); + + if (val.SaveDataId == saveDataId) + { + value = val; + return Result.Success; + } + + iterator.Next(); + } + + return ResultFs.TargetNotFound.Log(); + } + + public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) + { + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(false); + if (rc.IsFailure()) return rc; + + FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetLowerBoundIterator(in key); + + // Key was not found + if (iterator.IsEnd()) + return ResultFs.TargetNotFound.Log(); + + iterator.GetValue() = value; + return Result.Success; + } + + public int GetIndexCount() + { + lock (Locker) + { + return KvDatabase.Count; + } + } + + public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) + { + lock (Locker) { Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; @@ -488,372 +516,343 @@ namespace LibHac.FsSrv rc = TryLoadDatabase(false); if (rc.IsFailure()) return rc; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetLowerBoundIterator(in key); + // Create the reader and register it in the opened-reader list + using var reader = new SharedRef(new Reader(this)); + rc = RegisterReader(ref reader.Ref()); + if (rc.IsFailure()) return rc; - // Key was not found - if (iterator.IsEnd()) - return ResultFs.TargetNotFound.Log(); + outInfoReader.SetByCopy(in reader.Ref()); + return Result.Success; + } + } - iterator.GetValue() = value; + private FlatMapKeyValueStore.Iterator GetBeginIterator() + { + Assert.SdkRequires(IsKvdbLoaded); + + return KvDatabase.GetBeginIterator(); + } + + private void FixIterator(ref FlatMapKeyValueStore.Iterator iterator, + in SaveDataAttribute key) + { + KvDatabase.FixIterator(ref iterator, in key); + } + + /// + /// Initializes and ensures that the indexer's save data is created. + /// Does nothing if this has already been initialized. + /// + /// The of the operation. + private Result TryInitializeDatabase() + { + if (IsInitialized) return Result.Success; + + var mount = new Mounter(); + + try + { + Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); + if (rc.IsFailure()) return rc; + + Span rootPath = stackalloc byte[MaxPathLength]; + MakeRootPath(rootPath, MountName); + + rc = KvDatabase.Initialize(FsClient, new U8Span(rootPath), KvDatabaseCapacity, MemoryResource, BufferMemoryResource); + if (rc.IsFailure()) return rc; + + IsInitialized = true; + return Result.Success; + } + finally + { + mount.Dispose(); + } + } + + /// + /// Ensures that the database file exists and loads any existing entries. + /// Does nothing if the database has already been loaded and is . + /// + /// If , forces the database to be reloaded, + /// even it it was already loaded previously. + /// The of the operation. + private Result TryLoadDatabase(bool forceLoad) + { + if (forceLoad) + { + IsKvdbLoaded = false; + } + else if (IsKvdbLoaded) + { return Result.Success; } - public int GetIndexCount() + var mount = new Mounter(); + + try { - lock (Locker) - { - return KvDatabase.Count; - } - } + Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); + if (rc.IsFailure()) return rc; - public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; + rc = KvDatabase.Load(); + if (rc.IsFailure()) return rc; - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; + bool createdNewFile = false; - // Create the reader and register it in the opened-reader list - using var reader = new SharedRef(new Reader(this)); - rc = RegisterReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - outInfoReader.SetByCopy(in reader.Ref()); - return Result.Success; - } - } - - private FlatMapKeyValueStore.Iterator GetBeginIterator() - { - Assert.SdkRequires(IsKvdbLoaded); - - return KvDatabase.GetBeginIterator(); - } - - private void FixIterator(ref FlatMapKeyValueStore.Iterator iterator, - in SaveDataAttribute key) - { - KvDatabase.FixIterator(ref iterator, in key); - } - - /// - /// Initializes and ensures that the indexer's save data is created. - /// Does nothing if this has already been initialized. - /// - /// The of the operation. - private Result TryInitializeDatabase() - { - if (IsInitialized) return Result.Success; - - var mount = new Mounter(); + Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; + MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); try { - Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; + rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Read); - Span rootPath = stackalloc byte[MaxPathLength]; - MakeRootPath(rootPath, MountName); + // Create the last published ID file if it doesn't exist. + if (rc.IsFailure()) + { + if (!ResultFs.PathNotFound.Includes(rc)) return rc; - rc = KvDatabase.Initialize(FsClient, new U8Span(rootPath), KvDatabaseCapacity, MemoryResource, BufferMemoryResource); - if (rc.IsFailure()) return rc; + rc = FsClient.CreateFile(new U8Span(lastPublishedIdPath), LastPublishedIdFileSize); + if (rc.IsFailure()) return rc; - IsInitialized = true; - return Result.Success; - } - finally - { - mount.Dispose(); - } - } + rc = FsClient.OpenFile(out handle, new U8Span(lastPublishedIdPath), OpenMode.Read); + if (rc.IsFailure()) return rc; - /// - /// Ensures that the database file exists and loads any existing entries. - /// Does nothing if the database has already been loaded and is . - /// - /// If , forces the database to be reloaded, - /// even it it was already loaded previously. - /// The of the operation. - private Result TryLoadDatabase(bool forceLoad) - { - if (forceLoad) - { - IsKvdbLoaded = false; - } - else if (IsKvdbLoaded) - { - return Result.Success; - } + createdNewFile = true; - var mount = new Mounter(); - - try - { - Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Load(); - if (rc.IsFailure()) return rc; - - bool createdNewFile = false; - - Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; - MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); + _lastPublishedId = 0; + IsKvdbLoaded = true; + } try { - rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Read); - - // Create the last published ID file if it doesn't exist. - if (rc.IsFailure()) + // If we had to create the file earlier, we don't need to load the value again. + if (!createdNewFile) { - if (!ResultFs.PathNotFound.Includes(rc)) return rc; - - rc = FsClient.CreateFile(new U8Span(lastPublishedIdPath), LastPublishedIdFileSize); + rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId)); if (rc.IsFailure()) return rc; - rc = FsClient.OpenFile(out handle, new U8Span(lastPublishedIdPath), OpenMode.Read); - if (rc.IsFailure()) return rc; - - createdNewFile = true; - - _lastPublishedId = 0; IsKvdbLoaded = true; } - try - { - // If we had to create the file earlier, we don't need to load the value again. - if (!createdNewFile) - { - rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId)); - if (rc.IsFailure()) return rc; - - IsKvdbLoaded = true; - } - - return Result.Success; - } - finally - { - FsClient.CloseFile(handle); - } + return Result.Success; } finally { - // The save data needs to be committed if we created the last published ID file. - if (createdNewFile) - { - // Note: Nintendo does not check this return value, probably because it's in a scope-exit block. - FsClient.Commit(MountName).IgnoreResult(); - } + FsClient.CloseFile(handle); } } finally { - mount.Dispose(); + // The save data needs to be committed if we created the last published ID file. + if (createdNewFile) + { + // Note: Nintendo does not check this return value, probably because it's in a scope-exit block. + FsClient.Commit(MountName).IgnoreResult(); + } + } + } + finally + { + mount.Dispose(); + } + } + + private void UpdateHandle() => Handle++; + + /// + /// Adds a to the list of registered readers. + /// + /// The reader to add. + /// The of the operation. + private Result RegisterReader(ref SharedRef reader) + { + OpenReaders.Add(new ReaderAccessor(ref reader)); + + return Result.Success; + } + + /// + /// Removes any s that are no longer in use from the registered readers. + /// + private void UnregisterReader() + { + int i = 0; + List readers = OpenReaders; + + while (i < readers.Count) + { + // Remove the reader if there are no references to it. There is no need to increment + // i in this case because the next reader in the list will be shifted to index i + if (readers[i].IsExpired()) + { + readers.RemoveAt(i); + } + else + { + i++; + } + } + } + + /// + /// Adjusts the position of any opened s so that they still point to the + /// same element after the addition or removal of another element. If the reader was on the + /// element that was removed, it will now point to the element that was next in the list. + /// + /// The key of the element that was removed or added. + /// The of the operation. + private Result FixReader(in SaveDataAttribute key) + { + foreach (ReaderAccessor accessor in OpenReaders) + { + using SharedRef reader = accessor.Lock(); + + if (reader.HasValue) + { + reader.Get.Fix(in key); } } - private void UpdateHandle() => Handle++; + return Result.Success; + } - /// - /// Adds a to the list of registered readers. - /// - /// The reader to add. - /// The of the operation. - private Result RegisterReader(ref SharedRef reader) + /// + /// Mounts the storage for a , and unmounts the storage + /// when the is disposed; + /// + private ref struct Mounter + { + private FileSystemClient FsClient { get; set; } + private U8String MountName { get; set; } + private bool IsMounted { get; set; } + + public Result Mount(FileSystemClient fsClient, U8String mountName, SaveDataSpaceId spaceId, + ulong saveDataId) { - OpenReaders.Add(new ReaderAccessor(ref reader)); + FsClient = fsClient; + MountName = mountName; - return Result.Success; - } + FsClient.DisableAutoSaveDataCreation(); - /// - /// Removes any s that are no longer in use from the registered readers. - /// - private void UnregisterReader() - { - int i = 0; - List readers = OpenReaders; + Result rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - while (i < readers.Count) + if (rc.IsFailure()) { - // Remove the reader if there are no references to it. There is no need to increment - // i in this case because the next reader in the list will be shifted to index i - if (readers[i].IsExpired()) + if (ResultFs.TargetNotFound.Includes(rc)) { - readers.RemoveAt(i); + rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, SaveDataAvailableSize, SaveDataJournalSize, 0); + if (rc.IsFailure()) return rc; + + rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); + if (rc.IsFailure()) return rc; } else { - i++; + if (ResultFs.SignedSystemPartitionDataCorrupted.Includes(rc)) return rc; + if (!ResultFs.DataCorrupted.Includes(rc)) return rc; + + if (spaceId == SaveDataSpaceId.SdSystem) return rc; + + rc = FsClient.DeleteSaveData(spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); + if (rc.IsFailure()) return rc; + + rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); + if (rc.IsFailure()) return rc; } } - } - /// - /// Adjusts the position of any opened s so that they still point to the - /// same element after the addition or removal of another element. If the reader was on the - /// element that was removed, it will now point to the element that was next in the list. - /// - /// The key of the element that was removed or added. - /// The of the operation. - private Result FixReader(in SaveDataAttribute key) - { - foreach (ReaderAccessor accessor in OpenReaders) - { - using SharedRef reader = accessor.Lock(); - - if (reader.HasValue) - { - reader.Get.Fix(in key); - } - } + IsMounted = true; return Result.Success; } - /// - /// Mounts the storage for a , and unmounts the storage - /// when the is disposed; - /// - private ref struct Mounter - { - private FileSystemClient FsClient { get; set; } - private U8String MountName { get; set; } - private bool IsMounted { get; set; } - - public Result Mount(FileSystemClient fsClient, U8String mountName, SaveDataSpaceId spaceId, - ulong saveDataId) - { - FsClient = fsClient; - MountName = mountName; - - FsClient.DisableAutoSaveDataCreation(); - - Result rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - - if (rc.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(rc)) - { - rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, SaveDataAvailableSize, SaveDataJournalSize, 0); - if (rc.IsFailure()) return rc; - - rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - } - else - { - if (ResultFs.SignedSystemPartitionDataCorrupted.Includes(rc)) return rc; - if (!ResultFs.DataCorrupted.Includes(rc)) return rc; - - if (spaceId == SaveDataSpaceId.SdSystem) return rc; - - rc = FsClient.DeleteSaveData(spaceId, saveDataId); - if (rc.IsFailure()) return rc; - - rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); - if (rc.IsFailure()) return rc; - - rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - } - } - - IsMounted = true; - - return Result.Success; - } - - public void Dispose() - { - if (IsMounted) - { - FsClient.Unmount(MountName); - } - } - } - - private class ReaderAccessor : IDisposable - { - private WeakRef _reader; - - public ReaderAccessor(ref SharedRef reader) - { - _reader = new WeakRef(in reader); - } - - public void Dispose() => _reader.Destroy(); - public SharedRef Lock() => _reader.Lock(); - public bool IsExpired() => _reader.Expired; - } - - private class Reader : SaveDataInfoReaderImpl - { - private readonly SaveDataIndexer _indexer; - private FlatMapKeyValueStore.Iterator _iterator; - private readonly int _handle; - - public Reader(SaveDataIndexer indexer) - { - _indexer = indexer; - _handle = indexer.Handle; - - _iterator = indexer.GetBeginIterator(); - } - - public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) - { - UnsafeHelpers.SkipParamInit(out readCount); - - lock (_indexer.Locker) - { - // Indexer has been reloaded since this info reader was created - if (_handle != _indexer.Handle) - { - return ResultFs.InvalidHandle.Log(); - } - - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); - - int i; - for (i = 0; !_iterator.IsEnd() && i < outInfo.Length; i++) - { - ref SaveDataAttribute key = ref _iterator.Get().Key; - ref SaveDataIndexerValue value = ref _iterator.GetValue(); - - GenerateSaveDataInfo(out outInfo[i], in key, in value); - - _iterator.Next(); - } - - readCount = i; - return Result.Success; - } - } - - public void Fix(in SaveDataAttribute attribute) - { - _indexer.FixIterator(ref _iterator, in attribute); - } - - public void Dispose() - { - lock (_indexer.Locker) - { - _indexer.UnregisterReader(); - } - } - } - public void Dispose() { - KvDatabase?.Dispose(); + if (IsMounted) + { + FsClient.Unmount(MountName); + } } } + + private class ReaderAccessor : IDisposable + { + private WeakRef _reader; + + public ReaderAccessor(ref SharedRef reader) + { + _reader = new WeakRef(in reader); + } + + public void Dispose() => _reader.Destroy(); + public SharedRef Lock() => _reader.Lock(); + public bool IsExpired() => _reader.Expired; + } + + private class Reader : SaveDataInfoReaderImpl + { + private readonly SaveDataIndexer _indexer; + private FlatMapKeyValueStore.Iterator _iterator; + private readonly int _handle; + + public Reader(SaveDataIndexer indexer) + { + _indexer = indexer; + _handle = indexer.Handle; + + _iterator = indexer.GetBeginIterator(); + } + + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) + { + UnsafeHelpers.SkipParamInit(out readCount); + + lock (_indexer.Locker) + { + // Indexer has been reloaded since this info reader was created + if (_handle != _indexer.Handle) + { + return ResultFs.InvalidHandle.Log(); + } + + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + + int i; + for (i = 0; !_iterator.IsEnd() && i < outInfo.Length; i++) + { + ref SaveDataAttribute key = ref _iterator.Get().Key; + ref SaveDataIndexerValue value = ref _iterator.GetValue(); + + GenerateSaveDataInfo(out outInfo[i], in key, in value); + + _iterator.Next(); + } + + readCount = i; + return Result.Success; + } + } + + public void Fix(in SaveDataAttribute attribute) + { + _indexer.FixIterator(ref _iterator, in attribute); + } + + public void Dispose() + { + lock (_indexer.Locker) + { + _indexer.UnregisterReader(); + } + } + } + + public void Dispose() + { + KvDatabase?.Dispose(); + } } diff --git a/src/LibHac/FsSrv/SaveDataIndexerLite.cs b/src/LibHac/FsSrv/SaveDataIndexerLite.cs index 50844955..717771a0 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerLite.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerLite.cs @@ -4,267 +4,266 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Indexes metadata for temporary save data, holding a key-value pair of types +/// and respectively. +/// +/// +/// Only one temporary save data may exist at a time. When a new +/// save data is added to the index, the existing key-value pair is replaced. +///
Based on FS 10.0.0 (nnSdk 10.4.0) +///
+public class SaveDataIndexerLite : ISaveDataIndexer { - /// - /// Indexes metadata for temporary save data, holding a key-value pair of types - /// and respectively. - /// - /// - /// Only one temporary save data may exist at a time. When a new - /// save data is added to the index, the existing key-value pair is replaced. - ///
Based on FS 10.0.0 (nnSdk 10.4.0) - ///
- public class SaveDataIndexerLite : ISaveDataIndexer + private object Locker { get; } = new object(); + private ulong CurrentSaveDataId { get; set; } = 0x4000000000000000; + + // Todo: Use Optional + private bool IsKeyValueSet { get; set; } + + private SaveDataAttribute _key; + private SaveDataIndexerValue _value; + + public Result Commit() { - private object Locker { get; } = new object(); - private ulong CurrentSaveDataId { get; set; } = 0x4000000000000000; + return Result.Success; + } - // Todo: Use Optional - private bool IsKeyValueSet { get; set; } + public Result Rollback() + { + return Result.Success; + } - private SaveDataAttribute _key; - private SaveDataIndexerValue _value; - - public Result Commit() + public Result Reset() + { + lock (Locker) { + IsKeyValueSet = false; return Result.Success; } + } - public Result Rollback() + public Result Publish(out ulong saveDataId, in SaveDataAttribute key) + { + UnsafeHelpers.SkipParamInit(out saveDataId); + + lock (Locker) { + if (IsKeyValueSet && _key == key) + return ResultFs.AlreadyExists.Log(); + + _key = key; + IsKeyValueSet = true; + + _value = new SaveDataIndexerValue { SaveDataId = CurrentSaveDataId }; + saveDataId = CurrentSaveDataId; + CurrentSaveDataId++; + return Result.Success; } + } - public Result Reset() + public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) + { + UnsafeHelpers.SkipParamInit(out value); + + lock (Locker) { - lock (Locker) + if (IsKeyValueSet && _key == key) + { + value = _value; + return Result.Success; + } + + return ResultFs.TargetNotFound.Log(); + } + } + + public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) + { + lock (Locker) + { + if (IsKeyValueSet && _key == key) + return ResultFs.AlreadyExists.Log(); + + _key = key; + IsKeyValueSet = true; + + _value = new SaveDataIndexerValue(); + return Result.Success; + } + } + + public bool IsRemainedReservedOnly() + { + return false; + } + + public Result Delete(ulong saveDataId) + { + lock (Locker) + { + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { IsKeyValueSet = false; return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } + } - public Result Publish(out ulong saveDataId, in SaveDataAttribute key) + public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) + { + lock (Locker) { - UnsafeHelpers.SkipParamInit(out saveDataId); - - lock (Locker) + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { - if (IsKeyValueSet && _key == key) - return ResultFs.AlreadyExists.Log(); - - _key = key; - IsKeyValueSet = true; - - _value = new SaveDataIndexerValue { SaveDataId = CurrentSaveDataId }; - saveDataId = CurrentSaveDataId; - CurrentSaveDataId++; - + _value.SpaceId = spaceId; return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } + } - public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) + public Result SetSize(ulong saveDataId, long size) + { + // Nintendo doesn't lock in this function for some reason + lock (Locker) { - UnsafeHelpers.SkipParamInit(out value); - - lock (Locker) + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { - if (IsKeyValueSet && _key == key) - { - value = _value; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); - } - } - - public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) - { - lock (Locker) - { - if (IsKeyValueSet && _key == key) - return ResultFs.AlreadyExists.Log(); - - _key = key; - IsKeyValueSet = true; - - _value = new SaveDataIndexerValue(); + _value.Size = size; return Result.Success; } - } - public bool IsRemainedReservedOnly() - { - return false; + return ResultFs.TargetNotFound.Log(); } + } - public Result Delete(ulong saveDataId) + public Result SetState(ulong saveDataId, SaveDataState state) + { + // Nintendo doesn't lock in this function for some reason + lock (Locker) { - lock (Locker) + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - IsKeyValueSet = false; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); + _value.State = state; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } + } - public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) + public Result GetKey(out SaveDataAttribute key, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out key); + + lock (Locker) { - lock (Locker) + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.SpaceId = spaceId; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); + key = _key; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } + } - public Result SetSize(ulong saveDataId, long size) + public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId) + { + UnsafeHelpers.SkipParamInit(out value); + + lock (Locker) { - // Nintendo doesn't lock in this function for some reason - lock (Locker) + if (IsKeyValueSet && _value.SaveDataId == saveDataId) { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.Size = size; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); + value = _value; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } + } - public Result SetState(ulong saveDataId, SaveDataState state) + public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) + { + lock (Locker) { - // Nintendo doesn't lock in this function for some reason - lock (Locker) + if (IsKeyValueSet && _key == key) { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.State = state; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); + _value = value; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); + } + } + + public int GetIndexCount() + { + return 1; + } + + public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) + { + SaveDataIndexerLiteInfoReader reader; + + if (IsKeyValueSet) + { + reader = new SaveDataIndexerLiteInfoReader(in _key, in _value); + } + else + { + reader = new SaveDataIndexerLiteInfoReader(); } - public Result GetKey(out SaveDataAttribute key, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out key); + outInfoReader.Reset(reader); - lock (Locker) + return Result.Success; + } + + private class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl + { + private bool _finishedIterating; + private SaveDataInfo _info; + + public SaveDataIndexerLiteInfoReader() + { + _finishedIterating = true; + } + + public SaveDataIndexerLiteInfoReader(in SaveDataAttribute key, in SaveDataIndexerValue value) + { + SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value); + } + + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) + { + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + + // Note: Nintendo doesn't check if the buffer is large enough here + if (_finishedIterating || outInfo.IsEmpty) { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - key = _key; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); - } - } - - public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId) - { - UnsafeHelpers.SkipParamInit(out value); - - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - value = _value; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); - } - } - - public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) - { - lock (Locker) - { - if (IsKeyValueSet && _key == key) - { - _value = value; - return Result.Success; - } - - return ResultFs.TargetNotFound.Log(); - } - } - - public int GetIndexCount() - { - return 1; - } - - public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) - { - SaveDataIndexerLiteInfoReader reader; - - if (IsKeyValueSet) - { - reader = new SaveDataIndexerLiteInfoReader(in _key, in _value); + readCount = 0; } else { - reader = new SaveDataIndexerLiteInfoReader(); + outInfo[0] = _info; + readCount = 1; + _finishedIterating = true; } - outInfoReader.Reset(reader); - return Result.Success; } - private class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl - { - private bool _finishedIterating; - private SaveDataInfo _info; - - public SaveDataIndexerLiteInfoReader() - { - _finishedIterating = true; - } - - public SaveDataIndexerLiteInfoReader(in SaveDataAttribute key, in SaveDataIndexerValue value) - { - SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value); - } - - public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) - { - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); - - // Note: Nintendo doesn't check if the buffer is large enough here - if (_finishedIterating || outInfo.IsEmpty) - { - readCount = 0; - } - else - { - outInfo[0] = _info; - readCount = 1; - _finishedIterating = true; - } - - return Result.Success; - } - - public void Dispose() { } - } - public void Dispose() { } } + + public void Dispose() { } } diff --git a/src/LibHac/FsSrv/SaveDataIndexerManager.cs b/src/LibHac/FsSrv/SaveDataIndexerManager.cs index 612868b1..45fd089c 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerManager.cs @@ -5,252 +5,251 @@ using LibHac.Fs; using LibHac.FsSrv.Storage; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Initializes and holds s for each save data space. +/// Creates accessors for individual SaveDataIndexers. +/// +/// Based on FS 10.0.0 (nnSdk 10.4.0) +internal class SaveDataIndexerManager : ISaveDataIndexerManager { - /// - /// Initializes and holds s for each save data space. - /// Creates accessors for individual SaveDataIndexers. - /// - /// Based on FS 10.0.0 (nnSdk 10.4.0) - internal class SaveDataIndexerManager : ISaveDataIndexerManager + private FileSystemClient FsClient { get; } + private MemoryResource MemoryResource { get; } + private ulong SaveDataId { get; } + + private IndexerHolder _bisIndexer = new IndexerHolder(new object()); + private IndexerHolder _tempIndexer = new IndexerHolder(new object()); + + private IndexerHolder _sdCardIndexer = new IndexerHolder(new object()); + private StorageDeviceHandle _sdCardHandle; + private IDeviceHandleManager _sdCardHandleManager; + + private IndexerHolder _safeIndexer = new IndexerHolder(new object()); + private IndexerHolder _properSystemIndexer = new IndexerHolder(new object()); + + private bool IsBisUserRedirectionEnabled { get; } + + public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId, MemoryResource memoryResource, + IDeviceHandleManager sdCardHandleManager, bool isBisUserRedirectionEnabled) { - private FileSystemClient FsClient { get; } - private MemoryResource MemoryResource { get; } - private ulong SaveDataId { get; } + FsClient = fsClient; + SaveDataId = saveDataId; + MemoryResource = memoryResource; + _sdCardHandleManager = sdCardHandleManager; + IsBisUserRedirectionEnabled = isBisUserRedirectionEnabled; - private IndexerHolder _bisIndexer = new IndexerHolder(new object()); - private IndexerHolder _tempIndexer = new IndexerHolder(new object()); + _tempIndexer.Indexer = new SaveDataIndexerLite(); + } - private IndexerHolder _sdCardIndexer = new IndexerHolder(new object()); - private StorageDeviceHandle _sdCardHandle; - private IDeviceHandleManager _sdCardHandleManager; + /// + /// Opens a for the specified save data space. + /// + /// + /// The returned will have exclusive access to the requested indexer. + /// The accessor must be disposed after use. + /// + /// If the method returns successfully, contains the created accessor. + /// If the method returns successfully, contains + /// if the indexer needed to be initialized. + /// The of the indexer to open. + /// The of the operation. + public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, + out bool neededInit, SaveDataSpaceId spaceId) + { + UnsafeHelpers.SkipParamInit(out neededInit); - private IndexerHolder _safeIndexer = new IndexerHolder(new object()); - private IndexerHolder _properSystemIndexer = new IndexerHolder(new object()); - - private bool IsBisUserRedirectionEnabled { get; } - - public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId, MemoryResource memoryResource, - IDeviceHandleManager sdCardHandleManager, bool isBisUserRedirectionEnabled) + if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) { - FsClient = fsClient; - SaveDataId = saveDataId; - MemoryResource = memoryResource; - _sdCardHandleManager = sdCardHandleManager; - IsBisUserRedirectionEnabled = isBisUserRedirectionEnabled; - - _tempIndexer.Indexer = new SaveDataIndexerLite(); + spaceId = SaveDataSpaceId.ProperSystem; } - /// - /// Opens a for the specified save data space. - /// - /// - /// The returned will have exclusive access to the requested indexer. - /// The accessor must be disposed after use. - /// - /// If the method returns successfully, contains the created accessor. - /// If the method returns successfully, contains - /// if the indexer needed to be initialized. - /// The of the indexer to open. - /// The of the operation. - public Result OpenSaveDataIndexerAccessor(ref UniqueRef outAccessor, - out bool neededInit, SaveDataSpaceId spaceId) + UniqueLock indexerLock = default; + try { - UnsafeHelpers.SkipParamInit(out neededInit); - - if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) + ISaveDataIndexer indexer; + switch (spaceId) { - spaceId = SaveDataSpaceId.ProperSystem; + case SaveDataSpaceId.System: + case SaveDataSpaceId.User: + indexerLock = new UniqueLock(_bisIndexer.Locker); + + if (!_bisIndexer.IsInitialized) + { + _bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName), + SaveDataSpaceId.System, SaveDataId, MemoryResource); + + neededInit = true; + } + + indexer = _bisIndexer.Indexer; + break; + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: + // ReSharper doesn't realize that UniqueLock locks the indexer's lock object + // ReSharper disable InconsistentlySynchronizedField + indexerLock = new UniqueLock(_sdCardIndexer.Locker); + + // We need to reinitialize the indexer if the SD card has changed + if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized) + { + _sdCardIndexer.Indexer.Dispose(); + _sdCardIndexer.Indexer = null; + } + + if (!_sdCardIndexer.IsInitialized) + { + _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName), + SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource); + + _sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); + + neededInit = true; + } + + indexer = _sdCardIndexer.Indexer; + // ReSharper restore InconsistentlySynchronizedField + + break; + case SaveDataSpaceId.Temporary: + indexerLock = new UniqueLock(_tempIndexer.Locker); + + indexer = _tempIndexer.Indexer; + break; + case SaveDataSpaceId.ProperSystem: + indexerLock = new UniqueLock(_properSystemIndexer.Locker); + + if (!_properSystemIndexer.IsInitialized) + { + _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, + new U8Span(ProperSystemIndexerMountName), + SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource); + + neededInit = true; + } + + indexer = _properSystemIndexer.Indexer; + break; + case SaveDataSpaceId.SafeMode: + indexerLock = new UniqueLock(_safeIndexer.Locker); + + if (!_safeIndexer.IsInitialized) + { + _safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName), + SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource); + + neededInit = true; + } + + indexer = _safeIndexer.Indexer; + break; + + default: + outAccessor = default; + return ResultFs.InvalidArgument.Log(); } - UniqueLock indexerLock = default; - try - { - ISaveDataIndexer indexer; - switch (spaceId) - { - case SaveDataSpaceId.System: - case SaveDataSpaceId.User: - indexerLock = new UniqueLock(_bisIndexer.Locker); - if (!_bisIndexer.IsInitialized) - { - _bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName), - SaveDataSpaceId.System, SaveDataId, MemoryResource); + outAccessor.Reset(new SaveDataIndexerAccessor(indexer, ref indexerLock)); + return Result.Success; + } + finally + { + indexerLock.Dispose(); + } + } - neededInit = true; - } - - indexer = _bisIndexer.Indexer; - break; - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.SdCache: - // ReSharper doesn't realize that UniqueLock locks the indexer's lock object - // ReSharper disable InconsistentlySynchronizedField - indexerLock = new UniqueLock(_sdCardIndexer.Locker); - - // We need to reinitialize the indexer if the SD card has changed - if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer.Dispose(); - _sdCardIndexer.Indexer = null; - } - - if (!_sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName), - SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource); - - _sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); - - neededInit = true; - } - - indexer = _sdCardIndexer.Indexer; - // ReSharper restore InconsistentlySynchronizedField - - break; - case SaveDataSpaceId.Temporary: - indexerLock = new UniqueLock(_tempIndexer.Locker); - - indexer = _tempIndexer.Indexer; - break; - case SaveDataSpaceId.ProperSystem: - indexerLock = new UniqueLock(_properSystemIndexer.Locker); - - if (!_properSystemIndexer.IsInitialized) - { - _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, - new U8Span(ProperSystemIndexerMountName), - SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource); - - neededInit = true; - } - - indexer = _properSystemIndexer.Indexer; - break; - case SaveDataSpaceId.SafeMode: - indexerLock = new UniqueLock(_safeIndexer.Locker); - - if (!_safeIndexer.IsInitialized) - { - _safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName), - SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource); - - neededInit = true; - } - - indexer = _safeIndexer.Indexer; - break; - - default: - outAccessor = default; - return ResultFs.InvalidArgument.Log(); - } - - - outAccessor.Reset(new SaveDataIndexerAccessor(indexer, ref indexerLock)); - return Result.Success; - } - finally - { - indexerLock.Dispose(); - } + public void ResetIndexer(SaveDataSpaceId spaceId) + { + if (spaceId != SaveDataSpaceId.Temporary) + { + Abort.UnexpectedDefault(); } - public void ResetIndexer(SaveDataSpaceId spaceId) + // ReSharper disable once RedundantAssignment + Result rc = _tempIndexer.Indexer.Reset(); + Assert.SdkAssert(rc.IsSuccess()); + } + + public void InvalidateIndexer(SaveDataSpaceId spaceId) + { + // Note: Nintendo doesn't lock when doing this operation + lock (_sdCardIndexer.Locker) { - if (spaceId != SaveDataSpaceId.Temporary) + if (spaceId != SaveDataSpaceId.SdCache && spaceId != SaveDataSpaceId.SdSystem) { Abort.UnexpectedDefault(); } - // ReSharper disable once RedundantAssignment - Result rc = _tempIndexer.Indexer.Reset(); - Assert.SdkAssert(rc.IsSuccess()); - } - - public void InvalidateIndexer(SaveDataSpaceId spaceId) - { - // Note: Nintendo doesn't lock when doing this operation - lock (_sdCardIndexer.Locker) + if (_sdCardIndexer.IsInitialized) { - if (spaceId != SaveDataSpaceId.SdCache && spaceId != SaveDataSpaceId.SdSystem) - { - Abort.UnexpectedDefault(); - } - - if (_sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer.Dispose(); - _sdCardIndexer.Indexer = null; - } + _sdCardIndexer.Indexer.Dispose(); + _sdCardIndexer.Indexer = null; } } - - private struct IndexerHolder - { - public object Locker { get; } - public ISaveDataIndexer Indexer { get; set; } - - public IndexerHolder(object locker) - { - Locker = locker; - Indexer = null; - } - - public bool IsInitialized => Indexer != null; - } - - private static ReadOnlySpan SystemIndexerMountName => // saveDataIxrDb - new[] - { - (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', - (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b' - }; - - private static ReadOnlySpan SdCardIndexerMountName => // saveDataIxrDbSd - new[] - { - (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', - (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'S', (byte) 'd' - }; - - private static ReadOnlySpan ProperSystemIndexerMountName => // saveDataIxrDbPr - new[] - { - (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', - (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'P', (byte) 'r' - }; - - private static ReadOnlySpan SafeModeIndexerMountName => // saveDataIxrDbSf - new[] - { - (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', - (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'S', (byte) 'f' - }; } - /// - /// Gives exclusive access to an . - /// Releases the lock to the upon disposal. - /// - /// Based on FS 10.0.0 (nnSdk 10.4.0) - public class SaveDataIndexerAccessor : IDisposable + private struct IndexerHolder { - public ISaveDataIndexer Indexer { get; } - private UniqueLock _locker; + public object Locker { get; } + public ISaveDataIndexer Indexer { get; set; } - public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker) + public IndexerHolder(object locker) { - Indexer = indexer; - _locker = new UniqueLock(ref locker); + Locker = locker; + Indexer = null; } - public void Dispose() + public bool IsInitialized => Indexer != null; + } + + private static ReadOnlySpan SystemIndexerMountName => // saveDataIxrDb + new[] { - _locker.Dispose(); - } + (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', + (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b' + }; + + private static ReadOnlySpan SdCardIndexerMountName => // saveDataIxrDbSd + new[] + { + (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', + (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'S', (byte) 'd' + }; + + private static ReadOnlySpan ProperSystemIndexerMountName => // saveDataIxrDbPr + new[] + { + (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', + (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'P', (byte) 'r' + }; + + private static ReadOnlySpan SafeModeIndexerMountName => // saveDataIxrDbSf + new[] + { + (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', + (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'S', (byte) 'f' + }; +} + +/// +/// Gives exclusive access to an . +/// Releases the lock to the upon disposal. +/// +/// Based on FS 10.0.0 (nnSdk 10.4.0) +public class SaveDataIndexerAccessor : IDisposable +{ + public ISaveDataIndexer Indexer { get; } + private UniqueLock _locker; + + public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker) + { + Indexer = indexer; + _locker = new UniqueLock(ref locker); + } + + public void Dispose() + { + _locker.Dispose(); } } diff --git a/src/LibHac/FsSrv/SaveDataIndexerValue.cs b/src/LibHac/FsSrv/SaveDataIndexerValue.cs index 0395becb..34b09a5c 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerValue.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerValue.cs @@ -1,15 +1,14 @@ using System.Runtime.InteropServices; using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +[StructLayout(LayoutKind.Explicit, Size = 0x40)] +public struct SaveDataIndexerValue { - [StructLayout(LayoutKind.Explicit, Size = 0x40)] - public struct SaveDataIndexerValue - { - [FieldOffset(0x00)] public ulong SaveDataId; - [FieldOffset(0x08)] public long Size; - [FieldOffset(0x10)] public ulong Field10; - [FieldOffset(0x18)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x19)] public SaveDataState State; - } + [FieldOffset(0x00)] public ulong SaveDataId; + [FieldOffset(0x08)] public long Size; + [FieldOffset(0x10)] public ulong Field10; + [FieldOffset(0x18)] public SaveDataSpaceId SpaceId; + [FieldOffset(0x19)] public SaveDataState State; } diff --git a/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs b/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs index a0e99beb..c2cfa072 100644 --- a/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs +++ b/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs @@ -6,160 +6,159 @@ using LibHac.Ncm; using LibHac.Sf; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl { - internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl + private SharedRef _reader; + private SaveDataInfoFilter _infoFilter; + + public SaveDataInfoFilterReader(ref SharedRef reader, in SaveDataInfoFilter infoFilter) { - private SharedRef _reader; - private SaveDataInfoFilter _infoFilter; - - public SaveDataInfoFilterReader(ref SharedRef reader, in SaveDataInfoFilter infoFilter) - { - _reader = SharedRef.CreateMove(ref reader); - _infoFilter = infoFilter; - } - - public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) - { - UnsafeHelpers.SkipParamInit(out readCount); - - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); - - SaveDataInfo tempInfo = default; - Span tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo); - - SaveDataInfoReaderImpl reader = _reader.Get; - int count = 0; - - while (count < outInfo.Length) - { - Result rc = reader.Read(out long baseReadCount, new OutBuffer(tempInfoBytes)); - if (rc.IsFailure()) return rc; - - if (baseReadCount == 0) break; - - if (_infoFilter.Includes(in tempInfo)) - { - outInfo[count] = tempInfo; - - count++; - } - } - - readCount = count; - - return Result.Success; - } - - public void Dispose() - { - _reader.Destroy(); - } + _reader = SharedRef.CreateMove(ref reader); + _infoFilter = infoFilter; } - internal struct SaveDataInfoFilter + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) { - public Optional SpaceId; - public Optional ProgramId; - public Optional SaveDataType; - public Optional UserId; - public Optional SaveDataId; - public Optional Index; - public int Rank; + UnsafeHelpers.SkipParamInit(out readCount); - public SaveDataInfoFilter(in SaveDataInfoFilter filter) + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + + SaveDataInfo tempInfo = default; + Span tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo); + + SaveDataInfoReaderImpl reader = _reader.Get; + int count = 0; + + while (count < outInfo.Length) { - this = filter; - } + Result rc = reader.Read(out long baseReadCount, new OutBuffer(tempInfoBytes)); + if (rc.IsFailure()) return rc; - public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter) - { - // Start out with no optional values - this = new SaveDataInfoFilter(); + if (baseReadCount == 0) break; - SpaceId = new Optional(spaceId); - Rank = (int)filter.Rank; - - if (filter.FilterByProgramId) + if (_infoFilter.Includes(in tempInfo)) { - ProgramId = new Optional(in filter.Attribute.ProgramId); - } + outInfo[count] = tempInfo; - if (filter.FilterBySaveDataType) - { - SaveDataType = new Optional(in filter.Attribute.Type); - } - - if (filter.FilterByUserId) - { - UserId = new Optional(in filter.Attribute.UserId); - } - - if (filter.FilterBySaveDataId) - { - SaveDataId = new Optional(in filter.Attribute.StaticSaveDataId); - } - - if (filter.FilterByIndex) - { - Index = new Optional(in filter.Attribute.Index); + count++; } } - public SaveDataInfoFilter(Optional spaceId, Optional programId, - Optional saveDataType, Optional userId, Optional saveDataId, - Optional index, int rank) - { - SpaceId = spaceId; - ProgramId = programId; - SaveDataType = saveDataType; - UserId = userId; - SaveDataId = saveDataId; - Index = index; - Rank = rank; - } + readCount = count; - public bool Includes(in SaveDataInfo saveInfo) - { - if (SpaceId.HasValue && saveInfo.SpaceId != SpaceId.Value) - { - return false; - } + return Result.Success; + } - if (ProgramId.HasValue && saveInfo.ProgramId != ProgramId.Value) - { - return false; - } - - if (SaveDataType.HasValue && saveInfo.Type != SaveDataType.Value) - { - return false; - } - - if (UserId.HasValue && saveInfo.UserId != UserId.Value) - { - return false; - } - - if (SaveDataId.HasValue && saveInfo.SaveDataId != SaveDataId.Value) - { - return false; - } - - if (Index.HasValue && saveInfo.Index != Index.Value) - { - return false; - } - - var filterRank = (SaveDataRank)(Rank & 1); - - // When filtering by secondary rank, match on both primary and secondary ranks - if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary) - { - return false; - } - - return true; - } + public void Dispose() + { + _reader.Destroy(); + } +} + +internal struct SaveDataInfoFilter +{ + public Optional SpaceId; + public Optional ProgramId; + public Optional SaveDataType; + public Optional UserId; + public Optional SaveDataId; + public Optional Index; + public int Rank; + + public SaveDataInfoFilter(in SaveDataInfoFilter filter) + { + this = filter; + } + + public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + // Start out with no optional values + this = new SaveDataInfoFilter(); + + SpaceId = new Optional(spaceId); + Rank = (int)filter.Rank; + + if (filter.FilterByProgramId) + { + ProgramId = new Optional(in filter.Attribute.ProgramId); + } + + if (filter.FilterBySaveDataType) + { + SaveDataType = new Optional(in filter.Attribute.Type); + } + + if (filter.FilterByUserId) + { + UserId = new Optional(in filter.Attribute.UserId); + } + + if (filter.FilterBySaveDataId) + { + SaveDataId = new Optional(in filter.Attribute.StaticSaveDataId); + } + + if (filter.FilterByIndex) + { + Index = new Optional(in filter.Attribute.Index); + } + } + + public SaveDataInfoFilter(Optional spaceId, Optional programId, + Optional saveDataType, Optional userId, Optional saveDataId, + Optional index, int rank) + { + SpaceId = spaceId; + ProgramId = programId; + SaveDataType = saveDataType; + UserId = userId; + SaveDataId = saveDataId; + Index = index; + Rank = rank; + } + + public bool Includes(in SaveDataInfo saveInfo) + { + if (SpaceId.HasValue && saveInfo.SpaceId != SpaceId.Value) + { + return false; + } + + if (ProgramId.HasValue && saveInfo.ProgramId != ProgramId.Value) + { + return false; + } + + if (SaveDataType.HasValue && saveInfo.Type != SaveDataType.Value) + { + return false; + } + + if (UserId.HasValue && saveInfo.UserId != UserId.Value) + { + return false; + } + + if (SaveDataId.HasValue && saveInfo.SaveDataId != SaveDataId.Value) + { + return false; + } + + if (Index.HasValue && saveInfo.Index != Index.Value) + { + return false; + } + + var filterRank = (SaveDataRank)(Rank & 1); + + // When filtering by secondary rank, match on both primary and secondary ranks + if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary) + { + return false; + } + + return true; } } diff --git a/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs b/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs index 669e8fee..346a62fa 100644 --- a/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs +++ b/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs @@ -1,31 +1,30 @@ using LibHac.Fs; using LibHac.FsSrv.Sf; -namespace LibHac.FsSrv -{ - /// - /// Iterates through the of the save data - /// in a single save data space. - /// - // ReSharper disable once InconsistentNaming - // Kinda weird to name an interface / pure abstract class SaveDataInfoReaderImpl. Ask Nintendo, not me. - public interface SaveDataInfoReaderImpl : ISaveDataInfoReader - { - // We currently don't have adapter sf classes to forward calls to the non-sf classes, so at least for now - // we'll just inherit from the sf interface. +namespace LibHac.FsSrv; - /* - /// - /// Returns the next entries. This method will continue writing - /// entries to until there is either no more space - /// in the buffer, or until there are no more entries to iterate. - /// - /// If the method returns successfully, contains the number - /// of written to . - /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. - /// The buffer in which to write the . - /// The of the operation. - Result Read(out long readCount, OutBuffer saveDataInfoBuffer); - */ - } -} \ No newline at end of file +/// +/// Iterates through the of the save data +/// in a single save data space. +/// +// ReSharper disable once InconsistentNaming +// Kinda weird to name an interface / pure abstract class SaveDataInfoReaderImpl. Ask Nintendo, not me. +public interface SaveDataInfoReaderImpl : ISaveDataInfoReader +{ + // We currently don't have adapter sf classes to forward calls to the non-sf classes, so at least for now + // we'll just inherit from the sf interface. + + /* + /// + /// Returns the next entries. This method will continue writing + /// entries to until there is either no more space + /// in the buffer, or until there are no more entries to iterate. + /// + /// If the method returns successfully, contains the number + /// of written to . + /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. + /// The buffer in which to write the . + /// The of the operation. + Result Read(out long readCount, OutBuffer saveDataInfoBuffer); + */ +} diff --git a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs index 13426e30..d8497c6b 100644 --- a/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs +++ b/src/LibHac/FsSrv/SaveDataSharedFileStorage.cs @@ -9,426 +9,425 @@ using LibHac.Fs.Fsa; using LibHac.Os; using LibHac.Util; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +/// +/// Contains global functions for SaveDataSharedFileStorage. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1). +public static class SaveDataSharedFileStorageGlobalMethods { - /// - /// Contains global functions for SaveDataSharedFileStorage. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1). - public static class SaveDataSharedFileStorageGlobalMethods + public static Result OpenSaveDataStorage(this FileSystemServer fsSrv, + ref SharedRef outSaveDataStorage, ref SharedRef baseFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, OpenMode mode, + Optional type) { - public static Result OpenSaveDataStorage(this FileSystemServer fsSrv, - ref SharedRef outSaveDataStorage, ref SharedRef baseFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, OpenMode mode, - Optional type) + return fsSrv.Globals.SaveDataSharedFileStorage.SaveDataFileStorageHolder.OpenSaveDataStorage( + ref outSaveDataStorage, ref baseFileSystem, spaceId, saveDataId, mode, type); + } +} + +internal struct SaveDataSharedFileStorageGlobals +{ + public SdkMutexType Mutex; + public SaveDataFileStorageHolder SaveDataFileStorageHolder; + + public void Initialize(FileSystemServer fsServer) + { + Mutex.Initialize(); + SaveDataFileStorageHolder = new SaveDataFileStorageHolder(fsServer); + } +} + +/// +/// Provides access to a save data file from the provided +/// via an interface. +/// This class keeps track of which types of save data file systems have been opened from the save data file. +/// Only one of each file system type can be opened at the same time. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1). +public class SaveDataOpenTypeSetFileStorage : FileStorageBasedFileSystem +{ + public enum OpenType + { + None, + Normal, + Internal + } + + private bool _isNormalStorageOpened; + private bool _isInternalStorageOpened; + private bool _isInternalStorageInvalidated; + private SaveDataSpaceId _spaceId; + private ulong _saveDataId; + private SdkMutexType _mutex; + + // LibHac addition + private FileSystemServer _fsServer; + private ref SaveDataSharedFileStorageGlobals Globals => ref _fsServer.Globals.SaveDataSharedFileStorage; + + public SaveDataOpenTypeSetFileStorage(FileSystemServer fsServer, SaveDataSpaceId spaceId, ulong saveDataId) + { + _fsServer = fsServer; + _spaceId = spaceId; + _saveDataId = saveDataId; + _mutex.Initialize(); + } + + public Result Initialize(ref SharedRef baseFileSystem, in Path path, OpenMode mode, OpenType type) + { + Result rc = Initialize(ref baseFileSystem, in path, mode); + if (rc.IsFailure()) return rc; + + return SetOpenType(type); + } + + public Result SetOpenType(OpenType type) + { + Assert.SdkRequires(type == OpenType.Normal || type == OpenType.Internal); + + switch (type) { - return fsSrv.Globals.SaveDataSharedFileStorage.SaveDataFileStorageHolder.OpenSaveDataStorage( - ref outSaveDataStorage, ref baseFileSystem, spaceId, saveDataId, mode, type); + case OpenType.Normal: + if (_isNormalStorageOpened) + return ResultFs.TargetLocked.Log(); + + _isNormalStorageOpened = true; + return Result.Success; + + case OpenType.Internal: + if (_isInternalStorageOpened) + return ResultFs.TargetLocked.Log(); + + _isInternalStorageOpened = true; + _isInternalStorageInvalidated = false; + return Result.Success; + + default: + Abort.UnexpectedDefault(); + return Result.Success; } } - internal struct SaveDataSharedFileStorageGlobals + public void UnsetOpenType(OpenType type) { - public SdkMutexType Mutex; - public SaveDataFileStorageHolder SaveDataFileStorageHolder; + using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); - public void Initialize(FileSystemServer fsServer) + if (type == OpenType.Normal) { - Mutex.Initialize(); - SaveDataFileStorageHolder = new SaveDataFileStorageHolder(fsServer); + _isNormalStorageOpened = false; + } + else if (type == OpenType.Internal) + { + _isInternalStorageOpened = false; + } + + if (!IsOpened()) + { + Globals.SaveDataFileStorageHolder.Unregister(_spaceId, _saveDataId); } } - /// - /// Provides access to a save data file from the provided - /// via an interface. - /// This class keeps track of which types of save data file systems have been opened from the save data file. - /// Only one of each file system type can be opened at the same time. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1). - public class SaveDataOpenTypeSetFileStorage : FileStorageBasedFileSystem + public void InvalidateInternalStorage() { - public enum OpenType + _isInternalStorageInvalidated = true; + } + + public bool IsInternalStorageInvalidated() + { + return _isInternalStorageInvalidated; + } + + public bool IsOpened() + { + return _isNormalStorageOpened || _isInternalStorageOpened; + } + + public UniqueLockRef GetLock() + { + return new UniqueLockRef(ref _mutex); + } +} + +/// +/// Handles sharing a save data file storage between an internal save data file system +/// and a normal save data file system. +/// +/// +/// During save data import/export a save data image is opened as an "internal file system". +/// This file system allows access to portions of a save data image via an emulated file system +/// with different portions being represented as individual files. This class allows simultaneous +/// read-only access to a save data image via a normal save data file system and an internal file system. +/// Once an internal file system is opened, it will be considered valid until the save data image is +/// written to via the normal file system, at which point any accesses via the internal file system will +/// return +/// Based on FS 12.1.0 (nnSdk 12.3.1). +/// +public class SaveDataSharedFileStorage : IStorage +{ + private SharedRef _baseStorage; + private SaveDataOpenTypeSetFileStorage.OpenType _type; + + public SaveDataSharedFileStorage(ref SharedRef baseStorage, + SaveDataOpenTypeSetFileStorage.OpenType type) + { + _baseStorage = SharedRef.CreateMove(ref baseStorage); + _type = type; + } + + public override void Dispose() + { + if (_baseStorage.HasValue) + _baseStorage.Get.UnsetOpenType(_type); + + _baseStorage.Destroy(); + + base.Dispose(); + } + + private Result AccessCheck(bool isWriteAccess) + { + if (_type == SaveDataOpenTypeSetFileStorage.OpenType.Internal) { - None, - Normal, - Internal + if (_baseStorage.Get.IsInternalStorageInvalidated()) + return ResultFs.SaveDataPorterInvalidated.Log(); + } + else if (_type == SaveDataOpenTypeSetFileStorage.OpenType.Normal && isWriteAccess) + { + // Any opened internal file system will be invalid after a write to the normal file system + _baseStorage.Get.InvalidateInternalStorage(); } - private bool _isNormalStorageOpened; - private bool _isInternalStorageOpened; - private bool _isInternalStorageInvalidated; + return Result.Success; + } + + protected override Result DoRead(long offset, Span destination) + { + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: false); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: true); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.Write(offset, source); + } + + protected override Result DoSetSize(long size) + { + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: true); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + Unsafe.SkipInit(out size); + + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: false); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.GetSize(out size); + } + + protected override Result DoFlush() + { + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: true); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.Flush(); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); + + Result rc = AccessCheck(isWriteAccess: true); + if (rc.IsFailure()) return rc; + + return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + } +} + +/// +/// Holds references to any open shared save data image files. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1). +public class SaveDataFileStorageHolder +{ + [NonCopyable] + private struct Entry + { + private SharedRef _storage; private SaveDataSpaceId _spaceId; private ulong _saveDataId; - private SdkMutexType _mutex; - // LibHac addition - private FileSystemServer _fsServer; - private ref SaveDataSharedFileStorageGlobals Globals => ref _fsServer.Globals.SaveDataSharedFileStorage; - - public SaveDataOpenTypeSetFileStorage(FileSystemServer fsServer, SaveDataSpaceId spaceId, ulong saveDataId) + public Entry(ref SharedRef storage, SaveDataSpaceId spaceId, + ulong saveDataId) { - _fsServer = fsServer; + _storage = SharedRef.CreateMove(ref storage); _spaceId = spaceId; _saveDataId = saveDataId; - _mutex.Initialize(); - } - - public Result Initialize(ref SharedRef baseFileSystem, in Path path, OpenMode mode, OpenType type) - { - Result rc = Initialize(ref baseFileSystem, in path, mode); - if (rc.IsFailure()) return rc; - - return SetOpenType(type); - } - - public Result SetOpenType(OpenType type) - { - Assert.SdkRequires(type == OpenType.Normal || type == OpenType.Internal); - - switch (type) - { - case OpenType.Normal: - if (_isNormalStorageOpened) - return ResultFs.TargetLocked.Log(); - - _isNormalStorageOpened = true; - return Result.Success; - - case OpenType.Internal: - if (_isInternalStorageOpened) - return ResultFs.TargetLocked.Log(); - - _isInternalStorageOpened = true; - _isInternalStorageInvalidated = false; - return Result.Success; - - default: - Abort.UnexpectedDefault(); - return Result.Success; - } - } - - public void UnsetOpenType(OpenType type) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); - - if (type == OpenType.Normal) - { - _isNormalStorageOpened = false; - } - else if (type == OpenType.Internal) - { - _isInternalStorageOpened = false; - } - - if (!IsOpened()) - { - Globals.SaveDataFileStorageHolder.Unregister(_spaceId, _saveDataId); - } - } - - public void InvalidateInternalStorage() - { - _isInternalStorageInvalidated = true; - } - - public bool IsInternalStorageInvalidated() - { - return _isInternalStorageInvalidated; - } - - public bool IsOpened() - { - return _isNormalStorageOpened || _isInternalStorageOpened; - } - - public UniqueLockRef GetLock() - { - return new UniqueLockRef(ref _mutex); - } - } - - /// - /// Handles sharing a save data file storage between an internal save data file system - /// and a normal save data file system. - /// - /// - /// During save data import/export a save data image is opened as an "internal file system". - /// This file system allows access to portions of a save data image via an emulated file system - /// with different portions being represented as individual files. This class allows simultaneous - /// read-only access to a save data image via a normal save data file system and an internal file system. - /// Once an internal file system is opened, it will be considered valid until the save data image is - /// written to via the normal file system, at which point any accesses via the internal file system will - /// return - /// Based on FS 12.1.0 (nnSdk 12.3.1). - /// - public class SaveDataSharedFileStorage : IStorage - { - private SharedRef _baseStorage; - private SaveDataOpenTypeSetFileStorage.OpenType _type; - - public SaveDataSharedFileStorage(ref SharedRef baseStorage, - SaveDataOpenTypeSetFileStorage.OpenType type) - { - _baseStorage = SharedRef.CreateMove(ref baseStorage); - _type = type; - } - - public override void Dispose() - { - if (_baseStorage.HasValue) - _baseStorage.Get.UnsetOpenType(_type); - - _baseStorage.Destroy(); - - base.Dispose(); - } - - private Result AccessCheck(bool isWriteAccess) - { - if (_type == SaveDataOpenTypeSetFileStorage.OpenType.Internal) - { - if (_baseStorage.Get.IsInternalStorageInvalidated()) - return ResultFs.SaveDataPorterInvalidated.Log(); - } - else if (_type == SaveDataOpenTypeSetFileStorage.OpenType.Normal && isWriteAccess) - { - // Any opened internal file system will be invalid after a write to the normal file system - _baseStorage.Get.InvalidateInternalStorage(); - } - - return Result.Success; - } - - protected override Result DoRead(long offset, Span destination) - { - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: false); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.Read(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: true); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.Write(offset, source); - } - - protected override Result DoSetSize(long size) - { - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: true); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - Unsafe.SkipInit(out size); - - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: false); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.GetSize(out size); - } - - protected override Result DoFlush() - { - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: true); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.Flush(); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - using UniqueLockRef scopedLock = _baseStorage.Get.GetLock(); - - Result rc = AccessCheck(isWriteAccess: true); - if (rc.IsFailure()) return rc; - - return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } - } - - /// - /// Holds references to any open shared save data image files. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1). - public class SaveDataFileStorageHolder - { - [NonCopyable] - private struct Entry - { - private SharedRef _storage; - private SaveDataSpaceId _spaceId; - private ulong _saveDataId; - - public Entry(ref SharedRef storage, SaveDataSpaceId spaceId, - ulong saveDataId) - { - _storage = SharedRef.CreateMove(ref storage); - _spaceId = spaceId; - _saveDataId = saveDataId; - } - - public void Dispose() - { - _storage.Destroy(); - } - - public bool Contains(SaveDataSpaceId spaceId, ulong saveDataId) - { - return _spaceId == spaceId && _saveDataId == saveDataId; - } - - public SharedRef GetStorage() - { - return SharedRef.CreateCopy(in _storage); - } - } - - private LinkedList _entryList; - - // LibHac additions - private FileSystemServer _fsServer; - private ref SaveDataSharedFileStorageGlobals Globals => ref _fsServer.Globals.SaveDataSharedFileStorage; - - public SaveDataFileStorageHolder(FileSystemServer fsServer) - { - _fsServer = fsServer; - _entryList = new LinkedList(); } public void Dispose() { - using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); + _storage.Destroy(); + } - LinkedListNode currentEntry = _entryList.First; + public bool Contains(SaveDataSpaceId spaceId, ulong saveDataId) + { + return _spaceId == spaceId && _saveDataId == saveDataId; + } - while (currentEntry is not null) + public SharedRef GetStorage() + { + return SharedRef.CreateCopy(in _storage); + } + } + + private LinkedList _entryList; + + // LibHac additions + private FileSystemServer _fsServer; + private ref SaveDataSharedFileStorageGlobals Globals => ref _fsServer.Globals.SaveDataSharedFileStorage; + + public SaveDataFileStorageHolder(FileSystemServer fsServer) + { + _fsServer = fsServer; + _entryList = new LinkedList(); + } + + public void Dispose() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); + + LinkedListNode currentEntry = _entryList.First; + + while (currentEntry is not null) + { + ref Entry entry = ref currentEntry.ValueRef; + _entryList.Remove(currentEntry); + entry.Dispose(); + + currentEntry = _entryList.First; + } + } + + public Result OpenSaveDataStorage(ref SharedRef outSaveDataStorage, + ref SharedRef baseFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, OpenMode mode, + Optional type) + { + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + + using var saveImageName = new Path(); + Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; + + // If an open type isn't specified, open the save without the shared file storage layer + if (!type.HasValue) + { + using var fileStorage = new SharedRef(new FileStorageBasedFileSystem()); + rc = fileStorage.Get.Initialize(ref baseFileSystem, in saveImageName, mode); + if (rc.IsFailure()) return rc; + + outSaveDataStorage.SetByMove(ref fileStorage.Ref()); + return Result.Success; + } + + using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); + + using SharedRef baseFileStorage = GetStorage(spaceId, saveDataId); + + if (baseFileStorage.HasValue) + { + rc = baseFileStorage.Get.SetOpenType(type.ValueRo); + if (rc.IsFailure()) return rc; + } + else + { + baseFileStorage.Reset(new SaveDataOpenTypeSetFileStorage(_fsServer, spaceId, saveDataId)); + rc = baseFileStorage.Get.Initialize(ref baseFileSystem, in saveImageName, mode, type.ValueRo); + if (rc.IsFailure()) return rc; + + using SharedRef baseFileStorageCopy = + SharedRef.CreateCopy(in baseFileStorage); + + rc = Register(ref baseFileStorageCopy.Ref(), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + } + + outSaveDataStorage.Reset(new SaveDataSharedFileStorage(ref baseFileStorage.Ref(), type.ValueRo)); + + return Result.Success; + } + + public Result Register(ref SharedRef storage, SaveDataSpaceId spaceId, + ulong saveDataId) + { + Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); + + _entryList.AddLast(new Entry(ref storage, spaceId, saveDataId)); + + return Result.Success; + } + + public SharedRef GetStorage(SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); + + LinkedListNode currentEntry = _entryList.First; + + while (currentEntry is not null) + { + if (currentEntry.ValueRef.Contains(spaceId, saveDataId)) + { + return currentEntry.ValueRef.GetStorage(); + } + + currentEntry = currentEntry.Next; + } + + return new SharedRef(); + } + + public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); + + LinkedListNode currentEntry = _entryList.First; + + while (currentEntry is not null) + { + if (currentEntry.ValueRef.Contains(spaceId, saveDataId)) { ref Entry entry = ref currentEntry.ValueRef; _entryList.Remove(currentEntry); entry.Dispose(); - currentEntry = _entryList.First; - } - } - - public Result OpenSaveDataStorage(ref SharedRef outSaveDataStorage, - ref SharedRef baseFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, OpenMode mode, - Optional type) - { - // Hack around error CS8350. - const int bufferLength = 0x12; - Span buffer = stackalloc byte[bufferLength]; - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); - Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - - using var saveImageName = new Path(); - Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer, saveDataId); - if (rc.IsFailure()) return rc; - - // If an open type isn't specified, open the save without the shared file storage layer - if (!type.HasValue) - { - using var fileStorage = new SharedRef(new FileStorageBasedFileSystem()); - rc = fileStorage.Get.Initialize(ref baseFileSystem, in saveImageName, mode); - if (rc.IsFailure()) return rc; - - outSaveDataStorage.SetByMove(ref fileStorage.Ref()); - return Result.Success; } - using ScopedLock scopedLock = ScopedLock.Lock(ref Globals.Mutex); - - using SharedRef baseFileStorage = GetStorage(spaceId, saveDataId); - - if (baseFileStorage.HasValue) - { - rc = baseFileStorage.Get.SetOpenType(type.ValueRo); - if (rc.IsFailure()) return rc; - } - else - { - baseFileStorage.Reset(new SaveDataOpenTypeSetFileStorage(_fsServer, spaceId, saveDataId)); - rc = baseFileStorage.Get.Initialize(ref baseFileSystem, in saveImageName, mode, type.ValueRo); - if (rc.IsFailure()) return rc; - - using SharedRef baseFileStorageCopy = - SharedRef.CreateCopy(in baseFileStorage); - - rc = Register(ref baseFileStorageCopy.Ref(), spaceId, saveDataId); - if (rc.IsFailure()) return rc; - } - - outSaveDataStorage.Reset(new SaveDataSharedFileStorage(ref baseFileStorage.Ref(), type.ValueRo)); - - return Result.Success; - } - - public Result Register(ref SharedRef storage, SaveDataSpaceId spaceId, - ulong saveDataId) - { - Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); - - _entryList.AddLast(new Entry(ref storage, spaceId, saveDataId)); - - return Result.Success; - } - - public SharedRef GetStorage(SaveDataSpaceId spaceId, ulong saveDataId) - { - Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); - - LinkedListNode currentEntry = _entryList.First; - - while (currentEntry is not null) - { - if (currentEntry.ValueRef.Contains(spaceId, saveDataId)) - { - return currentEntry.ValueRef.GetStorage(); - } - - currentEntry = currentEntry.Next; - } - - return new SharedRef(); - } - - public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) - { - Assert.SdkRequires(Globals.Mutex.IsLockedByCurrentThread()); - - LinkedListNode currentEntry = _entryList.First; - - while (currentEntry is not null) - { - if (currentEntry.ValueRef.Contains(spaceId, saveDataId)) - { - ref Entry entry = ref currentEntry.ValueRef; - _entryList.Remove(currentEntry); - entry.Dispose(); - - } - - currentEntry = currentEntry.Next; - } + currentEntry = currentEntry.Next; } } } diff --git a/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs b/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs index ebbc3501..e3c9b86a 100644 --- a/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs +++ b/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs @@ -1,35 +1,34 @@ using System; using LibHac.Crypto; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public class SaveDataTransferCryptoConfiguration { - public class SaveDataTransferCryptoConfiguration + private Data100 _tokenSigningKeyModulus; + private Data100 _keySeedPackageSigningKeyModulus; + private Data100 _kekEncryptionKeyModulus; + private Data100 _keyPackageSigningModulus; + + public Span TokenSigningKeyModulus => _tokenSigningKeyModulus.Data; + public Span KeySeedPackageSigningKeyModulus => _keySeedPackageSigningKeyModulus.Data; + public Span KekEncryptionKeyModulus => _kekEncryptionKeyModulus.Data; + public Span KeyPackageSigningModulus => _keyPackageSigningModulus.Data; + + public SaveTransferAesKeyGenerator GenerateAesKey { get; set; } + public RandomDataGenerator GenerateRandomData { get; set; } + public SaveTransferCmacGenerator GenerateCmac { get; set; } + + public enum KeyIndex { - private Data100 _tokenSigningKeyModulus; - private Data100 _keySeedPackageSigningKeyModulus; - private Data100 _kekEncryptionKeyModulus; - private Data100 _keyPackageSigningModulus; - - public Span TokenSigningKeyModulus => _tokenSigningKeyModulus.Data; - public Span KeySeedPackageSigningKeyModulus => _keySeedPackageSigningKeyModulus.Data; - public Span KekEncryptionKeyModulus => _kekEncryptionKeyModulus.Data; - public Span KeyPackageSigningModulus => _keyPackageSigningModulus.Data; - - public SaveTransferAesKeyGenerator GenerateAesKey { get; set; } - public RandomDataGenerator GenerateRandomData { get; set; } - public SaveTransferCmacGenerator GenerateCmac { get; set; } - - public enum KeyIndex - { - SaveDataTransferToken, - SaveDataTransfer, - SaveDataTransferKeySeedPackage, - CloudBackUpInitialData, - CloudBackUpImportContext, - CloudBackUpInitialDataMac, - SaveDataRepairKeyPackage, - SaveDataRepairInitialDataMacBeforeRepair, - SaveDataRepairInitialDataMacAfterRepair - } + SaveDataTransferToken, + SaveDataTransfer, + SaveDataTransferKeySeedPackage, + CloudBackUpInitialData, + CloudBackUpImportContext, + CloudBackUpInitialDataMac, + SaveDataRepairKeyPackage, + SaveDataRepairInitialDataMacBeforeRepair, + SaveDataRepairInitialDataMacAfterRepair } } diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs index 172560bf..862b2620 100644 --- a/src/LibHac/FsSrv/Sf/FspPath.cs +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -6,47 +6,46 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] +public readonly struct FspPath { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] - public readonly struct FspPath - { - internal const int MaxLength = 0x300; + internal const int MaxLength = 0x300; #if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; #endif - public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); - public static Result FromSpan(out FspPath fspPath, ReadOnlySpan path) - { - UnsafeHelpers.SkipParamInit(out fspPath); + public static Result FromSpan(out FspPath fspPath, ReadOnlySpan path) + { + UnsafeHelpers.SkipParamInit(out fspPath); - Span str = SpanHelpers.AsByteSpan(ref fspPath); + Span str = SpanHelpers.AsByteSpan(ref fspPath); - // Ensure null terminator even if the creation fails for safety - str[MaxLength] = 0; + // Ensure null terminator even if the creation fails for safety + str[MaxLength] = 0; - var sb = new U8StringBuilder(str); - bool overflowed = sb.Append(path).Overflowed; + var sb = new U8StringBuilder(str); + bool overflowed = sb.Append(path).Overflowed; - return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; - } - - public static void CreateEmpty(out FspPath fspPath) - { - UnsafeHelpers.SkipParamInit(out fspPath); - SpanHelpers.AsByteSpan(ref fspPath)[0] = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator U8Span(in FspPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); - - public override string ToString() => StringUtils.Utf8ZToString(Str); + return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; } + + public static void CreateEmpty(out FspPath fspPath) + { + UnsafeHelpers.SkipParamInit(out fspPath); + SpanHelpers.AsByteSpan(ref fspPath)[0] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator U8Span(in FspPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); + + public override string ToString() => StringUtils.Utf8ZToString(Str); } diff --git a/src/LibHac/FsSrv/Sf/IDeviceOperator.cs b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs index f8185a1c..3a22e3d9 100644 --- a/src/LibHac/FsSrv/Sf/IDeviceOperator.cs +++ b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs @@ -1,11 +1,10 @@ using System; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IDeviceOperator : IDisposable { - public interface IDeviceOperator : IDisposable - { - Result IsSdCardInserted(out bool isInserted); - Result IsGameCardInserted(out bool isInserted); - Result GetGameCardHandle(out GameCardHandle handle); - } -} \ No newline at end of file + Result IsSdCardInserted(out bool isInserted); + Result IsGameCardInserted(out bool isInserted); + Result GetGameCardHandle(out GameCardHandle handle); +} diff --git a/src/LibHac/FsSrv/Sf/IDirectory.cs b/src/LibHac/FsSrv/Sf/IDirectory.cs index 086e9e9a..28000787 100644 --- a/src/LibHac/FsSrv/Sf/IDirectory.cs +++ b/src/LibHac/FsSrv/Sf/IDirectory.cs @@ -1,11 +1,10 @@ using System; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IDirectory : IDisposable { - public interface IDirectory : IDisposable - { - Result Read(out long entriesRead, OutBuffer entryBuffer); - Result GetEntryCount(out long entryCount); - } + Result Read(out long entriesRead, OutBuffer entryBuffer); + Result GetEntryCount(out long entryCount); } diff --git a/src/LibHac/FsSrv/Sf/IEventNotifier.cs b/src/LibHac/FsSrv/Sf/IEventNotifier.cs index 2097a577..b3e25435 100644 --- a/src/LibHac/FsSrv/Sf/IEventNotifier.cs +++ b/src/LibHac/FsSrv/Sf/IEventNotifier.cs @@ -1,10 +1,9 @@ using System; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IEventNotifier : IDisposable { - public interface IEventNotifier : IDisposable - { - Result GetEventHandle(out NativeHandle handle); - } + Result GetEventHandle(out NativeHandle handle); } diff --git a/src/LibHac/FsSrv/Sf/IFile.cs b/src/LibHac/FsSrv/Sf/IFile.cs index 95d086df..88466e9f 100644 --- a/src/LibHac/FsSrv/Sf/IFile.cs +++ b/src/LibHac/FsSrv/Sf/IFile.cs @@ -2,16 +2,15 @@ using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IFile : IDisposable { - public interface IFile : IDisposable - { - Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option); - Result Write(long offset, InBuffer source, long size, WriteOption option); - Result Flush(); - Result SetSize(long size); - Result GetSize(out long size); - Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); - Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size); - } -} \ No newline at end of file + Result Read(out long bytesRead, long offset, OutBuffer destination, long size, ReadOption option); + Result Write(long offset, InBuffer source, long size, WriteOption option); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); + Result OperateRangeWithBuffer(OutBuffer outBuffer, InBuffer inBuffer, int operationId, long offset, long size); +} diff --git a/src/LibHac/FsSrv/Sf/IFileSystem.cs b/src/LibHac/FsSrv/Sf/IFileSystem.cs index f535edcb..9eaac0ad 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystem.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystem.cs @@ -5,26 +5,25 @@ using LibHac.Sf; using IFileSf = LibHac.FsSrv.Sf.IFile; using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IFileSystem : IDisposable { - public interface IFileSystem : IDisposable - { - Result GetImpl(ref SharedRef fileSystem); - Result CreateFile(in Path path, long size, int option); - Result DeleteFile(in Path path); - Result CreateDirectory(in Path path); - Result DeleteDirectory(in Path path); - Result DeleteDirectoryRecursively(in Path path); - Result RenameFile(in Path currentPath, in Path newPath); - Result RenameDirectory(in Path currentPath, in Path newPath); - Result GetEntryType(out uint entryType, in Path path); - Result OpenFile(ref SharedRef outFile, in Path path, uint mode); - Result OpenDirectory(ref SharedRef outDirectory, in Path path, uint mode); - Result Commit(); - Result GetFreeSpaceSize(out long freeSpace, in Path path); - Result GetTotalSpaceSize(out long totalSpace, in Path path); - Result CleanDirectoryRecursively(in Path path); - Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path); - Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path); - } + Result GetImpl(ref SharedRef fileSystem); + Result CreateFile(in Path path, long size, int option); + Result DeleteFile(in Path path); + Result CreateDirectory(in Path path); + Result DeleteDirectory(in Path path); + Result DeleteDirectoryRecursively(in Path path); + Result RenameFile(in Path currentPath, in Path newPath); + Result RenameDirectory(in Path currentPath, in Path newPath); + Result GetEntryType(out uint entryType, in Path path); + Result OpenFile(ref SharedRef outFile, in Path path, uint mode); + Result OpenDirectory(ref SharedRef outDirectory, in Path path, uint mode); + Result Commit(); + Result GetFreeSpaceSize(out long freeSpace, in Path path); + Result GetTotalSpaceSize(out long totalSpace, in Path path); + Result CleanDirectoryRecursively(in Path path); + Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path); + Result QueryEntry(OutBuffer outBuffer, InBuffer inBuffer, int queryId, in Path path); } diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs index ce7aafa3..d6145bb0 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs @@ -8,125 +8,124 @@ using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IFileSf = LibHac.FsSrv.Sf.IFile; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IFileSystemProxy : IDisposable { - public interface IFileSystemProxy : IDisposable - { - Result SetCurrentProcess(ulong processId); - Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem); - Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, ProgramId programId, FileSystemProxyType fsType); - Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, ulong id, FileSystemProxyType fsType); - Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, ProgramId programId); - Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, BisPartitionId partitionId); - Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId); - Result InvalidateBisCache(); - Result OpenHostFileSystem(ref SharedRef outFileSystem, in FspPath path); - Result OpenSdCardFileSystem(ref SharedRef outFileSystem); - Result FormatSdCardFileSystem(); - Result DeleteSaveDataFileSystem(ulong saveDataId); - Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo); - Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo); - Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds); - Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); - Result FormatSdCardDryRun(); - Result IsExFatSupported(out bool isSupported); - Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, GameCardPartitionRaw partitionId); - Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, GameCardPartition partitionId); - Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); - Result DeleteCacheStorage(ushort index); - Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index); - Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt); - Result OpenHostFileSystemWithOption(ref SharedRef outFileSystem, in FspPath path, MountHostOption option); - Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result OpenReadOnlySaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId); - Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId); - Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer); - Result OpenSaveDataInfoReader(ref SharedRef outInfoReader); - Result OpenSaveDataInfoReaderBySaveDataSpaceId(ref SharedRef outInfoReader, SaveDataSpaceId spaceId); - Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader); - Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId); - Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId); - Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); - Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, in SaveDataFilter filter); - Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, SaveDataSpaceId spaceId, in SaveDataFilter filter); - Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); - Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); - Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer); - Result OpenSaveDataMetaFile(ref SharedRef outFile, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType type); - Result OpenSaveDataTransferManager(ref SharedRef outTransferManager); - Result OpenSaveDataTransferManagerVersion2(ref SharedRef outTransferManager); - Result OpenSaveDataTransferProhibiter(ref SharedRef outProhibiter, Ncm.ApplicationId applicationId); - Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, int startIndex, int bufferIdCount); - Result OpenSaveDataTransferManagerForSaveDataRepair(ref SharedRef outTransferManager); - Result OpenSaveDataMover(ref SharedRef outSaveDataMover, SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize); - Result OpenSaveDataTransferManagerForRepair(ref SharedRef outTransferManager); - Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, ImageDirectoryId directoryId); - Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId); - Result FormatBaseFileSystem(BaseFileSystemId fileSystemId); - Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, ContentStorageId storageId); - Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, CloudBackupWorkStorageId storageId); - Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId); - Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage); - Result OpenDataStorageByProgramId(ref SharedRef outStorage, ProgramId programId); - Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, StorageId storageId); - Result OpenPatchDataStorageByCurrentProcess(ref SharedRef outStorage); - Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, byte programIndex); - Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, byte programIndex); - Result OpenDeviceOperator(ref SharedRef outDeviceOperator); - Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier); - Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier); - Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier); - Result NotifySystemDataUpdateEvent(); - Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent); - Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); - Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer); - Result CorruptSaveDataFileSystem(ulong saveDataId); - Result CreatePaddingFile(long size); - Result DeleteAllPaddingFiles(); - Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId); - Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey); - Result UnregisterAllExternalKey(); - Result GetRightsIdByPath(out RightsId rightsId, in FspPath path); - Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path); - Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference); - Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); - Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, OutBuffer readBuffer); - Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); - Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId); - Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId); - Result UnregisterExternalKey(in RightsId rightsId); - Result SetSdCardEncryptionSeed(in EncryptionSeed seed); - Result SetSdCardAccessibility(bool isAccessible); - Result IsSdCardAccessible(out bool isAccessible); - Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outEventNotifier, ulong processId, bool notifyOnDeepRetry); - Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle); - Result IsAccessFailureDetected(out bool isDetected, ulong processId); - Result ResolveAccessFailure(ulong processId); - Result AbandonAccessFailure(ulong processId); - Result GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo); - Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount); - Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path); - Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize); - Result SetSaveDataRootPath(in FspPath path); - Result DisableAutoSaveDataCreation(); - Result SetGlobalAccessLogMode(GlobalAccessLogMode mode); - Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode); - Result OutputAccessLogToSdCard(InBuffer textBuffer); - Result RegisterUpdatePartition(); - Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem); - Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo); - Result GetProgramIndexForAccessLog(out int programIndex, out int programCount); - Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType); - Result UnsetSaveDataRootPath(); - Result FlushAccessLogOnSdCard(); - Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo); - Result OutputMultiProgramTagAccessLog(); - Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key); - Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset); - Result OpenMultiCommitManager(ref SharedRef outCommitManager); - Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize); - } -} \ No newline at end of file + Result SetCurrentProcess(ulong processId); + Result OpenDataFileSystemByCurrentProcess(ref SharedRef outFileSystem); + Result OpenFileSystemWithPatch(ref SharedRef outFileSystem, ProgramId programId, FileSystemProxyType fsType); + Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, ulong id, FileSystemProxyType fsType); + Result OpenDataFileSystemByProgramId(ref SharedRef outFileSystem, ProgramId programId); + Result OpenBisFileSystem(ref SharedRef outFileSystem, in FspPath rootPath, BisPartitionId partitionId); + Result OpenBisStorage(ref SharedRef outStorage, BisPartitionId partitionId); + Result InvalidateBisCache(); + Result OpenHostFileSystem(ref SharedRef outFileSystem, in FspPath path); + Result OpenSdCardFileSystem(ref SharedRef outFileSystem); + Result FormatSdCardFileSystem(); + Result DeleteSaveDataFileSystem(ulong saveDataId); + Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo); + Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo); + Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds); + Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); + Result FormatSdCardDryRun(); + Result IsExFatSupported(out bool isSupported); + Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenGameCardStorage(ref SharedRef outStorage, GameCardHandle handle, GameCardPartitionRaw partitionId); + Result OpenGameCardFileSystem(ref SharedRef outFileSystem, GameCardHandle handle, GameCardPartition partitionId); + Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); + Result DeleteCacheStorage(ushort index); + Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index); + Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt); + Result OpenHostFileSystemWithOption(ref SharedRef outFileSystem, in FspPath path, MountHostOption option); + Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenSaveDataFileSystemBySystemSaveDataId(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenReadOnlySaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId); + Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId); + Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer); + Result OpenSaveDataInfoReader(ref SharedRef outInfoReader); + Result OpenSaveDataInfoReaderBySaveDataSpaceId(ref SharedRef outInfoReader, SaveDataSpaceId spaceId); + Result OpenSaveDataInfoReaderOnlyCacheStorage(ref SharedRef outInfoReader); + Result OpenSaveDataInternalStorageFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId); + Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId); + Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); + Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, in SaveDataFilter filter); + Result OpenSaveDataInfoReaderWithFilter(ref SharedRef outInfoReader, SaveDataSpaceId spaceId, in SaveDataFilter filter); + Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); + Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer); + Result OpenSaveDataMetaFile(ref SharedRef outFile, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType type); + Result OpenSaveDataTransferManager(ref SharedRef outTransferManager); + Result OpenSaveDataTransferManagerVersion2(ref SharedRef outTransferManager); + Result OpenSaveDataTransferProhibiter(ref SharedRef outProhibiter, Ncm.ApplicationId applicationId); + Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, int startIndex, int bufferIdCount); + Result OpenSaveDataTransferManagerForSaveDataRepair(ref SharedRef outTransferManager); + Result OpenSaveDataMover(ref SharedRef outSaveDataMover, SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize); + Result OpenSaveDataTransferManagerForRepair(ref SharedRef outTransferManager); + Result OpenImageDirectoryFileSystem(ref SharedRef outFileSystem, ImageDirectoryId directoryId); + Result OpenBaseFileSystem(ref SharedRef outFileSystem, BaseFileSystemId fileSystemId); + Result FormatBaseFileSystem(BaseFileSystemId fileSystemId); + Result OpenContentStorageFileSystem(ref SharedRef outFileSystem, ContentStorageId storageId); + Result OpenCloudBackupWorkStorageFileSystem(ref SharedRef outFileSystem, CloudBackupWorkStorageId storageId); + Result OpenCustomStorageFileSystem(ref SharedRef outFileSystem, CustomStorageId storageId); + Result OpenDataStorageByCurrentProcess(ref SharedRef outStorage); + Result OpenDataStorageByProgramId(ref SharedRef outStorage, ProgramId programId); + Result OpenDataStorageByDataId(ref SharedRef outStorage, DataId dataId, StorageId storageId); + Result OpenPatchDataStorageByCurrentProcess(ref SharedRef outStorage); + Result OpenDataFileSystemWithProgramIndex(ref SharedRef outFileSystem, byte programIndex); + Result OpenDataStorageWithProgramIndex(ref SharedRef outStorage, byte programIndex); + Result OpenDeviceOperator(ref SharedRef outDeviceOperator); + Result OpenSdCardDetectionEventNotifier(ref SharedRef outEventNotifier); + Result OpenGameCardDetectionEventNotifier(ref SharedRef outEventNotifier); + Result OpenSystemDataUpdateEventNotifier(ref SharedRef outEventNotifier); + Result NotifySystemDataUpdateEvent(); + Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent); + Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); + Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer); + Result CorruptSaveDataFileSystem(ulong saveDataId); + Result CreatePaddingFile(long size); + Result DeleteAllPaddingFiles(); + Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId); + Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey); + Result UnregisterAllExternalKey(); + Result GetRightsIdByPath(out RightsId rightsId, in FspPath path); + Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path); + Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference); + Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); + Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, OutBuffer readBuffer); + Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); + Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId); + Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId); + Result UnregisterExternalKey(in RightsId rightsId); + Result SetSdCardEncryptionSeed(in EncryptionSeed seed); + Result SetSdCardAccessibility(bool isAccessible); + Result IsSdCardAccessible(out bool isAccessible); + Result OpenAccessFailureDetectionEventNotifier(ref SharedRef outEventNotifier, ulong processId, bool notifyOnDeepRetry); + Result GetAccessFailureDetectionEvent(out NativeHandle eventHandle); + Result IsAccessFailureDetected(out bool isDetected, ulong processId); + Result ResolveAccessFailure(ulong processId); + Result AbandonAccessFailure(ulong processId); + Result GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo); + Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount); + Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path); + Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize); + Result SetSaveDataRootPath(in FspPath path); + Result DisableAutoSaveDataCreation(); + Result SetGlobalAccessLogMode(GlobalAccessLogMode mode); + Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode); + Result OutputAccessLogToSdCard(InBuffer textBuffer); + Result RegisterUpdatePartition(); + Result OpenRegisteredUpdatePartition(ref SharedRef outFileSystem); + Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo); + Result GetProgramIndexForAccessLog(out int programIndex, out int programCount); + Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType); + Result UnsetSaveDataRootPath(); + Result FlushAccessLogOnSdCard(); + Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo); + Result OutputMultiProgramTagAccessLog(); + Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key); + Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset); + Result OpenMultiCommitManager(ref SharedRef outCommitManager); + Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize); +} diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs index 03574254..da531efb 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs @@ -4,14 +4,13 @@ using LibHac.Fs; using LibHac.Ncm; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.FsSrv.Sf -{ - public interface IFileSystemProxyForLoader : IDisposable - { - Result OpenCodeFileSystem(ref SharedRef fileSystem, - out CodeVerificationData verificationData, in FspPath path, ProgramId programId); +namespace LibHac.FsSrv.Sf; - Result IsArchivedProgram(out bool isArchived, ulong processId); - Result SetCurrentProcess(ulong processId); - } +public interface IFileSystemProxyForLoader : IDisposable +{ + Result OpenCodeFileSystem(ref SharedRef fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId); + + Result IsArchivedProgram(out bool isArchived, ulong processId); + Result SetCurrentProcess(ulong processId); } diff --git a/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs b/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs index 0c9db1a0..190a4398 100644 --- a/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs +++ b/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs @@ -2,11 +2,10 @@ using LibHac.Common; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IMultiCommitManager : IDisposable { - public interface IMultiCommitManager : IDisposable - { - Result Add(ref SharedRef fileSystem); - Result Commit(); - } + Result Add(ref SharedRef fileSystem); + Result Commit(); } diff --git a/src/LibHac/FsSrv/Sf/IProgramRegistry.cs b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs index 47e4211b..00a8cfb5 100644 --- a/src/LibHac/FsSrv/Sf/IProgramRegistry.cs +++ b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs @@ -2,14 +2,13 @@ using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.FsSrv.Sf -{ - public interface IProgramRegistry : IDisposable - { - Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - InBuffer accessControlData, InBuffer accessControlDescriptor); +namespace LibHac.FsSrv.Sf; - Result UnregisterProgram(ulong processId); - Result SetCurrentProcess(ulong processId); - } +public interface IProgramRegistry : IDisposable +{ + Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + InBuffer accessControlData, InBuffer accessControlDescriptor); + + Result UnregisterProgram(ulong processId); + Result SetCurrentProcess(ulong processId); } diff --git a/src/LibHac/FsSrv/Sf/ISaveDataChunkExporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataChunkExporter.cs index 4b66c019..5b90ffe6 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataChunkExporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataChunkExporter.cs @@ -1,11 +1,10 @@ using System; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataChunkExporter : IDisposable { - public interface ISaveDataChunkExporter : IDisposable - { - public Result Pull(out ulong bytesRead, OutBuffer buffer, ulong size); - public Result GetRestRawDataSize(out long remainingSize); - } -} \ No newline at end of file + public Result Pull(out ulong bytesRead, OutBuffer buffer, ulong size); + public Result GetRestRawDataSize(out long remainingSize); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataChunkImporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataChunkImporter.cs index 4ac58f3c..4cfcaef5 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataChunkImporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataChunkImporter.cs @@ -1,10 +1,9 @@ using System; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataChunkImporter : IDisposable { - public interface ISaveDataChunkImporter : IDisposable - { - public Result Push(InBuffer buffer, ulong size); - } -} \ No newline at end of file + public Result Push(InBuffer buffer, ulong size); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataChunkIterator.cs b/src/LibHac/FsSrv/Sf/ISaveDataChunkIterator.cs index dea391b1..3441454d 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataChunkIterator.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataChunkIterator.cs @@ -1,11 +1,10 @@ using System; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataChunkIterator : IDisposable { - public interface ISaveDataChunkIterator : IDisposable - { - public Result Next(); - public Result IsEnd(out bool isEnd); - public Result GetId(out uint chunkId); - } -} \ No newline at end of file + public Result Next(); + public Result IsEnd(out bool isEnd); + public Result GetId(out uint chunkId); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs index a3fc05d6..aaad50a4 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataDivisionExporter.cs @@ -3,22 +3,21 @@ using LibHac.Common; using LibHac.Fs.Impl; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataDivisionExporter : IDisposable { - public interface ISaveDataDivisionExporter : IDisposable - { - public Result SetDivisionCount(int divisionCount); - public Result ReadSaveDataExtraData(OutBuffer extraData); - public Result OpenSaveDataDiffChunkIterator(ref SharedRef outIterator); - public Result OpenSaveDataChunkExporter(ref SharedRef outExporter, uint chunkId); - public Result CancelExport(); - public Result SuspendExport(OutBuffer exportContext); - public Result GetKeySeed(out KeySeed keySeed); - public Result GetInitialDataMac(out InitialDataMac initialDataMac); - public Result FinalizeExport(); - public Result GetInitialDataMacKeyGeneration(out int keyGeneration); - public Result GetImportInitialDataAad(out InitialDataAad initialDataAad); - public Result SetExportInitialDataAad(in InitialDataAad initialDataAad); - public Result GetReportInfo(out ImportReportInfo reportInfo); - } -} \ No newline at end of file + public Result SetDivisionCount(int divisionCount); + public Result ReadSaveDataExtraData(OutBuffer extraData); + public Result OpenSaveDataDiffChunkIterator(ref SharedRef outIterator); + public Result OpenSaveDataChunkExporter(ref SharedRef outExporter, uint chunkId); + public Result CancelExport(); + public Result SuspendExport(OutBuffer exportContext); + public Result GetKeySeed(out KeySeed keySeed); + public Result GetInitialDataMac(out InitialDataMac initialDataMac); + public Result FinalizeExport(); + public Result GetInitialDataMacKeyGeneration(out int keyGeneration); + public Result GetImportInitialDataAad(out InitialDataAad initialDataAad); + public Result SetExportInitialDataAad(in InitialDataAad initialDataAad); + public Result GetReportInfo(out ImportReportInfo reportInfo); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataDivisionImporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataDivisionImporter.cs index b4aab60b..e22afbed 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataDivisionImporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataDivisionImporter.cs @@ -3,20 +3,19 @@ using LibHac.Common; using LibHac.Fs.Impl; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataDivisionImporter : IDisposable { - public interface ISaveDataDivisionImporter : IDisposable - { - public Result ReadSaveDataExtraData(OutBuffer extraData); - public Result OpenSaveDataDiffChunkIterator(ref SharedRef outIterator); - public Result InitializeImport(out long remaining, long sizeToProcess); - public Result FinalizeImport(); - public Result CancelImport(); - public Result GetImportContext(OutBuffer context); - public Result SuspendImport(); - public Result FinalizeImportWithoutSwap(); - public Result OpenSaveDataChunkImporter(ref SharedRef outImporter, uint chunkId); - public Result GetImportInitialDataAad(out InitialDataAad initialDataAad); - public Result GetReportInfo(out ImportReportInfo reportInfo); - } -} \ No newline at end of file + public Result ReadSaveDataExtraData(OutBuffer extraData); + public Result OpenSaveDataDiffChunkIterator(ref SharedRef outIterator); + public Result InitializeImport(out long remaining, long sizeToProcess); + public Result FinalizeImport(); + public Result CancelImport(); + public Result GetImportContext(OutBuffer context); + public Result SuspendImport(); + public Result FinalizeImportWithoutSwap(); + public Result OpenSaveDataChunkImporter(ref SharedRef outImporter, uint chunkId); + public Result GetImportInitialDataAad(out InitialDataAad initialDataAad); + public Result GetReportInfo(out ImportReportInfo reportInfo); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataExporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataExporter.cs index dfa498f0..8bd65555 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataExporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataExporter.cs @@ -2,13 +2,12 @@ using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataExporter : IDisposable { - public interface ISaveDataExporter : IDisposable - { - public Result GetSaveDataInfo(out SaveDataInfo info); - public Result GetRestSize(out ulong remainingSize); - public Result Pull(out ulong bytesRead, OutBuffer buffer); - public Result PullInitialData(OutBuffer initialData); - } -} \ No newline at end of file + public Result GetSaveDataInfo(out SaveDataInfo info); + public Result GetRestSize(out ulong remainingSize); + public Result Pull(out ulong bytesRead, OutBuffer buffer); + public Result PullInitialData(OutBuffer initialData); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataImporter.cs b/src/LibHac/FsSrv/Sf/ISaveDataImporter.cs index 289fd424..05cc6268 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataImporter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataImporter.cs @@ -2,14 +2,13 @@ using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataImporter : IDisposable { - public interface ISaveDataImporter : IDisposable - { - public Result GetSaveDataInfo(out SaveDataInfo info); - public Result GetRestSize(out ulong remainingSize); - public Result Push(InBuffer buffer); - // Can't name the method "Finalize" because it's basically a reserved method in .NET - public Result FinalizeImport(); - } -} \ No newline at end of file + public Result GetSaveDataInfo(out SaveDataInfo info); + public Result GetRestSize(out ulong remainingSize); + public Result Push(InBuffer buffer); + // Can't name the method "Finalize" because it's basically a reserved method in .NET + public Result FinalizeImport(); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs b/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs index 8512a22f..f8b63101 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs @@ -2,24 +2,23 @@ using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +/// +/// Iterates through the of the save data +/// in a single save data space. +/// +public interface ISaveDataInfoReader : IDisposable { /// - /// Iterates through the of the save data - /// in a single save data space. + /// Returns the next entries. This method will continue writing + /// entries to until there is either no more space + /// in the buffer, or until there are no more entries to iterate. /// - public interface ISaveDataInfoReader : IDisposable - { - /// - /// Returns the next entries. This method will continue writing - /// entries to until there is either no more space - /// in the buffer, or until there are no more entries to iterate. - /// - /// If the method returns successfully, contains the number - /// of written to . - /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. - /// The buffer in which to write the . - /// The of the operation. - Result Read(out long readCount, OutBuffer saveDataInfoBuffer); - } + /// If the method returns successfully, contains the number + /// of written to . + /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. + /// The buffer in which to write the . + /// The of the operation. + Result Read(out long readCount, OutBuffer saveDataInfoBuffer); } diff --git a/src/LibHac/FsSrv/Sf/ISaveDataMover.cs b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs index 8e26bddd..165cb3d1 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataMover.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs @@ -1,11 +1,10 @@ using System; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataMover : IDisposable { - public interface ISaveDataMover : IDisposable - { - Result Register(ulong saveDataId); - Result Process(out long remainingSize, long sizeToProcess); - Result Cancel(); - } -} \ No newline at end of file + Result Register(ulong saveDataId); + Result Process(out long remainingSize, long sizeToProcess); + Result Cancel(); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataTransferManager.cs b/src/LibHac/FsSrv/Sf/ISaveDataTransferManager.cs index d637f140..0579874a 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataTransferManager.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataTransferManager.cs @@ -3,13 +3,12 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataTransferManager : IDisposable { - public interface ISaveDataTransferManager : IDisposable - { - public Result GetChallenge(OutBuffer challenge); - public Result SetToken(InBuffer token); - public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataImporter(ref SharedRef outImporter, out long requiredSize, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId); - } -} \ No newline at end of file + public Result GetChallenge(OutBuffer challenge); + public Result SetToken(InBuffer token); + public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataImporter(ref SharedRef outImporter, out long requiredSize, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForRepair.cs b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForRepair.cs index 623206ac..a81dee5e 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForRepair.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForRepair.cs @@ -3,11 +3,10 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataTransferManagerForRepair : IDisposable { - public interface ISaveDataTransferManagerForRepair : IDisposable - { - public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataImporter(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId); - } -} \ No newline at end of file + public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataImporter(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForSaveDataRepair.cs b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForSaveDataRepair.cs index b66f048e..90f5d10f 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForSaveDataRepair.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerForSaveDataRepair.cs @@ -3,17 +3,16 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataTransferManagerForSaveDataRepair : IDisposable { - public interface ISaveDataTransferManagerForSaveDataRepair : IDisposable - { - public Result GetChallenge(OutBuffer challenge); - public Result SetKeyPackage(InBuffer keyPackage); - public Result OpenSaveDataExporterAndGetEncryptedKey(ref SharedRef outExporter, out RsaEncryptedKey key, SaveDataSpaceId spaceId, ulong saveDataId); - public Result PrepareOpenSaveDataImporter(out RsaEncryptedKey key); - public Result OpenSaveDataImporterForSaveDataAfterRepair(ref SharedRef outImporter, InBuffer initialDataBeforeRepair, InBuffer initialDataAfterRepair, UserId userId, SaveDataSpaceId spaceId); - public Result OpenSaveDataImporterForSaveDataBeforeRepair(ref SharedRef outImporter, InBuffer initialData, UserId userId, SaveDataSpaceId spaceId); - public Result OpenSaveDataExporterWithKey(ref SharedRef outExporter, in AesKey key, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataImporterWithKey(ref SharedRef outImporter, in AesKey key, InBuffer initialData, UserId userId, ulong saveDataSpaceId); - } + public Result GetChallenge(OutBuffer challenge); + public Result SetKeyPackage(InBuffer keyPackage); + public Result OpenSaveDataExporterAndGetEncryptedKey(ref SharedRef outExporter, out RsaEncryptedKey key, SaveDataSpaceId spaceId, ulong saveDataId); + public Result PrepareOpenSaveDataImporter(out RsaEncryptedKey key); + public Result OpenSaveDataImporterForSaveDataAfterRepair(ref SharedRef outImporter, InBuffer initialDataBeforeRepair, InBuffer initialDataAfterRepair, UserId userId, SaveDataSpaceId spaceId); + public Result OpenSaveDataImporterForSaveDataBeforeRepair(ref SharedRef outImporter, InBuffer initialData, UserId userId, SaveDataSpaceId spaceId); + public Result OpenSaveDataExporterWithKey(ref SharedRef outExporter, in AesKey key, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataImporterWithKey(ref SharedRef outImporter, in AesKey key, InBuffer initialData, UserId userId, ulong saveDataSpaceId); } diff --git a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerWithDivision.cs b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerWithDivision.cs index 5b03526f..a84cae2b 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerWithDivision.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataTransferManagerWithDivision.cs @@ -3,22 +3,21 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataTransferManagerWithDivision : IDisposable { - public interface ISaveDataTransferManagerWithDivision : IDisposable - { - public Result GetChallenge(OutBuffer challenge); - public Result SetKeySeedPackage(InBuffer keySeedPackage); - public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataExporterForDiffExport(ref SharedRef outExporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataExporterByContext(ref SharedRef outExporter, InBuffer exportContext); - public Result OpenSaveDataImporterDeprecated(ref SharedRef outImporter, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId); - public Result OpenSaveDataImporterForDiffImport(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataImporterForDuplicateDiffImport(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); - public Result OpenSaveDataImporter(ref SharedRef outImporter, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId, bool useSwap); - public Result OpenSaveDataImporterByContext(ref SharedRef outImporter, InBuffer importContext); - public Result CancelSuspendingImport(Ncm.ApplicationId applicationId, in UserId userId); - public Result CancelSuspendingImportByAttribute(in SaveDataAttribute attribute); - public Result SwapSecondary(in SaveDataAttribute attribute, bool doSwap, long primaryCommitId); - } -} \ No newline at end of file + public Result GetChallenge(OutBuffer challenge); + public Result SetKeySeedPackage(InBuffer keySeedPackage); + public Result OpenSaveDataExporter(ref SharedRef outExporter, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataExporterForDiffExport(ref SharedRef outExporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataExporterByContext(ref SharedRef outExporter, InBuffer exportContext); + public Result OpenSaveDataImporterDeprecated(ref SharedRef outImporter, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId); + public Result OpenSaveDataImporterForDiffImport(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataImporterForDuplicateDiffImport(ref SharedRef outImporter, InBuffer initialData, SaveDataSpaceId spaceId, ulong saveDataId); + public Result OpenSaveDataImporter(ref SharedRef outImporter, InBuffer initialData, in UserId userId, SaveDataSpaceId spaceId, bool useSwap); + public Result OpenSaveDataImporterByContext(ref SharedRef outImporter, InBuffer importContext); + public Result CancelSuspendingImport(Ncm.ApplicationId applicationId, in UserId userId); + public Result CancelSuspendingImportByAttribute(in SaveDataAttribute attribute); + public Result SwapSecondary(in SaveDataAttribute attribute, bool doSwap, long primaryCommitId); +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataTransferProhibiter.cs b/src/LibHac/FsSrv/Sf/ISaveDataTransferProhibiter.cs index 62a904ad..14d86246 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataTransferProhibiter.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataTransferProhibiter.cs @@ -1,9 +1,8 @@ using System; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface ISaveDataTransferProhibiter : IDisposable { - public interface ISaveDataTransferProhibiter : IDisposable - { - // No methods. Disposing the service object removes the prohibition. - } -} \ No newline at end of file + // No methods. Disposing the service object removes the prohibition. +} diff --git a/src/LibHac/FsSrv/Sf/IStorage.cs b/src/LibHac/FsSrv/Sf/IStorage.cs index 5074556d..f832c5ff 100644 --- a/src/LibHac/FsSrv/Sf/IStorage.cs +++ b/src/LibHac/FsSrv/Sf/IStorage.cs @@ -2,15 +2,14 @@ using LibHac.Fs; using LibHac.Sf; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IStorage : IDisposable { - public interface IStorage : IDisposable - { - Result Read(long offset, OutBuffer destination, long size); - Result Write(long offset, InBuffer source, long size); - Result Flush(); - Result SetSize(long size); - Result GetSize(out long size); - Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); - } -} \ No newline at end of file + Result Read(long offset, OutBuffer destination, long size); + Result Write(long offset, InBuffer source, long size); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); +} diff --git a/src/LibHac/FsSrv/Sf/IWiper.cs b/src/LibHac/FsSrv/Sf/IWiper.cs index b19b0a7b..b86999e9 100644 --- a/src/LibHac/FsSrv/Sf/IWiper.cs +++ b/src/LibHac/FsSrv/Sf/IWiper.cs @@ -1,10 +1,9 @@ using System; -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +public interface IWiper : IDisposable { - public interface IWiper : IDisposable - { - public Result Startup(out long spaceToWipe); - public Result Process(out long remainingSpaceToWipe); - } + public Result Startup(out long spaceToWipe); + public Result Process(out long remainingSpaceToWipe); } diff --git a/src/LibHac/FsSrv/Sf/Path.cs b/src/LibHac/FsSrv/Sf/Path.cs index 54e89f0d..79996102 100644 --- a/src/LibHac/FsSrv/Sf/Path.cs +++ b/src/LibHac/FsSrv/Sf/Path.cs @@ -7,18 +7,17 @@ using LibHac.Fs; using System.Diagnostics; #endif -namespace LibHac.FsSrv.Sf +namespace LibHac.FsSrv.Sf; + +[StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax + 1)] +public readonly struct Path { - [StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax + 1)] - public readonly struct Path - { #if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; #endif - public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); - } + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); } diff --git a/src/LibHac/FsSrv/SpeedEmulationStorage.cs b/src/LibHac/FsSrv/SpeedEmulationStorage.cs index e014094b..25f926e9 100644 --- a/src/LibHac/FsSrv/SpeedEmulationStorage.cs +++ b/src/LibHac/FsSrv/SpeedEmulationStorage.cs @@ -2,53 +2,52 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +// Todo: Implement +public class SpeedEmulationStorage : IStorage { - // Todo: Implement - public class SpeedEmulationStorage : IStorage + private SharedRef _baseStorage; + + public SpeedEmulationStorage(ref SharedRef baseStorage, FileSystemServer fsServer) { - private SharedRef _baseStorage; + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } - public SpeedEmulationStorage(ref SharedRef baseStorage, FileSystemServer fsServer) - { - _baseStorage = SharedRef.CreateMove(ref baseStorage); - } + public override void Dispose() + { + _baseStorage.Destroy(); + base.Dispose(); + } - public override void Dispose() - { - _baseStorage.Destroy(); - base.Dispose(); - } + protected override Result DoRead(long offset, Span destination) + { + return _baseStorage.Get.Read(offset, destination); + } - protected override Result DoRead(long offset, Span destination) - { - return _baseStorage.Get.Read(offset, destination); - } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return _baseStorage.Get.Write(offset, source); + } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return _baseStorage.Get.Write(offset, source); - } + protected override Result DoFlush() + { + return _baseStorage.Get.Flush(); + } - protected override Result DoFlush() - { - return _baseStorage.Get.Flush(); - } + protected override Result DoSetSize(long size) + { + return _baseStorage.Get.SetSize(size); + } - protected override Result DoSetSize(long size) - { - return _baseStorage.Get.SetSize(size); - } + protected override Result DoGetSize(out long size) + { + return _baseStorage.Get.GetSize(out size); + } - protected override Result DoGetSize(out long size) - { - return _baseStorage.Get.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); } } diff --git a/src/LibHac/FsSrv/StatusReportService.cs b/src/LibHac/FsSrv/StatusReportService.cs index eb77b198..08d56406 100644 --- a/src/LibHac/FsSrv/StatusReportService.cs +++ b/src/LibHac/FsSrv/StatusReportService.cs @@ -2,142 +2,141 @@ using LibHac.Fs; using LibHac.Os; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct StatusReportService { - public readonly struct StatusReportService + private readonly StatusReportServiceImpl _serviceImpl; + + public StatusReportService(StatusReportServiceImpl serviceImpl) { - private readonly StatusReportServiceImpl _serviceImpl; - - public StatusReportService(StatusReportServiceImpl serviceImpl) - { - _serviceImpl = serviceImpl; - } - - public Result GetAndClearFileSystemProxyErrorInfo(out FileSystemProxyErrorInfo errorInfo) - { - return _serviceImpl.GetAndClearFileSystemProxyErrorInfo(out errorInfo); - } - - public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) - { - return _serviceImpl.GetAndClearMemoryReportInfo(out reportInfo); - } - - public Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType) - { - stackUsage = _serviceImpl.ReportStackUsage(threadType); - return Result.Success; - } + _serviceImpl = serviceImpl; } - public class StatusReportServiceImpl + public Result GetAndClearFileSystemProxyErrorInfo(out FileSystemProxyErrorInfo errorInfo) { - private Configuration _config; - private SdkMutexType _mutex; + return _serviceImpl.GetAndClearFileSystemProxyErrorInfo(out errorInfo); + } - public StatusReportServiceImpl(in Configuration configuration) + public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) + { + return _serviceImpl.GetAndClearMemoryReportInfo(out reportInfo); + } + + public Result GetFsStackUsage(out uint stackUsage, FsStackUsageThreadType threadType) + { + stackUsage = _serviceImpl.ReportStackUsage(threadType); + return Result.Success; + } +} + +public class StatusReportServiceImpl +{ + private Configuration _config; + private SdkMutexType _mutex; + + public StatusReportServiceImpl(in Configuration configuration) + { + _config = configuration; + _mutex.Initialize(); + } + + public struct Configuration + { + public NcaFileSystemServiceImpl NcaFsServiceImpl; + public SaveDataFileSystemServiceImpl SaveFsServiceImpl; + // Missing: FatFileSystemCreator (Not an IFatFileSystemCreator) + public MemoryReport BufferManagerMemoryReport; + public MemoryReport ExpHeapMemoryReport; + public MemoryReport BufferPoolMemoryReport; + public PatrolAllocateCountGetter GetPatrolAllocateCounts; + public IStackUsageReporter MainThreadStackUsageReporter; + public IStackUsageReporter IpcWorkerThreadStackUsageReporter; + public IStackUsageReporter PipeLineWorkerThreadStackUsageReporter; + + // LibHac additions + public FileSystemServer FsServer; + } + + public Result GetAndClearFileSystemProxyErrorInfo(out FileSystemProxyErrorInfo errorInfo) + { + errorInfo = new FileSystemProxyErrorInfo(); + + _config.NcaFsServiceImpl.GetAndClearRomFsErrorInfo(out errorInfo.RomFsRemountForDataCorruptionCount, + out errorInfo.RomFsUnrecoverableDataCorruptionByRemountCount, + out errorInfo.RomFsRecoveredByInvalidateCacheCount); + + // Missing: GetFatInfo + + Result rc = _config.SaveFsServiceImpl.GetSaveDataIndexCount(out int count); + if (rc.IsFailure()) return rc; + + errorInfo.SaveDataIndexCount = count; + return Result.Success; + } + + public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) + { + using ScopedLock lk = ScopedLock.Lock(ref _mutex); + + reportInfo = new MemoryReportInfo(); + + // Missing: Get and clear pooled buffer stats + reportInfo.PooledBufferFreeSizePeak = 0; + reportInfo.PooledBufferRetriedCount = 0; + reportInfo.PooledBufferReduceAllocationCount = 0; + + MemoryReport report = _config.BufferManagerMemoryReport; + if (report != null) { - _config = configuration; - _mutex.Initialize(); + reportInfo.BufferManagerFreeSizePeak = report.GetFreeSizePeak(); + reportInfo.BufferManagerTotalAllocatableSizePeak = report.GetTotalAllocatableSizePeak(); + reportInfo.BufferManagerRetriedCount = report.GetRetriedCount(); + report.Clear(); } - public struct Configuration + report = _config.ExpHeapMemoryReport; + if (report != null) { - public NcaFileSystemServiceImpl NcaFsServiceImpl; - public SaveDataFileSystemServiceImpl SaveFsServiceImpl; - // Missing: FatFileSystemCreator (Not an IFatFileSystemCreator) - public MemoryReport BufferManagerMemoryReport; - public MemoryReport ExpHeapMemoryReport; - public MemoryReport BufferPoolMemoryReport; - public PatrolAllocateCountGetter GetPatrolAllocateCounts; - public IStackUsageReporter MainThreadStackUsageReporter; - public IStackUsageReporter IpcWorkerThreadStackUsageReporter; - public IStackUsageReporter PipeLineWorkerThreadStackUsageReporter; - - // LibHac additions - public FileSystemServer FsServer; + reportInfo.ExpHeapFreeSizePeak = report.GetFreeSizePeak(); + report.Clear(); } - public Result GetAndClearFileSystemProxyErrorInfo(out FileSystemProxyErrorInfo errorInfo) + report = _config.BufferPoolMemoryReport; + if (report != null) { - errorInfo = new FileSystemProxyErrorInfo(); - - _config.NcaFsServiceImpl.GetAndClearRomFsErrorInfo(out errorInfo.RomFsRemountForDataCorruptionCount, - out errorInfo.RomFsUnrecoverableDataCorruptionByRemountCount, - out errorInfo.RomFsRecoveredByInvalidateCacheCount); - - // Missing: GetFatInfo - - Result rc = _config.SaveFsServiceImpl.GetSaveDataIndexCount(out int count); - if (rc.IsFailure()) return rc; - - errorInfo.SaveDataIndexCount = count; - return Result.Success; + reportInfo.BufferPoolFreeSizePeak = report.GetFreeSizePeak(); + reportInfo.BufferPoolAllocateSizeMax = report.GetAllocateSizeMax(); + report.Clear(); } - public Result GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo) + if (_config.GetPatrolAllocateCounts != null) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - reportInfo = new MemoryReportInfo(); - - // Missing: Get and clear pooled buffer stats - reportInfo.PooledBufferFreeSizePeak = 0; - reportInfo.PooledBufferRetriedCount = 0; - reportInfo.PooledBufferReduceAllocationCount = 0; - - MemoryReport report = _config.BufferManagerMemoryReport; - if (report != null) - { - reportInfo.BufferManagerFreeSizePeak = report.GetFreeSizePeak(); - reportInfo.BufferManagerTotalAllocatableSizePeak = report.GetTotalAllocatableSizePeak(); - reportInfo.BufferManagerRetriedCount = report.GetRetriedCount(); - report.Clear(); - } - - report = _config.ExpHeapMemoryReport; - if (report != null) - { - reportInfo.ExpHeapFreeSizePeak = report.GetFreeSizePeak(); - report.Clear(); - } - - report = _config.BufferPoolMemoryReport; - if (report != null) - { - reportInfo.BufferPoolFreeSizePeak = report.GetFreeSizePeak(); - reportInfo.BufferPoolAllocateSizeMax = report.GetAllocateSizeMax(); - report.Clear(); - } - - if (_config.GetPatrolAllocateCounts != null) - { - _config.GetPatrolAllocateCounts(out reportInfo.PatrolAllocateSuccessCount, - out reportInfo.PatrolAllocateFailureCount); - } - - return Result.Success; + _config.GetPatrolAllocateCounts(out reportInfo.PatrolAllocateSuccessCount, + out reportInfo.PatrolAllocateFailureCount); } - public uint ReportStackUsage(FsStackUsageThreadType threadType) + return Result.Success; + } + + public uint ReportStackUsage(FsStackUsageThreadType threadType) + { + switch (threadType) { - switch (threadType) - { - case FsStackUsageThreadType.MainThread: - Assert.SdkRequiresNotNull(_config.MainThreadStackUsageReporter); - return _config.MainThreadStackUsageReporter.GetStackUsage(); + case FsStackUsageThreadType.MainThread: + Assert.SdkRequiresNotNull(_config.MainThreadStackUsageReporter); + return _config.MainThreadStackUsageReporter.GetStackUsage(); - case FsStackUsageThreadType.IpcWorker: - Assert.SdkRequiresNotNull(_config.IpcWorkerThreadStackUsageReporter); - return _config.IpcWorkerThreadStackUsageReporter.GetStackUsage(); + case FsStackUsageThreadType.IpcWorker: + Assert.SdkRequiresNotNull(_config.IpcWorkerThreadStackUsageReporter); + return _config.IpcWorkerThreadStackUsageReporter.GetStackUsage(); - case FsStackUsageThreadType.PipelineWorker: - Assert.SdkRequiresNotNull(_config.PipeLineWorkerThreadStackUsageReporter); - return _config.PipeLineWorkerThreadStackUsageReporter.GetStackUsage(); + case FsStackUsageThreadType.PipelineWorker: + Assert.SdkRequiresNotNull(_config.PipeLineWorkerThreadStackUsageReporter); + return _config.PipeLineWorkerThreadStackUsageReporter.GetStackUsage(); - default: - return 0; - } + default: + return 0; } } } diff --git a/src/LibHac/FsSrv/Storage/IStorageDeviceManagerFactory.cs b/src/LibHac/FsSrv/Storage/IStorageDeviceManagerFactory.cs index d741f97e..eb5a3e75 100644 --- a/src/LibHac/FsSrv/Storage/IStorageDeviceManagerFactory.cs +++ b/src/LibHac/FsSrv/Storage/IStorageDeviceManagerFactory.cs @@ -3,12 +3,11 @@ using LibHac.Common; using LibHac.FsSrv.Storage.Sf; using LibHac.Sf; -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +public interface IStorageDeviceManagerFactory : IDisposable { - public interface IStorageDeviceManagerFactory : IDisposable - { - Result Create(ref SharedRef outDeviceManager, StorageDevicePortId portId); - Result SetReady(StorageDevicePortId portId, NativeHandle handle); - Result UnsetReady(StorageDevicePortId portId); - } -} \ No newline at end of file + Result Create(ref SharedRef outDeviceManager, StorageDevicePortId portId); + Result SetReady(StorageDevicePortId portId, NativeHandle handle); + Result UnsetReady(StorageDevicePortId portId); +} diff --git a/src/LibHac/FsSrv/Storage/MmcService.cs b/src/LibHac/FsSrv/Storage/MmcService.cs index 83e014fa..b1d03c5b 100644 --- a/src/LibHac/FsSrv/Storage/MmcService.cs +++ b/src/LibHac/FsSrv/Storage/MmcService.cs @@ -9,255 +9,254 @@ using LibHac.SdmmcSrv; using LibHac.Sf; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +internal static class MmcService { - internal static class MmcService + private static int MakeOperationId(MmcManagerOperationIdValue operation) => (int)operation; + private static int MakeOperationId(MmcOperationIdValue operation) => (int)operation; + + private static Result GetMmcManager(this StorageService service, + ref SharedRef outManager) { - private static int MakeOperationId(MmcManagerOperationIdValue operation) => (int)operation; - private static int MakeOperationId(MmcOperationIdValue operation) => (int)operation; + return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.Mmc); + } - private static Result GetMmcManager(this StorageService service, - ref SharedRef outManager) + private static Result GetMmcManagerOperator(this StorageService service, + ref SharedRef outDeviceOperator) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + return storageDeviceManager.Get.OpenOperator(ref outDeviceOperator); + } + + private static Result GetAttribute(out ulong attribute, MmcPartition partition) + { + UnsafeHelpers.SkipParamInit(out attribute); + + switch (partition) { - return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.Mmc); - } - - private static Result GetMmcManagerOperator(this StorageService service, - ref SharedRef outDeviceOperator) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; - - return storageDeviceManager.Get.OpenOperator(ref outDeviceOperator); - } - - private static Result GetAttribute(out ulong attribute, MmcPartition partition) - { - UnsafeHelpers.SkipParamInit(out attribute); - - switch (partition) - { - case MmcPartition.UserData: - attribute = 0; - return Result.Success; - case MmcPartition.BootPartition1: - attribute = 1; - return Result.Success; - case MmcPartition.BootPartition2: - attribute = 2; - return Result.Success; - default: - return ResultFs.InvalidArgument.Log(); - } - } - - private static bool IsSpeedEmulationNeeded(MmcPartition partition) - { - return partition == MmcPartition.UserData; - } - - private static Result GetMmcOperator(this StorageService service, - ref SharedRef outMmcOperator, MmcPartition partition) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; - - rc = GetAttribute(out ulong attribute, partition); - if (rc.IsFailure()) return rc; - - using var storageDevice = new SharedRef(); - rc = storageDeviceManager.Get.OpenDevice(ref storageDevice.Ref(), attribute); - if (rc.IsFailure()) return rc; - - return storageDevice.Get.OpenOperator(ref outMmcOperator); - } - - public static Result OpenMmcStorage(this StorageService service, ref SharedRef outStorage, - MmcPartition partition) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; - - rc = GetAttribute(out ulong attribute, partition); - if (rc.IsFailure()) return rc; - - using var mmcStorage = new SharedRef(); - rc = storageDeviceManager.Get.OpenStorage(ref mmcStorage.Ref(), attribute); - if (rc.IsFailure()) return rc; - - using var storage = new SharedRef(new StorageServiceObjectAdapter(ref mmcStorage.Ref())); - - if (IsSpeedEmulationNeeded(partition)) - { - using var emulationStorage = - new SharedRef(new SpeedEmulationStorage(ref storage.Ref(), service.FsSrv)); - - outStorage.SetByMove(ref emulationStorage.Ref()); + case MmcPartition.UserData: + attribute = 0; return Result.Success; - } - - outStorage.SetByMove(ref storage.Ref()); - return Result.Success; - } - - public static Result GetMmcSpeedMode(this StorageService service, out MmcSpeedMode speedMode) - { - UnsafeHelpers.SkipParamInit(out speedMode); - - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); - if (rc.IsFailure()) return rc; - - Unsafe.SkipInit(out SpeedMode sdmmcSpeedMode); - OutBuffer outBuffer = OutBuffer.FromStruct(ref sdmmcSpeedMode); - int operationId = MakeOperationId(MmcOperationIdValue.GetSpeedMode); - - rc = mmcOperator.Get.OperateOut(out _, outBuffer, operationId); - if (rc.IsFailure()) return rc; - - speedMode = sdmmcSpeedMode switch - { - SpeedMode.MmcIdentification => MmcSpeedMode.Identification, - SpeedMode.MmcLegacySpeed => MmcSpeedMode.LegacySpeed, - SpeedMode.MmcHighSpeed => MmcSpeedMode.HighSpeed, - SpeedMode.MmcHs200 => MmcSpeedMode.Hs200, - SpeedMode.MmcHs400 => MmcSpeedMode.Hs400, - _ => MmcSpeedMode.Unknown - }; - - return Result.Success; - } - - public static Result GetMmcCid(this StorageService service, Span cidBuffer) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(MmcOperationIdValue.GetCid); - var outBuffer = new OutBuffer(cidBuffer); - - return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result EraseMmc(this StorageService service, MmcPartition partition) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); - if (rc.IsFailure()) return rc; - - return mmcOperator.Get.Operate(MakeOperationId(MmcOperationIdValue.Erase)); - } - - public static Result GetMmcPartitionSize(this StorageService service, out long size, MmcPartition partition) - { - UnsafeHelpers.SkipParamInit(out size); - - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(MmcOperationIdValue.GetPartitionSize); - OutBuffer outBuffer = OutBuffer.FromStruct(ref size); - - return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetMmcPatrolCount(this StorageService service, out uint count) - { - UnsafeHelpers.SkipParamInit(out count); - - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(MmcManagerOperationIdValue.GetPatrolCount); - OutBuffer outBuffer = OutBuffer.FromStruct(ref count); - - return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetAndClearMmcErrorInfo(this StorageService service, out StorageErrorInfo errorInfo, - out long logSize, Span logBuffer) - { - UnsafeHelpers.SkipParamInit(out errorInfo, out logSize); - - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - OutBuffer errorInfoOutBuffer = OutBuffer.FromStruct(ref errorInfo); - var logOutBuffer = new OutBuffer(logBuffer); - int operationId = MakeOperationId(MmcManagerOperationIdValue.GetAndClearErrorInfo); - - return mmcOperator.Get.OperateOut2(out _, errorInfoOutBuffer, out logSize, logOutBuffer, operationId); - } - - public static Result GetMmcExtendedCsd(this StorageService service, Span buffer) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(MmcOperationIdValue.GetExtendedCsd); - var outBuffer = new OutBuffer(buffer); - - return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result SuspendMmcPatrol(this StorageService service) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.SuspendPatrol)); - } - - public static Result ResumeMmcPatrol(this StorageService service) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.ResumePatrol)); - } - - public static Result GetAndClearPatrolReadAllocateBufferCount(this StorageService service, - out long successCount, out long failureCount) - { - UnsafeHelpers.SkipParamInit(out successCount, out failureCount); - - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(MmcManagerOperationIdValue.GetAndClearPatrolReadAllocateBufferCount); - OutBuffer successCountBuffer = OutBuffer.FromStruct(ref successCount); - OutBuffer failureCountBuffer = OutBuffer.FromStruct(ref failureCount); - - return mmcOperator.Get.OperateOut2(out _, successCountBuffer, out _, failureCountBuffer, operationId); - } - - public static Result SuspendSdmmcControl(this StorageService service) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.SuspendControl)); - } - - public static Result ResumeMmcControl(this StorageService service) - { - using var mmcOperator = new SharedRef(); - Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); - if (rc.IsFailure()) return rc; - - return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.ResumeControl)); + case MmcPartition.BootPartition1: + attribute = 1; + return Result.Success; + case MmcPartition.BootPartition2: + attribute = 2; + return Result.Success; + default: + return ResultFs.InvalidArgument.Log(); } } + + private static bool IsSpeedEmulationNeeded(MmcPartition partition) + { + return partition == MmcPartition.UserData; + } + + private static Result GetMmcOperator(this StorageService service, + ref SharedRef outMmcOperator, MmcPartition partition) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + rc = GetAttribute(out ulong attribute, partition); + if (rc.IsFailure()) return rc; + + using var storageDevice = new SharedRef(); + rc = storageDeviceManager.Get.OpenDevice(ref storageDevice.Ref(), attribute); + if (rc.IsFailure()) return rc; + + return storageDevice.Get.OpenOperator(ref outMmcOperator); + } + + public static Result OpenMmcStorage(this StorageService service, ref SharedRef outStorage, + MmcPartition partition) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetMmcManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + rc = GetAttribute(out ulong attribute, partition); + if (rc.IsFailure()) return rc; + + using var mmcStorage = new SharedRef(); + rc = storageDeviceManager.Get.OpenStorage(ref mmcStorage.Ref(), attribute); + if (rc.IsFailure()) return rc; + + using var storage = new SharedRef(new StorageServiceObjectAdapter(ref mmcStorage.Ref())); + + if (IsSpeedEmulationNeeded(partition)) + { + using var emulationStorage = + new SharedRef(new SpeedEmulationStorage(ref storage.Ref(), service.FsSrv)); + + outStorage.SetByMove(ref emulationStorage.Ref()); + return Result.Success; + } + + outStorage.SetByMove(ref storage.Ref()); + return Result.Success; + } + + public static Result GetMmcSpeedMode(this StorageService service, out MmcSpeedMode speedMode) + { + UnsafeHelpers.SkipParamInit(out speedMode); + + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); + if (rc.IsFailure()) return rc; + + Unsafe.SkipInit(out SpeedMode sdmmcSpeedMode); + OutBuffer outBuffer = OutBuffer.FromStruct(ref sdmmcSpeedMode); + int operationId = MakeOperationId(MmcOperationIdValue.GetSpeedMode); + + rc = mmcOperator.Get.OperateOut(out _, outBuffer, operationId); + if (rc.IsFailure()) return rc; + + speedMode = sdmmcSpeedMode switch + { + SpeedMode.MmcIdentification => MmcSpeedMode.Identification, + SpeedMode.MmcLegacySpeed => MmcSpeedMode.LegacySpeed, + SpeedMode.MmcHighSpeed => MmcSpeedMode.HighSpeed, + SpeedMode.MmcHs200 => MmcSpeedMode.Hs200, + SpeedMode.MmcHs400 => MmcSpeedMode.Hs400, + _ => MmcSpeedMode.Unknown + }; + + return Result.Success; + } + + public static Result GetMmcCid(this StorageService service, Span cidBuffer) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); + if (rc.IsFailure()) return rc; + + int operationId = MakeOperationId(MmcOperationIdValue.GetCid); + var outBuffer = new OutBuffer(cidBuffer); + + return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); + } + + public static Result EraseMmc(this StorageService service, MmcPartition partition) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); + if (rc.IsFailure()) return rc; + + return mmcOperator.Get.Operate(MakeOperationId(MmcOperationIdValue.Erase)); + } + + public static Result GetMmcPartitionSize(this StorageService service, out long size, MmcPartition partition) + { + UnsafeHelpers.SkipParamInit(out size); + + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); + if (rc.IsFailure()) return rc; + + int operationId = MakeOperationId(MmcOperationIdValue.GetPartitionSize); + OutBuffer outBuffer = OutBuffer.FromStruct(ref size); + + return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); + } + + public static Result GetMmcPatrolCount(this StorageService service, out uint count) + { + UnsafeHelpers.SkipParamInit(out count); + + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + int operationId = MakeOperationId(MmcManagerOperationIdValue.GetPatrolCount); + OutBuffer outBuffer = OutBuffer.FromStruct(ref count); + + return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); + } + + public static Result GetAndClearMmcErrorInfo(this StorageService service, out StorageErrorInfo errorInfo, + out long logSize, Span logBuffer) + { + UnsafeHelpers.SkipParamInit(out errorInfo, out logSize); + + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + OutBuffer errorInfoOutBuffer = OutBuffer.FromStruct(ref errorInfo); + var logOutBuffer = new OutBuffer(logBuffer); + int operationId = MakeOperationId(MmcManagerOperationIdValue.GetAndClearErrorInfo); + + return mmcOperator.Get.OperateOut2(out _, errorInfoOutBuffer, out logSize, logOutBuffer, operationId); + } + + public static Result GetMmcExtendedCsd(this StorageService service, Span buffer) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcOperator(ref mmcOperator.Ref(), MmcPartition.UserData); + if (rc.IsFailure()) return rc; + + int operationId = MakeOperationId(MmcOperationIdValue.GetExtendedCsd); + var outBuffer = new OutBuffer(buffer); + + return mmcOperator.Get.OperateOut(out _, outBuffer, operationId); + } + + public static Result SuspendMmcPatrol(this StorageService service) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.SuspendPatrol)); + } + + public static Result ResumeMmcPatrol(this StorageService service) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.ResumePatrol)); + } + + public static Result GetAndClearPatrolReadAllocateBufferCount(this StorageService service, + out long successCount, out long failureCount) + { + UnsafeHelpers.SkipParamInit(out successCount, out failureCount); + + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + int operationId = MakeOperationId(MmcManagerOperationIdValue.GetAndClearPatrolReadAllocateBufferCount); + OutBuffer successCountBuffer = OutBuffer.FromStruct(ref successCount); + OutBuffer failureCountBuffer = OutBuffer.FromStruct(ref failureCount); + + return mmcOperator.Get.OperateOut2(out _, successCountBuffer, out _, failureCountBuffer, operationId); + } + + public static Result SuspendSdmmcControl(this StorageService service) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.SuspendControl)); + } + + public static Result ResumeMmcControl(this StorageService service) + { + using var mmcOperator = new SharedRef(); + Result rc = service.GetMmcManagerOperator(ref mmcOperator.Ref()); + if (rc.IsFailure()) return rc; + + return mmcOperator.Get.Operate(MakeOperationId(MmcManagerOperationIdValue.ResumeControl)); + } } diff --git a/src/LibHac/FsSrv/Storage/SdCardService.cs b/src/LibHac/FsSrv/Storage/SdCardService.cs index adbcb8db..d4e2b442 100644 --- a/src/LibHac/FsSrv/Storage/SdCardService.cs +++ b/src/LibHac/FsSrv/Storage/SdCardService.cs @@ -12,273 +12,272 @@ using LibHac.Sf; using IStorage = LibHac.Fs.IStorage; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +internal static class SdCardService { - internal static class SdCardService + private static int MakeOperationId(SdCardManagerOperationIdValue operation) => (int)operation; + private static int MakeOperationId(SdCardOperationIdValue operation) => (int)operation; + + private static Result GetSdCardManager(this StorageService service, + ref SharedRef outManager) { - private static int MakeOperationId(SdCardManagerOperationIdValue operation) => (int)operation; - private static int MakeOperationId(SdCardOperationIdValue operation) => (int)operation; + return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.SdCard); + } - private static Result GetSdCardManager(this StorageService service, - ref SharedRef outManager) + private static Result GetSdCardManagerOperator(this StorageService service, + ref SharedRef outDeviceOperator) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + return storageDeviceManager.Get.OpenOperator(ref outDeviceOperator); + } + + private static Result GetSdCardOperator(this StorageService service, + ref SharedRef outSdCardOperator) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + using var storageDevice = new SharedRef(); + rc = storageDeviceManager.Get.OpenDevice(ref storageDevice.Ref(), 0); + if (rc.IsFailure()) return rc; + + return storageDevice.Get.OpenOperator(ref outSdCardOperator); + } + + public static Result OpenSdStorage(this StorageService service, ref SharedRef outStorage) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + using var sdCardStorage = new SharedRef(); + rc = storageDeviceManager.Get.OpenStorage(ref sdCardStorage.Ref(), 0); + if (rc.IsFailure()) return rc; + + using var storage = new SharedRef(new StorageServiceObjectAdapter(ref sdCardStorage.Ref())); + + SdCardEventSimulator eventSimulator = service.FsSrv.Impl.GetSdCardEventSimulator(); + using var deviceEventSimulationStorage = + new SharedRef(new DeviceEventSimulationStorage(ref storage.Ref(), eventSimulator)); + + using var emulationStorage = new SharedRef( + new SpeedEmulationStorage(ref deviceEventSimulationStorage.Ref(), service.FsSrv)); + + outStorage.SetByMove(ref emulationStorage.Ref()); + return Result.Success; + } + + public static Result GetCurrentSdCardHandle(this StorageService service, out StorageDeviceHandle handle) + { + UnsafeHelpers.SkipParamInit(out handle); + + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + using var sdCardStorageDevice = new SharedRef(); + rc = storageDeviceManager.Get.OpenDevice(ref sdCardStorageDevice.Ref(), 0); + if (rc.IsFailure()) return rc; + + rc = sdCardStorageDevice.Get.GetHandle(out uint handleValue); + if (rc.IsFailure()) return rc; + + handle = new StorageDeviceHandle(handleValue, StorageDevicePortId.SdCard); + return Result.Success; + } + + public static Result IsSdCardHandleValid(this StorageService service, out bool isValid, + in StorageDeviceHandle handle) + { + UnsafeHelpers.SkipParamInit(out isValid); + + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + // Note: I don't know why the official code doesn't check the handle port + return storageDeviceManager.Get.IsHandleValid(out isValid, handle.Value); + } + + public static Result InvalidateSdCard(this StorageService service) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc; + + return storageDeviceManager.Get.Invalidate(); + } + + public static Result IsSdCardInserted(this StorageService service, out bool isInserted) + { + UnsafeHelpers.SkipParamInit(out isInserted); + + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + rc = storageDeviceManager.Get.IsInserted(out bool actualState); + if (rc.IsFailure()) return rc.Miss(); + + isInserted = service.FsSrv.Impl.GetSdCardEventSimulator().FilterDetectionState(actualState); + return Result.Success; + } + + public static Result GetSdCardSpeedMode(this StorageService service, out SdCardSpeedMode speedMode) + { + UnsafeHelpers.SkipParamInit(out speedMode); + + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; + + Unsafe.SkipInit(out SpeedMode sdmmcSpeedMode); + OutBuffer outBuffer = OutBuffer.FromStruct(ref sdmmcSpeedMode); + int operationId = MakeOperationId(SdCardOperationIdValue.GetSpeedMode); + + rc = sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + if (rc.IsFailure()) return rc; + + speedMode = sdmmcSpeedMode switch { - return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.SdCard); - } + SpeedMode.SdCardIdentification => SdCardSpeedMode.Identification, + SpeedMode.SdCardDefaultSpeed => SdCardSpeedMode.DefaultSpeed, + SpeedMode.SdCardHighSpeed => SdCardSpeedMode.HighSpeed, + SpeedMode.SdCardSdr12 => SdCardSpeedMode.Sdr12, + SpeedMode.SdCardSdr25 => SdCardSpeedMode.Sdr25, + SpeedMode.SdCardSdr50 => SdCardSpeedMode.Sdr50, + SpeedMode.SdCardSdr104 => SdCardSpeedMode.Sdr104, + SpeedMode.SdCardDdr50 => SdCardSpeedMode.Ddr50, + _ => SdCardSpeedMode.Unknown + }; - private static Result GetSdCardManagerOperator(this StorageService service, - ref SharedRef outDeviceOperator) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return storageDeviceManager.Get.OpenOperator(ref outDeviceOperator); - } + public static Result GetSdCardCid(this StorageService service, Span cidBuffer) + { + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - private static Result GetSdCardOperator(this StorageService service, - ref SharedRef outSdCardOperator) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + int operationId = MakeOperationId(SdCardOperationIdValue.GetCid); + var outBuffer = new OutBuffer(cidBuffer); - using var storageDevice = new SharedRef(); - rc = storageDeviceManager.Get.OpenDevice(ref storageDevice.Ref(), 0); - if (rc.IsFailure()) return rc; + return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + } - return storageDevice.Get.OpenOperator(ref outSdCardOperator); - } + public static Result GetSdCardUserAreaNumSectors(this StorageService service, out uint count) + { + UnsafeHelpers.SkipParamInit(out count); - public static Result OpenSdStorage(this StorageService service, ref SharedRef outStorage) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - using var sdCardStorage = new SharedRef(); - rc = storageDeviceManager.Get.OpenStorage(ref sdCardStorage.Ref(), 0); - if (rc.IsFailure()) return rc; + int operationId = MakeOperationId(SdCardOperationIdValue.GetUserAreaNumSectors); + OutBuffer outBuffer = OutBuffer.FromStruct(ref count); - using var storage = new SharedRef(new StorageServiceObjectAdapter(ref sdCardStorage.Ref())); + return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + } - SdCardEventSimulator eventSimulator = service.FsSrv.Impl.GetSdCardEventSimulator(); - using var deviceEventSimulationStorage = - new SharedRef(new DeviceEventSimulationStorage(ref storage.Ref(), eventSimulator)); + public static Result GetSdCardUserAreaSize(this StorageService service, out long size) + { + UnsafeHelpers.SkipParamInit(out size); - using var emulationStorage = new SharedRef( - new SpeedEmulationStorage(ref deviceEventSimulationStorage.Ref(), service.FsSrv)); + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - outStorage.SetByMove(ref emulationStorage.Ref()); - return Result.Success; - } + int operationId = MakeOperationId(SdCardOperationIdValue.GetUserAreaSize); + OutBuffer outBuffer = OutBuffer.FromStruct(ref size); - public static Result GetCurrentSdCardHandle(this StorageService service, out StorageDeviceHandle handle) - { - UnsafeHelpers.SkipParamInit(out handle); + return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + } - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + public static Result GetSdCardProtectedAreaNumSectors(this StorageService service, out uint count) + { + UnsafeHelpers.SkipParamInit(out count); - using var sdCardStorageDevice = new SharedRef(); - rc = storageDeviceManager.Get.OpenDevice(ref sdCardStorageDevice.Ref(), 0); - if (rc.IsFailure()) return rc; + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - rc = sdCardStorageDevice.Get.GetHandle(out uint handleValue); - if (rc.IsFailure()) return rc; + int operationId = MakeOperationId(SdCardOperationIdValue.GetProtectedAreaNumSectors); + OutBuffer outBuffer = OutBuffer.FromStruct(ref count); - handle = new StorageDeviceHandle(handleValue, StorageDevicePortId.SdCard); - return Result.Success; - } + return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + } - public static Result IsSdCardHandleValid(this StorageService service, out bool isValid, - in StorageDeviceHandle handle) - { - UnsafeHelpers.SkipParamInit(out isValid); + public static Result GetSdCardProtectedAreaSize(this StorageService service, out long size) + { + UnsafeHelpers.SkipParamInit(out size); - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - // Note: I don't know why the official code doesn't check the handle port - return storageDeviceManager.Get.IsHandleValid(out isValid, handle.Value); - } + int operationId = MakeOperationId(SdCardOperationIdValue.GetProtectedAreaSize); + OutBuffer outBuffer = OutBuffer.FromStruct(ref size); - public static Result InvalidateSdCard(this StorageService service) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc; + return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); + } - return storageDeviceManager.Get.Invalidate(); - } + public static Result GetAndClearSdCardErrorInfo(this StorageService service, out StorageErrorInfo errorInfo, + out long logSize, Span logBuffer) + { + UnsafeHelpers.SkipParamInit(out errorInfo, out logSize); - public static Result IsSdCardInserted(this StorageService service, out bool isInserted) - { - UnsafeHelpers.SkipParamInit(out isInserted); + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc.Miss(); + OutBuffer errorInfoOutBuffer = OutBuffer.FromStruct(ref errorInfo); + var logOutBuffer = new OutBuffer(logBuffer); + int operationId = MakeOperationId(SdCardManagerOperationIdValue.GetAndClearErrorInfo); - rc = storageDeviceManager.Get.IsInserted(out bool actualState); - if (rc.IsFailure()) return rc.Miss(); + return sdCardOperator.Get.OperateOut2(out _, errorInfoOutBuffer, out logSize, logOutBuffer, operationId); + } - isInserted = service.FsSrv.Impl.GetSdCardEventSimulator().FilterDetectionState(actualState); - return Result.Success; - } + public static Result OpenSdCardDetectionEvent(this StorageService service, + ref SharedRef outEventNotifier) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); - public static Result GetSdCardSpeedMode(this StorageService service, out SdCardSpeedMode speedMode) - { - UnsafeHelpers.SkipParamInit(out speedMode); + return storageDeviceManager.Get.OpenDetectionEvent(ref outEventNotifier); + } - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; + public static Result SimulateSdCardDetectionEventSignaled(this StorageService service) + { + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - Unsafe.SkipInit(out SpeedMode sdmmcSpeedMode); - OutBuffer outBuffer = OutBuffer.FromStruct(ref sdmmcSpeedMode); - int operationId = MakeOperationId(SdCardOperationIdValue.GetSpeedMode); + return sdCardOperator.Get.Operate( + MakeOperationId(SdCardManagerOperationIdValue.SimulateDetectionEventSignaled)); + } - rc = sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - if (rc.IsFailure()) return rc; + public static Result SuspendSdCardControl(this StorageService service) + { + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - speedMode = sdmmcSpeedMode switch - { - SpeedMode.SdCardIdentification => SdCardSpeedMode.Identification, - SpeedMode.SdCardDefaultSpeed => SdCardSpeedMode.DefaultSpeed, - SpeedMode.SdCardHighSpeed => SdCardSpeedMode.HighSpeed, - SpeedMode.SdCardSdr12 => SdCardSpeedMode.Sdr12, - SpeedMode.SdCardSdr25 => SdCardSpeedMode.Sdr25, - SpeedMode.SdCardSdr50 => SdCardSpeedMode.Sdr50, - SpeedMode.SdCardSdr104 => SdCardSpeedMode.Sdr104, - SpeedMode.SdCardDdr50 => SdCardSpeedMode.Ddr50, - _ => SdCardSpeedMode.Unknown - }; + return sdCardOperator.Get.Operate(MakeOperationId(SdCardManagerOperationIdValue.SuspendControl)); + } - return Result.Success; - } + public static Result ResumeSdCardControl(this StorageService service) + { + using var sdCardOperator = new SharedRef(); + Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); + if (rc.IsFailure()) return rc; - public static Result GetSdCardCid(this StorageService service, Span cidBuffer) - { - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(SdCardOperationIdValue.GetCid); - var outBuffer = new OutBuffer(cidBuffer); - - return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetSdCardUserAreaNumSectors(this StorageService service, out uint count) - { - UnsafeHelpers.SkipParamInit(out count); - - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(SdCardOperationIdValue.GetUserAreaNumSectors); - OutBuffer outBuffer = OutBuffer.FromStruct(ref count); - - return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetSdCardUserAreaSize(this StorageService service, out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(SdCardOperationIdValue.GetUserAreaSize); - OutBuffer outBuffer = OutBuffer.FromStruct(ref size); - - return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetSdCardProtectedAreaNumSectors(this StorageService service, out uint count) - { - UnsafeHelpers.SkipParamInit(out count); - - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(SdCardOperationIdValue.GetProtectedAreaNumSectors); - OutBuffer outBuffer = OutBuffer.FromStruct(ref count); - - return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetSdCardProtectedAreaSize(this StorageService service, out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - int operationId = MakeOperationId(SdCardOperationIdValue.GetProtectedAreaSize); - OutBuffer outBuffer = OutBuffer.FromStruct(ref size); - - return sdCardOperator.Get.OperateOut(out _, outBuffer, operationId); - } - - public static Result GetAndClearSdCardErrorInfo(this StorageService service, out StorageErrorInfo errorInfo, - out long logSize, Span logBuffer) - { - UnsafeHelpers.SkipParamInit(out errorInfo, out logSize); - - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - OutBuffer errorInfoOutBuffer = OutBuffer.FromStruct(ref errorInfo); - var logOutBuffer = new OutBuffer(logBuffer); - int operationId = MakeOperationId(SdCardManagerOperationIdValue.GetAndClearErrorInfo); - - return sdCardOperator.Get.OperateOut2(out _, errorInfoOutBuffer, out logSize, logOutBuffer, operationId); - } - - public static Result OpenSdCardDetectionEvent(this StorageService service, - ref SharedRef outEventNotifier) - { - using var storageDeviceManager = new SharedRef(); - Result rc = service.GetSdCardManager(ref storageDeviceManager.Ref()); - if (rc.IsFailure()) return rc.Miss(); - - return storageDeviceManager.Get.OpenDetectionEvent(ref outEventNotifier); - } - - public static Result SimulateSdCardDetectionEventSignaled(this StorageService service) - { - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - return sdCardOperator.Get.Operate( - MakeOperationId(SdCardManagerOperationIdValue.SimulateDetectionEventSignaled)); - } - - public static Result SuspendSdCardControl(this StorageService service) - { - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - return sdCardOperator.Get.Operate(MakeOperationId(SdCardManagerOperationIdValue.SuspendControl)); - } - - public static Result ResumeSdCardControl(this StorageService service) - { - using var sdCardOperator = new SharedRef(); - Result rc = service.GetSdCardManagerOperator(ref sdCardOperator.Ref()); - if (rc.IsFailure()) return rc; - - return sdCardOperator.Get.Operate(MakeOperationId(SdCardManagerOperationIdValue.ResumeControl)); - } + return sdCardOperator.Get.Operate(MakeOperationId(SdCardManagerOperationIdValue.ResumeControl)); } } diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs index 60393627..004d3aed 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs @@ -2,18 +2,17 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSrv.Storage.Sf +namespace LibHac.FsSrv.Storage.Sf; + +public interface IStorageDevice : IDisposable { - public interface IStorageDevice : IDisposable - { - Result GetHandle(out uint handle); - Result IsHandleValid(out bool isValid); - Result OpenOperator(ref SharedRef outDeviceOperator); - Result Read(long offset, Span destination); - Result Write(long offset, ReadOnlySpan source); - Result Flush(); - Result SetSize(long size); - Result GetSize(out long size); - Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); - } + Result GetHandle(out uint handle); + Result IsHandleValid(out bool isValid); + Result OpenOperator(ref SharedRef outDeviceOperator); + Result Read(long offset, Span destination); + Result Write(long offset, ReadOnlySpan source); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); } diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs index 954805d1..b9bf8bce 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs @@ -3,16 +3,15 @@ using LibHac.Common; using LibHac.FsSrv.Sf; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -namespace LibHac.FsSrv.Storage.Sf +namespace LibHac.FsSrv.Storage.Sf; + +public interface IStorageDeviceManager : IDisposable { - public interface IStorageDeviceManager : IDisposable - { - Result IsInserted(out bool isInserted); - Result IsHandleValid(out bool isValid, uint handle); - Result OpenDetectionEvent(ref SharedRef outEventNotifier); - Result OpenOperator(ref SharedRef outDeviceOperator); - Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute); - Result OpenStorage(ref SharedRef outStorage, ulong attribute); - Result Invalidate(); - } + Result IsInserted(out bool isInserted); + Result IsHandleValid(out bool isValid, uint handle); + Result OpenDetectionEvent(ref SharedRef outEventNotifier); + Result OpenOperator(ref SharedRef outDeviceOperator); + Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute); + Result OpenStorage(ref SharedRef outStorage, ulong attribute); + Result Invalidate(); } diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs index 9c19682e..974b6271 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs @@ -1,15 +1,14 @@ using System; using LibHac.Sf; -namespace LibHac.FsSrv.Storage.Sf +namespace LibHac.FsSrv.Storage.Sf; + +public interface IStorageDeviceOperator : IDisposable { - public interface IStorageDeviceOperator : IDisposable - { - Result Operate(int operationId); - Result OperateIn(InBuffer buffer, long offset, long size, int operationId); - Result OperateOut(out long bytesWritten, OutBuffer buffer, int operationId); - Result OperateOut2(out long bytesWrittenBuffer1, OutBuffer buffer1, out long bytesWrittenBuffer2, OutBuffer buffer2, int operationId); - Result OperateInOut(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer, long offset, long size, int operationId); - Result OperateIn2Out(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer1, InBuffer inBuffer2, long offset, long size, int operationId); - } + Result Operate(int operationId); + Result OperateIn(InBuffer buffer, long offset, long size, int operationId); + Result OperateOut(out long bytesWritten, OutBuffer buffer, int operationId); + Result OperateOut2(out long bytesWrittenBuffer1, OutBuffer buffer1, out long bytesWrittenBuffer2, OutBuffer buffer2, int operationId); + Result OperateInOut(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer, long offset, long size, int operationId); + Result OperateIn2Out(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer1, InBuffer inBuffer2, long offset, long size, int operationId); } diff --git a/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs b/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs index 55b60fab..1210d9ff 100644 --- a/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs +++ b/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs @@ -1,26 +1,25 @@ using System; using System.Runtime.InteropServices; -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public readonly struct StorageDeviceHandle : IEquatable { - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public readonly struct StorageDeviceHandle : IEquatable + public readonly uint Value; + public readonly StorageDevicePortId PortId; + + public StorageDeviceHandle(uint value, StorageDevicePortId portId) { - public readonly uint Value; - public readonly StorageDevicePortId PortId; - - public StorageDeviceHandle(uint value, StorageDevicePortId portId) - { - Value = value; - PortId = portId; - } - - public override bool Equals(object obj) => obj is StorageDeviceHandle other && Equals(other); - public bool Equals(StorageDeviceHandle other) => Value == other.Value && PortId == other.PortId; - - public static bool operator ==(StorageDeviceHandle left, StorageDeviceHandle right) => left.Equals(right); - public static bool operator !=(StorageDeviceHandle left, StorageDeviceHandle right) => !(left == right); - - public override int GetHashCode() => HashCode.Combine(Value, (int) PortId); + Value = value; + PortId = portId; } + + public override bool Equals(object obj) => obj is StorageDeviceHandle other && Equals(other); + public bool Equals(StorageDeviceHandle other) => Value == other.Value && PortId == other.PortId; + + public static bool operator ==(StorageDeviceHandle left, StorageDeviceHandle right) => left.Equals(right); + public static bool operator !=(StorageDeviceHandle left, StorageDeviceHandle right) => !(left == right); + + public override int GetHashCode() => HashCode.Combine(Value, (int)PortId); } diff --git a/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs b/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs index 8ad4c0e3..11523b29 100644 --- a/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs +++ b/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs @@ -2,51 +2,50 @@ using LibHac.Diag; using LibHac.FsSrv.Storage.Sf; -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +internal struct StorageDeviceManagerFactoryGlobals { - internal struct StorageDeviceManagerFactoryGlobals + public nint FactoryGuard; + public IStorageDeviceManagerFactory Factory; +} + +public static class StorageDeviceManagerFactoryApi +{ + /// + /// Sets the to be used by the . + /// Calling this method more than once will do nothing. + /// + /// The Storage instance to use. + /// The to be used by this Storage instance. + public static void InitializeStorageDeviceManagerFactory(this StorageService storage, + IStorageDeviceManagerFactory factory) { - public nint FactoryGuard; - public IStorageDeviceManagerFactory Factory; - } - - public static class StorageDeviceManagerFactoryApi - { - /// - /// Sets the to be used by the . - /// Calling this method more than once will do nothing. - /// - /// The Storage instance to use. - /// The to be used by this Storage instance. - public static void InitializeStorageDeviceManagerFactory(this StorageService storage, - IStorageDeviceManagerFactory factory) - { - storage.GetStorageDeviceManagerFactory(factory); - } - } - - internal static class StorageDeviceManagerFactory - { - public static Result CreateStorageDeviceManager(this StorageService storage, - ref SharedRef outDeviceManager, StorageDevicePortId portId) - { - IStorageDeviceManagerFactory factory = storage.GetStorageDeviceManagerFactory(null); - Assert.SdkNotNull(factory); - - return factory.Create(ref outDeviceManager, portId); - } - - public static IStorageDeviceManagerFactory GetStorageDeviceManagerFactory(this StorageService storage, - IStorageDeviceManagerFactory factory) - { - ref StorageDeviceManagerFactoryGlobals g = ref storage.Globals.StorageDeviceManagerFactory; - using var initGuard = new InitializationGuard(ref g.FactoryGuard, storage.Globals.InitMutex); - - if (initGuard.IsInitialized) - return g.Factory; - - g.Factory = factory; - return g.Factory; - } + storage.GetStorageDeviceManagerFactory(factory); + } +} + +internal static class StorageDeviceManagerFactory +{ + public static Result CreateStorageDeviceManager(this StorageService storage, + ref SharedRef outDeviceManager, StorageDevicePortId portId) + { + IStorageDeviceManagerFactory factory = storage.GetStorageDeviceManagerFactory(null); + Assert.SdkNotNull(factory); + + return factory.Create(ref outDeviceManager, portId); + } + + public static IStorageDeviceManagerFactory GetStorageDeviceManagerFactory(this StorageService storage, + IStorageDeviceManagerFactory factory) + { + ref StorageDeviceManagerFactoryGlobals g = ref storage.Globals.StorageDeviceManagerFactory; + using var initGuard = new InitializationGuard(ref g.FactoryGuard, storage.Globals.InitMutex); + + if (initGuard.IsInitialized) + return g.Factory; + + g.Factory = factory; + return g.Factory; } } diff --git a/src/LibHac/FsSrv/Storage/StorageDevicePortId.cs b/src/LibHac/FsSrv/Storage/StorageDevicePortId.cs index 7f20e552..b9c591de 100644 --- a/src/LibHac/FsSrv/Storage/StorageDevicePortId.cs +++ b/src/LibHac/FsSrv/Storage/StorageDevicePortId.cs @@ -1,10 +1,9 @@ -namespace LibHac.FsSrv.Storage +namespace LibHac.FsSrv.Storage; + +public enum StorageDevicePortId : byte { - public enum StorageDevicePortId : byte - { - Invalid = 0, - Mmc = 1, - SdCard = 2, - GameCard = 3 - } + Invalid = 0, + Mmc = 1, + SdCard = 2, + GameCard = 3 } diff --git a/src/LibHac/FsSrv/TimeService.cs b/src/LibHac/FsSrv/TimeService.cs index 5d20243c..bf8f0977 100644 --- a/src/LibHac/FsSrv/TimeService.cs +++ b/src/LibHac/FsSrv/TimeService.cs @@ -4,101 +4,100 @@ using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.Os; -namespace LibHac.FsSrv +namespace LibHac.FsSrv; + +public readonly struct TimeService { - public readonly struct TimeService + private readonly TimeServiceImpl _serviceImpl; + private readonly ulong _processId; + + public TimeService(TimeServiceImpl serviceImpl, ulong processId) { - private readonly TimeServiceImpl _serviceImpl; - private readonly ulong _processId; - - public TimeService(TimeServiceImpl serviceImpl, ulong processId) - { - _serviceImpl = serviceImpl; - _processId = processId; - } - - public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - if (!programInfo.AccessControl.CanCall(OperationType.SetCurrentPosixTime)) - return ResultFs.PermissionDenied.Log(); - - _serviceImpl.SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); - return Result.Success; - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return _serviceImpl.GetProgramInfo(out programInfo, _processId); - } + _serviceImpl = serviceImpl; + _processId = processId; } - public class TimeServiceImpl + public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) { - private long _basePosixTime; - private int _timeDifference; - private SdkMutexType _mutex; + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; - private FileSystemServer _fsServer; + if (!programInfo.AccessControl.CanCall(OperationType.SetCurrentPosixTime)) + return ResultFs.PermissionDenied.Log(); - public TimeServiceImpl(FileSystemServer fsServer) - { - _fsServer = fsServer; - _basePosixTime = 0; - _timeDifference = 0; - _mutex.Initialize(); - } + _serviceImpl.SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); + return Result.Success; + } - private long GetSystemSeconds() - { - OsState os = _fsServer.Hos.Os; - - Tick tick = os.GetSystemTick(); - TimeSpan timeSpan = os.ConvertToTimeSpan(tick); - return timeSpan.GetSeconds(); - } - - public Result GetCurrentPosixTime(out long currentTime) - { - return GetCurrentPosixTimeWithTimeDifference(out currentTime, out int _); - } - - public Result GetCurrentPosixTimeWithTimeDifference(out long currentTime, out int timeDifference) - { - UnsafeHelpers.SkipParamInit(out currentTime, out timeDifference); - - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - if (_basePosixTime == 0) - return ResultFs.NotInitialized.Log(); - - if (!Unsafe.IsNullRef(ref currentTime)) - { - currentTime = _basePosixTime + GetSystemSeconds(); - } - - if (!Unsafe.IsNullRef(ref timeDifference)) - { - timeDifference = _timeDifference; - } - - return Result.Success; - } - - public void SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) - { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); - - _basePosixTime = currentTime - GetSystemSeconds(); - _timeDifference = timeDifference; - } - - internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) - { - var registry = new ProgramRegistryImpl(_fsServer); - return registry.GetProgramInfo(out programInfo, processId); - } + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } +} + +public class TimeServiceImpl +{ + private long _basePosixTime; + private int _timeDifference; + private SdkMutexType _mutex; + + private FileSystemServer _fsServer; + + public TimeServiceImpl(FileSystemServer fsServer) + { + _fsServer = fsServer; + _basePosixTime = 0; + _timeDifference = 0; + _mutex.Initialize(); + } + + private long GetSystemSeconds() + { + OsState os = _fsServer.Hos.Os; + + Tick tick = os.GetSystemTick(); + TimeSpan timeSpan = os.ConvertToTimeSpan(tick); + return timeSpan.GetSeconds(); + } + + public Result GetCurrentPosixTime(out long currentTime) + { + return GetCurrentPosixTimeWithTimeDifference(out currentTime, out int _); + } + + public Result GetCurrentPosixTimeWithTimeDifference(out long currentTime, out int timeDifference) + { + UnsafeHelpers.SkipParamInit(out currentTime, out timeDifference); + + using ScopedLock lk = ScopedLock.Lock(ref _mutex); + + if (_basePosixTime == 0) + return ResultFs.NotInitialized.Log(); + + if (!Unsafe.IsNullRef(ref currentTime)) + { + currentTime = _basePosixTime + GetSystemSeconds(); + } + + if (!Unsafe.IsNullRef(ref timeDifference)) + { + timeDifference = _timeDifference; + } + + return Result.Success; + } + + public void SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) + { + using ScopedLock lk = ScopedLock.Lock(ref _mutex); + + _basePosixTime = currentTime - GetSystemSeconds(); + _timeDifference = timeDifference; + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + var registry = new ProgramRegistryImpl(_fsServer); + return registry.GetProgramInfo(out programInfo, processId); } } diff --git a/src/LibHac/FsSystem/Aes128CtrExStorage.cs b/src/LibHac/FsSystem/Aes128CtrExStorage.cs index 35fc2ddd..b753c825 100644 --- a/src/LibHac/FsSystem/Aes128CtrExStorage.cs +++ b/src/LibHac/FsSystem/Aes128CtrExStorage.cs @@ -3,104 +3,103 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Aes128CtrExStorage : Aes128CtrStorage { - public class Aes128CtrExStorage : Aes128CtrStorage + public static readonly int NodeSize = 1024 * 16; + + private BucketTree Table { get; } = new BucketTree(); + + private readonly object _locker = new object(); + + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct Entry { - public static readonly int NodeSize = 1024 * 16; + public long Offset; + public int Reserved; + public int Generation; + } - private BucketTree Table { get; } = new BucketTree(); + public Aes128CtrExStorage(IStorage baseStorage, SubStorage nodeStorage, SubStorage entryStorage, + int entryCount, byte[] key, byte[] counter, bool leaveOpen) + : base(baseStorage, key, counter, leaveOpen) + { + Result rc = Table.Initialize(nodeStorage, entryStorage, NodeSize, Unsafe.SizeOf(), entryCount); + rc.ThrowIfFailure(); + } - private readonly object _locker = new object(); + protected override Result DoRead(long offset, Span destination) + { + if (destination.Length == 0) + return Result.Success; - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct Entry + var visitor = new BucketTree.Visitor(); + + try { - public long Offset; - public int Reserved; - public int Generation; - } + Result rc = Table.Find(ref visitor, offset); + if (rc.IsFailure()) return rc; - public Aes128CtrExStorage(IStorage baseStorage, SubStorage nodeStorage, SubStorage entryStorage, - int entryCount, byte[] key, byte[] counter, bool leaveOpen) - : base(baseStorage, key, counter, leaveOpen) - { - Result rc = Table.Initialize(nodeStorage, entryStorage, NodeSize, Unsafe.SizeOf(), entryCount); - rc.ThrowIfFailure(); - } + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; - protected override Result DoRead(long offset, Span destination) - { - if (destination.Length == 0) - return Result.Success; - - var visitor = new BucketTree.Visitor(); - - try + while (remaining > 0) { - Result rc = Table.Find(ref visitor, offset); - if (rc.IsFailure()) return rc; + var currentEntry = visitor.Get(); - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; - - while (remaining > 0) + // Get and validate the next entry offset + long nextEntryOffset; + if (visitor.CanMoveNext()) { - var currentEntry = visitor.Get(); + rc = visitor.MoveNext(); + if (rc.IsFailure()) return rc; - // Get and validate the next entry offset - long nextEntryOffset; - if (visitor.CanMoveNext()) - { - rc = visitor.MoveNext(); - if (rc.IsFailure()) return rc; - - nextEntryOffset = visitor.Get().Offset; - if (!Table.Includes(nextEntryOffset)) - return ResultFs.InvalidIndirectEntryOffset.Log(); - } - else - { - nextEntryOffset = Table.GetEnd(); - } - - int bytesToRead = (int)Math.Min(nextEntryOffset - inPos, remaining); - - lock (_locker) - { - UpdateCounterSubsection((uint)currentEntry.Generation); - - rc = base.DoRead(inPos, destination.Slice(outPos, bytesToRead)); - if (rc.IsFailure()) return rc; - } - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; + nextEntryOffset = visitor.Get().Offset; + if (!Table.Includes(nextEntryOffset)) + return ResultFs.InvalidIndirectEntryOffset.Log(); } + else + { + nextEntryOffset = Table.GetEnd(); + } + + int bytesToRead = (int)Math.Min(nextEntryOffset - inPos, remaining); + + lock (_locker) + { + UpdateCounterSubsection((uint)currentEntry.Generation); + + rc = base.DoRead(inPos, destination.Slice(outPos, bytesToRead)); + if (rc.IsFailure()) return rc; + } + + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; } - finally { visitor.Dispose(); } - - return Result.Success; } + finally { visitor.Dispose(); } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return ResultFs.UnsupportedWriteForAesCtrCounterExtendedStorage.Log(); - } + return Result.Success; + } - protected override Result DoFlush() - { - return Result.Success; - } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedWriteForAesCtrCounterExtendedStorage.Log(); + } - private void UpdateCounterSubsection(uint value) - { - Counter[7] = (byte)value; - Counter[6] = (byte)(value >> 8); - Counter[5] = (byte)(value >> 16); - Counter[4] = (byte)(value >> 24); - } + protected override Result DoFlush() + { + return Result.Success; + } + + private void UpdateCounterSubsection(uint value) + { + Counter[7] = (byte)value; + Counter[6] = (byte)(value >> 8); + Counter[5] = (byte)(value >> 16); + Counter[4] = (byte)(value >> 24); } } diff --git a/src/LibHac/FsSystem/Aes128CtrStorage.cs b/src/LibHac/FsSystem/Aes128CtrStorage.cs index b4b7f47f..9da95257 100644 --- a/src/LibHac/FsSystem/Aes128CtrStorage.cs +++ b/src/LibHac/FsSystem/Aes128CtrStorage.cs @@ -3,123 +3,122 @@ using System.Buffers; using System.Buffers.Binary; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Aes128CtrStorage : SectorStorage { - public class Aes128CtrStorage : SectorStorage + private const int BlockSize = 0x10; + + private readonly long _counterOffset; + private readonly Aes128CtrTransform _decryptor; + + protected readonly byte[] Counter; + private readonly object _locker = new object(); + + public Aes128CtrStorage(IStorage baseStorage, byte[] key, byte[] counter, bool leaveOpen) + : base(baseStorage, BlockSize, leaveOpen) { - private const int BlockSize = 0x10; + if (key == null) throw new NullReferenceException(nameof(key)); + if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); + if (counter == null) throw new NullReferenceException(nameof(counter)); + if (counter.Length != BlockSize) throw new ArgumentException(nameof(counter), $"Counter must be {BlockSize} bytes long"); - private readonly long _counterOffset; - private readonly Aes128CtrTransform _decryptor; - - protected readonly byte[] Counter; - private readonly object _locker = new object(); - - public Aes128CtrStorage(IStorage baseStorage, byte[] key, byte[] counter, bool leaveOpen) - : base(baseStorage, BlockSize, leaveOpen) + // Make the stream seekable by remembering the initial counter value + for (int i = 0; i < 8; i++) { - if (key == null) throw new NullReferenceException(nameof(key)); - if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); - if (counter == null) throw new NullReferenceException(nameof(counter)); - if (counter.Length != BlockSize) throw new ArgumentException(nameof(counter), $"Counter must be {BlockSize} bytes long"); - - // Make the stream seekable by remembering the initial counter value - for (int i = 0; i < 8; i++) - { - _counterOffset |= (long)counter[0xF - i] << (4 + i * 8); - } - - _decryptor = new Aes128CtrTransform(key, counter); - Counter = _decryptor.Counter; + _counterOffset |= (long)counter[0xF - i] << (4 + i * 8); } - /// - /// Creates a new AES storage - /// - /// The input . - /// The decryption key. - /// Offset to add to the counter. - /// The value of the upper 64 bits of the counter. Can be null. - /// to leave the storage open after the object is disposed; otherwise, . - public Aes128CtrStorage(IStorage baseStorage, byte[] key, long counterOffset, byte[] counterHi, bool leaveOpen) - : base(baseStorage, BlockSize, leaveOpen) + _decryptor = new Aes128CtrTransform(key, counter); + Counter = _decryptor.Counter; + } + + /// + /// Creates a new AES storage + /// + /// The input . + /// The decryption key. + /// Offset to add to the counter. + /// The value of the upper 64 bits of the counter. Can be null. + /// to leave the storage open after the object is disposed; otherwise, . + public Aes128CtrStorage(IStorage baseStorage, byte[] key, long counterOffset, byte[] counterHi, bool leaveOpen) + : base(baseStorage, BlockSize, leaveOpen) + { + if (key == null) throw new NullReferenceException(nameof(key)); + if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); + + byte[] initialCounter = new byte[BlockSize]; + if (counterHi != null) { - if (key == null) throw new NullReferenceException(nameof(key)); - if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); - - byte[] initialCounter = new byte[BlockSize]; - if (counterHi != null) - { - Array.Copy(counterHi, initialCounter, 8); - } - - _counterOffset = counterOffset; - - _decryptor = new Aes128CtrTransform(key, initialCounter); - Counter = _decryptor.Counter; + Array.Copy(counterHi, initialCounter, 8); } - protected override Result DoRead(long offset, Span destination) + _counterOffset = counterOffset; + + _decryptor = new Aes128CtrTransform(key, initialCounter); + Counter = _decryptor.Counter; + } + + protected override Result DoRead(long offset, Span destination) + { + Result rc = base.DoRead(offset, destination); + if (rc.IsFailure()) return rc; + + lock (_locker) { - Result rc = base.DoRead(offset, destination); - if (rc.IsFailure()) return rc; + UpdateCounter(_counterOffset + offset); + _decryptor.TransformBlock(destination); + } + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + byte[] encrypted = ArrayPool.Shared.Rent(source.Length); + try + { + Span encryptedSpan = encrypted.AsSpan(0, source.Length); + source.CopyTo(encryptedSpan); lock (_locker) { UpdateCounter(_counterOffset + offset); - _decryptor.TransformBlock(destination); + _decryptor.TransformBlock(encryptedSpan); } - return Result.Success; + Result rc = base.DoWrite(offset, encryptedSpan); + if (rc.IsFailure()) return rc; } - - protected override Result DoWrite(long offset, ReadOnlySpan source) + finally { - byte[] encrypted = ArrayPool.Shared.Rent(source.Length); - try - { - Span encryptedSpan = encrypted.AsSpan(0, source.Length); - source.CopyTo(encryptedSpan); - - lock (_locker) - { - UpdateCounter(_counterOffset + offset); - _decryptor.TransformBlock(encryptedSpan); - } - - Result rc = base.DoWrite(offset, encryptedSpan); - if (rc.IsFailure()) return rc; - } - finally - { - ArrayPool.Shared.Return(encrypted); - } - - return Result.Success; + ArrayPool.Shared.Return(encrypted); } - private void UpdateCounter(long offset) + return Result.Success; + } + + private void UpdateCounter(long offset) + { + ulong off = (ulong)offset >> 4; + for (uint j = 0; j < 0x7; j++) { - ulong off = (ulong)offset >> 4; - for (uint j = 0; j < 0x7; j++) - { - Counter[0x10 - j - 1] = (byte)(off & 0xFF); - off >>= 8; - } - - // Because the value stored in the counter is offset >> 4, the top 4 bits - // of byte 8 need to have their original value preserved - Counter[8] = (byte)((Counter[8] & 0xF0) | (int)(off & 0x0F)); + Counter[0x10 - j - 1] = (byte)(off & 0xFF); + off >>= 8; } - public static byte[] CreateCounter(ulong hiBytes, long offset) - { - byte[] counter = new byte[0x10]; + // Because the value stored in the counter is offset >> 4, the top 4 bits + // of byte 8 need to have their original value preserved + Counter[8] = (byte)((Counter[8] & 0xF0) | (int)(off & 0x0F)); + } - BinaryPrimitives.WriteUInt64BigEndian(counter, hiBytes); - BinaryPrimitives.WriteInt64BigEndian(counter.AsSpan(8), offset / 0x10); + public static byte[] CreateCounter(ulong hiBytes, long offset) + { + byte[] counter = new byte[0x10]; - return counter; - } + BinaryPrimitives.WriteUInt64BigEndian(counter, hiBytes); + BinaryPrimitives.WriteInt64BigEndian(counter.AsSpan(8), offset / 0x10); + + return counter; } } diff --git a/src/LibHac/FsSystem/Aes128CtrTransform.cs b/src/LibHac/FsSystem/Aes128CtrTransform.cs index cbc01883..807f8df5 100644 --- a/src/LibHac/FsSystem/Aes128CtrTransform.cs +++ b/src/LibHac/FsSystem/Aes128CtrTransform.cs @@ -5,71 +5,70 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Aes128CtrTransform { - public class Aes128CtrTransform + private const int BlockSize = 128; + private const int BlockSizeBytes = BlockSize / 8; + + public readonly byte[] Counter = new byte[BlockSizeBytes]; + + private readonly ICryptoTransform _encryptor; + + public Aes128CtrTransform(byte[] key, byte[] counter) { - private const int BlockSize = 128; - private const int BlockSizeBytes = BlockSize / 8; + if (key == null) throw new ArgumentNullException(nameof(key)); + if (counter == null) throw new ArgumentNullException(nameof(counter)); + if (key.Length != BlockSizeBytes) + throw new ArgumentException($"{nameof(key)} must be {BlockSizeBytes} bytes long"); + if (counter.Length != BlockSizeBytes) + throw new ArgumentException($"{nameof(counter)} must be {BlockSizeBytes} bytes long"); - public readonly byte[] Counter = new byte[BlockSizeBytes]; + var aes = Aes.Create(); + if (aes == null) throw new CryptographicException("Unable to create AES object"); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; - private readonly ICryptoTransform _encryptor; + _encryptor = aes.CreateEncryptor(key, new byte[BlockSizeBytes]); - public Aes128CtrTransform(byte[] key, byte[] counter) + Array.Copy(counter, Counter, BlockSizeBytes); + } + + public int TransformBlock(Span data) + { + int blockCount = BitUtil.DivideUp(data.Length, BlockSizeBytes); + int length = blockCount * BlockSizeBytes; + + byte[] counterXor = ArrayPool.Shared.Rent(length); + try { - if (key == null) throw new ArgumentNullException(nameof(key)); - if (counter == null) throw new ArgumentNullException(nameof(counter)); - if (key.Length != BlockSizeBytes) - throw new ArgumentException($"{nameof(key)} must be {BlockSizeBytes} bytes long"); - if (counter.Length != BlockSizeBytes) - throw new ArgumentException($"{nameof(counter)} must be {BlockSizeBytes} bytes long"); + Counter.CopyTo(counterXor, 0); + FillDecryptedCounter(counterXor.AsSpan(0, length)); - var aes = Aes.Create(); - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - - _encryptor = aes.CreateEncryptor(key, new byte[BlockSizeBytes]); - - Array.Copy(counter, Counter, BlockSizeBytes); + _encryptor.TransformBlock(counterXor, 0, length, counterXor, 0); + Utilities.XorArrays(data, counterXor); + } + finally + { + ArrayPool.Shared.Return(counterXor); } - public int TransformBlock(Span data) + return data.Length; + } + + public static void FillDecryptedCounter(Span buffer) + { + Span bufL = MemoryMarshal.Cast(buffer); + + ulong hi = bufL[0]; + ulong lo = BinaryPrimitives.ReverseEndianness(bufL[1]); + + for (int i = 2; i < bufL.Length; i += 2) { - int blockCount = BitUtil.DivideUp(data.Length, BlockSizeBytes); - int length = blockCount * BlockSizeBytes; - - byte[] counterXor = ArrayPool.Shared.Rent(length); - try - { - Counter.CopyTo(counterXor, 0); - FillDecryptedCounter(counterXor.AsSpan(0, length)); - - _encryptor.TransformBlock(counterXor, 0, length, counterXor, 0); - Utilities.XorArrays(data, counterXor); - } - finally - { - ArrayPool.Shared.Return(counterXor); - } - - return data.Length; - } - - public static void FillDecryptedCounter(Span buffer) - { - Span bufL = MemoryMarshal.Cast(buffer); - - ulong hi = bufL[0]; - ulong lo = BinaryPrimitives.ReverseEndianness(bufL[1]); - - for (int i = 2; i < bufL.Length; i += 2) - { - lo++; - bufL[i] = hi; - bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo); - } + lo++; + bufL[i] = hi; + bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo); } } } diff --git a/src/LibHac/FsSystem/Aes128XtsStorage.cs b/src/LibHac/FsSystem/Aes128XtsStorage.cs index ec2621d3..b2813fe7 100644 --- a/src/LibHac/FsSystem/Aes128XtsStorage.cs +++ b/src/LibHac/FsSystem/Aes128XtsStorage.cs @@ -1,79 +1,78 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Aes128XtsStorage : SectorStorage { - public class Aes128XtsStorage : SectorStorage + private const int BlockSize = 0x10; + + private readonly byte[] _tempBuffer; + + private Aes128XtsTransform _readTransform; + private Aes128XtsTransform _writeTransform; + + private readonly byte[] _key1; + private readonly byte[] _key2; + + private readonly bool _decryptRead; + + public Aes128XtsStorage(IStorage baseStorage, Span key, int sectorSize, bool leaveOpen, bool decryptRead = true) + : base(baseStorage, sectorSize, leaveOpen) { - private const int BlockSize = 0x10; + if (key == null) throw new NullReferenceException(nameof(key)); + if (key.Length != BlockSize * 2) throw new ArgumentException(nameof(key), $"Key must be {BlockSize * 2} bytes long"); - private readonly byte[] _tempBuffer; + _tempBuffer = new byte[sectorSize]; + _decryptRead = decryptRead; + _key1 = key.Slice(0, BlockSize).ToArray(); + _key2 = key.Slice(BlockSize, BlockSize).ToArray(); + } - private Aes128XtsTransform _readTransform; - private Aes128XtsTransform _writeTransform; + public Aes128XtsStorage(IStorage baseStorage, Span key1, Span key2, int sectorSize, bool leaveOpen, bool decryptRead = true) + : base(baseStorage, sectorSize, leaveOpen) + { + if (key1 == null) throw new NullReferenceException(nameof(key1)); + if (key2 == null) throw new NullReferenceException(nameof(key2)); + if (key1.Length != BlockSize || key1.Length != BlockSize) throw new ArgumentException($"Keys must be {BlockSize} bytes long"); - private readonly byte[] _key1; - private readonly byte[] _key2; + _tempBuffer = new byte[sectorSize]; + _decryptRead = decryptRead; + _key1 = key1.ToArray(); + _key2 = key2.ToArray(); + } - private readonly bool _decryptRead; + protected override Result DoRead(long offset, Span destination) + { + int size = destination.Length; + long sectorIndex = offset / SectorSize; - public Aes128XtsStorage(IStorage baseStorage, Span key, int sectorSize, bool leaveOpen, bool decryptRead = true) - : base(baseStorage, sectorSize, leaveOpen) - { - if (key == null) throw new NullReferenceException(nameof(key)); - if (key.Length != BlockSize * 2) throw new ArgumentException(nameof(key), $"Key must be {BlockSize * 2} bytes long"); + if (_readTransform == null) _readTransform = new Aes128XtsTransform(_key1, _key2, _decryptRead); - _tempBuffer = new byte[sectorSize]; - _decryptRead = decryptRead; - _key1 = key.Slice(0, BlockSize).ToArray(); - _key2 = key.Slice(BlockSize, BlockSize).ToArray(); - } + Result rc = base.DoRead(offset, _tempBuffer.AsSpan(0, size)); + if (rc.IsFailure()) return rc; - public Aes128XtsStorage(IStorage baseStorage, Span key1, Span key2, int sectorSize, bool leaveOpen, bool decryptRead = true) - : base(baseStorage, sectorSize, leaveOpen) - { - if (key1 == null) throw new NullReferenceException(nameof(key1)); - if (key2 == null) throw new NullReferenceException(nameof(key2)); - if (key1.Length != BlockSize || key1.Length != BlockSize) throw new ArgumentException($"Keys must be {BlockSize} bytes long"); + _readTransform.TransformBlock(_tempBuffer, 0, size, (ulong)sectorIndex); + _tempBuffer.AsSpan(0, size).CopyTo(destination); - _tempBuffer = new byte[sectorSize]; - _decryptRead = decryptRead; - _key1 = key1.ToArray(); - _key2 = key2.ToArray(); - } + return Result.Success; + } - protected override Result DoRead(long offset, Span destination) - { - int size = destination.Length; - long sectorIndex = offset / SectorSize; + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + int size = source.Length; + long sectorIndex = offset / SectorSize; - if (_readTransform == null) _readTransform = new Aes128XtsTransform(_key1, _key2, _decryptRead); + if (_writeTransform == null) _writeTransform = new Aes128XtsTransform(_key1, _key2, !_decryptRead); - Result rc = base.DoRead(offset, _tempBuffer.AsSpan(0, size)); - if (rc.IsFailure()) return rc; + source.CopyTo(_tempBuffer); + _writeTransform.TransformBlock(_tempBuffer, 0, size, (ulong)sectorIndex); - _readTransform.TransformBlock(_tempBuffer, 0, size, (ulong)sectorIndex); - _tempBuffer.AsSpan(0, size).CopyTo(destination); + return base.DoWrite(offset, _tempBuffer.AsSpan(0, size)); + } - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - int size = source.Length; - long sectorIndex = offset / SectorSize; - - if (_writeTransform == null) _writeTransform = new Aes128XtsTransform(_key1, _key2, !_decryptRead); - - source.CopyTo(_tempBuffer); - _writeTransform.TransformBlock(_tempBuffer, 0, size, (ulong)sectorIndex); - - return base.DoWrite(offset, _tempBuffer.AsSpan(0, size)); - } - - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } + protected override Result DoFlush() + { + return BaseStorage.Flush(); } } diff --git a/src/LibHac/FsSystem/Aes128XtsTransform.cs b/src/LibHac/FsSystem/Aes128XtsTransform.cs index 51ab087c..a4409ad5 100644 --- a/src/LibHac/FsSystem/Aes128XtsTransform.cs +++ b/src/LibHac/FsSystem/Aes128XtsTransform.cs @@ -30,210 +30,209 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Aes128XtsTransform { - public class Aes128XtsTransform + private const int BlockSize = 128; + private const int BlockSizeBytes = BlockSize / 8; + + private readonly byte[] _cc = new byte[16]; + private readonly bool _decrypting; + private readonly ICryptoTransform _key1; + private readonly ICryptoTransform _key2; + + private readonly byte[] _pp = new byte[16]; + private readonly byte[] _t = new byte[16]; + + public Aes128XtsTransform(byte[] key1, byte[] key2, bool decrypting) { - private const int BlockSize = 128; - private const int BlockSizeBytes = BlockSize / 8; + if (key1?.Length != BlockSizeBytes || key2?.Length != BlockSizeBytes) + throw new ArgumentException($"Each key must be {BlockSizeBytes} bytes long"); - private readonly byte[] _cc = new byte[16]; - private readonly bool _decrypting; - private readonly ICryptoTransform _key1; - private readonly ICryptoTransform _key2; + var aes = Aes.Create(); + if (aes == null) throw new CryptographicException("Unable to create AES object"); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; - private readonly byte[] _pp = new byte[16]; - private readonly byte[] _t = new byte[16]; + _decrypting = decrypting; - public Aes128XtsTransform(byte[] key1, byte[] key2, bool decrypting) + if (decrypting) { - if (key1?.Length != BlockSizeBytes || key2?.Length != BlockSizeBytes) - throw new ArgumentException($"Each key must be {BlockSizeBytes} bytes long"); - - var aes = Aes.Create(); - if (aes == null) throw new CryptographicException("Unable to create AES object"); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - - _decrypting = decrypting; - - if (decrypting) - { - _key1 = aes.CreateDecryptor(key1, new byte[BlockSizeBytes]); - _key2 = aes.CreateEncryptor(key2, new byte[BlockSizeBytes]); - } - else - { - _key1 = aes.CreateEncryptor(key1, new byte[BlockSizeBytes]); - _key2 = aes.CreateEncryptor(key2, new byte[BlockSizeBytes]); - } + _key1 = aes.CreateDecryptor(key1, new byte[BlockSizeBytes]); + _key2 = aes.CreateEncryptor(key2, new byte[BlockSizeBytes]); } - - /// - /// Transforms a single block. - /// - /// The input for which to compute the transform. - /// The offset into the byte array from which to begin using data. - /// The number of bytes in the byte array to use as data. - /// The sector number of the block - /// The number of bytes written. - public int TransformBlock(byte[] buffer, int offset, int count, ulong sector) + else { - int lim; - - /* get number of blocks */ - int m = count >> 4; - int mo = count & 15; - int alignedCount = Alignment.AlignUp(count, BlockSizeBytes); - - /* for i = 0 to m-2 do */ - if (mo == 0) - lim = m; - else - lim = m - 1; - - byte[] tweak = ArrayPool.Shared.Rent(alignedCount); - try - { - FillArrayFromSector(tweak, sector); - - /* encrypt the tweak */ - _key2.TransformBlock(tweak, 0, 16, tweak, 0); - - FillTweakBuffer(tweak.AsSpan(0, alignedCount)); - - if (lim > 0) - { - Utilities.XorArrays(buffer.AsSpan(offset, lim * 16), tweak); - _key1.TransformBlock(buffer, offset, lim * 16, buffer, offset); - Utilities.XorArrays(buffer.AsSpan(offset, lim * 16), tweak); - } - - if (mo > 0) - { - Buffer.BlockCopy(tweak, lim * 16, _t, 0, 16); - - if (_decrypting) - { - Buffer.BlockCopy(tweak, lim * 16 + 16, _cc, 0, 16); - - /* CC = tweak encrypt block m-1 */ - TweakCrypt(buffer, offset, _pp, 0, _cc); - - /* Cm = first ptlen % 16 bytes of CC */ - int i; - for (i = 0; i < mo; i++) - { - _cc[i] = buffer[16 + i + offset]; - buffer[16 + i + offset] = _pp[i]; - } - - for (; i < 16; i++) - { - _cc[i] = _pp[i]; - } - - /* Cm-1 = Tweak encrypt PP */ - TweakCrypt(_cc, 0, buffer, offset, _t); - } - else - { - /* CC = tweak encrypt block m-1 */ - TweakCrypt(buffer, offset, _cc, 0, _t); - - /* Cm = first ptlen % 16 bytes of CC */ - int i; - for (i = 0; i < mo; i++) - { - _pp[i] = buffer[16 + i + offset]; - buffer[16 + i + offset] = _cc[i]; - } - - for (; i < 16; i++) - { - _pp[i] = _cc[i]; - } - - /* Cm-1 = Tweak encrypt PP */ - TweakCrypt(_pp, 0, buffer, offset, _t); - } - } - } - finally { ArrayPool.Shared.Return(tweak); } - - return count; - } - - private static void FillTweakBuffer(Span buffer) - { - Span bufL = MemoryMarshal.Cast(buffer); - - ulong a = bufL[1]; - ulong b = bufL[0]; - - for (int i = 2; i < bufL.Length; i += 2) - { - ulong tt = (ulong)((long)a >> 63) & 0x87; - - a = (a << 1) | (b >> 63); - b = (b << 1) ^ tt; - - bufL[i + 1] = a; - bufL[i] = b; - } - } - - /// - /// Fills a byte array from a sector number (little endian) - /// - /// The destination - /// The sector number - private static void FillArrayFromSector(byte[] value, ulong sector) - { - for (int i = 0xF; i >= 0; i--) - { - value[i] = (byte)sector; - sector >>= 8; - } - } - - /// - /// Performs the Xts TweakCrypt operation - /// - private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t) - { - for (int x = 0; x < 16; x++) - { - outputBuffer[x + outputOffset] = (byte)(inputBuffer[x + inputOffset] ^ t[x]); - } - - _key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset); - - for (int x = 0; x < 16; x++) - { - outputBuffer[x + outputOffset] = (byte)(outputBuffer[x + outputOffset] ^ t[x]); - } - - MultiplyByX(t); - } - - /// - /// Multiply by x - /// - /// The value to multiply by x (LFSR shift) - private static void MultiplyByX(byte[] i) - { - byte t = 0, tt = 0; - - for (int x = 0; x < 16; x++) - { - tt = (byte)(i[x] >> 7); - i[x] = (byte)(((i[x] << 1) | t) & 0xFF); - t = tt; - } - - if (tt > 0) - i[0] ^= 0x87; + _key1 = aes.CreateEncryptor(key1, new byte[BlockSizeBytes]); + _key2 = aes.CreateEncryptor(key2, new byte[BlockSizeBytes]); } } + + /// + /// Transforms a single block. + /// + /// The input for which to compute the transform. + /// The offset into the byte array from which to begin using data. + /// The number of bytes in the byte array to use as data. + /// The sector number of the block + /// The number of bytes written. + public int TransformBlock(byte[] buffer, int offset, int count, ulong sector) + { + int lim; + + /* get number of blocks */ + int m = count >> 4; + int mo = count & 15; + int alignedCount = Alignment.AlignUp(count, BlockSizeBytes); + + /* for i = 0 to m-2 do */ + if (mo == 0) + lim = m; + else + lim = m - 1; + + byte[] tweak = ArrayPool.Shared.Rent(alignedCount); + try + { + FillArrayFromSector(tweak, sector); + + /* encrypt the tweak */ + _key2.TransformBlock(tweak, 0, 16, tweak, 0); + + FillTweakBuffer(tweak.AsSpan(0, alignedCount)); + + if (lim > 0) + { + Utilities.XorArrays(buffer.AsSpan(offset, lim * 16), tweak); + _key1.TransformBlock(buffer, offset, lim * 16, buffer, offset); + Utilities.XorArrays(buffer.AsSpan(offset, lim * 16), tweak); + } + + if (mo > 0) + { + Buffer.BlockCopy(tweak, lim * 16, _t, 0, 16); + + if (_decrypting) + { + Buffer.BlockCopy(tweak, lim * 16 + 16, _cc, 0, 16); + + /* CC = tweak encrypt block m-1 */ + TweakCrypt(buffer, offset, _pp, 0, _cc); + + /* Cm = first ptlen % 16 bytes of CC */ + int i; + for (i = 0; i < mo; i++) + { + _cc[i] = buffer[16 + i + offset]; + buffer[16 + i + offset] = _pp[i]; + } + + for (; i < 16; i++) + { + _cc[i] = _pp[i]; + } + + /* Cm-1 = Tweak encrypt PP */ + TweakCrypt(_cc, 0, buffer, offset, _t); + } + else + { + /* CC = tweak encrypt block m-1 */ + TweakCrypt(buffer, offset, _cc, 0, _t); + + /* Cm = first ptlen % 16 bytes of CC */ + int i; + for (i = 0; i < mo; i++) + { + _pp[i] = buffer[16 + i + offset]; + buffer[16 + i + offset] = _cc[i]; + } + + for (; i < 16; i++) + { + _pp[i] = _cc[i]; + } + + /* Cm-1 = Tweak encrypt PP */ + TweakCrypt(_pp, 0, buffer, offset, _t); + } + } + } + finally { ArrayPool.Shared.Return(tweak); } + + return count; + } + + private static void FillTweakBuffer(Span buffer) + { + Span bufL = MemoryMarshal.Cast(buffer); + + ulong a = bufL[1]; + ulong b = bufL[0]; + + for (int i = 2; i < bufL.Length; i += 2) + { + ulong tt = (ulong)((long)a >> 63) & 0x87; + + a = (a << 1) | (b >> 63); + b = (b << 1) ^ tt; + + bufL[i + 1] = a; + bufL[i] = b; + } + } + + /// + /// Fills a byte array from a sector number (little endian) + /// + /// The destination + /// The sector number + private static void FillArrayFromSector(byte[] value, ulong sector) + { + for (int i = 0xF; i >= 0; i--) + { + value[i] = (byte)sector; + sector >>= 8; + } + } + + /// + /// Performs the Xts TweakCrypt operation + /// + private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t) + { + for (int x = 0; x < 16; x++) + { + outputBuffer[x + outputOffset] = (byte)(inputBuffer[x + inputOffset] ^ t[x]); + } + + _key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset); + + for (int x = 0; x < 16; x++) + { + outputBuffer[x + outputOffset] = (byte)(outputBuffer[x + outputOffset] ^ t[x]); + } + + MultiplyByX(t); + } + + /// + /// Multiply by x + /// + /// The value to multiply by x (LFSR shift) + private static void MultiplyByX(byte[] i) + { + byte t = 0, tt = 0; + + for (int x = 0; x < 16; x++) + { + tt = (byte)(i[x] >> 7); + i[x] = (byte)(((i[x] << 1) | t) & 0xFF); + t = tt; + } + + if (tt > 0) + i[0] ^= 0x87; + } } diff --git a/src/LibHac/FsSystem/AesCbcStorage.cs b/src/LibHac/FsSystem/AesCbcStorage.cs index b17b221a..36ec8e67 100644 --- a/src/LibHac/FsSystem/AesCbcStorage.cs +++ b/src/LibHac/FsSystem/AesCbcStorage.cs @@ -3,82 +3,81 @@ using LibHac.Fs; using System; using LibHac.Common; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class AesCbcStorage : SectorStorage { - public class AesCbcStorage : SectorStorage + private const int BlockSize = 0x10; + + private readonly byte[] _key; + private readonly byte[] _iv; + + private readonly long _size; + + public AesCbcStorage(IStorage baseStorage, ReadOnlySpan key, ReadOnlySpan iv, + bool leaveOpen) : base(baseStorage, BlockSize, leaveOpen) { - private const int BlockSize = 0x10; + if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); + if (iv.Length != BlockSize) throw new ArgumentException(nameof(iv), $"Counter must be {BlockSize} bytes long"); - private readonly byte[] _key; - private readonly byte[] _iv; + _key = key.ToArray(); + _iv = iv.ToArray(); - private readonly long _size; + baseStorage.GetSize(out _size).ThrowIfFailure(); + } - public AesCbcStorage(IStorage baseStorage, ReadOnlySpan key, ReadOnlySpan iv, - bool leaveOpen) : base(baseStorage, BlockSize, leaveOpen) + protected override Result DoRead(long offset, Span destination) + { + if (!CheckAccessRange(offset, destination.Length, _size)) + return ResultFs.OutOfRange.Log(); + + Result rc = base.DoRead(offset, destination); + if (rc.IsFailure()) return rc; + + rc = GetDecryptor(out ICipher cipher, offset); + if (rc.IsFailure()) return rc; + + cipher.Transform(destination, destination); + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedOperation.Log(); + } + + protected override Result DoFlush() + { + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedOperation.Log(); + } + + private Result GetDecryptor(out ICipher decryptor, long offset) + { + if (offset == 0) { - if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); - if (iv.Length != BlockSize) throw new ArgumentException(nameof(iv), $"Counter must be {BlockSize} bytes long"); - - _key = key.ToArray(); - _iv = iv.ToArray(); - - baseStorage.GetSize(out _size).ThrowIfFailure(); - } - - protected override Result DoRead(long offset, Span destination) - { - if (!CheckAccessRange(offset, destination.Length, _size)) - return ResultFs.OutOfRange.Log(); - - Result rc = base.DoRead(offset, destination); - if (rc.IsFailure()) return rc; - - rc = GetDecryptor(out ICipher cipher, offset); - if (rc.IsFailure()) return rc; - - cipher.Transform(destination, destination); - + // Use the IV directly + decryptor = Aes.CreateCbcDecryptor(_key, _iv); return Result.Success; } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return ResultFs.UnsupportedOperation.Log(); - } + UnsafeHelpers.SkipParamInit(out decryptor); - protected override Result DoFlush() - { - return Result.Success; - } + // Need to get the output of the previous block + Span prevBlock = stackalloc byte[BlockSize]; + Result rc = BaseStorage.Read(offset - BlockSize, prevBlock); + if (rc.IsFailure()) return rc; - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedOperation.Log(); - } + ICipher tmpDecryptor = Aes.CreateCbcDecryptor(_key, _iv); - private Result GetDecryptor(out ICipher decryptor, long offset) - { - if (offset == 0) - { - // Use the IV directly - decryptor = Aes.CreateCbcDecryptor(_key, _iv); - return Result.Success; - } - - UnsafeHelpers.SkipParamInit(out decryptor); + tmpDecryptor.Transform(prevBlock, prevBlock); - // Need to get the output of the previous block - Span prevBlock = stackalloc byte[BlockSize]; - Result rc = BaseStorage.Read(offset - BlockSize, prevBlock); - if (rc.IsFailure()) return rc; - - ICipher tmpDecryptor = Aes.CreateCbcDecryptor(_key, _iv); - - tmpDecryptor.Transform(prevBlock, prevBlock); - - decryptor = tmpDecryptor; - return Result.Success; - } + decryptor = tmpDecryptor; + return Result.Success; } } diff --git a/src/LibHac/FsSystem/AesXtsDirectory.cs b/src/LibHac/FsSystem/AesXtsDirectory.cs index ab64ead4..260d5d3a 100644 --- a/src/LibHac/FsSystem/AesXtsDirectory.cs +++ b/src/LibHac/FsSystem/AesXtsDirectory.cs @@ -4,94 +4,93 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class AesXtsDirectory : IDirectory { - public class AesXtsDirectory : IDirectory + private U8String _path; + private OpenDirectoryMode _mode; + + private IFileSystem _baseFileSystem; + private UniqueRef _baseDirectory; + + public AesXtsDirectory(IFileSystem baseFs, ref UniqueRef baseDir, U8String path, OpenDirectoryMode mode) { - private U8String _path; - private OpenDirectoryMode _mode; + _baseFileSystem = baseFs; + _baseDirectory = new UniqueRef(ref baseDir); + _mode = mode; + _path = path; + } - private IFileSystem _baseFileSystem; - private UniqueRef _baseDirectory; + public override void Dispose() + { + _baseDirectory.Destroy(); + base.Dispose(); + } - public AesXtsDirectory(IFileSystem baseFs, ref UniqueRef baseDir, U8String path, OpenDirectoryMode mode) + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + Result rc = _baseDirectory.Get.Read(out entriesRead, entryBuffer); + if (rc.IsFailure()) return rc; + + for (int i = 0; i < entriesRead; i++) { - _baseFileSystem = baseFs; - _baseDirectory = new UniqueRef(ref baseDir); - _mode = mode; - _path = path; - } + ref DirectoryEntry entry = ref entryBuffer[i]; - public override void Dispose() - { - _baseDirectory.Destroy(); - base.Dispose(); - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - Result rc = _baseDirectory.Get.Read(out entriesRead, entryBuffer); - if (rc.IsFailure()) return rc; - - for (int i = 0; i < entriesRead; i++) + if (entry.Type == DirectoryEntryType.File) { - ref DirectoryEntry entry = ref entryBuffer[i]; - - if (entry.Type == DirectoryEntryType.File) + if (_mode.HasFlag(OpenDirectoryMode.NoFileSize)) { - if (_mode.HasFlag(OpenDirectoryMode.NoFileSize)) - { - entry.Size = 0; - } - else - { - string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name); - entry.Size = GetAesXtsFileSize(PathTools.Combine(_path.ToString(), entryName).ToU8Span()); - } + entry.Size = 0; + } + else + { + string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name); + entry.Size = GetAesXtsFileSize(PathTools.Combine(_path.ToString(), entryName).ToU8Span()); } } - - return Result.Success; } - protected override Result DoGetEntryCount(out long entryCount) + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + return _baseDirectory.Get.GetEntryCount(out entryCount); + } + + /// + /// Reads the size of a NAX0 file from its header. Returns 0 on error. + /// + /// + /// + private long GetAesXtsFileSize(U8Span path) + { + const long magicOffset = 0x20; + const long fileSizeOffset = 0x48; + + // Todo: Remove try/catch when more code uses Result + try { - return _baseDirectory.Get.GetEntryCount(out entryCount); + using var file = new UniqueRef(); + Result rc = _baseFileSystem.OpenFile(ref file.Ref(), path, OpenMode.Read); + if (rc.IsFailure()) return 0; + + uint magic = 0; + long fileSize = 0; + long bytesRead; + + file.Get.Read(out bytesRead, magicOffset, SpanHelpers.AsByteSpan(ref magic), ReadOption.None); + if (bytesRead != sizeof(uint) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0; + + file.Get.Read(out bytesRead, fileSizeOffset, SpanHelpers.AsByteSpan(ref fileSize), ReadOption.None); + if (bytesRead != sizeof(long) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0; + + return fileSize; } - - /// - /// Reads the size of a NAX0 file from its header. Returns 0 on error. - /// - /// - /// - private long GetAesXtsFileSize(U8Span path) + catch (Exception) { - const long magicOffset = 0x20; - const long fileSizeOffset = 0x48; - - // Todo: Remove try/catch when more code uses Result - try - { - using var file = new UniqueRef(); - Result rc = _baseFileSystem.OpenFile(ref file.Ref(), path, OpenMode.Read); - if (rc.IsFailure()) return 0; - - uint magic = 0; - long fileSize = 0; - long bytesRead; - - file.Get.Read(out bytesRead, magicOffset, SpanHelpers.AsByteSpan(ref magic), ReadOption.None); - if (bytesRead != sizeof(uint) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0; - - file.Get.Read(out bytesRead, fileSizeOffset, SpanHelpers.AsByteSpan(ref fileSize), ReadOption.None); - if (bytesRead != sizeof(long) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0; - - return fileSize; - } - catch (Exception) - { - return 0; - } + return 0; } } } diff --git a/src/LibHac/FsSystem/AesXtsFile.cs b/src/LibHac/FsSystem/AesXtsFile.cs index d57d0921..c74c9b98 100644 --- a/src/LibHac/FsSystem/AesXtsFile.cs +++ b/src/LibHac/FsSystem/AesXtsFile.cs @@ -4,131 +4,130 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class AesXtsFile : IFile { - public class AesXtsFile : IFile + private UniqueRef _baseFile; + private U8String Path { get; } + private byte[] KekSeed { get; } + private byte[] VerificationKey { get; } + private int BlockSize { get; } + private OpenMode Mode { get; } + + public AesXtsFileHeader Header { get; } + private IStorage BaseStorage { get; } + + internal const int HeaderLength = 0x4000; + + public AesXtsFile(OpenMode mode, ref UniqueRef baseFile, U8String path, ReadOnlySpan kekSeed, ReadOnlySpan verificationKey, int blockSize) { - private UniqueRef _baseFile; - private U8String Path { get; } - private byte[] KekSeed { get; } - private byte[] VerificationKey { get; } - private int BlockSize { get; } - private OpenMode Mode { get; } + Mode = mode; + _baseFile = new UniqueRef(ref baseFile); + Path = path; + KekSeed = kekSeed.ToArray(); + VerificationKey = verificationKey.ToArray(); + BlockSize = blockSize; - public AesXtsFileHeader Header { get; } - private IStorage BaseStorage { get; } + Header = new AesXtsFileHeader(_baseFile.Get); - internal const int HeaderLength = 0x4000; + _baseFile.Get.GetSize(out long fileSize).ThrowIfFailure(); - public AesXtsFile(OpenMode mode, ref UniqueRef baseFile, U8String path, ReadOnlySpan kekSeed, ReadOnlySpan verificationKey, int blockSize) + if (!Header.TryDecryptHeader(Path.ToString(), KekSeed, VerificationKey)) { - Mode = mode; - _baseFile = new UniqueRef(ref baseFile); - Path = path; - KekSeed = kekSeed.ToArray(); - VerificationKey = verificationKey.ToArray(); - BlockSize = blockSize; - - Header = new AesXtsFileHeader(_baseFile.Get); - - _baseFile.Get.GetSize(out long fileSize).ThrowIfFailure(); - - if (!Header.TryDecryptHeader(Path.ToString(), KekSeed, VerificationKey)) - { - ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeys.Value, "NAX0 key derivation failed."); - } - - if (HeaderLength + Alignment.AlignUp(Header.Size, 0x10) > fileSize) - { - ThrowHelper.ThrowResult(ResultFs.AesXtsFileTooShort.Value, "NAX0 key derivation failed."); - } - - var fileStorage = new FileStorage(_baseFile.Get); - var encStorage = new SubStorage(fileStorage, HeaderLength, fileSize - HeaderLength); - encStorage.SetResizable(true); - - BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Header.DecryptedKey1, Header.DecryptedKey2, BlockSize, true), 4, true); + ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeys.Value, "NAX0 key derivation failed."); } - public byte[] GetKey() + if (HeaderLength + Alignment.AlignUp(Header.Size, 0x10) > fileSize) { - byte[] key = new byte[0x20]; - Array.Copy(Header.DecryptedKey1, 0, key, 0, 0x10); - Array.Copy(Header.DecryptedKey2, 0, key, 0x10, 0x10); - - return key; + ThrowHelper.ThrowResult(ResultFs.AesXtsFileTooShort.Value, "NAX0 key derivation failed."); } - protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); + var fileStorage = new FileStorage(_baseFile.Get); + var encStorage = new SubStorage(fileStorage, HeaderLength, fileSize - HeaderLength); + encStorage.SetResizable(true); - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Header.DecryptedKey1, Header.DecryptedKey2, BlockSize, true), 4, true); + } + + public byte[] GetKey() + { + byte[] key = new byte[0x20]; + Array.Copy(Header.DecryptedKey1, 0, key, 0, 0x10); + Array.Copy(Header.DecryptedKey2, 0, key, 0x10, 0x10); + + return key; + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); + if (rc.IsFailure()) return rc; + + bytesRead = toRead; + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (isResizeNeeded) + { + rc = DoSetSize(offset + source.Length); if (rc.IsFailure()) return rc; - - rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); - if (rc.IsFailure()) return rc; - - bytesRead = toRead; - return Result.Success; } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + rc = BaseStorage.Write(offset, source); + if (rc.IsFailure()) return rc; + + if (option.HasFlushFlag()) { - Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - if (isResizeNeeded) - { - rc = DoSetSize(offset + source.Length); - if (rc.IsFailure()) return rc; - } - - rc = BaseStorage.Write(offset, source); - if (rc.IsFailure()) return rc; - - if (option.HasFlushFlag()) - { - return Flush(); - } - - return Result.Success; + return Flush(); } - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } + return Result.Success; + } - protected override Result DoGetSize(out long size) - { - size = Header.Size; - return Result.Success; - } + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - throw new NotImplementedException(); - } + protected override Result DoGetSize(out long size) + { + size = Header.Size; + return Result.Success; + } - protected override Result DoSetSize(long size) - { - Header.SetSize(size, VerificationKey); + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + throw new NotImplementedException(); + } - Result rc = _baseFile.Get.Write(0, Header.ToBytes(false)); - if (rc.IsFailure()) return rc; + protected override Result DoSetSize(long size) + { + Header.SetSize(size, VerificationKey); - return BaseStorage.SetSize(Alignment.AlignUp(size, 0x10)); - } + Result rc = _baseFile.Get.Write(0, Header.ToBytes(false)); + if (rc.IsFailure()) return rc; - public override void Dispose() - { - BaseStorage.Flush(); - BaseStorage.Dispose(); - _baseFile.Destroy(); + return BaseStorage.SetSize(Alignment.AlignUp(size, 0x10)); + } - base.Dispose(); - } + public override void Dispose() + { + BaseStorage.Flush(); + BaseStorage.Dispose(); + _baseFile.Destroy(); + + base.Dispose(); } } diff --git a/src/LibHac/FsSystem/AesXtsFileHeader.cs b/src/LibHac/FsSystem/AesXtsFileHeader.cs index 80eb9738..9ffa6d4e 100644 --- a/src/LibHac/FsSystem/AesXtsFileHeader.cs +++ b/src/LibHac/FsSystem/AesXtsFileHeader.cs @@ -7,126 +7,125 @@ using LibHac.Fs.Fsa; using Aes = LibHac.Crypto.Aes; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class AesXtsFileHeader { - public class AesXtsFileHeader + internal const uint AesXtsFileMagic = 0x3058414E; + public byte[] Signature { get; set; } = new byte[0x20]; + public uint Magic { get; } + public byte[] EncryptedKey1 { get; } = new byte[0x10]; + public byte[] EncryptedKey2 { get; } = new byte[0x10]; + public long Size { get; private set; } + + public byte[] DecryptedKey1 { get; } = new byte[0x10]; + public byte[] DecryptedKey2 { get; } = new byte[0x10]; + public byte[] Kek1 { get; } = new byte[0x10]; + public byte[] Kek2 { get; } = new byte[0x10]; + + public AesXtsFileHeader(IFile aesXtsFile) { - internal const uint AesXtsFileMagic = 0x3058414E; - public byte[] Signature { get; set; } = new byte[0x20]; - public uint Magic { get; } - public byte[] EncryptedKey1 { get; } = new byte[0x10]; - public byte[] EncryptedKey2 { get; } = new byte[0x10]; - public long Size { get; private set; } + aesXtsFile.GetSize(out long fileSize).ThrowIfFailure(); - public byte[] DecryptedKey1 { get; } = new byte[0x10]; - public byte[] DecryptedKey2 { get; } = new byte[0x10]; - public byte[] Kek1 { get; } = new byte[0x10]; - public byte[] Kek2 { get; } = new byte[0x10]; - - public AesXtsFileHeader(IFile aesXtsFile) + if (fileSize < 0x80) { - aesXtsFile.GetSize(out long fileSize).ThrowIfFailure(); - - if (fileSize < 0x80) - { - ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderTooShort.Value); - } - - var reader = new FileReader(aesXtsFile); - - reader.ReadBytes(Signature); - Magic = reader.ReadUInt32(); - reader.Position += 4; - reader.ReadBytes(EncryptedKey1); - reader.ReadBytes(EncryptedKey2); - Size = reader.ReadInt64(); - - if (Magic != AesXtsFileMagic) - { - ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidMagic.Value, "Invalid NAX0 magic value"); - } + ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderTooShort.Value); } - public AesXtsFileHeader(byte[] key, long fileSize, string path, byte[] kekSeed, byte[] verificationKey) + var reader = new FileReader(aesXtsFile); + + reader.ReadBytes(Signature); + Magic = reader.ReadUInt32(); + reader.Position += 4; + reader.ReadBytes(EncryptedKey1); + reader.ReadBytes(EncryptedKey2); + Size = reader.ReadInt64(); + + if (Magic != AesXtsFileMagic) { - Array.Copy(key, 0, DecryptedKey1, 0, 0x10); - Array.Copy(key, 0x10, DecryptedKey2, 0, 0x10); - Magic = AesXtsFileMagic; - Size = fileSize; - - EncryptHeader(path, kekSeed, verificationKey); - } - - public void EncryptHeader(string path, byte[] kekSeed, byte[] verificationKey) - { - GenerateKek(kekSeed, path); - EncryptKeys(); - Signature = CalculateHmac(verificationKey); - } - - public bool TryDecryptHeader(string path, byte[] kekSeed, byte[] verificationKey) - { - GenerateKek(kekSeed, path); - DecryptKeys(); - - byte[] hmac = CalculateHmac(verificationKey); - return Utilities.ArraysEqual(hmac, Signature); - } - - public void SetSize(long size, byte[] verificationKey) - { - Size = size; - Signature = CalculateHmac(verificationKey); - } - - private void DecryptKeys() - { - Aes.DecryptEcb128(EncryptedKey1, DecryptedKey1, Kek1); - Aes.DecryptEcb128(EncryptedKey2, DecryptedKey2, Kek2); - } - - private void EncryptKeys() - { - Aes.EncryptEcb128(DecryptedKey1, EncryptedKey1, Kek1); - Aes.EncryptEcb128(DecryptedKey2, EncryptedKey2, Kek2); - } - - private void GenerateKek(byte[] kekSeed, string path) - { - var hash = new HMACSHA256(kekSeed); - byte[] pathBytes = Encoding.UTF8.GetBytes(path); - - byte[] checksum = hash.ComputeHash(pathBytes, 0, pathBytes.Length); - Array.Copy(checksum, 0, Kek1, 0, 0x10); - Array.Copy(checksum, 0x10, Kek2, 0, 0x10); - } - - private byte[] CalculateHmac(byte[] key) - { - byte[] message = ToBytes(true).AsSpan(0x20).ToArray(); - var hash = new HMACSHA256(message); - - return hash.ComputeHash(key); - } - - public byte[] ToBytes(bool writeDecryptedKey) - { - uint magic = Magic; - long size = Size; - byte[] key1 = writeDecryptedKey ? DecryptedKey1 : EncryptedKey1; - byte[] key2 = writeDecryptedKey ? DecryptedKey2 : EncryptedKey2; - - byte[] data = new byte[0x80]; - - Array.Copy(Signature, data, 0x20); - MemoryMarshal.Write(data.AsSpan(0x20), ref magic); - - Array.Copy(key1, 0, data, 0x28, 0x10); - Array.Copy(key2, 0, data, 0x38, 0x10); - - MemoryMarshal.Write(data.AsSpan(0x48), ref size); - - return data; + ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidMagic.Value, "Invalid NAX0 magic value"); } } + + public AesXtsFileHeader(byte[] key, long fileSize, string path, byte[] kekSeed, byte[] verificationKey) + { + Array.Copy(key, 0, DecryptedKey1, 0, 0x10); + Array.Copy(key, 0x10, DecryptedKey2, 0, 0x10); + Magic = AesXtsFileMagic; + Size = fileSize; + + EncryptHeader(path, kekSeed, verificationKey); + } + + public void EncryptHeader(string path, byte[] kekSeed, byte[] verificationKey) + { + GenerateKek(kekSeed, path); + EncryptKeys(); + Signature = CalculateHmac(verificationKey); + } + + public bool TryDecryptHeader(string path, byte[] kekSeed, byte[] verificationKey) + { + GenerateKek(kekSeed, path); + DecryptKeys(); + + byte[] hmac = CalculateHmac(verificationKey); + return Utilities.ArraysEqual(hmac, Signature); + } + + public void SetSize(long size, byte[] verificationKey) + { + Size = size; + Signature = CalculateHmac(verificationKey); + } + + private void DecryptKeys() + { + Aes.DecryptEcb128(EncryptedKey1, DecryptedKey1, Kek1); + Aes.DecryptEcb128(EncryptedKey2, DecryptedKey2, Kek2); + } + + private void EncryptKeys() + { + Aes.EncryptEcb128(DecryptedKey1, EncryptedKey1, Kek1); + Aes.EncryptEcb128(DecryptedKey2, EncryptedKey2, Kek2); + } + + private void GenerateKek(byte[] kekSeed, string path) + { + var hash = new HMACSHA256(kekSeed); + byte[] pathBytes = Encoding.UTF8.GetBytes(path); + + byte[] checksum = hash.ComputeHash(pathBytes, 0, pathBytes.Length); + Array.Copy(checksum, 0, Kek1, 0, 0x10); + Array.Copy(checksum, 0x10, Kek2, 0, 0x10); + } + + private byte[] CalculateHmac(byte[] key) + { + byte[] message = ToBytes(true).AsSpan(0x20).ToArray(); + var hash = new HMACSHA256(message); + + return hash.ComputeHash(key); + } + + public byte[] ToBytes(bool writeDecryptedKey) + { + uint magic = Magic; + long size = Size; + byte[] key1 = writeDecryptedKey ? DecryptedKey1 : EncryptedKey1; + byte[] key2 = writeDecryptedKey ? DecryptedKey2 : EncryptedKey2; + + byte[] data = new byte[0x80]; + + Array.Copy(Signature, data, 0x20); + MemoryMarshal.Write(data.AsSpan(0x20), ref magic); + + Array.Copy(key1, 0, data, 0x28, 0x10); + Array.Copy(key2, 0, data, 0x38, 0x10); + + MemoryMarshal.Write(data.AsSpan(0x48), ref size); + + return data; + } } diff --git a/src/LibHac/FsSystem/AesXtsFileSystem.cs b/src/LibHac/FsSystem/AesXtsFileSystem.cs index 9a4e9120..1065f5cc 100644 --- a/src/LibHac/FsSystem/AesXtsFileSystem.cs +++ b/src/LibHac/FsSystem/AesXtsFileSystem.cs @@ -5,283 +5,282 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class AesXtsFileSystem : IFileSystem { - public class AesXtsFileSystem : IFileSystem + public int BlockSize { get; } + + private IFileSystem _baseFileSystem; + private SharedRef _sharedBaseFileSystem; + private byte[] _kekSource; + private byte[] _validationKey; + + public AesXtsFileSystem(ref SharedRef fs, byte[] keys, int blockSize) { - public int BlockSize { get; } + _sharedBaseFileSystem = SharedRef.CreateMove(ref fs); + _baseFileSystem = _sharedBaseFileSystem.Get; + _kekSource = keys.AsSpan(0, 0x10).ToArray(); + _validationKey = keys.AsSpan(0x10, 0x10).ToArray(); + BlockSize = blockSize; + } - private IFileSystem _baseFileSystem; - private SharedRef _sharedBaseFileSystem; - private byte[] _kekSource; - private byte[] _validationKey; + public AesXtsFileSystem(IFileSystem fs, byte[] keys, int blockSize) + { + _baseFileSystem = fs; + _kekSource = keys.AsSpan(0, 0x10).ToArray(); + _validationKey = keys.AsSpan(0x10, 0x10).ToArray(); + BlockSize = blockSize; + } - public AesXtsFileSystem(ref SharedRef fs, byte[] keys, int blockSize) + public override void Dispose() + { + _sharedBaseFileSystem.Destroy(); + base.Dispose(); + } + + protected override Result DoCreateDirectory(in Path path) + { + return _baseFileSystem.CreateDirectory(path); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + return CreateFile(path, size, option, new byte[0x20]); + } + + /// + /// Creates a new using the provided key. + /// + /// The full path of the file to create. + /// The initial size of the created file. + /// Flags to control how the file is created. + /// Should usually be + /// The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key. + public Result CreateFile(in Path path, long size, CreateFileOptions options, byte[] key) + { + long containerSize = AesXtsFile.HeaderLength + Alignment.AlignUp(size, 0x10); + + Result rc = _baseFileSystem.CreateFile(in path, containerSize, options); + if (rc.IsFailure()) return rc; + + var header = new AesXtsFileHeader(key, size, path.ToString(), _kekSource, _validationKey); + + using var baseFile = new UniqueRef(); + rc = _baseFileSystem.OpenFile(ref baseFile.Ref(), in path, OpenMode.Write); + if (rc.IsFailure()) return rc; + + rc = baseFile.Get.Write(0, header.ToBytes(false)); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoDeleteDirectory(in Path path) + { + return _baseFileSystem.DeleteDirectory(path); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + return _baseFileSystem.DeleteDirectoryRecursively(path); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + return _baseFileSystem.CleanDirectoryRecursively(path); + } + + protected override Result DoDeleteFile(in Path path) + { + return _baseFileSystem.DeleteFile(path); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var baseDir = new UniqueRef(); + Result rc = _baseFileSystem.OpenDirectory(ref baseDir.Ref(), path, mode); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(new AesXtsDirectory(_baseFileSystem, ref baseDir.Ref(), new U8String(path.GetString().ToArray()), mode)); + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var baseFile = new UniqueRef(); + Result rc = _baseFileSystem.OpenFile(ref baseFile.Ref(), path, mode | OpenMode.Read); + if (rc.IsFailure()) return rc; + + var xtsFile = new AesXtsFile(mode, ref baseFile.Ref(), new U8String(path.GetString().ToArray()), _kekSource, + _validationKey, BlockSize); + + outFile.Reset(xtsFile); + return Result.Success; + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + // todo: Return proper result codes + + // Official code procedure: + // Make sure all file headers can be decrypted + // Rename directory to the new path + // Reencrypt file headers with new path + // If no errors, return + // Reencrypt any modified file headers with the old path + // Rename directory to the old path + + Result rc = _baseFileSystem.RenameDirectory(currentPath, newPath); + if (rc.IsFailure()) return rc; + + try { - _sharedBaseFileSystem = SharedRef.CreateMove(ref fs); - _baseFileSystem = _sharedBaseFileSystem.Get; - _kekSource = keys.AsSpan(0, 0x10).ToArray(); - _validationKey = keys.AsSpan(0x10, 0x10).ToArray(); - BlockSize = blockSize; + RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), false); + } + catch (Exception) + { + RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), true); + _baseFileSystem.RenameDirectory(currentPath, newPath); + + throw; } - public AesXtsFileSystem(IFileSystem fs, byte[] keys, int blockSize) + return Result.Success; + } + + private void RenameDirectoryImpl(string srcDir, string dstDir, bool doRollback) + { + foreach (DirectoryEntryEx entry in this.EnumerateEntries(dstDir, "*")) { - _baseFileSystem = fs; - _kekSource = keys.AsSpan(0, 0x10).ToArray(); - _validationKey = keys.AsSpan(0x10, 0x10).ToArray(); - BlockSize = blockSize; - } + string subSrcPath = $"{srcDir}/{entry.Name}"; + string subDstPath = $"{dstDir}/{entry.Name}"; - public override void Dispose() - { - _sharedBaseFileSystem.Destroy(); - base.Dispose(); - } - - protected override Result DoCreateDirectory(in Path path) - { - return _baseFileSystem.CreateDirectory(path); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - return CreateFile(path, size, option, new byte[0x20]); - } - - /// - /// Creates a new using the provided key. - /// - /// The full path of the file to create. - /// The initial size of the created file. - /// Flags to control how the file is created. - /// Should usually be - /// The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key. - public Result CreateFile(in Path path, long size, CreateFileOptions options, byte[] key) - { - long containerSize = AesXtsFile.HeaderLength + Alignment.AlignUp(size, 0x10); - - Result rc = _baseFileSystem.CreateFile(in path, containerSize, options); - if (rc.IsFailure()) return rc; - - var header = new AesXtsFileHeader(key, size, path.ToString(), _kekSource, _validationKey); - - using var baseFile = new UniqueRef(); - rc = _baseFileSystem.OpenFile(ref baseFile.Ref(), in path, OpenMode.Write); - if (rc.IsFailure()) return rc; - - rc = baseFile.Get.Write(0, header.ToBytes(false)); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoDeleteDirectory(in Path path) - { - return _baseFileSystem.DeleteDirectory(path); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - return _baseFileSystem.DeleteDirectoryRecursively(path); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - return _baseFileSystem.CleanDirectoryRecursively(path); - } - - protected override Result DoDeleteFile(in Path path) - { - return _baseFileSystem.DeleteFile(path); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var baseDir = new UniqueRef(); - Result rc = _baseFileSystem.OpenDirectory(ref baseDir.Ref(), path, mode); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new AesXtsDirectory(_baseFileSystem, ref baseDir.Ref(), new U8String(path.GetString().ToArray()), mode)); - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var baseFile = new UniqueRef(); - Result rc = _baseFileSystem.OpenFile(ref baseFile.Ref(), path, mode | OpenMode.Read); - if (rc.IsFailure()) return rc; - - var xtsFile = new AesXtsFile(mode, ref baseFile.Ref(), new U8String(path.GetString().ToArray()), _kekSource, - _validationKey, BlockSize); - - outFile.Reset(xtsFile); - return Result.Success; - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - // todo: Return proper result codes - - // Official code procedure: - // Make sure all file headers can be decrypted - // Rename directory to the new path - // Reencrypt file headers with new path - // If no errors, return - // Reencrypt any modified file headers with the old path - // Rename directory to the old path - - Result rc = _baseFileSystem.RenameDirectory(currentPath, newPath); - if (rc.IsFailure()) return rc; - - try + if (entry.Type == DirectoryEntryType.Directory) { - RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), false); - } - catch (Exception) - { - RenameDirectoryImpl(currentPath.ToString(), newPath.ToString(), true); - _baseFileSystem.RenameDirectory(currentPath, newPath); - - throw; + RenameDirectoryImpl(subSrcPath, subDstPath, doRollback); } - return Result.Success; - } - - private void RenameDirectoryImpl(string srcDir, string dstDir, bool doRollback) - { - foreach (DirectoryEntryEx entry in this.EnumerateEntries(dstDir, "*")) + if (entry.Type == DirectoryEntryType.File) { - string subSrcPath = $"{srcDir}/{entry.Name}"; - string subDstPath = $"{dstDir}/{entry.Name}"; - - if (entry.Type == DirectoryEntryType.Directory) + if (doRollback) { - RenameDirectoryImpl(subSrcPath, subDstPath, doRollback); - } - - if (entry.Type == DirectoryEntryType.File) - { - if (doRollback) + if (TryReadXtsHeader(subDstPath, subDstPath, out AesXtsFileHeader header)) { - if (TryReadXtsHeader(subDstPath, subDstPath, out AesXtsFileHeader header)) - { - WriteXtsHeader(header, subDstPath, subSrcPath); - } - } - else - { - AesXtsFileHeader header = ReadXtsHeader(subDstPath, subSrcPath); - WriteXtsHeader(header, subDstPath, subDstPath); + WriteXtsHeader(header, subDstPath, subSrcPath); } } + else + { + AesXtsFileHeader header = ReadXtsHeader(subDstPath, subSrcPath); + WriteXtsHeader(header, subDstPath, subDstPath); + } } } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - // todo: Return proper result codes - - AesXtsFileHeader header = ReadXtsHeader(currentPath.ToString(), currentPath.ToString()); - - Result rc = _baseFileSystem.RenameFile(currentPath, newPath); - if (rc.IsFailure()) return rc; - - try - { - WriteXtsHeader(header, newPath.ToString(), newPath.ToString()); - } - catch (Exception) - { - _baseFileSystem.RenameFile(newPath, currentPath); - WriteXtsHeader(header, currentPath.ToString(), currentPath.ToString()); - - throw; - } - - return Result.Success; - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - return _baseFileSystem.GetEntryType(out entryType, path); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path); - } - - protected override Result DoCommit() - { - return _baseFileSystem.Commit(); - } - - protected override Result DoCommitProvisionally(long counter) - { - return _baseFileSystem.CommitProvisionally(counter); - } - - protected override Result DoRollback() - { - return _baseFileSystem.Rollback(); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - return _baseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, path); - } - - private AesXtsFileHeader ReadXtsHeader(string filePath, string keyPath) - { - if (!TryReadXtsHeader(filePath, keyPath, out AesXtsFileHeader header)) - { - ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeysInRenameFile.Value, "Could not decrypt AES-XTS keys"); - } - - return header; - } - - private bool TryReadXtsHeader(string filePath, string keyPath, out AesXtsFileHeader header) - { - Debug.Assert(PathTools.IsNormalized(filePath.AsSpan())); - Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan())); - - header = null; - - using var file = new UniqueRef(); - Result rc = _baseFileSystem.OpenFile(ref file.Ref(), filePath.ToU8Span(), OpenMode.Read); - if (rc.IsFailure()) return false; - - header = new AesXtsFileHeader(file.Get); - - return header.TryDecryptHeader(keyPath, _kekSource, _validationKey); - } - - private void WriteXtsHeader(AesXtsFileHeader header, string filePath, string keyPath) - { - Debug.Assert(PathTools.IsNormalized(filePath.AsSpan())); - Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan())); - - header.EncryptHeader(keyPath, _kekSource, _validationKey); - - using var file = new UniqueRef(); - _baseFileSystem.OpenFile(ref file.Ref(), filePath.ToU8Span(), OpenMode.ReadWrite); - - file.Get.Write(0, header.ToBytes(false), WriteOption.Flush).ThrowIfFailure(); - } + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + // todo: Return proper result codes + + AesXtsFileHeader header = ReadXtsHeader(currentPath.ToString(), currentPath.ToString()); + + Result rc = _baseFileSystem.RenameFile(currentPath, newPath); + if (rc.IsFailure()) return rc; + + try + { + WriteXtsHeader(header, newPath.ToString(), newPath.ToString()); + } + catch (Exception) + { + _baseFileSystem.RenameFile(newPath, currentPath); + WriteXtsHeader(header, currentPath.ToString(), currentPath.ToString()); + + throw; + } + + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + return _baseFileSystem.GetEntryType(out entryType, path); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path); + } + + protected override Result DoCommit() + { + return _baseFileSystem.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + return _baseFileSystem.CommitProvisionally(counter); + } + + protected override Result DoRollback() + { + return _baseFileSystem.Rollback(); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + return _baseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, path); + } + + private AesXtsFileHeader ReadXtsHeader(string filePath, string keyPath) + { + if (!TryReadXtsHeader(filePath, keyPath, out AesXtsFileHeader header)) + { + ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeysInRenameFile.Value, "Could not decrypt AES-XTS keys"); + } + + return header; + } + + private bool TryReadXtsHeader(string filePath, string keyPath, out AesXtsFileHeader header) + { + Debug.Assert(PathTools.IsNormalized(filePath.AsSpan())); + Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan())); + + header = null; + + using var file = new UniqueRef(); + Result rc = _baseFileSystem.OpenFile(ref file.Ref(), filePath.ToU8Span(), OpenMode.Read); + if (rc.IsFailure()) return false; + + header = new AesXtsFileHeader(file.Get); + + return header.TryDecryptHeader(keyPath, _kekSource, _validationKey); + } + + private void WriteXtsHeader(AesXtsFileHeader header, string filePath, string keyPath) + { + Debug.Assert(PathTools.IsNormalized(filePath.AsSpan())); + Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan())); + + header.EncryptHeader(keyPath, _kekSource, _validationKey); + + using var file = new UniqueRef(); + _baseFileSystem.OpenFile(ref file.Ref(), filePath.ToU8Span(), OpenMode.ReadWrite); + + file.Get.Write(0, header.ToBytes(false), WriteOption.Flush).ThrowIfFailure(); } } diff --git a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs index cfca28fb..c96ea630 100644 --- a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs +++ b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs @@ -3,89 +3,88 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAccessor { - public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAccessor + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) { - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - protected override Result DoDeleteFile(in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoDeleteFile(in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoCreateDirectory(in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoCreateDirectory(in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoDeleteDirectory(in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoDeleteDirectory(in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoCleanDirectoryRecursively(in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoCleanDirectoryRecursively(in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - throw new NotImplementedException(); - } + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + throw new NotImplementedException(); + } - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - throw new NotImplementedException(); - } + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + throw new NotImplementedException(); + } - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - throw new NotImplementedException(); - } + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + throw new NotImplementedException(); + } - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - throw new NotImplementedException(); - } + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + throw new NotImplementedException(); + } - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - throw new NotImplementedException(); - } + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } - protected override Result DoCommit() - { - throw new NotImplementedException(); - } + protected override Result DoCommit() + { + throw new NotImplementedException(); + } - public Result WriteExtraData(in SaveDataExtraData extraData) - { - throw new NotImplementedException(); - } + public Result WriteExtraData(in SaveDataExtraData extraData) + { + throw new NotImplementedException(); + } - public Result CommitExtraData(bool updateTimeStamp) - { - throw new NotImplementedException(); - } + public Result CommitExtraData(bool updateTimeStamp) + { + throw new NotImplementedException(); + } - public Result ReadExtraData(out SaveDataExtraData extraData) - { - throw new NotImplementedException(); - } + public Result ReadExtraData(out SaveDataExtraData extraData) + { + throw new NotImplementedException(); + } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } + public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSystem/BucketTree.cs b/src/LibHac/FsSystem/BucketTree.cs index 87aa99f7..ea89041f 100644 --- a/src/LibHac/FsSystem/BucketTree.cs +++ b/src/LibHac/FsSystem/BucketTree.cs @@ -8,737 +8,736 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public partial class BucketTree { - public partial class BucketTree + private const uint ExpectedMagic = 0x52544B42; // BKTR + private const int MaxVersion = 1; + + private const int NodeSizeMin = 1024; + private const int NodeSizeMax = 1024 * 512; + + private static int NodeHeaderSize => Unsafe.SizeOf(); + + private SubStorage NodeStorage { get; set; } + private SubStorage EntryStorage { get; set; } + + private NodeBuffer _nodeL1; + + private long NodeSize { get; set; } + private long EntrySize { get; set; } + private int OffsetCount { get; set; } + private int EntrySetCount { get; set; } + private long StartOffset { get; set; } + private long EndOffset { get; set; } + + public Result Initialize(SubStorage nodeStorage, SubStorage entryStorage, int nodeSize, int entrySize, + int entryCount) { - private const uint ExpectedMagic = 0x52544B42; // BKTR - private const int MaxVersion = 1; + Assert.SdkRequiresLessEqual(sizeof(long), entrySize); + Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); + Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); + Assert.SdkRequires(!IsInitialized()); - private const int NodeSizeMin = 1024; - private const int NodeSizeMax = 1024 * 512; + // Ensure valid entry count. + if (entryCount <= 0) + return ResultFs.InvalidArgument.Log(); - private static int NodeHeaderSize => Unsafe.SizeOf(); + // Allocate node. + if (!_nodeL1.Allocate(nodeSize)) + return ResultFs.BufferAllocationFailed.Log(); - private SubStorage NodeStorage { get; set; } - private SubStorage EntryStorage { get; set; } - - private NodeBuffer _nodeL1; - - private long NodeSize { get; set; } - private long EntrySize { get; set; } - private int OffsetCount { get; set; } - private int EntrySetCount { get; set; } - private long StartOffset { get; set; } - private long EndOffset { get; set; } - - public Result Initialize(SubStorage nodeStorage, SubStorage entryStorage, int nodeSize, int entrySize, - int entryCount) + bool needFree = true; + try { - Assert.SdkRequiresLessEqual(sizeof(long), entrySize); - Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); - Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); - Assert.SdkRequires(!IsInitialized()); - - // Ensure valid entry count. - if (entryCount <= 0) - return ResultFs.InvalidArgument.Log(); - - // Allocate node. - if (!_nodeL1.Allocate(nodeSize)) - return ResultFs.BufferAllocationFailed.Log(); - - bool needFree = true; - try - { - // Read node. - Result rc = nodeStorage.Read(0, _nodeL1.GetBuffer()); - if (rc.IsFailure()) return rc; - - // Verify node. - rc = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long)); - if (rc.IsFailure()) return rc; - - // Validate offsets. - int offsetCount = GetOffsetCount(nodeSize); - int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); - BucketTreeNode node = _nodeL1.GetNode(); - - long startOffset; - if (offsetCount < entrySetCount && node.GetCount() < offsetCount) - { - startOffset = node.GetL2BeginOffset(); - } - else - { - startOffset = node.GetBeginOffset(); - } - - long endOffset = node.GetEndOffset(); - - if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset) - return ResultFs.InvalidBucketTreeEntryOffset.Log(); - - NodeStorage = nodeStorage; - EntryStorage = entryStorage; - NodeSize = nodeSize; - EntrySize = entrySize; - OffsetCount = offsetCount; - EntrySetCount = entrySetCount; - StartOffset = startOffset; - EndOffset = endOffset; - - needFree = false; - - return Result.Success; - } - finally - { - if (needFree) - _nodeL1.Free(); - } - } - - public bool IsInitialized() => NodeSize > 0; - public bool IsEmpty() => EntrySize == 0; - - public long GetStart() => StartOffset; - public long GetEnd() => EndOffset; - public long GetSize() => EndOffset - StartOffset; - - public bool Includes(long offset) - { - return StartOffset <= offset && offset < EndOffset; - } - - public bool Includes(long offset, long size) - { - return size > 0 && StartOffset <= offset && size <= EndOffset - offset; - } - - public Result Find(ref Visitor visitor, long virtualAddress) - { - Assert.SdkRequires(IsInitialized()); - - if (virtualAddress < 0) - return ResultFs.InvalidOffset.Log(); - - if (IsEmpty()) - return ResultFs.OutOfRange.Log(); - - Result rc = visitor.Initialize(this); + // Read node. + Result rc = nodeStorage.Read(0, _nodeL1.GetBuffer()); if (rc.IsFailure()) return rc; - return visitor.Find(virtualAddress); - } + // Verify node. + rc = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long)); + if (rc.IsFailure()) return rc; - public static int QueryHeaderStorageSize() => Unsafe.SizeOf
(); - - public static long QueryNodeStorageSize(long nodeSize, long entrySize, int entryCount) - { - Assert.SdkRequiresLessEqual(sizeof(long), entrySize); - Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); - Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); - Assert.SdkRequiresLessEqual(0, entryCount); - - if (entryCount <= 0) - return 0; - - return (1 + GetNodeL2Count(nodeSize, entrySize, entryCount)) * nodeSize; - } - - public static long QueryEntryStorageSize(long nodeSize, long entrySize, int entryCount) - { - Assert.SdkRequiresLessEqual(sizeof(long), entrySize); - Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); - Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); - Assert.SdkRequiresLessEqual(0, entryCount); - - if (entryCount <= 0) - return 0; - - return GetEntrySetCount(nodeSize, entrySize, entryCount) * nodeSize; - } - - private static int GetEntryCount(long nodeSize, long entrySize) - { - return (int)((nodeSize - Unsafe.SizeOf()) / entrySize); - } - - private static int GetOffsetCount(long nodeSize) - { - return (int)((nodeSize - Unsafe.SizeOf()) / sizeof(long)); - } - - private static int GetEntrySetCount(long nodeSize, long entrySize, int entryCount) - { - int entryCountPerNode = GetEntryCount(nodeSize, entrySize); - return BitUtil.DivideUp(entryCount, entryCountPerNode); - } - - public static int GetNodeL2Count(long nodeSize, long entrySize, int entryCount) - { - int offsetCountPerNode = GetOffsetCount(nodeSize); + // Validate offsets. + int offsetCount = GetOffsetCount(nodeSize); int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); + BucketTreeNode node = _nodeL1.GetNode(); - if (entrySetCount <= offsetCountPerNode) - return 0; + long startOffset; + if (offsetCount < entrySetCount && node.GetCount() < offsetCount) + { + startOffset = node.GetL2BeginOffset(); + } + else + { + startOffset = node.GetBeginOffset(); + } - int nodeL2Count = BitUtil.DivideUp(entrySetCount, offsetCountPerNode); - Abort.DoAbortUnless(nodeL2Count <= offsetCountPerNode); + long endOffset = node.GetEndOffset(); - return BitUtil.DivideUp(entrySetCount - (offsetCountPerNode - (nodeL2Count - 1)), offsetCountPerNode); + if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset) + return ResultFs.InvalidBucketTreeEntryOffset.Log(); + + NodeStorage = nodeStorage; + EntryStorage = entryStorage; + NodeSize = nodeSize; + EntrySize = entrySize; + OffsetCount = offsetCount; + EntrySetCount = entrySetCount; + StartOffset = startOffset; + EndOffset = endOffset; + + needFree = false; + + return Result.Success; } - - private static long GetBucketTreeEntryOffset(long entrySetOffset, long entrySize, int entryIndex) + finally { - return entrySetOffset + Unsafe.SizeOf() + entryIndex * entrySize; + if (needFree) + _nodeL1.Free(); } + } - private static long GetBucketTreeEntryOffset(int entrySetIndex, long nodeSize, long entrySize, int entryIndex) - { - return GetBucketTreeEntryOffset(entrySetIndex * nodeSize, entrySize, entryIndex); - } + public bool IsInitialized() => NodeSize > 0; + public bool IsEmpty() => EntrySize == 0; - private bool IsExistL2() => OffsetCount < EntrySetCount; - private bool IsExistOffsetL2OnL1() => IsExistL2() && _nodeL1.GetHeader().Count < OffsetCount; + public long GetStart() => StartOffset; + public long GetEnd() => EndOffset; + public long GetSize() => EndOffset - StartOffset; - private long GetEntrySetIndex(int nodeIndex, int offsetIndex) - { - return (OffsetCount - _nodeL1.GetHeader().Count) + (OffsetCount * nodeIndex) + offsetIndex; - } + public bool Includes(long offset) + { + return StartOffset <= offset && offset < EndOffset; + } - public struct Header - { - public uint Magic; - public uint Version; - public int EntryCount; + public bool Includes(long offset, long size) + { + return size > 0 && StartOffset <= offset && size <= EndOffset - offset; + } + + public Result Find(ref Visitor visitor, long virtualAddress) + { + Assert.SdkRequires(IsInitialized()); + + if (virtualAddress < 0) + return ResultFs.InvalidOffset.Log(); + + if (IsEmpty()) + return ResultFs.OutOfRange.Log(); + + Result rc = visitor.Initialize(this); + if (rc.IsFailure()) return rc; + + return visitor.Find(virtualAddress); + } + + public static int QueryHeaderStorageSize() => Unsafe.SizeOf
(); + + public static long QueryNodeStorageSize(long nodeSize, long entrySize, int entryCount) + { + Assert.SdkRequiresLessEqual(sizeof(long), entrySize); + Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); + Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); + Assert.SdkRequiresLessEqual(0, entryCount); + + if (entryCount <= 0) + return 0; + + return (1 + GetNodeL2Count(nodeSize, entrySize, entryCount)) * nodeSize; + } + + public static long QueryEntryStorageSize(long nodeSize, long entrySize, int entryCount) + { + Assert.SdkRequiresLessEqual(sizeof(long), entrySize); + Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); + Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); + Assert.SdkRequiresLessEqual(0, entryCount); + + if (entryCount <= 0) + return 0; + + return GetEntrySetCount(nodeSize, entrySize, entryCount) * nodeSize; + } + + private static int GetEntryCount(long nodeSize, long entrySize) + { + return (int)((nodeSize - Unsafe.SizeOf()) / entrySize); + } + + private static int GetOffsetCount(long nodeSize) + { + return (int)((nodeSize - Unsafe.SizeOf()) / sizeof(long)); + } + + private static int GetEntrySetCount(long nodeSize, long entrySize, int entryCount) + { + int entryCountPerNode = GetEntryCount(nodeSize, entrySize); + return BitUtil.DivideUp(entryCount, entryCountPerNode); + } + + public static int GetNodeL2Count(long nodeSize, long entrySize, int entryCount) + { + int offsetCountPerNode = GetOffsetCount(nodeSize); + int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); + + if (entrySetCount <= offsetCountPerNode) + return 0; + + int nodeL2Count = BitUtil.DivideUp(entrySetCount, offsetCountPerNode); + Abort.DoAbortUnless(nodeL2Count <= offsetCountPerNode); + + return BitUtil.DivideUp(entrySetCount - (offsetCountPerNode - (nodeL2Count - 1)), offsetCountPerNode); + } + + private static long GetBucketTreeEntryOffset(long entrySetOffset, long entrySize, int entryIndex) + { + return entrySetOffset + Unsafe.SizeOf() + entryIndex * entrySize; + } + + private static long GetBucketTreeEntryOffset(int entrySetIndex, long nodeSize, long entrySize, int entryIndex) + { + return GetBucketTreeEntryOffset(entrySetIndex * nodeSize, entrySize, entryIndex); + } + + private bool IsExistL2() => OffsetCount < EntrySetCount; + private bool IsExistOffsetL2OnL1() => IsExistL2() && _nodeL1.GetHeader().Count < OffsetCount; + + private long GetEntrySetIndex(int nodeIndex, int offsetIndex) + { + return (OffsetCount - _nodeL1.GetHeader().Count) + (OffsetCount * nodeIndex) + offsetIndex; + } + + public struct Header + { + public uint Magic; + public uint Version; + public int EntryCount; #pragma warning disable 414 - private int _reserved; + private int _reserved; #pragma warning restore 414 - public void Format(int entryCount) + public void Format(int entryCount) + { + Magic = ExpectedMagic; + Version = MaxVersion; + EntryCount = entryCount; + _reserved = 0; + } + + public Result Verify() + { + if (Magic != ExpectedMagic) + return ResultFs.InvalidBucketTreeSignature.Log(); + + if (EntryCount < 0) + return ResultFs.InvalidBucketTreeEntryCount.Log(); + + if (Version > MaxVersion) + return ResultFs.UnsupportedVersion.Log(); + + return Result.Success; + } + } + + public struct NodeHeader + { + public int Index; + public int Count; + public long Offset; + + public Result Verify(int nodeIndex, long nodeSize, long entrySize) + { + if (Index != nodeIndex) + return ResultFs.InvalidBucketTreeNodeIndex.Log(); + + if (entrySize == 0 || nodeSize < entrySize + NodeHeaderSize) + return ResultFs.InvalidSize.Log(); + + long maxEntryCount = (nodeSize - NodeHeaderSize) / entrySize; + + if (Count <= 0 || maxEntryCount < Count) + return ResultFs.InvalidBucketTreeNodeEntryCount.Log(); + + if (Offset < 0) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); + + return Result.Success; + } + } + + private struct NodeBuffer + { + // Use long to ensure alignment + private long[] _header; + + public bool Allocate(int nodeSize) + { + Assert.SdkRequiresNull(_header); + + _header = new long[nodeSize / sizeof(long)]; + + return _header != null; + } + + public void Free() + { + _header = null; + } + + public void FillZero() + { + if (_header != null) { - Magic = ExpectedMagic; - Version = MaxVersion; - EntryCount = entryCount; - _reserved = 0; - } - - public Result Verify() - { - if (Magic != ExpectedMagic) - return ResultFs.InvalidBucketTreeSignature.Log(); - - if (EntryCount < 0) - return ResultFs.InvalidBucketTreeEntryCount.Log(); - - if (Version > MaxVersion) - return ResultFs.UnsupportedVersion.Log(); - - return Result.Success; + Array.Fill(_header, 0); } } - public struct NodeHeader + public ref NodeHeader GetHeader() { - public int Index; - public int Count; - public long Offset; + Assert.SdkRequiresGreaterEqual(_header.Length / sizeof(long), Unsafe.SizeOf()); - public Result Verify(int nodeIndex, long nodeSize, long entrySize) + return ref Unsafe.As(ref _header[0]); + } + + public Span GetBuffer() + { + return MemoryMarshal.AsBytes(_header.AsSpan()); + } + + public BucketTreeNode GetNode() where TEntry : unmanaged + { + return new BucketTreeNode(GetBuffer()); + } + } + + public readonly ref struct BucketTreeNode where TEntry : unmanaged + { + private readonly Span _buffer; + + public BucketTreeNode(Span buffer) + { + _buffer = buffer; + + Assert.SdkRequiresGreaterEqual(_buffer.Length, Unsafe.SizeOf()); + Assert.SdkRequiresGreaterEqual(_buffer.Length, + Unsafe.SizeOf() + GetHeader().Count * Unsafe.SizeOf()); + } + + public int GetCount() => GetHeader().Count; + + public ReadOnlySpan GetArray() => GetWritableArray(); + internal Span GetWritableArray() => GetWritableArray(); + + public long GetBeginOffset() => GetArray()[0]; + public long GetEndOffset() => GetHeader().Offset; + public long GetL2BeginOffset() => GetArray()[GetCount()]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetArray() where TElement : unmanaged + { + return GetWritableArray(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetWritableArray() where TElement : unmanaged + { + return MemoryMarshal.Cast(_buffer.Slice(Unsafe.SizeOf())); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref NodeHeader GetHeader() + { + return ref Unsafe.As(ref MemoryMarshal.GetReference(_buffer)); + } + } + + public ref struct Visitor + { + private BucketTree Tree { get; set; } + private byte[] Entry { get; set; } + private int EntryIndex { get; set; } + private int EntrySetCount { get; set; } + private EntrySetHeader _entrySet; + + [StructLayout(LayoutKind.Explicit)] + private struct EntrySetHeader + { + // ReSharper disable once MemberHidesStaticFromOuterClass + [FieldOffset(0)] public NodeHeader Header; + [FieldOffset(0)] public EntrySetInfo Info; + + [StructLayout(LayoutKind.Sequential)] + public struct EntrySetInfo { - if (Index != nodeIndex) - return ResultFs.InvalidBucketTreeNodeIndex.Log(); - - if (entrySize == 0 || nodeSize < entrySize + NodeHeaderSize) - return ResultFs.InvalidSize.Log(); - - long maxEntryCount = (nodeSize - NodeHeaderSize) / entrySize; - - if (Count <= 0 || maxEntryCount < Count) - return ResultFs.InvalidBucketTreeNodeEntryCount.Log(); - - if (Offset < 0) - return ResultFs.InvalidBucketTreeNodeOffset.Log(); - - return Result.Success; + public int Index; + public int Count; + public long End; + public long Start; } } - private struct NodeBuffer + public Result Initialize(BucketTree tree) { - // Use long to ensure alignment - private long[] _header; + Assert.SdkRequiresNotNull(tree); + Assert.SdkRequires(Tree == null || tree == Tree); - public bool Allocate(int nodeSize) + if (Entry == null) { - Assert.SdkRequiresNull(_header); - - _header = new long[nodeSize / sizeof(long)]; - - return _header != null; + Entry = ArrayPool.Shared.Rent((int)tree.EntrySize); + Tree = tree; + EntryIndex = -1; } - public void Free() - { - _header = null; - } + return Result.Success; + } - public void FillZero() + public void Dispose() + { + if (Entry != null) { - if (_header != null) - { - Array.Fill(_header, 0); - } - } - - public ref NodeHeader GetHeader() - { - Assert.SdkRequiresGreaterEqual(_header.Length / sizeof(long), Unsafe.SizeOf()); - - return ref Unsafe.As(ref _header[0]); - } - - public Span GetBuffer() - { - return MemoryMarshal.AsBytes(_header.AsSpan()); - } - - public BucketTreeNode GetNode() where TEntry : unmanaged - { - return new BucketTreeNode(GetBuffer()); + ArrayPool.Shared.Return(Entry); + Entry = null; } } - public readonly ref struct BucketTreeNode where TEntry : unmanaged + public bool IsValid() => EntryIndex >= 0; + + public bool CanMoveNext() { - private readonly Span _buffer; - - public BucketTreeNode(Span buffer) - { - _buffer = buffer; - - Assert.SdkRequiresGreaterEqual(_buffer.Length, Unsafe.SizeOf()); - Assert.SdkRequiresGreaterEqual(_buffer.Length, - Unsafe.SizeOf() + GetHeader().Count * Unsafe.SizeOf()); - } - - public int GetCount() => GetHeader().Count; - - public ReadOnlySpan GetArray() => GetWritableArray(); - internal Span GetWritableArray() => GetWritableArray(); - - public long GetBeginOffset() => GetArray()[0]; - public long GetEndOffset() => GetHeader().Offset; - public long GetL2BeginOffset() => GetArray()[GetCount()]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetArray() where TElement : unmanaged - { - return GetWritableArray(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Span GetWritableArray() where TElement : unmanaged - { - return MemoryMarshal.Cast(_buffer.Slice(Unsafe.SizeOf())); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref NodeHeader GetHeader() - { - return ref Unsafe.As(ref MemoryMarshal.GetReference(_buffer)); - } + return IsValid() && (EntryIndex + 1 < _entrySet.Info.Count || _entrySet.Info.Index + 1 < EntrySetCount); } - public ref struct Visitor + public bool CanMovePrevious() { - private BucketTree Tree { get; set; } - private byte[] Entry { get; set; } - private int EntryIndex { get; set; } - private int EntrySetCount { get; set; } - private EntrySetHeader _entrySet; + return IsValid() && (EntryIndex > 0 || _entrySet.Info.Index > 0); + } - [StructLayout(LayoutKind.Explicit)] - private struct EntrySetHeader + public ref T Get() where T : unmanaged + { + return ref MemoryMarshal.Cast(Entry)[0]; + } + + public Result MoveNext() + { + Result rc; + + if (!IsValid()) + return ResultFs.OutOfRange.Log(); + + int entryIndex = EntryIndex + 1; + + // Invalidate our index, and read the header for the next index. + if (entryIndex == _entrySet.Info.Count) { - // ReSharper disable once MemberHidesStaticFromOuterClass - [FieldOffset(0)] public NodeHeader Header; - [FieldOffset(0)] public EntrySetInfo Info; - - [StructLayout(LayoutKind.Sequential)] - public struct EntrySetInfo - { - public int Index; - public int Count; - public long End; - public long Start; - } - } - - public Result Initialize(BucketTree tree) - { - Assert.SdkRequiresNotNull(tree); - Assert.SdkRequires(Tree == null || tree == Tree); - - if (Entry == null) - { - Entry = ArrayPool.Shared.Rent((int)tree.EntrySize); - Tree = tree; - EntryIndex = -1; - } - - return Result.Success; - } - - public void Dispose() - { - if (Entry != null) - { - ArrayPool.Shared.Return(Entry); - Entry = null; - } - } - - public bool IsValid() => EntryIndex >= 0; - - public bool CanMoveNext() - { - return IsValid() && (EntryIndex + 1 < _entrySet.Info.Count || _entrySet.Info.Index + 1 < EntrySetCount); - } - - public bool CanMovePrevious() - { - return IsValid() && (EntryIndex > 0 || _entrySet.Info.Index > 0); - } - - public ref T Get() where T : unmanaged - { - return ref MemoryMarshal.Cast(Entry)[0]; - } - - public Result MoveNext() - { - Result rc; - - if (!IsValid()) + int entrySetIndex = _entrySet.Info.Index + 1; + if (entrySetIndex >= EntrySetCount) return ResultFs.OutOfRange.Log(); - int entryIndex = EntryIndex + 1; + EntryIndex = -1; - // Invalidate our index, and read the header for the next index. - if (entryIndex == _entrySet.Info.Count) + long end = _entrySet.Info.End; + + long entrySetSize = Tree.NodeSize; + long entrySetOffset = entrySetIndex * entrySetSize; + + rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); + if (rc.IsFailure()) return rc; + + rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); + if (rc.IsFailure()) return rc; + + if (_entrySet.Info.Start != end || _entrySet.Info.Start >= _entrySet.Info.End) + return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); + + entryIndex = 0; + } + else + { + EntryIndex = 1; + } + + // Read the new entry + long entrySize = Tree.EntrySize; + long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); + + rc = Tree.EntryStorage.Read(entryOffset, Entry); + if (rc.IsFailure()) return rc; + + // Note that we changed index. + EntryIndex = entryIndex; + return Result.Success; + } + + public Result MovePrevious() + { + Result rc; + + if (!IsValid()) + return ResultFs.OutOfRange.Log(); + + int entryIndex = EntryIndex; + + if (entryIndex == 0) + { + if (_entrySet.Info.Index <= 0) + return ResultFs.OutOfRange.Log(); + + EntryIndex = -1; + + long start = _entrySet.Info.Start; + + long entrySetSize = Tree.NodeSize; + int entrySetIndex = _entrySet.Info.Index - 1; + long entrySetOffset = entrySetIndex * entrySetSize; + + rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); + if (rc.IsFailure()) return rc; + + rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); + if (rc.IsFailure()) return rc; + + if (_entrySet.Info.End != start || _entrySet.Info.Start >= _entrySet.Info.End) + return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); + + entryIndex = _entrySet.Info.Count; + } + else + { + EntryIndex = -1; + } + + // Read the new entry + long entrySize = Tree.EntrySize; + long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); + + rc = Tree.EntryStorage.Read(entryOffset, Entry); + if (rc.IsFailure()) return rc; + + // Note that we changed index. + EntryIndex = entryIndex; + return Result.Success; + } + + public Result Find(long virtualAddress) + { + Result rc; + + // Get the node. + BucketTreeNode node = Tree._nodeL1.GetNode(); + + if (virtualAddress >= node.GetEndOffset()) + return ResultFs.OutOfRange.Log(); + + int entrySetIndex; + + if (Tree.IsExistOffsetL2OnL1() && virtualAddress < node.GetBeginOffset()) + { + // The portion of the L2 offsets containing our target offset is stored in the L1 node + ReadOnlySpan offsets = node.GetArray().Slice(node.GetCount()); + int index = offsets.BinarySearch(virtualAddress); + if (index < 0) index = (~index) - 1; + + if (index < 0) + return ResultFs.OutOfRange.Log(); + + entrySetIndex = index; + } + else + { + ReadOnlySpan offsets = node.GetArray().Slice(0, node.GetCount()); + int index = offsets.BinarySearch(virtualAddress); + if (index < 0) index = (~index) - 1; + + if (index < 0) + return ResultFs.OutOfRange.Log(); + + if (Tree.IsExistL2()) { - int entrySetIndex = _entrySet.Info.Index + 1; - if (entrySetIndex >= EntrySetCount) - return ResultFs.OutOfRange.Log(); + if (index >= Tree.OffsetCount) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); - EntryIndex = -1; - - long end = _entrySet.Info.End; - - long entrySetSize = Tree.NodeSize; - long entrySetOffset = entrySetIndex * entrySetSize; - - rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); + rc = FindEntrySet(out entrySetIndex, virtualAddress, index); if (rc.IsFailure()) return rc; - - rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); - if (rc.IsFailure()) return rc; - - if (_entrySet.Info.Start != end || _entrySet.Info.Start >= _entrySet.Info.End) - return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); - - entryIndex = 0; } else { - EntryIndex = 1; - } - - // Read the new entry - long entrySize = Tree.EntrySize; - long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); - - rc = Tree.EntryStorage.Read(entryOffset, Entry); - if (rc.IsFailure()) return rc; - - // Note that we changed index. - EntryIndex = entryIndex; - return Result.Success; - } - - public Result MovePrevious() - { - Result rc; - - if (!IsValid()) - return ResultFs.OutOfRange.Log(); - - int entryIndex = EntryIndex; - - if (entryIndex == 0) - { - if (_entrySet.Info.Index <= 0) - return ResultFs.OutOfRange.Log(); - - EntryIndex = -1; - - long start = _entrySet.Info.Start; - - long entrySetSize = Tree.NodeSize; - int entrySetIndex = _entrySet.Info.Index - 1; - long entrySetOffset = entrySetIndex * entrySetSize; - - rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); - if (rc.IsFailure()) return rc; - - rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); - if (rc.IsFailure()) return rc; - - if (_entrySet.Info.End != start || _entrySet.Info.Start >= _entrySet.Info.End) - return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); - - entryIndex = _entrySet.Info.Count; - } - else - { - EntryIndex = -1; - } - - // Read the new entry - long entrySize = Tree.EntrySize; - long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); - - rc = Tree.EntryStorage.Read(entryOffset, Entry); - if (rc.IsFailure()) return rc; - - // Note that we changed index. - EntryIndex = entryIndex; - return Result.Success; - } - - public Result Find(long virtualAddress) - { - Result rc; - - // Get the node. - BucketTreeNode node = Tree._nodeL1.GetNode(); - - if (virtualAddress >= node.GetEndOffset()) - return ResultFs.OutOfRange.Log(); - - int entrySetIndex; - - if (Tree.IsExistOffsetL2OnL1() && virtualAddress < node.GetBeginOffset()) - { - // The portion of the L2 offsets containing our target offset is stored in the L1 node - ReadOnlySpan offsets = node.GetArray().Slice(node.GetCount()); - int index = offsets.BinarySearch(virtualAddress); - if (index < 0) index = (~index) - 1; - - if (index < 0) - return ResultFs.OutOfRange.Log(); - entrySetIndex = index; } - else + } + + // Validate the entry set index. + if (entrySetIndex < 0 || entrySetIndex >= Tree.EntrySetCount) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); + + // Find the entry. + rc = FindEntry(virtualAddress, entrySetIndex); + if (rc.IsFailure()) return rc; + + // Set count. + EntrySetCount = Tree.EntrySetCount; + return Result.Success; + } + + private Result FindEntrySet(out int entrySetIndex, long virtualAddress, int nodeIndex) + { + long nodeSize = Tree.NodeSize; + + using (var rented = new RentedArray((int)nodeSize)) + { + return FindEntrySetWithBuffer(out entrySetIndex, virtualAddress, nodeIndex, rented.Span); + } + } + + private Result FindEntrySetWithBuffer(out int outIndex, long virtualAddress, int nodeIndex, + Span buffer) + { + UnsafeHelpers.SkipParamInit(out outIndex); + + // Calculate node extents. + long nodeSize = Tree.NodeSize; + long nodeOffset = (nodeIndex + 1) * nodeSize; + SubStorage storage = Tree.NodeStorage; + + // Read the node. + Result rc = storage.Read(nodeOffset, buffer.Slice(0, (int)nodeSize)); + if (rc.IsFailure()) return rc; + + // Validate the header. + NodeHeader header = MemoryMarshal.Cast(buffer)[0]; + rc = header.Verify(nodeIndex, nodeSize, sizeof(long)); + if (rc.IsFailure()) return rc; + + // Create the node and find. + var node = new StorageNode(sizeof(long), header.Count); + node.Find(buffer, virtualAddress); + + if (node.GetIndex() < 0) + return ResultFs.InvalidBucketTreeVirtualOffset.Log(); + + // Return the index. + outIndex = (int)Tree.GetEntrySetIndex(header.Index, node.GetIndex()); + return Result.Success; + } + + private Result FindEntry(long virtualAddress, int entrySetIndex) + { + long entrySetSize = Tree.NodeSize; + + using (var rented = new RentedArray((int)entrySetSize)) + { + return FindEntryWithBuffer(virtualAddress, entrySetIndex, rented.Span); + } + } + + private Result FindEntryWithBuffer(long virtualAddress, int entrySetIndex, Span buffer) + { + // Calculate entry set extents. + long entrySize = Tree.EntrySize; + long entrySetSize = Tree.NodeSize; + long entrySetOffset = entrySetIndex * entrySetSize; + SubStorage storage = Tree.EntryStorage; + + // Read the entry set. + Result rc = storage.Read(entrySetOffset, buffer.Slice(0, (int)entrySetSize)); + if (rc.IsFailure()) return rc; + + // Validate the entry set. + EntrySetHeader entrySet = MemoryMarshal.Cast(buffer)[0]; + rc = entrySet.Header.Verify(entrySetIndex, entrySetSize, entrySize); + if (rc.IsFailure()) return rc; + + // Create the node, and find. + var node = new StorageNode(entrySize, entrySet.Info.Count); + node.Find(buffer, virtualAddress); + + if (node.GetIndex() < 0) + return ResultFs.InvalidBucketTreeVirtualOffset.Log(); + + // Copy the data into entry. + int entryIndex = node.GetIndex(); + long entryOffset = GetBucketTreeEntryOffset(0, entrySize, entryIndex); + buffer.Slice((int)entryOffset, (int)entrySize).CopyTo(Entry); + + // Set our entry set/index. + _entrySet = entrySet; + EntryIndex = entryIndex; + + return Result.Success; + } + + private struct StorageNode + { + private Offset _start; + private int _count; + private int _index; + + public StorageNode(long size, int count) + { + _start = new Offset(NodeHeaderSize, (int)size); + _count = count; + _index = -1; + } + + public int GetIndex() => _index; + + public void Find(ReadOnlySpan buffer, long virtualAddress) + { + int end = _count; + Offset pos = _start; + + while (end > 0) { - ReadOnlySpan offsets = node.GetArray().Slice(0, node.GetCount()); - int index = offsets.BinarySearch(virtualAddress); - if (index < 0) index = (~index) - 1; + int half = end / 2; + Offset mid = pos + half; - if (index < 0) - return ResultFs.OutOfRange.Log(); + long offset = BinaryPrimitives.ReadInt64LittleEndian(buffer.Slice((int)mid.Get())); - if (Tree.IsExistL2()) + if (offset <= virtualAddress) { - if (index >= Tree.OffsetCount) - return ResultFs.InvalidBucketTreeNodeOffset.Log(); - - rc = FindEntrySet(out entrySetIndex, virtualAddress, index); - if (rc.IsFailure()) return rc; + pos = mid + 1; + end -= half + 1; } else { - entrySetIndex = index; + end = half; } } - // Validate the entry set index. - if (entrySetIndex < 0 || entrySetIndex >= Tree.EntrySetCount) - return ResultFs.InvalidBucketTreeNodeOffset.Log(); - - // Find the entry. - rc = FindEntry(virtualAddress, entrySetIndex); - if (rc.IsFailure()) return rc; - - // Set count. - EntrySetCount = Tree.EntrySetCount; - return Result.Success; + _index = (int)(pos - _start) - 1; } - private Result FindEntrySet(out int entrySetIndex, long virtualAddress, int nodeIndex) + private readonly struct Offset { - long nodeSize = Tree.NodeSize; + private readonly long _offset; + private readonly int _stride; - using (var rented = new RentedArray((int)nodeSize)) + public Offset(long offset, int stride) { - return FindEntrySetWithBuffer(out entrySetIndex, virtualAddress, nodeIndex, rented.Span); - } - } - - private Result FindEntrySetWithBuffer(out int outIndex, long virtualAddress, int nodeIndex, - Span buffer) - { - UnsafeHelpers.SkipParamInit(out outIndex); - - // Calculate node extents. - long nodeSize = Tree.NodeSize; - long nodeOffset = (nodeIndex + 1) * nodeSize; - SubStorage storage = Tree.NodeStorage; - - // Read the node. - Result rc = storage.Read(nodeOffset, buffer.Slice(0, (int)nodeSize)); - if (rc.IsFailure()) return rc; - - // Validate the header. - NodeHeader header = MemoryMarshal.Cast(buffer)[0]; - rc = header.Verify(nodeIndex, nodeSize, sizeof(long)); - if (rc.IsFailure()) return rc; - - // Create the node and find. - var node = new StorageNode(sizeof(long), header.Count); - node.Find(buffer, virtualAddress); - - if (node.GetIndex() < 0) - return ResultFs.InvalidBucketTreeVirtualOffset.Log(); - - // Return the index. - outIndex = (int)Tree.GetEntrySetIndex(header.Index, node.GetIndex()); - return Result.Success; - } - - private Result FindEntry(long virtualAddress, int entrySetIndex) - { - long entrySetSize = Tree.NodeSize; - - using (var rented = new RentedArray((int)entrySetSize)) - { - return FindEntryWithBuffer(virtualAddress, entrySetIndex, rented.Span); - } - } - - private Result FindEntryWithBuffer(long virtualAddress, int entrySetIndex, Span buffer) - { - // Calculate entry set extents. - long entrySize = Tree.EntrySize; - long entrySetSize = Tree.NodeSize; - long entrySetOffset = entrySetIndex * entrySetSize; - SubStorage storage = Tree.EntryStorage; - - // Read the entry set. - Result rc = storage.Read(entrySetOffset, buffer.Slice(0, (int)entrySetSize)); - if (rc.IsFailure()) return rc; - - // Validate the entry set. - EntrySetHeader entrySet = MemoryMarshal.Cast(buffer)[0]; - rc = entrySet.Header.Verify(entrySetIndex, entrySetSize, entrySize); - if (rc.IsFailure()) return rc; - - // Create the node, and find. - var node = new StorageNode(entrySize, entrySet.Info.Count); - node.Find(buffer, virtualAddress); - - if (node.GetIndex() < 0) - return ResultFs.InvalidBucketTreeVirtualOffset.Log(); - - // Copy the data into entry. - int entryIndex = node.GetIndex(); - long entryOffset = GetBucketTreeEntryOffset(0, entrySize, entryIndex); - buffer.Slice((int)entryOffset, (int)entrySize).CopyTo(Entry); - - // Set our entry set/index. - _entrySet = entrySet; - EntryIndex = entryIndex; - - return Result.Success; - } - - private struct StorageNode - { - private Offset _start; - private int _count; - private int _index; - - public StorageNode(long size, int count) - { - _start = new Offset(NodeHeaderSize, (int)size); - _count = count; - _index = -1; + _offset = offset; + _stride = stride; } - public int GetIndex() => _index; + public long Get() => _offset; - public void Find(ReadOnlySpan buffer, long virtualAddress) - { - int end = _count; - Offset pos = _start; + public static Offset operator ++(Offset left) => left + 1; + public static Offset operator --(Offset left) => left - 1; - while (end > 0) - { - int half = end / 2; - Offset mid = pos + half; + public static Offset operator +(Offset left, long right) => new Offset(left._offset + right * left._stride, left._stride); + public static Offset operator -(Offset left, long right) => new Offset(left._offset - right * left._stride, left._stride); - long offset = BinaryPrimitives.ReadInt64LittleEndian(buffer.Slice((int)mid.Get())); + public static long operator -(Offset left, Offset right) => + (left._offset - right._offset) / left._stride; - if (offset <= virtualAddress) - { - pos = mid + 1; - end -= half + 1; - } - else - { - end = half; - } - } + public static bool operator ==(Offset left, Offset right) => left._offset == right._offset; + public static bool operator !=(Offset left, Offset right) => left._offset != right._offset; - _index = (int)(pos - _start) - 1; - } - - private readonly struct Offset - { - private readonly long _offset; - private readonly int _stride; - - public Offset(long offset, int stride) - { - _offset = offset; - _stride = stride; - } - - public long Get() => _offset; - - public static Offset operator ++(Offset left) => left + 1; - public static Offset operator --(Offset left) => left - 1; - - public static Offset operator +(Offset left, long right) => new Offset(left._offset + right * left._stride, left._stride); - public static Offset operator -(Offset left, long right) => new Offset(left._offset - right * left._stride, left._stride); - - public static long operator -(Offset left, Offset right) => - (left._offset - right._offset) / left._stride; - - public static bool operator ==(Offset left, Offset right) => left._offset == right._offset; - public static bool operator !=(Offset left, Offset right) => left._offset != right._offset; - - public bool Equals(Offset other) => _offset == other._offset; - public override bool Equals(object obj) => obj is Offset other && Equals(other); - public override int GetHashCode() => _offset.GetHashCode(); - } + public bool Equals(Offset other) => _offset == other._offset; + public override bool Equals(object obj) => obj is Offset other && Equals(other); + public override int GetHashCode() => _offset.GetHashCode(); } } } diff --git a/src/LibHac/FsSystem/BucketTreeBuilder.cs b/src/LibHac/FsSystem/BucketTreeBuilder.cs index e7070c6e..d66438b3 100644 --- a/src/LibHac/FsSystem/BucketTreeBuilder.cs +++ b/src/LibHac/FsSystem/BucketTreeBuilder.cs @@ -6,305 +6,304 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public partial class BucketTree { - public partial class BucketTree + public class Builder { - public class Builder + private SubStorage NodeStorage { get; set; } + private SubStorage EntryStorage { get; set; } + + private NodeBuffer _l1Node; + private NodeBuffer _l2Node; + private NodeBuffer _entrySet; + + private int NodeSize { get; set; } + private int EntrySize { get; set; } + private int EntryCount { get; set; } + private int EntriesPerEntrySet { get; set; } + private int OffsetsPerNode { get; set; } + + private int CurrentL2OffsetIndex { get; set; } + private int CurrentEntryIndex { get; set; } + private long CurrentOffset { get; set; } = -1; + + /// + /// Initializes the bucket tree builder. + /// + /// The the tree's header will be written to.Must be at least the size in bytes returned by . + /// The the tree's nodes will be written to. Must be at least the size in bytes returned by . + /// The the tree's entries will be written to. Must be at least the size in bytes returned by . + /// The size of each node in the bucket tree. Must be a power of 2. + /// The size of each entry that will be stored in the bucket tree. + /// The exact number of entries that will be added to the bucket tree. + /// The of the operation. + public Result Initialize(SubStorage headerStorage, SubStorage nodeStorage, SubStorage entryStorage, + int nodeSize, int entrySize, int entryCount) { - private SubStorage NodeStorage { get; set; } - private SubStorage EntryStorage { get; set; } + Assert.SdkRequiresLessEqual(sizeof(long), entrySize); + Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); + Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); - private NodeBuffer _l1Node; - private NodeBuffer _l2Node; - private NodeBuffer _entrySet; + if (headerStorage is null || nodeStorage is null || entryStorage is null) + return ResultFs.NullptrArgument.Log(); - private int NodeSize { get; set; } - private int EntrySize { get; set; } - private int EntryCount { get; set; } - private int EntriesPerEntrySet { get; set; } - private int OffsetsPerNode { get; set; } + // Set the builder parameters + NodeSize = nodeSize; + EntrySize = entrySize; + EntryCount = entryCount; - private int CurrentL2OffsetIndex { get; set; } - private int CurrentEntryIndex { get; set; } - private long CurrentOffset { get; set; } = -1; + EntriesPerEntrySet = GetEntryCount(nodeSize, entrySize); + OffsetsPerNode = GetOffsetCount(nodeSize); + CurrentL2OffsetIndex = GetNodeL2Count(nodeSize, entrySize, entryCount); - /// - /// Initializes the bucket tree builder. - /// - /// The the tree's header will be written to.Must be at least the size in bytes returned by . - /// The the tree's nodes will be written to. Must be at least the size in bytes returned by . - /// The the tree's entries will be written to. Must be at least the size in bytes returned by . - /// The size of each node in the bucket tree. Must be a power of 2. - /// The size of each entry that will be stored in the bucket tree. - /// The exact number of entries that will be added to the bucket tree. - /// The of the operation. - public Result Initialize(SubStorage headerStorage, SubStorage nodeStorage, SubStorage entryStorage, - int nodeSize, int entrySize, int entryCount) + // Create and write the header + var header = new Header(); + header.Format(entryCount); + Result rc = headerStorage.Write(0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + // Allocate buffers for the L1 node and entry sets + _l1Node.Allocate(nodeSize); + _entrySet.Allocate(nodeSize); + + int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); + + // Allocate an L2 node buffer if there are more entry sets than will fit in the L1 node + if (OffsetsPerNode < entrySetCount) { - Assert.SdkRequiresLessEqual(sizeof(long), entrySize); - Assert.SdkRequiresLessEqual(entrySize + Unsafe.SizeOf(), nodeSize); - Assert.SdkRequiresWithinMinMax(nodeSize, NodeSizeMin, NodeSizeMax); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(nodeSize)); + _l2Node.Allocate(nodeSize); + } - if (headerStorage is null || nodeStorage is null || entryStorage is null) - return ResultFs.NullptrArgument.Log(); + _l1Node.FillZero(); + _l2Node.FillZero(); + _entrySet.FillZero(); - // Set the builder parameters - NodeSize = nodeSize; - EntrySize = entrySize; - EntryCount = entryCount; + NodeStorage = nodeStorage; + EntryStorage = entryStorage; - EntriesPerEntrySet = GetEntryCount(nodeSize, entrySize); - OffsetsPerNode = GetOffsetCount(nodeSize); - CurrentL2OffsetIndex = GetNodeL2Count(nodeSize, entrySize, entryCount); + // Set the initial position + CurrentEntryIndex = 0; + CurrentOffset = -1; - // Create and write the header - var header = new Header(); - header.Format(entryCount); - Result rc = headerStorage.Write(0, SpanHelpers.AsByteSpan(ref header)); + return Result.Success; + } + + /// + /// Adds a new entry to the bucket tree. + /// + /// The type of the entry to add. Added entries should all be the same type. + /// The entry to add. + /// The of the operation. + public Result Add(ref T entry) where T : unmanaged + { + Assert.SdkRequiresEqual(Unsafe.SizeOf(), EntrySize); + + if (CurrentEntryIndex >= EntryCount) + return ResultFs.OutOfRange.Log(); + + // The entry offset must always be the first 8 bytes of the struct + long entryOffset = BinaryPrimitives.ReadInt64LittleEndian(SpanHelpers.AsByteSpan(ref entry)); + + if (entryOffset <= CurrentOffset) + return ResultFs.InvalidOffset.Log(); + + Result rc = FinalizePreviousEntrySet(entryOffset); + if (rc.IsFailure()) return rc; + + AddEntryOffset(entryOffset); + + // Write the new entry + int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; + _entrySet.GetNode().GetWritableArray()[indexInEntrySet] = entry; + + CurrentOffset = entryOffset; + CurrentEntryIndex++; + + return Result.Success; + } + + /// + /// Checks if a new entry set is being started. If so, sets the end offset of the previous + /// entry set and writes it to the output storage. + /// + /// The end offset of the previous entry. + /// The of the operation. + private Result FinalizePreviousEntrySet(long endOffset) + { + int prevEntrySetIndex = CurrentEntryIndex / EntriesPerEntrySet - 1; + int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; + + // If the previous Add finished an entry set + if (CurrentEntryIndex > 0 && indexInEntrySet == 0) + { + // Set the end offset of that entry set + ref NodeHeader entrySetHeader = ref _entrySet.GetHeader(); + + entrySetHeader.Index = prevEntrySetIndex; + entrySetHeader.Count = EntriesPerEntrySet; + entrySetHeader.Offset = endOffset; + + // Write the entry set to the entry storage + long storageOffset = (long)NodeSize * prevEntrySetIndex; + Result rc = EntryStorage.Write(storageOffset, _entrySet.GetBuffer()); if (rc.IsFailure()) return rc; - // Allocate buffers for the L1 node and entry sets - _l1Node.Allocate(nodeSize); - _entrySet.Allocate(nodeSize); - - int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); - - // Allocate an L2 node buffer if there are more entry sets than will fit in the L1 node - if (OffsetsPerNode < entrySetCount) - { - _l2Node.Allocate(nodeSize); - } - - _l1Node.FillZero(); - _l2Node.FillZero(); + // Clear the entry set buffer to begin the new entry set _entrySet.FillZero(); - NodeStorage = nodeStorage; - EntryStorage = entryStorage; - - // Set the initial position - CurrentEntryIndex = 0; - CurrentOffset = -1; - - return Result.Success; - } - - /// - /// Adds a new entry to the bucket tree. - /// - /// The type of the entry to add. Added entries should all be the same type. - /// The entry to add. - /// The of the operation. - public Result Add(ref T entry) where T : unmanaged - { - Assert.SdkRequiresEqual(Unsafe.SizeOf(), EntrySize); - - if (CurrentEntryIndex >= EntryCount) - return ResultFs.OutOfRange.Log(); - - // The entry offset must always be the first 8 bytes of the struct - long entryOffset = BinaryPrimitives.ReadInt64LittleEndian(SpanHelpers.AsByteSpan(ref entry)); - - if (entryOffset <= CurrentOffset) - return ResultFs.InvalidOffset.Log(); - - Result rc = FinalizePreviousEntrySet(entryOffset); - if (rc.IsFailure()) return rc; - - AddEntryOffset(entryOffset); - - // Write the new entry - int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; - _entrySet.GetNode().GetWritableArray()[indexInEntrySet] = entry; - - CurrentOffset = entryOffset; - CurrentEntryIndex++; - - return Result.Success; - } - - /// - /// Checks if a new entry set is being started. If so, sets the end offset of the previous - /// entry set and writes it to the output storage. - /// - /// The end offset of the previous entry. - /// The of the operation. - private Result FinalizePreviousEntrySet(long endOffset) - { - int prevEntrySetIndex = CurrentEntryIndex / EntriesPerEntrySet - 1; - int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; - - // If the previous Add finished an entry set - if (CurrentEntryIndex > 0 && indexInEntrySet == 0) + // Check if we're writing in L2 nodes + if (CurrentL2OffsetIndex > OffsetsPerNode) { - // Set the end offset of that entry set - ref NodeHeader entrySetHeader = ref _entrySet.GetHeader(); + int prevL2NodeIndex = CurrentL2OffsetIndex / OffsetsPerNode - 2; + int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; - entrySetHeader.Index = prevEntrySetIndex; - entrySetHeader.Count = EntriesPerEntrySet; - entrySetHeader.Offset = endOffset; - - // Write the entry set to the entry storage - long storageOffset = (long)NodeSize * prevEntrySetIndex; - Result rc = EntryStorage.Write(storageOffset, _entrySet.GetBuffer()); - if (rc.IsFailure()) return rc; - - // Clear the entry set buffer to begin the new entry set - _entrySet.FillZero(); - - // Check if we're writing in L2 nodes - if (CurrentL2OffsetIndex > OffsetsPerNode) + // If the previous Add finished an L2 node + if (indexInL2Node == 0) { - int prevL2NodeIndex = CurrentL2OffsetIndex / OffsetsPerNode - 2; - int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; + // Set the end offset of that node + ref NodeHeader l2NodeHeader = ref _l2Node.GetHeader(); - // If the previous Add finished an L2 node - if (indexInL2Node == 0) - { - // Set the end offset of that node - ref NodeHeader l2NodeHeader = ref _l2Node.GetHeader(); + l2NodeHeader.Index = prevL2NodeIndex; + l2NodeHeader.Count = OffsetsPerNode; + l2NodeHeader.Offset = endOffset; - l2NodeHeader.Index = prevL2NodeIndex; - l2NodeHeader.Count = OffsetsPerNode; - l2NodeHeader.Offset = endOffset; + // Write the L2 node to the node storage + long nodeOffset = (long)NodeSize * (prevL2NodeIndex + 1); + rc = NodeStorage.Write(nodeOffset, _l2Node.GetBuffer()); + if (rc.IsFailure()) return rc; - // Write the L2 node to the node storage - long nodeOffset = (long)NodeSize * (prevL2NodeIndex + 1); - rc = NodeStorage.Write(nodeOffset, _l2Node.GetBuffer()); - if (rc.IsFailure()) return rc; - - // Clear the L2 node buffer to begin the new node - _l2Node.FillZero(); - } - } - } - - return Result.Success; - } - - /// - /// If needed, adds a new entry set's start offset to the L1 or L2 nodes. - /// - /// The start offset of the entry being added. - private void AddEntryOffset(long entryOffset) - { - int entrySetIndex = CurrentEntryIndex / EntriesPerEntrySet; - int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; - - // If we're starting a new entry set we need to add its start offset to the L1/L2 nodes - if (indexInEntrySet == 0) - { - Span l1Data = _l1Node.GetNode().GetWritableArray(); - - if (CurrentL2OffsetIndex == 0) - { - // There are no L2 nodes. Write the entry set end offset directly to L1 - l1Data[entrySetIndex] = entryOffset; - } - else - { - if (CurrentL2OffsetIndex < OffsetsPerNode) - { - // The current L2 offset is stored in the L1 node - l1Data[CurrentL2OffsetIndex] = entryOffset; - } - else - { - // Write the entry set offset to the current L2 node - int l2NodeIndex = CurrentL2OffsetIndex / OffsetsPerNode; - int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; - - Span l2Data = _l2Node.GetNode().GetWritableArray(); - l2Data[indexInL2Node] = entryOffset; - - // If we're starting a new L2 node we need to add its start offset to the L1 node - if (indexInL2Node == 0) - { - l1Data[l2NodeIndex - 1] = entryOffset; - } - } - - CurrentL2OffsetIndex++; + // Clear the L2 node buffer to begin the new node + _l2Node.FillZero(); } } } - /// - /// Finalizes the bucket tree. Must be called after all entries are added. - /// - /// The end offset of the bucket tree. - /// The of the operation. - public Result Finalize(long endOffset) + return Result.Success; + } + + /// + /// If needed, adds a new entry set's start offset to the L1 or L2 nodes. + /// + /// The start offset of the entry being added. + private void AddEntryOffset(long entryOffset) + { + int entrySetIndex = CurrentEntryIndex / EntriesPerEntrySet; + int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; + + // If we're starting a new entry set we need to add its start offset to the L1/L2 nodes + if (indexInEntrySet == 0) { - // Finalize must only be called after all entries are added - if (EntryCount != CurrentEntryIndex) - return ResultFs.OutOfRange.Log(); + Span l1Data = _l1Node.GetNode().GetWritableArray(); - if (endOffset <= CurrentOffset) - return ResultFs.InvalidOffset.Log(); - - if (CurrentOffset == -1) - return Result.Success; - - Result rc = FinalizePreviousEntrySet(endOffset); - if (rc.IsFailure()) return rc; - - int entrySetIndex = CurrentEntryIndex / EntriesPerEntrySet; - int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; - - // Finalize the current entry set if needed - if (indexInEntrySet != 0) - { - ref NodeHeader entrySetHeader = ref _entrySet.GetHeader(); - - entrySetHeader.Index = entrySetIndex; - entrySetHeader.Count = indexInEntrySet; - entrySetHeader.Offset = endOffset; - - long entryStorageOffset = (long)NodeSize * entrySetIndex; - rc = EntryStorage.Write(entryStorageOffset, _entrySet.GetBuffer()); - if (rc.IsFailure()) return rc; - } - - int l2NodeIndex = BitUtil.DivideUp(CurrentL2OffsetIndex, OffsetsPerNode) - 2; - int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; - - // Finalize the current L2 node if needed - if (CurrentL2OffsetIndex > OffsetsPerNode && (indexInEntrySet != 0 || indexInL2Node != 0)) - { - ref NodeHeader l2NodeHeader = ref _l2Node.GetHeader(); - l2NodeHeader.Index = l2NodeIndex; - l2NodeHeader.Count = indexInL2Node != 0 ? indexInL2Node : OffsetsPerNode; - l2NodeHeader.Offset = endOffset; - - long l2NodeStorageOffset = NodeSize * (l2NodeIndex + 1); - rc = NodeStorage.Write(l2NodeStorageOffset, _l2Node.GetBuffer()); - if (rc.IsFailure()) return rc; - } - - // Finalize the L1 node - ref NodeHeader l1NodeHeader = ref _l1Node.GetHeader(); - l1NodeHeader.Index = 0; - l1NodeHeader.Offset = endOffset; - - // L1 count depends on the existence or absence of L2 nodes if (CurrentL2OffsetIndex == 0) { - l1NodeHeader.Count = BitUtil.DivideUp(CurrentEntryIndex, EntriesPerEntrySet); + // There are no L2 nodes. Write the entry set end offset directly to L1 + l1Data[entrySetIndex] = entryOffset; } else { - l1NodeHeader.Count = l2NodeIndex + 1; + if (CurrentL2OffsetIndex < OffsetsPerNode) + { + // The current L2 offset is stored in the L1 node + l1Data[CurrentL2OffsetIndex] = entryOffset; + } + else + { + // Write the entry set offset to the current L2 node + int l2NodeIndex = CurrentL2OffsetIndex / OffsetsPerNode; + int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; + + Span l2Data = _l2Node.GetNode().GetWritableArray(); + l2Data[indexInL2Node] = entryOffset; + + // If we're starting a new L2 node we need to add its start offset to the L1 node + if (indexInL2Node == 0) + { + l1Data[l2NodeIndex - 1] = entryOffset; + } + } + + CurrentL2OffsetIndex++; } - - rc = NodeStorage.Write(0, _l1Node.GetBuffer()); - if (rc.IsFailure()) return rc; - - CurrentOffset = long.MaxValue; - return Result.Success; } } + + /// + /// Finalizes the bucket tree. Must be called after all entries are added. + /// + /// The end offset of the bucket tree. + /// The of the operation. + public Result Finalize(long endOffset) + { + // Finalize must only be called after all entries are added + if (EntryCount != CurrentEntryIndex) + return ResultFs.OutOfRange.Log(); + + if (endOffset <= CurrentOffset) + return ResultFs.InvalidOffset.Log(); + + if (CurrentOffset == -1) + return Result.Success; + + Result rc = FinalizePreviousEntrySet(endOffset); + if (rc.IsFailure()) return rc; + + int entrySetIndex = CurrentEntryIndex / EntriesPerEntrySet; + int indexInEntrySet = CurrentEntryIndex % EntriesPerEntrySet; + + // Finalize the current entry set if needed + if (indexInEntrySet != 0) + { + ref NodeHeader entrySetHeader = ref _entrySet.GetHeader(); + + entrySetHeader.Index = entrySetIndex; + entrySetHeader.Count = indexInEntrySet; + entrySetHeader.Offset = endOffset; + + long entryStorageOffset = (long)NodeSize * entrySetIndex; + rc = EntryStorage.Write(entryStorageOffset, _entrySet.GetBuffer()); + if (rc.IsFailure()) return rc; + } + + int l2NodeIndex = BitUtil.DivideUp(CurrentL2OffsetIndex, OffsetsPerNode) - 2; + int indexInL2Node = CurrentL2OffsetIndex % OffsetsPerNode; + + // Finalize the current L2 node if needed + if (CurrentL2OffsetIndex > OffsetsPerNode && (indexInEntrySet != 0 || indexInL2Node != 0)) + { + ref NodeHeader l2NodeHeader = ref _l2Node.GetHeader(); + l2NodeHeader.Index = l2NodeIndex; + l2NodeHeader.Count = indexInL2Node != 0 ? indexInL2Node : OffsetsPerNode; + l2NodeHeader.Offset = endOffset; + + long l2NodeStorageOffset = NodeSize * (l2NodeIndex + 1); + rc = NodeStorage.Write(l2NodeStorageOffset, _l2Node.GetBuffer()); + if (rc.IsFailure()) return rc; + } + + // Finalize the L1 node + ref NodeHeader l1NodeHeader = ref _l1Node.GetHeader(); + l1NodeHeader.Index = 0; + l1NodeHeader.Offset = endOffset; + + // L1 count depends on the existence or absence of L2 nodes + if (CurrentL2OffsetIndex == 0) + { + l1NodeHeader.Count = BitUtil.DivideUp(CurrentEntryIndex, EntriesPerEntrySet); + } + else + { + l1NodeHeader.Count = l2NodeIndex + 1; + } + + rc = NodeStorage.Write(0, _l1Node.GetBuffer()); + if (rc.IsFailure()) return rc; + + CurrentOffset = long.MaxValue; + return Result.Success; + } } } diff --git a/src/LibHac/FsSystem/Buffers/BufferManagerUtility.cs b/src/LibHac/FsSystem/Buffers/BufferManagerUtility.cs index 32915a4b..e9d4e759 100644 --- a/src/LibHac/FsSystem/Buffers/BufferManagerUtility.cs +++ b/src/LibHac/FsSystem/Buffers/BufferManagerUtility.cs @@ -5,140 +5,139 @@ using LibHac.Diag; using LibHac.Fs; using Buffer = LibHac.Fs.Buffer; -namespace LibHac.FsSystem.Buffers +namespace LibHac.FsSystem.Buffers; + +public struct BufferManagerContext { - public struct BufferManagerContext - { - private bool _needsBlocking; + private bool _needsBlocking; - public bool IsNeedBlocking() => _needsBlocking; - public void SetNeedBlocking(bool needsBlocking) => _needsBlocking = needsBlocking; + public bool IsNeedBlocking() => _needsBlocking; + public void SetNeedBlocking(bool needsBlocking) => _needsBlocking = needsBlocking; +} + +public struct ScopedBufferManagerContextRegistration : IDisposable +{ + private BufferManagerContext _oldContext; + + // ReSharper disable once UnusedParameter.Local + public ScopedBufferManagerContextRegistration(int unused = default) + { + _oldContext = BufferManagerUtility.GetBufferManagerContext(); } - public struct ScopedBufferManagerContextRegistration : IDisposable + public void Dispose() { - private BufferManagerContext _oldContext; - - // ReSharper disable once UnusedParameter.Local - public ScopedBufferManagerContextRegistration(int unused = default) - { - _oldContext = BufferManagerUtility.GetBufferManagerContext(); - } - - public void Dispose() - { - BufferManagerUtility.RegisterBufferManagerContext(in _oldContext); - } - } - - internal static class BufferManagerUtility - { - // Todo: Use TimeSpan - private const int RetryWait = 10; - - [ThreadStatic] - private static BufferManagerContext _context; - - public delegate bool IsValidBufferFunction(in Buffer buffer); - - public static Result DoContinuouslyUntilBufferIsAllocated(Func function, Func onFailure, - [CallerMemberName] string callerName = "") - { - const int bufferAllocationRetryLogCountMax = 10; - const int bufferAllocationRetryLogInterval = 100; - - Result result; - - for (int count = 1; ; count++) - { - result = function(); - if (!ResultFs.BufferAllocationFailed.Includes(result)) - break; - - // Failed to allocate. Wait and try again. - if (1 <= count && count <= bufferAllocationRetryLogCountMax || - count % bufferAllocationRetryLogInterval == 0) - { - // Todo: Log allocation failure - } - - Result rc = onFailure(); - if (rc.IsFailure()) return rc; - - Thread.Sleep(RetryWait); - } - - return result; - } - - public static Result DoContinuouslyUntilBufferIsAllocated(Func function, - [CallerMemberName] string callerName = "") - { - return DoContinuouslyUntilBufferIsAllocated(function, static () => Result.Success, callerName); - } - - public static void RegisterBufferManagerContext(in BufferManagerContext context) - { - _context = context; - } - - public static ref BufferManagerContext GetBufferManagerContext() => ref _context; - - public static void EnableBlockingBufferManagerAllocation() - { - ref BufferManagerContext context = ref GetBufferManagerContext(); - context.SetNeedBlocking(true); - } - - public static Result AllocateBufferUsingBufferManagerContext(out Buffer outBuffer, IBufferManager bufferManager, - int size, IBufferManager.BufferAttribute attribute, IsValidBufferFunction isValidBuffer, - [CallerMemberName] string callerName = "") - { - Assert.SdkNotNullOut(out outBuffer); - Assert.SdkNotNull(bufferManager); - Assert.SdkNotNull(callerName); - - // Clear the output. - outBuffer = new Buffer(); - var tempBuffer = new Buffer(); - - // Get the context. - ref BufferManagerContext context = ref GetBufferManagerContext(); - - Result AllocateBufferImpl() - { - Buffer buffer = bufferManager.AllocateBuffer(size, attribute); - - if (!isValidBuffer(in buffer)) - { - if (!buffer.IsNull) - { - bufferManager.DeallocateBuffer(buffer); - } - - return ResultFs.BufferAllocationFailed.Log(); - } - - tempBuffer = buffer; - return Result.Success; - } - - if (!context.IsNeedBlocking()) - { - // If we don't need to block, just allocate the buffer. - Result rc = AllocateBufferImpl(); - if (rc.IsFailure()) return rc; - } - else - { - // Otherwise, try to allocate repeatedly. - Result rc = DoContinuouslyUntilBufferIsAllocated(AllocateBufferImpl); - if (rc.IsFailure()) return rc; - } - - Assert.SdkAssert(!tempBuffer.IsNull); - outBuffer = tempBuffer; - return Result.Success; - } + BufferManagerUtility.RegisterBufferManagerContext(in _oldContext); + } +} + +internal static class BufferManagerUtility +{ + // Todo: Use TimeSpan + private const int RetryWait = 10; + + [ThreadStatic] + private static BufferManagerContext _context; + + public delegate bool IsValidBufferFunction(in Buffer buffer); + + public static Result DoContinuouslyUntilBufferIsAllocated(Func function, Func onFailure, + [CallerMemberName] string callerName = "") + { + const int bufferAllocationRetryLogCountMax = 10; + const int bufferAllocationRetryLogInterval = 100; + + Result result; + + for (int count = 1; ; count++) + { + result = function(); + if (!ResultFs.BufferAllocationFailed.Includes(result)) + break; + + // Failed to allocate. Wait and try again. + if (1 <= count && count <= bufferAllocationRetryLogCountMax || + count % bufferAllocationRetryLogInterval == 0) + { + // Todo: Log allocation failure + } + + Result rc = onFailure(); + if (rc.IsFailure()) return rc; + + Thread.Sleep(RetryWait); + } + + return result; + } + + public static Result DoContinuouslyUntilBufferIsAllocated(Func function, + [CallerMemberName] string callerName = "") + { + return DoContinuouslyUntilBufferIsAllocated(function, static () => Result.Success, callerName); + } + + public static void RegisterBufferManagerContext(in BufferManagerContext context) + { + _context = context; + } + + public static ref BufferManagerContext GetBufferManagerContext() => ref _context; + + public static void EnableBlockingBufferManagerAllocation() + { + ref BufferManagerContext context = ref GetBufferManagerContext(); + context.SetNeedBlocking(true); + } + + public static Result AllocateBufferUsingBufferManagerContext(out Buffer outBuffer, IBufferManager bufferManager, + int size, IBufferManager.BufferAttribute attribute, IsValidBufferFunction isValidBuffer, + [CallerMemberName] string callerName = "") + { + Assert.SdkNotNullOut(out outBuffer); + Assert.SdkNotNull(bufferManager); + Assert.SdkNotNull(callerName); + + // Clear the output. + outBuffer = new Buffer(); + var tempBuffer = new Buffer(); + + // Get the context. + ref BufferManagerContext context = ref GetBufferManagerContext(); + + Result AllocateBufferImpl() + { + Buffer buffer = bufferManager.AllocateBuffer(size, attribute); + + if (!isValidBuffer(in buffer)) + { + if (!buffer.IsNull) + { + bufferManager.DeallocateBuffer(buffer); + } + + return ResultFs.BufferAllocationFailed.Log(); + } + + tempBuffer = buffer; + return Result.Success; + } + + if (!context.IsNeedBlocking()) + { + // If we don't need to block, just allocate the buffer. + Result rc = AllocateBufferImpl(); + if (rc.IsFailure()) return rc; + } + else + { + // Otherwise, try to allocate repeatedly. + Result rc = DoContinuouslyUntilBufferIsAllocated(AllocateBufferImpl); + if (rc.IsFailure()) return rc; + } + + Assert.SdkAssert(!tempBuffer.IsNull); + outBuffer = tempBuffer; + return Result.Success; } } diff --git a/src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs b/src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs index 22c3a8bb..fff6df7f 100644 --- a/src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs +++ b/src/LibHac/FsSystem/Buffers/FileSystemBuddyHeap.cs @@ -8,641 +8,640 @@ using LibHac.Util; using Buffer = LibHac.Fs.Buffer; // ReSharper disable once CheckNamespace -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public unsafe class FileSystemBuddyHeap : IDisposable { - public unsafe class FileSystemBuddyHeap : IDisposable + private static readonly nuint BufferAlignment = (nuint)Unsafe.SizeOf(); + private static readonly nuint BlockSizeMin = (nuint)(2 * Unsafe.SizeOf()); + private const int OrderUpperLimit = 8 * sizeof(int) - 1; + + private nuint BlockSize { get; set; } + private int OrderMax { get; set; } + private UIntPtr HeapStart { get; set; } + private nuint HeapSize { get; set; } + + private PageList* FreeLists { get; set; } + private nuint TotalFreeSize { get; set; } + private PageList* ExternalFreeLists { get; set; } + private PageList[] InternalFreeLists { get; set; } + + private struct PageList { - private static readonly nuint BufferAlignment = (nuint)Unsafe.SizeOf(); - private static readonly nuint BlockSizeMin = (nuint)(2 * Unsafe.SizeOf()); - private const int OrderUpperLimit = 8 * sizeof(int) - 1; + private PageEntry* FirstPageEntry { get; set; } + private PageEntry* LastPageEntry { get; set; } + private int EntryCount { get; set; } - private nuint BlockSize { get; set; } - private int OrderMax { get; set; } - private UIntPtr HeapStart { get; set; } - private nuint HeapSize { get; set; } + public bool IsEmpty() => EntryCount == 0; + public int GetSize() => EntryCount; - private PageList* FreeLists { get; set; } - private nuint TotalFreeSize { get; set; } - private PageList* ExternalFreeLists { get; set; } - private PageList[] InternalFreeLists { get; set; } + // ReSharper disable once UnusedMember.Local + public PageEntry* GetFront() => FirstPageEntry; - private struct PageList + public PageEntry* PopFront() { - private PageEntry* FirstPageEntry { get; set; } - private PageEntry* LastPageEntry { get; set; } - private int EntryCount { get; set; } + Assert.SdkRequires(EntryCount > 0); - public bool IsEmpty() => EntryCount == 0; - public int GetSize() => EntryCount; + // Get the first entry. + PageEntry* pageEntry = FirstPageEntry; - // ReSharper disable once UnusedMember.Local - public PageEntry* GetFront() => FirstPageEntry; + // Advance our list. + FirstPageEntry = pageEntry->Next; + pageEntry->Next = null; - public PageEntry* PopFront() + // Decrement our count. + EntryCount--; + Assert.SdkGreaterEqual(EntryCount, 0); + + // If this was our last page, clear our last entry. + if (EntryCount == 0) { - Assert.SdkRequires(EntryCount > 0); - - // Get the first entry. - PageEntry* pageEntry = FirstPageEntry; - - // Advance our list. - FirstPageEntry = pageEntry->Next; - pageEntry->Next = null; - - // Decrement our count. - EntryCount--; - Assert.SdkGreaterEqual(EntryCount, 0); - - // If this was our last page, clear our last entry. - if (EntryCount == 0) - { - LastPageEntry = null; - } - - return pageEntry; + LastPageEntry = null; } - public void PushBack(PageEntry* pageEntry) + return pageEntry; + } + + public void PushBack(PageEntry* pageEntry) + { + Assert.SdkRequires(pageEntry != null); + + // If we're empty, we want to set the first page entry. + if (IsEmpty()) { - Assert.SdkRequires(pageEntry != null); - - // If we're empty, we want to set the first page entry. - if (IsEmpty()) - { - FirstPageEntry = pageEntry; - } - else - { - // We're not empty, so push the page to the back. - Assert.SdkAssert(LastPageEntry != pageEntry); - LastPageEntry->Next = pageEntry; - } - - // Set our last page entry to be this one, and link it to the list. - LastPageEntry = pageEntry; - LastPageEntry->Next = null; - - // Increment our entry count. - EntryCount++; - Assert.SdkGreater(EntryCount, 0); + FirstPageEntry = pageEntry; + } + else + { + // We're not empty, so push the page to the back. + Assert.SdkAssert(LastPageEntry != pageEntry); + LastPageEntry->Next = pageEntry; } - public bool Remove(PageEntry* pageEntry) - { - Assert.SdkRequires(pageEntry != null); + // Set our last page entry to be this one, and link it to the list. + LastPageEntry = pageEntry; + LastPageEntry->Next = null; - // If we're empty, we can't remove the page list. - if (IsEmpty()) + // Increment our entry count. + EntryCount++; + Assert.SdkGreater(EntryCount, 0); + } + + public bool Remove(PageEntry* pageEntry) + { + Assert.SdkRequires(pageEntry != null); + + // If we're empty, we can't remove the page list. + if (IsEmpty()) + { + return false; + } + + // We're going to loop over all pages to find this one, then unlink it. + PageEntry* prevEntry = null; + PageEntry* curEntry = FirstPageEntry; + + while (true) + { + // Check if we found the page. + if (curEntry == pageEntry) + { + if (curEntry == FirstPageEntry) + { + // If it's the first page, we just set our first. + FirstPageEntry = curEntry->Next; + } + else if (curEntry == LastPageEntry) + { + // If it's the last page, we set our last. + LastPageEntry = prevEntry; + LastPageEntry->Next = null; + } + else + { + // If it's in the middle, we just unlink. + prevEntry->Next = curEntry->Next; + } + + // Unlink this entry's next. + curEntry->Next = null; + + // Update our entry count. + EntryCount--; + Assert.SdkGreaterEqual(EntryCount, 0); + + return true; + } + + // If we have no next page, we can't remove. + if (curEntry->Next == null) { return false; } - // We're going to loop over all pages to find this one, then unlink it. - PageEntry* prevEntry = null; - PageEntry* curEntry = FirstPageEntry; - - while (true) - { - // Check if we found the page. - if (curEntry == pageEntry) - { - if (curEntry == FirstPageEntry) - { - // If it's the first page, we just set our first. - FirstPageEntry = curEntry->Next; - } - else if (curEntry == LastPageEntry) - { - // If it's the last page, we set our last. - LastPageEntry = prevEntry; - LastPageEntry->Next = null; - } - else - { - // If it's in the middle, we just unlink. - prevEntry->Next = curEntry->Next; - } - - // Unlink this entry's next. - curEntry->Next = null; - - // Update our entry count. - EntryCount--; - Assert.SdkGreaterEqual(EntryCount, 0); - - return true; - } - - // If we have no next page, we can't remove. - if (curEntry->Next == null) - { - return false; - } - - // Advance to the next item in the list. - prevEntry = curEntry; - curEntry = curEntry->Next; - } + // Advance to the next item in the list. + prevEntry = curEntry; + curEntry = curEntry->Next; } } - - private struct PageEntry - { - public PageEntry* Next; - } - - public void Dispose() - { - FreeLists = null; - ExternalFreeLists = null; - InternalFreeLists = null; - PinnedHeapMemoryHandle.Dispose(); - PinnedWorkMemoryHandle.Dispose(); - } - - public static int GetBlockCountFromOrder(int order) - { - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLess(order, OrderUpperLimit); - return 1 << order; - } - - public static nuint QueryWorkBufferSize(int orderMax) - { - Assert.SdkRequiresInRange(orderMax, 1, OrderUpperLimit); - - var pageListSize = (nint)Unsafe.SizeOf(); - uint pageListAlignment = (uint)Unsafe.SizeOf(); - const uint ulongAlignment = 8; - - return (nuint)Alignment.AlignUpPow2(pageListSize * (orderMax + 1) + pageListAlignment, ulongAlignment); - } - - public static int QueryOrderMax(nuint size, nuint blockSize) - { - Assert.SdkRequiresGreaterEqual(size, blockSize); - Assert.SdkRequiresGreaterEqual(blockSize, BlockSizeMin); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); - - int blockCount = (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize); - for (int order = 1; ; order++) - { - if (blockCount <= GetBlockCountFromOrder(order)) - return order; - } - } - - public Result Initialize(UIntPtr address, nuint size, nuint blockSize, void* workBuffer, nuint workBufferSize) - { - return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize), workBuffer, workBufferSize); - } - - public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax, void* workBuffer, - nuint workBufferSize) - { - Assert.SdkRequiresGreaterEqual(workBufferSize, QueryWorkBufferSize(orderMax)); - - uint pageListAlignment = (uint)Unsafe.SizeOf(); - var alignedWork = (void*)Alignment.AlignUpPow2((ulong)workBuffer, pageListAlignment); - ExternalFreeLists = (PageList*)alignedWork; - - // Note: The original code does not have a buffer size assert after adjusting for alignment. - Assert.SdkRequiresGreaterEqual(workBufferSize - ((nuint)alignedWork - (nuint)workBuffer), - QueryWorkBufferSize(orderMax)); - - return Initialize(address, size, blockSize, orderMax); - } - - public Result Initialize(UIntPtr address, nuint size, nuint blockSize) - { - return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize)); - } - - public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax) - { - Assert.SdkRequires(FreeLists == null); - Assert.SdkRequiresNotEqual(address, UIntPtr.Zero); - Assert.SdkRequiresAligned(address.ToUInt64(), (int)BufferAlignment); - Assert.SdkRequiresGreaterEqual(blockSize, BlockSizeMin); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); - Assert.SdkRequiresGreaterEqual(size, blockSize); - Assert.SdkRequiresGreater(orderMax, 0); - Assert.SdkRequiresLess(orderMax, OrderUpperLimit); - - // Set up our basic member variables - BlockSize = blockSize; - OrderMax = orderMax; - HeapStart = address; - HeapSize = size; - - TotalFreeSize = 0; - - // Determine page sizes - nuint maxPageSize = BlockSize << OrderMax; - nuint maxPageCount = (nuint)Alignment.AlignUp(HeapSize, (uint)maxPageSize) / maxPageSize; - Assert.SdkGreater((int)maxPageCount, 0); - - // Setup the free lists - if (ExternalFreeLists != null) - { - Assert.SdkAssert(InternalFreeLists == null); - FreeLists = ExternalFreeLists; - } - else - { - InternalFreeLists = GC.AllocateArray(OrderMax + 1, true); - FreeLists = (PageList*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(InternalFreeLists)); - if (InternalFreeLists == null) - return ResultFs.AllocationMemoryFailedInFileSystemBuddyHeapA.Log(); - } - - // All but the last page region should go to the max order. - for (nuint i = 0; i < maxPageCount - 1; i++) - { - PageEntry* pageEntry = GetPageEntryFromAddress(HeapStart + i * maxPageSize); - FreeLists[orderMax].PushBack(pageEntry); - } - - TotalFreeSize += (nuint)FreeLists[orderMax].GetSize() * GetBytesFromOrder(orderMax); - - // Allocate remaining space to smaller orders as possible. - { - nuint remaining = HeapSize - (maxPageCount - 1) * maxPageSize; - nuint curAddress = HeapStart - (maxPageCount - 1) * maxPageSize; - Assert.SdkAligned(remaining, (int)BlockSize); - - do - { - // Determine what order we can use. - int order = GetOrderFromBytes(remaining + 1); - if (order < 0) - { - Assert.SdkEqual(OrderMax, GetOrderFromBytes(remaining)); - order = OrderMax + 1; - } - - Assert.SdkGreater(order, 0); - Assert.SdkLessEqual(order, OrderMax + 1); - - // Add to the correct free list. - FreeLists[order - 1].PushBack(GetPageEntryFromAddress(curAddress)); - TotalFreeSize += GetBytesFromOrder(order - 1); - - // Move on to the next order. - nuint pageSize = GetBytesFromOrder(order - 1); - curAddress += pageSize; - remaining -= pageSize; - } while (BlockSize <= remaining); - } - - return Result.Success; - } - - public void* AllocateByOrder(int order) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - // Get the page entry. - PageEntry* pageEntry = GetFreePageEntry(order); - - if (pageEntry != null) - { - // Ensure we're allocating an unlinked page. - Assert.SdkAssert(pageEntry->Next == null); - - // Return the address for this entry. - return (void*)GetAddressFromPageEntry(pageEntry); - } - else - { - return null; - } - } - - public void Free(void* pointer, int order) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - // Allow Free(null) - if (pointer == null) - return; - - // Ensure the pointer is block aligned. - Assert.SdkAligned((nuint)pointer - HeapStart, (int)GetBlockSize()); - - // Get the page entry. - PageEntry* pageEntry = GetPageEntryFromAddress((UIntPtr)pointer); - Assert.SdkAssert(IsAlignedToOrder(pageEntry, order)); - - /* Reinsert into the free lists. */ - JoinBuddies(pageEntry, order); - } - - public nuint GetTotalFreeSize() - { - Assert.SdkRequires(FreeLists != null); - return TotalFreeSize; - } - - public nuint GetAllocatableSizeMax() - { - Assert.SdkRequires(FreeLists != null); - - // The maximum allocatable size is a chunk from the biggest non-empty order. - for (int order = GetOrderMax(); order >= 0; order--) - { - if (FreeLists[order].IsEmpty()) - { - return GetBytesFromOrder(order); - } - } - - // If all orders are empty, then we can't allocate anything. - return 0; - } - - public void Dump() - { - Assert.SdkRequires(FreeLists != null); - throw new NotImplementedException(); - } - - public int GetOrderFromBytes(nuint size) - { - Assert.SdkRequires(FreeLists != null); - return GetOrderFromBlockCount(GetBlockCountFromSize(size)); - } - - public nuint GetBytesFromOrder(int order) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - return GetBlockSize() << order; - } - - public int GetOrderMax() - { - Assert.SdkRequires(FreeLists != null); - return OrderMax; - } - - public nuint GetBlockSize() - { - Assert.SdkRequires(FreeLists != null); - return BlockSize; - } - - private void DivideBuddies(PageEntry* pageEntry, int requiredOrder, int chosenOrder) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(requiredOrder, 0); - Assert.SdkRequiresGreaterEqual(chosenOrder, requiredOrder); - Assert.SdkRequiresLessEqual(chosenOrder, GetOrderMax()); - - // Start at the end of the entry. - nuint address = GetAddressFromPageEntry(pageEntry) + GetBytesFromOrder(chosenOrder); - - for (int order = chosenOrder; order > requiredOrder; order--) - { - // For each order, subtract that order's size from the address to get the start of a new block. - address -= GetBytesFromOrder(order - 1); - PageEntry* dividedEntry = GetPageEntryFromAddress(address); - - // Push back to the list. - FreeLists[order - 1].PushBack(dividedEntry); - TotalFreeSize += GetBytesFromOrder(order - 1); - } - } - - private void JoinBuddies(PageEntry* pageEntry, int order) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - PageEntry* curEntry = pageEntry; - int curOrder = order; - - while (curOrder < GetOrderMax()) - { - // Get the buddy page. - PageEntry* buddyEntry = GetBuddy(curEntry, curOrder); - - // Check whether the buddy is in the relevant free list. - if (buddyEntry != null && FreeLists[curOrder].Remove(buddyEntry)) - { - TotalFreeSize -= GetBytesFromOrder(curOrder); - - // Ensure we coalesce with the correct buddy when page is aligned - if (!IsAlignedToOrder(curEntry, curOrder + 1)) - { - curEntry = buddyEntry; - } - - curOrder++; - } - else - { - // Buddy isn't in the free list, so we can't coalesce. - break; - } - } - - // Insert the coalesced entry into the free list. - FreeLists[curOrder].PushBack(curEntry); - TotalFreeSize += GetBytesFromOrder(curOrder); - } - - private PageEntry* GetBuddy(PageEntry* pageEntry, int order) - { - Assert.SdkRequires(FreeLists != null); - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - nuint address = GetAddressFromPageEntry(pageEntry); - nuint offset = (nuint)GetBlockCountFromOrder(order) * GetBlockSize(); - - if (IsAlignedToOrder(pageEntry, order + 1)) - { - // If the page entry is aligned to the next order, - // return the buddy block to the right of the current entry. - return address + offset < HeapStart + HeapSize ? GetPageEntryFromAddress(address + offset) : null; - } - else - { - // If the page entry isn't aligned, return the buddy block to the left of the current entry. - return HeapStart <= address - offset ? GetPageEntryFromAddress(address - offset) : null; - } - } - - private PageEntry* GetFreePageEntry(int order) - { - Assert.SdkRequiresGreaterEqual(order, 0); - Assert.SdkRequiresLessEqual(order, GetOrderMax()); - - // Try orders from low to high until we find a free page entry. - for (int curOrder = order; curOrder <= GetOrderMax(); curOrder++) - { - ref PageList freeList = ref FreeLists[curOrder]; - if (!freeList.IsEmpty()) - { - // The current list isn't empty, so grab an entry from it. - PageEntry* pageEntry = freeList.PopFront(); - Assert.SdkAssert(pageEntry != null); - - // Update size bookkeeping. - TotalFreeSize -= GetBytesFromOrder(curOrder); - - // If we allocated more memory than needed, free the unneeded portion. - DivideBuddies(pageEntry, order, curOrder); - Assert.SdkAssert(pageEntry->Next == null); - - // Return the newly-divided entry. - return pageEntry; - } - } - - // We failed to find a free page. - return null; - } - - private int GetOrderFromBlockCount(int blockCount) - { - Assert.SdkRequiresGreaterEqual(blockCount, 0); - - // Return the first order with a big enough block count. - for (int order = 0; order <= GetOrderMax(); ++order) - { - if (blockCount <= GetBlockCountFromOrder(order)) - { - return order; - } - } - - return -1; - } - - private int GetBlockCountFromSize(nuint size) - { - nuint blockSize = GetBlockSize(); - return (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize); - } - - private UIntPtr GetAddressFromPageEntry(PageEntry* pageEntry) - { - var address = new UIntPtr(pageEntry); - - Assert.SdkRequiresGreaterEqual(address, HeapStart); - Assert.SdkRequiresLess((nuint)address, HeapStart + HeapSize); - Assert.SdkRequiresAligned((nuint)address - HeapStart, (int)GetBlockSize()); - - return address; - } - - private PageEntry* GetPageEntryFromAddress(UIntPtr address) - { - Assert.SdkRequiresGreaterEqual(address, HeapStart); - Assert.SdkRequiresLess((nuint)address, HeapStart + HeapSize); - - ulong blockStart = (ulong)HeapStart + - Alignment.AlignDownPow2((nuint)address - HeapStart, (uint)GetBlockSize()); - return (PageEntry*)blockStart; - } - - private int GetIndexFromPageEntry(PageEntry* pageEntry) - { - var address = (nuint)pageEntry; - - Assert.SdkRequiresGreaterEqual(address, (nuint)HeapStart); - Assert.SdkRequiresLess(address, HeapStart + HeapSize); - Assert.SdkRequiresAligned(address - HeapStart, (int)GetBlockSize()); - - return (int)((address - HeapStart) / GetBlockSize()); - } - - private bool IsAlignedToOrder(PageEntry* pageEntry, int order) - { - return Alignment.IsAlignedPow2(GetIndexFromPageEntry(pageEntry), (uint)GetBlockCountFromOrder(order)); - } - - // Addition: The below fields and methods allow using Memory with the class instead - // of raw pointers. - private MemoryHandle PinnedHeapMemoryHandle { get; set; } - private Memory HeapBuffer { get; set; } - private MemoryHandle PinnedWorkMemoryHandle { get; set; } - - public Result Initialize(Memory heapBuffer, int blockSize, Memory workBuffer) - { - return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize), - workBuffer); - } - - public Result Initialize(Memory heapBuffer, int blockSize, int orderMax, Memory workBuffer) - { - PinnedWorkMemoryHandle = workBuffer.Pin(); - - PinnedHeapMemoryHandle = heapBuffer.Pin(); - HeapBuffer = heapBuffer; - - var heapAddress = (UIntPtr)PinnedHeapMemoryHandle.Pointer; - var heapSize = (nuint)heapBuffer.Length; - - void* workAddress = PinnedWorkMemoryHandle.Pointer; - var workSize = (nuint)workBuffer.Length; - - return Initialize(heapAddress, heapSize, (nuint)blockSize, orderMax, workAddress, workSize); - } - - public Result Initialize(Memory heapBuffer, int blockSize) - { - return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize)); - } - - public Result Initialize(Memory heapBuffer, int blockSize, int orderMax) - { - PinnedHeapMemoryHandle = heapBuffer.Pin(); - HeapBuffer = heapBuffer; - - var address = (UIntPtr)PinnedHeapMemoryHandle.Pointer; - var size = (nuint)heapBuffer.Length; - - return Initialize(address, size, (nuint)blockSize, orderMax); - } - - public Buffer AllocateBufferByOrder(int order) - { - Assert.SdkRequires(!HeapBuffer.IsEmpty); - - void* address = AllocateByOrder(order); - - if (address == null) - return Buffer.Empty; - - nuint size = GetBytesFromOrder(order); - Assert.SdkLessEqual(size, (nuint)int.MaxValue); - - // Get the offset relative to the heap start - nuint offset = (nuint)address - (nuint)PinnedHeapMemoryHandle.Pointer; - Assert.SdkLessEqual(offset, (nuint)HeapBuffer.Length); - - // Get a slice of the Memory containing the entire heap - return new Buffer(HeapBuffer.Slice((int)offset, (int)size)); - } - - public void Free(Buffer buffer) - { - Assert.SdkRequires(!HeapBuffer.IsEmpty); - Assert.SdkRequires(!buffer.IsNull); - - int order = GetOrderFromBytes((nuint)buffer.Length); - void* pointer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer.Span)); - Free(pointer, order); - } + } + + private struct PageEntry + { + public PageEntry* Next; + } + + public void Dispose() + { + FreeLists = null; + ExternalFreeLists = null; + InternalFreeLists = null; + PinnedHeapMemoryHandle.Dispose(); + PinnedWorkMemoryHandle.Dispose(); + } + + public static int GetBlockCountFromOrder(int order) + { + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLess(order, OrderUpperLimit); + return 1 << order; + } + + public static nuint QueryWorkBufferSize(int orderMax) + { + Assert.SdkRequiresInRange(orderMax, 1, OrderUpperLimit); + + var pageListSize = (nint)Unsafe.SizeOf(); + uint pageListAlignment = (uint)Unsafe.SizeOf(); + const uint ulongAlignment = 8; + + return (nuint)Alignment.AlignUpPow2(pageListSize * (orderMax + 1) + pageListAlignment, ulongAlignment); + } + + public static int QueryOrderMax(nuint size, nuint blockSize) + { + Assert.SdkRequiresGreaterEqual(size, blockSize); + Assert.SdkRequiresGreaterEqual(blockSize, BlockSizeMin); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); + + int blockCount = (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize); + for (int order = 1; ; order++) + { + if (blockCount <= GetBlockCountFromOrder(order)) + return order; + } + } + + public Result Initialize(UIntPtr address, nuint size, nuint blockSize, void* workBuffer, nuint workBufferSize) + { + return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize), workBuffer, workBufferSize); + } + + public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax, void* workBuffer, + nuint workBufferSize) + { + Assert.SdkRequiresGreaterEqual(workBufferSize, QueryWorkBufferSize(orderMax)); + + uint pageListAlignment = (uint)Unsafe.SizeOf(); + var alignedWork = (void*)Alignment.AlignUpPow2((ulong)workBuffer, pageListAlignment); + ExternalFreeLists = (PageList*)alignedWork; + + // Note: The original code does not have a buffer size assert after adjusting for alignment. + Assert.SdkRequiresGreaterEqual(workBufferSize - ((nuint)alignedWork - (nuint)workBuffer), + QueryWorkBufferSize(orderMax)); + + return Initialize(address, size, blockSize, orderMax); + } + + public Result Initialize(UIntPtr address, nuint size, nuint blockSize) + { + return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize)); + } + + public Result Initialize(UIntPtr address, nuint size, nuint blockSize, int orderMax) + { + Assert.SdkRequires(FreeLists == null); + Assert.SdkRequiresNotEqual(address, UIntPtr.Zero); + Assert.SdkRequiresAligned(address.ToUInt64(), (int)BufferAlignment); + Assert.SdkRequiresGreaterEqual(blockSize, BlockSizeMin); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); + Assert.SdkRequiresGreaterEqual(size, blockSize); + Assert.SdkRequiresGreater(orderMax, 0); + Assert.SdkRequiresLess(orderMax, OrderUpperLimit); + + // Set up our basic member variables + BlockSize = blockSize; + OrderMax = orderMax; + HeapStart = address; + HeapSize = size; + + TotalFreeSize = 0; + + // Determine page sizes + nuint maxPageSize = BlockSize << OrderMax; + nuint maxPageCount = (nuint)Alignment.AlignUp(HeapSize, (uint)maxPageSize) / maxPageSize; + Assert.SdkGreater((int)maxPageCount, 0); + + // Setup the free lists + if (ExternalFreeLists != null) + { + Assert.SdkAssert(InternalFreeLists == null); + FreeLists = ExternalFreeLists; + } + else + { + InternalFreeLists = GC.AllocateArray(OrderMax + 1, true); + FreeLists = (PageList*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(InternalFreeLists)); + if (InternalFreeLists == null) + return ResultFs.AllocationMemoryFailedInFileSystemBuddyHeapA.Log(); + } + + // All but the last page region should go to the max order. + for (nuint i = 0; i < maxPageCount - 1; i++) + { + PageEntry* pageEntry = GetPageEntryFromAddress(HeapStart + i * maxPageSize); + FreeLists[orderMax].PushBack(pageEntry); + } + + TotalFreeSize += (nuint)FreeLists[orderMax].GetSize() * GetBytesFromOrder(orderMax); + + // Allocate remaining space to smaller orders as possible. + { + nuint remaining = HeapSize - (maxPageCount - 1) * maxPageSize; + nuint curAddress = HeapStart - (maxPageCount - 1) * maxPageSize; + Assert.SdkAligned(remaining, (int)BlockSize); + + do + { + // Determine what order we can use. + int order = GetOrderFromBytes(remaining + 1); + if (order < 0) + { + Assert.SdkEqual(OrderMax, GetOrderFromBytes(remaining)); + order = OrderMax + 1; + } + + Assert.SdkGreater(order, 0); + Assert.SdkLessEqual(order, OrderMax + 1); + + // Add to the correct free list. + FreeLists[order - 1].PushBack(GetPageEntryFromAddress(curAddress)); + TotalFreeSize += GetBytesFromOrder(order - 1); + + // Move on to the next order. + nuint pageSize = GetBytesFromOrder(order - 1); + curAddress += pageSize; + remaining -= pageSize; + } while (BlockSize <= remaining); + } + + return Result.Success; + } + + public void* AllocateByOrder(int order) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + // Get the page entry. + PageEntry* pageEntry = GetFreePageEntry(order); + + if (pageEntry != null) + { + // Ensure we're allocating an unlinked page. + Assert.SdkAssert(pageEntry->Next == null); + + // Return the address for this entry. + return (void*)GetAddressFromPageEntry(pageEntry); + } + else + { + return null; + } + } + + public void Free(void* pointer, int order) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + // Allow Free(null) + if (pointer == null) + return; + + // Ensure the pointer is block aligned. + Assert.SdkAligned((nuint)pointer - HeapStart, (int)GetBlockSize()); + + // Get the page entry. + PageEntry* pageEntry = GetPageEntryFromAddress((UIntPtr)pointer); + Assert.SdkAssert(IsAlignedToOrder(pageEntry, order)); + + /* Reinsert into the free lists. */ + JoinBuddies(pageEntry, order); + } + + public nuint GetTotalFreeSize() + { + Assert.SdkRequires(FreeLists != null); + return TotalFreeSize; + } + + public nuint GetAllocatableSizeMax() + { + Assert.SdkRequires(FreeLists != null); + + // The maximum allocatable size is a chunk from the biggest non-empty order. + for (int order = GetOrderMax(); order >= 0; order--) + { + if (FreeLists[order].IsEmpty()) + { + return GetBytesFromOrder(order); + } + } + + // If all orders are empty, then we can't allocate anything. + return 0; + } + + public void Dump() + { + Assert.SdkRequires(FreeLists != null); + throw new NotImplementedException(); + } + + public int GetOrderFromBytes(nuint size) + { + Assert.SdkRequires(FreeLists != null); + return GetOrderFromBlockCount(GetBlockCountFromSize(size)); + } + + public nuint GetBytesFromOrder(int order) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + return GetBlockSize() << order; + } + + public int GetOrderMax() + { + Assert.SdkRequires(FreeLists != null); + return OrderMax; + } + + public nuint GetBlockSize() + { + Assert.SdkRequires(FreeLists != null); + return BlockSize; + } + + private void DivideBuddies(PageEntry* pageEntry, int requiredOrder, int chosenOrder) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(requiredOrder, 0); + Assert.SdkRequiresGreaterEqual(chosenOrder, requiredOrder); + Assert.SdkRequiresLessEqual(chosenOrder, GetOrderMax()); + + // Start at the end of the entry. + nuint address = GetAddressFromPageEntry(pageEntry) + GetBytesFromOrder(chosenOrder); + + for (int order = chosenOrder; order > requiredOrder; order--) + { + // For each order, subtract that order's size from the address to get the start of a new block. + address -= GetBytesFromOrder(order - 1); + PageEntry* dividedEntry = GetPageEntryFromAddress(address); + + // Push back to the list. + FreeLists[order - 1].PushBack(dividedEntry); + TotalFreeSize += GetBytesFromOrder(order - 1); + } + } + + private void JoinBuddies(PageEntry* pageEntry, int order) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + PageEntry* curEntry = pageEntry; + int curOrder = order; + + while (curOrder < GetOrderMax()) + { + // Get the buddy page. + PageEntry* buddyEntry = GetBuddy(curEntry, curOrder); + + // Check whether the buddy is in the relevant free list. + if (buddyEntry != null && FreeLists[curOrder].Remove(buddyEntry)) + { + TotalFreeSize -= GetBytesFromOrder(curOrder); + + // Ensure we coalesce with the correct buddy when page is aligned + if (!IsAlignedToOrder(curEntry, curOrder + 1)) + { + curEntry = buddyEntry; + } + + curOrder++; + } + else + { + // Buddy isn't in the free list, so we can't coalesce. + break; + } + } + + // Insert the coalesced entry into the free list. + FreeLists[curOrder].PushBack(curEntry); + TotalFreeSize += GetBytesFromOrder(curOrder); + } + + private PageEntry* GetBuddy(PageEntry* pageEntry, int order) + { + Assert.SdkRequires(FreeLists != null); + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + nuint address = GetAddressFromPageEntry(pageEntry); + nuint offset = (nuint)GetBlockCountFromOrder(order) * GetBlockSize(); + + if (IsAlignedToOrder(pageEntry, order + 1)) + { + // If the page entry is aligned to the next order, + // return the buddy block to the right of the current entry. + return address + offset < HeapStart + HeapSize ? GetPageEntryFromAddress(address + offset) : null; + } + else + { + // If the page entry isn't aligned, return the buddy block to the left of the current entry. + return HeapStart <= address - offset ? GetPageEntryFromAddress(address - offset) : null; + } + } + + private PageEntry* GetFreePageEntry(int order) + { + Assert.SdkRequiresGreaterEqual(order, 0); + Assert.SdkRequiresLessEqual(order, GetOrderMax()); + + // Try orders from low to high until we find a free page entry. + for (int curOrder = order; curOrder <= GetOrderMax(); curOrder++) + { + ref PageList freeList = ref FreeLists[curOrder]; + if (!freeList.IsEmpty()) + { + // The current list isn't empty, so grab an entry from it. + PageEntry* pageEntry = freeList.PopFront(); + Assert.SdkAssert(pageEntry != null); + + // Update size bookkeeping. + TotalFreeSize -= GetBytesFromOrder(curOrder); + + // If we allocated more memory than needed, free the unneeded portion. + DivideBuddies(pageEntry, order, curOrder); + Assert.SdkAssert(pageEntry->Next == null); + + // Return the newly-divided entry. + return pageEntry; + } + } + + // We failed to find a free page. + return null; + } + + private int GetOrderFromBlockCount(int blockCount) + { + Assert.SdkRequiresGreaterEqual(blockCount, 0); + + // Return the first order with a big enough block count. + for (int order = 0; order <= GetOrderMax(); ++order) + { + if (blockCount <= GetBlockCountFromOrder(order)) + { + return order; + } + } + + return -1; + } + + private int GetBlockCountFromSize(nuint size) + { + nuint blockSize = GetBlockSize(); + return (int)(Alignment.AlignUpPow2(size, (uint)blockSize) / blockSize); + } + + private UIntPtr GetAddressFromPageEntry(PageEntry* pageEntry) + { + var address = new UIntPtr(pageEntry); + + Assert.SdkRequiresGreaterEqual(address, HeapStart); + Assert.SdkRequiresLess((nuint)address, HeapStart + HeapSize); + Assert.SdkRequiresAligned((nuint)address - HeapStart, (int)GetBlockSize()); + + return address; + } + + private PageEntry* GetPageEntryFromAddress(UIntPtr address) + { + Assert.SdkRequiresGreaterEqual(address, HeapStart); + Assert.SdkRequiresLess((nuint)address, HeapStart + HeapSize); + + ulong blockStart = (ulong)HeapStart + + Alignment.AlignDownPow2((nuint)address - HeapStart, (uint)GetBlockSize()); + return (PageEntry*)blockStart; + } + + private int GetIndexFromPageEntry(PageEntry* pageEntry) + { + var address = (nuint)pageEntry; + + Assert.SdkRequiresGreaterEqual(address, (nuint)HeapStart); + Assert.SdkRequiresLess(address, HeapStart + HeapSize); + Assert.SdkRequiresAligned(address - HeapStart, (int)GetBlockSize()); + + return (int)((address - HeapStart) / GetBlockSize()); + } + + private bool IsAlignedToOrder(PageEntry* pageEntry, int order) + { + return Alignment.IsAlignedPow2(GetIndexFromPageEntry(pageEntry), (uint)GetBlockCountFromOrder(order)); + } + + // Addition: The below fields and methods allow using Memory with the class instead + // of raw pointers. + private MemoryHandle PinnedHeapMemoryHandle { get; set; } + private Memory HeapBuffer { get; set; } + private MemoryHandle PinnedWorkMemoryHandle { get; set; } + + public Result Initialize(Memory heapBuffer, int blockSize, Memory workBuffer) + { + return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize), + workBuffer); + } + + public Result Initialize(Memory heapBuffer, int blockSize, int orderMax, Memory workBuffer) + { + PinnedWorkMemoryHandle = workBuffer.Pin(); + + PinnedHeapMemoryHandle = heapBuffer.Pin(); + HeapBuffer = heapBuffer; + + var heapAddress = (UIntPtr)PinnedHeapMemoryHandle.Pointer; + var heapSize = (nuint)heapBuffer.Length; + + void* workAddress = PinnedWorkMemoryHandle.Pointer; + var workSize = (nuint)workBuffer.Length; + + return Initialize(heapAddress, heapSize, (nuint)blockSize, orderMax, workAddress, workSize); + } + + public Result Initialize(Memory heapBuffer, int blockSize) + { + return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize)); + } + + public Result Initialize(Memory heapBuffer, int blockSize, int orderMax) + { + PinnedHeapMemoryHandle = heapBuffer.Pin(); + HeapBuffer = heapBuffer; + + var address = (UIntPtr)PinnedHeapMemoryHandle.Pointer; + var size = (nuint)heapBuffer.Length; + + return Initialize(address, size, (nuint)blockSize, orderMax); + } + + public Buffer AllocateBufferByOrder(int order) + { + Assert.SdkRequires(!HeapBuffer.IsEmpty); + + void* address = AllocateByOrder(order); + + if (address == null) + return Buffer.Empty; + + nuint size = GetBytesFromOrder(order); + Assert.SdkLessEqual(size, (nuint)int.MaxValue); + + // Get the offset relative to the heap start + nuint offset = (nuint)address - (nuint)PinnedHeapMemoryHandle.Pointer; + Assert.SdkLessEqual(offset, (nuint)HeapBuffer.Length); + + // Get a slice of the Memory containing the entire heap + return new Buffer(HeapBuffer.Slice((int)offset, (int)size)); + } + + public void Free(Buffer buffer) + { + Assert.SdkRequires(!HeapBuffer.IsEmpty); + Assert.SdkRequires(!buffer.IsNull); + + int order = GetOrderFromBytes((nuint)buffer.Length); + void* pointer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer.Span)); + Free(pointer, order); } } diff --git a/src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs b/src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs index f069fe8c..a2297083 100644 --- a/src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs +++ b/src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs @@ -10,610 +10,609 @@ using Buffer = LibHac.Fs.Buffer; using CacheHandle = System.Int64; // ReSharper disable once CheckNamespace -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class FileSystemBufferManager : IBufferManager { - public class FileSystemBufferManager : IBufferManager + private class CacheHandleTable { - private class CacheHandleTable + private struct Entry { - private struct Entry + private CacheHandle _handle; + private Buffer _buffer; + private BufferAttribute _attribute; + + public void Initialize(CacheHandle handle, Buffer buffer, BufferAttribute attribute) { - private CacheHandle _handle; - private Buffer _buffer; - private BufferAttribute _attribute; - - public void Initialize(CacheHandle handle, Buffer buffer, BufferAttribute attribute) - { - _handle = handle; - _buffer = buffer; - _attribute = attribute; - } - - public readonly CacheHandle GetHandle() => _handle; - public readonly Buffer GetBuffer() => _buffer; - public readonly int GetSize() => _buffer.Length; - public readonly BufferAttribute GetBufferAttribute() => _attribute; + _handle = handle; + _buffer = buffer; + _attribute = attribute; } - private struct AttrInfo + public readonly CacheHandle GetHandle() => _handle; + public readonly Buffer GetBuffer() => _buffer; + public readonly int GetSize() => _buffer.Length; + public readonly BufferAttribute GetBufferAttribute() => _attribute; + } + + private struct AttrInfo + { + private int _level; + private int _cacheCount; + private int _cacheSize; + + public AttrInfo(int level, int cacheCount, int cacheSize) { - private int _level; - private int _cacheCount; - private int _cacheSize; - - public AttrInfo(int level, int cacheCount, int cacheSize) - { - _level = level; - _cacheCount = cacheCount; - _cacheSize = cacheSize; - } - - public int GetLevel() => _level; - public int GetCacheCount() => _cacheCount; - public void IncrementCacheCount() => _cacheCount++; - public void DecrementCacheCount() => _cacheCount--; - public int GetCacheSize() => _cacheSize; - public void AddCacheSize(int diff) => _cacheSize += diff; - - public void SubtractCacheSize(int diff) - { - Assert.SdkRequiresGreaterEqual(_cacheSize, diff); - _cacheSize -= diff; - } + _level = level; + _cacheCount = cacheCount; + _cacheSize = cacheSize; } - private Entry[] Entries { get; set; } - private int EntryCount { get; set; } - private int EntryCountMax { get; set; } - private LinkedList AttrList { get; set; } = new(); - private int CacheCountMin { get; set; } - private int CacheSizeMin { get; set; } - private int TotalCacheSize { get; set; } - private CacheHandle CurrentHandle { get; set; } + public int GetLevel() => _level; + public int GetCacheCount() => _cacheCount; + public void IncrementCacheCount() => _cacheCount++; + public void DecrementCacheCount() => _cacheCount--; + public int GetCacheSize() => _cacheSize; + public void AddCacheSize(int diff) => _cacheSize += diff; - // ReSharper disable once UnusedMember.Local - // We can't use an external buffer in C# without ensuring all allocated buffers are pinned. - // This function is left here anyway for completion's sake. - public static int QueryWorkBufferSize(int maxCacheCount) + public void SubtractCacheSize(int diff) { - Assert.SdkRequiresGreater(maxCacheCount, 0); + Assert.SdkRequiresGreaterEqual(_cacheSize, diff); + _cacheSize -= diff; + } + } - int entryAlignment = sizeof(CacheHandle); - int attrInfoAlignment = Unsafe.SizeOf(); + private Entry[] Entries { get; set; } + private int EntryCount { get; set; } + private int EntryCountMax { get; set; } + private LinkedList AttrList { get; set; } = new(); + private int CacheCountMin { get; set; } + private int CacheSizeMin { get; set; } + private int TotalCacheSize { get; set; } + private CacheHandle CurrentHandle { get; set; } - int entrySize = Unsafe.SizeOf() * maxCacheCount; - int attrListSize = Unsafe.SizeOf() * 0x100; - return (int)Alignment.AlignUpPow2( - (ulong)(entrySize + attrListSize + entryAlignment + attrInfoAlignment), 8); + // ReSharper disable once UnusedMember.Local + // We can't use an external buffer in C# without ensuring all allocated buffers are pinned. + // This function is left here anyway for completion's sake. + public static int QueryWorkBufferSize(int maxCacheCount) + { + Assert.SdkRequiresGreater(maxCacheCount, 0); + + int entryAlignment = sizeof(CacheHandle); + int attrInfoAlignment = Unsafe.SizeOf(); + + int entrySize = Unsafe.SizeOf() * maxCacheCount; + int attrListSize = Unsafe.SizeOf() * 0x100; + return (int)Alignment.AlignUpPow2( + (ulong)(entrySize + attrListSize + entryAlignment + attrInfoAlignment), 8); + } + + public Result Initialize(int maxCacheCount) + { + // Validate pre-conditions. + Assert.SdkRequiresNull(Entries); + + // Note: We don't have the option of using an external Entry buffer like the original C++ code + // because Entry includes managed references so we can't cast a byte* to Entry* without pinning. + // If we don't have an external buffer, try to allocate an internal one. + + Entries = new Entry[maxCacheCount]; + + if (Entries == null) + { + return ResultFs.AllocationMemoryFailedInFileSystemBufferManagerA.Log(); } - public Result Initialize(int maxCacheCount) - { - // Validate pre-conditions. - Assert.SdkRequiresNull(Entries); + // Set entries. + EntryCount = 0; + EntryCountMax = maxCacheCount; - // Note: We don't have the option of using an external Entry buffer like the original C++ code - // because Entry includes managed references so we can't cast a byte* to Entry* without pinning. - // If we don't have an external buffer, try to allocate an internal one. + Assert.SdkNotNull(Entries); - Entries = new Entry[maxCacheCount]; + CacheCountMin = maxCacheCount / 16; + CacheSizeMin = CacheCountMin * 0x100; - if (Entries == null) - { - return ResultFs.AllocationMemoryFailedInFileSystemBufferManagerA.Log(); - } + return Result.Success; + } - // Set entries. - EntryCount = 0; - EntryCountMax = maxCacheCount; + // ReSharper disable once UnusedParameter.Local + private int GetCacheCountMin(BufferAttribute attr) + { + return CacheCountMin; + } - Assert.SdkNotNull(Entries); + // ReSharper disable once UnusedParameter.Local + private int GetCacheSizeMin(BufferAttribute attr) + { + return CacheSizeMin; + } - CacheCountMin = maxCacheCount / 16; - CacheSizeMin = CacheCountMin * 0x100; + public bool Register(out CacheHandle handle, Buffer buffer, BufferAttribute attr) + { + UnsafeHelpers.SkipParamInit(out handle); - return Result.Success; - } + // Validate pre-conditions. + Assert.SdkRequiresNotNull(Entries); + Assert.SdkRequiresNotNull(ref handle); - // ReSharper disable once UnusedParameter.Local - private int GetCacheCountMin(BufferAttribute attr) - { - return CacheCountMin; - } - - // ReSharper disable once UnusedParameter.Local - private int GetCacheSizeMin(BufferAttribute attr) - { - return CacheSizeMin; - } - - public bool Register(out CacheHandle handle, Buffer buffer, BufferAttribute attr) - { - UnsafeHelpers.SkipParamInit(out handle); - - // Validate pre-conditions. - Assert.SdkRequiresNotNull(Entries); - Assert.SdkRequiresNotNull(ref handle); - - // Get the entry. - ref Entry entry = ref AcquireEntry(buffer, attr); - - // If we don't have an entry, we can't register. - if (Unsafe.IsNullRef(ref entry)) - return false; - - // Get the attr info. If we have one, increment. - ref AttrInfo attrInfo = ref FindAttrInfo(attr); - if (!Unsafe.IsNullRef(ref attrInfo)) - { - attrInfo.IncrementCacheCount(); - attrInfo.AddCacheSize(buffer.Length); - } - else - { - // Make a new attr info and add it to the list. - // Note: Not using attr info buffer - var newInfo = new AttrInfo(attr.Level, 1, buffer.Length); - AttrList.AddLast(newInfo); - } - - TotalCacheSize += buffer.Length; - handle = entry.GetHandle(); - return true; - } - - public bool Unregister(out Buffer buffer, CacheHandle handle) - { - // Validate pre-conditions. - Unsafe.SkipInit(out buffer); - Assert.SdkRequiresNotNull(Entries); - Assert.SdkRequiresNotNull(ref buffer); - - UnsafeHelpers.SkipParamInit(out buffer); - - // Find the lower bound for the entry. - for (int i = 0; i < EntryCount; i++) - { - if (Entries[i].GetHandle() == handle) - { - UnregisterCore(out buffer, ref Entries[i]); - return true; - } - } + // Get the entry. + ref Entry entry = ref AcquireEntry(buffer, attr); + // If we don't have an entry, we can't register. + if (Unsafe.IsNullRef(ref entry)) return false; - } - // ReSharper disable UnusedParameter.Local - public bool UnregisterOldest(out Buffer buffer, BufferAttribute attr, int requiredSize = 0) - // ReSharper restore UnusedParameter.Local + // Get the attr info. If we have one, increment. + ref AttrInfo attrInfo = ref FindAttrInfo(attr); + if (!Unsafe.IsNullRef(ref attrInfo)) { - // Validate pre-conditions. - Unsafe.SkipInit(out buffer); - Assert.SdkRequiresNotNull(Entries); - Assert.SdkRequiresNotNull(ref buffer); - - UnsafeHelpers.SkipParamInit(out buffer); - - // If we have no entries, we can't unregister any. - if (EntryCount == 0) - { - return false; - } - - static bool CanUnregister(CacheHandleTable table, ref Entry entry) - { - ref AttrInfo attrInfo = ref table.FindAttrInfo(entry.GetBufferAttribute()); - Assert.SdkNotNull(ref attrInfo); - - int ccm = table.GetCacheCountMin(entry.GetBufferAttribute()); - int csm = table.GetCacheSizeMin(entry.GetBufferAttribute()); - - return ccm < attrInfo.GetCacheCount() && csm + entry.GetSize() <= attrInfo.GetCacheSize(); - } - - // Find an entry, falling back to the first entry. - ref Entry entry = ref Unsafe.NullRef(); - for (int i = 0; i < EntryCount; i++) - { - if (CanUnregister(this, ref Entries[i])) - { - entry = ref Entries[i]; - break; - } - } - - if (Unsafe.IsNullRef(ref entry)) - { - entry = ref Entries[0]; - } - - Assert.SdkNotNull(ref entry); - UnregisterCore(out buffer, ref entry); - return true; - } - - private void UnregisterCore(out Buffer buffer, ref Entry entry) - { - // Validate pre-conditions. - Unsafe.SkipInit(out buffer); - Assert.SdkRequiresNotNull(Entries); - Assert.SdkRequiresNotNull(ref buffer); - Assert.SdkRequiresNotNull(ref entry); - - UnsafeHelpers.SkipParamInit(out buffer); - - // Get the attribute info. - ref AttrInfo attrInfo = ref FindAttrInfo(entry.GetBufferAttribute()); - Assert.SdkNotNull(ref attrInfo); - Assert.SdkGreater(attrInfo.GetCacheCount(), 0); - Assert.SdkGreaterEqual(attrInfo.GetCacheSize(), entry.GetSize()); - - // Release from the attr info. - attrInfo.DecrementCacheCount(); - attrInfo.SubtractCacheSize(entry.GetSize()); - - // Release from cached size. - Assert.SdkGreaterEqual(TotalCacheSize, entry.GetSize()); - TotalCacheSize -= entry.GetSize(); - - // Release the entry. - buffer = entry.GetBuffer(); - ReleaseEntry(ref entry); - } - - public CacheHandle PublishCacheHandle() - { - Assert.SdkRequires(Entries != null); - return ++CurrentHandle; - } - - public int GetTotalCacheSize() - { - return TotalCacheSize; - } - - private ref Entry AcquireEntry(Buffer buffer, BufferAttribute attr) - { - // Validate pre-conditions. - Assert.SdkRequiresNotNull(Entries); - - ref Entry entry = ref Unsafe.NullRef(); - if (EntryCount < EntryCountMax) - { - entry = ref Entries[EntryCount]; - entry.Initialize(PublishCacheHandle(), buffer, attr); - EntryCount++; - Assert.SdkAssert(EntryCount == 1 || Entries[EntryCount - 2].GetHandle() < entry.GetHandle()); - } - - return ref entry; - } - - private void ReleaseEntry(ref Entry entry) - { - // Validate pre-conditions. - Assert.SdkRequiresNotNull(Entries); - Assert.SdkRequiresNotNull(ref entry); - - // Ensure the entry is valid. - Span entryBuffer = Entries; - Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref entry, ref MemoryMarshal.GetReference(entryBuffer))); - Assert.SdkAssert(Unsafe.IsAddressLessThan(ref entry, - ref Unsafe.Add(ref MemoryMarshal.GetReference(entryBuffer), entryBuffer.Length))); - - // Get the index of the entry. - int index = Unsafe.ByteOffset(ref MemoryMarshal.GetReference(entryBuffer), ref entry).ToInt32() / - Unsafe.SizeOf(); - - // Copy the entries back by one. - Span source = entryBuffer.Slice(index + 1, EntryCount - (index + 1)); - Span dest = entryBuffer.Slice(index); - source.CopyTo(dest); - - // Decrement our entry count. - EntryCount--; - } - - private ref AttrInfo FindAttrInfo(BufferAttribute attr) - { - LinkedListNode curNode = AttrList.First; - - while (curNode != null) - { - if (curNode.ValueRef.GetLevel() == attr.Level) - { - return ref curNode.ValueRef; - } - - curNode = curNode.Next; - } - - return ref Unsafe.NullRef(); - } - } - - private FileSystemBuddyHeap BuddyHeap { get; } = new(); - private CacheHandleTable CacheTable { get; } = new(); - private int TotalSize { get; set; } - private int PeakFreeSize { get; set; } - private int PeakTotalAllocatableSize { get; set; } - private int RetriedCount { get; set; } - private object Locker { get; } = new(); - - protected override void Dispose(bool disposing) - { - if (disposing) - { - BuddyHeap.Dispose(); - } - - base.Dispose(disposing); - } - - public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize) - { - Result rc = CacheTable.Initialize(maxCacheCount); - if (rc.IsFailure()) return rc; - - rc = BuddyHeap.Initialize(heapBuffer, blockSize); - if (rc.IsFailure()) return rc; - - TotalSize = (int)BuddyHeap.GetTotalFreeSize(); - PeakFreeSize = TotalSize; - PeakTotalAllocatableSize = TotalSize; - - return Result.Success; - } - - public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, int maxOrder) - { - Result rc = CacheTable.Initialize(maxCacheCount); - if (rc.IsFailure()) return rc; - - rc = BuddyHeap.Initialize(heapBuffer, blockSize, maxOrder); - if (rc.IsFailure()) return rc; - - TotalSize = (int)BuddyHeap.GetTotalFreeSize(); - PeakFreeSize = TotalSize; - PeakTotalAllocatableSize = TotalSize; - - return Result.Success; - } - - public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, Memory workBuffer) - { - // Note: We can't use an external buffer for the cache handle table since it contains managed pointers, - // so pass the work buffer directly to the buddy heap. - - Result rc = CacheTable.Initialize(maxCacheCount); - if (rc.IsFailure()) return rc; - - rc = BuddyHeap.Initialize(heapBuffer, blockSize, workBuffer); - if (rc.IsFailure()) return rc; - - TotalSize = (int)BuddyHeap.GetTotalFreeSize(); - PeakFreeSize = TotalSize; - PeakTotalAllocatableSize = TotalSize; - - return Result.Success; - } - - public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, int maxOrder, - Memory workBuffer) - { - // Note: We can't use an external buffer for the cache handle table since it contains managed pointers, - // so pass the work buffer directly to the buddy heap. - - Result rc = CacheTable.Initialize(maxCacheCount); - if (rc.IsFailure()) return rc; - - rc = BuddyHeap.Initialize(heapBuffer, blockSize, maxOrder, workBuffer); - if (rc.IsFailure()) return rc; - - TotalSize = (int)BuddyHeap.GetTotalFreeSize(); - PeakFreeSize = TotalSize; - PeakTotalAllocatableSize = TotalSize; - - return Result.Success; - } - - protected override Buffer DoAllocateBuffer(int size, BufferAttribute attribute) - { - lock (Locker) - { - return AllocateBufferImpl(size, attribute); - } - } - - private Buffer AllocateBufferImpl(int size, BufferAttribute attribute) - { - int order = BuddyHeap.GetOrderFromBytes((nuint)size); - Assert.SdkAssert(order >= 0); - - // Allocate space on the heap - Buffer buffer; - while ((buffer = BuddyHeap.AllocateBufferByOrder(order)).IsNull) - { - // Not enough space in heap. Deallocate cached buffer and try again. - RetriedCount++; - - if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute, size)) - { - // No cached buffers left to deallocate. - return Buffer.Empty; - } - - DeallocateBufferImpl(deallocateBuffer); - } - - // Successfully allocated a buffer. - int allocatedSize = (int)BuddyHeap.GetBytesFromOrder(order); - Assert.SdkAssert(size <= allocatedSize); - - // Update heap stats - int freeSize = (int)BuddyHeap.GetTotalFreeSize(); - PeakFreeSize = Math.Min(PeakFreeSize, freeSize); - - int totalAllocatableSize = freeSize + CacheTable.GetTotalCacheSize(); - PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize); - - return buffer; - } - - protected override void DoDeallocateBuffer(Buffer buffer) - { - lock (Locker) - { - DeallocateBufferImpl(buffer); - } - } - - private void DeallocateBufferImpl(Buffer buffer) - { - Assert.SdkRequires(BitUtil.IsPowerOfTwo(buffer.Length)); - - BuddyHeap.Free(buffer); - } - - protected override CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute) - { - lock (Locker) - { - return RegisterCacheImpl(buffer, attribute); - } - } - - private CacheHandle RegisterCacheImpl(Buffer buffer, BufferAttribute attribute) - { - CacheHandle handle; - - // Try to register the handle. - while (!CacheTable.Register(out handle, buffer, attribute)) - { - // Unregister a buffer and try registering again. - RetriedCount++; - if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute)) - { - // Can't unregister any existing buffers. - // Register the input buffer to /dev/null. - DeallocateBufferImpl(buffer); - return CacheTable.PublishCacheHandle(); - } - - // Deallocate the unregistered buffer. - DeallocateBufferImpl(deallocateBuffer); - } - - return handle; - } - - protected override Buffer DoAcquireCache(CacheHandle handle) - { - lock (Locker) - { - return AcquireCacheImpl(handle); - } - } - - private Buffer AcquireCacheImpl(CacheHandle handle) - { - if (CacheTable.Unregister(out Buffer range, handle)) - { - int totalAllocatableSize = (int)BuddyHeap.GetTotalFreeSize() + CacheTable.GetTotalCacheSize(); - PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize); + attrInfo.IncrementCacheCount(); + attrInfo.AddCacheSize(buffer.Length); } else { - range = Buffer.Empty; + // Make a new attr info and add it to the list. + // Note: Not using attr info buffer + var newInfo = new AttrInfo(attr.Level, 1, buffer.Length); + AttrList.AddLast(newInfo); } - return range; + TotalCacheSize += buffer.Length; + handle = entry.GetHandle(); + return true; } - protected override int DoGetTotalSize() + public bool Unregister(out Buffer buffer, CacheHandle handle) { - return TotalSize; - } + // Validate pre-conditions. + Unsafe.SkipInit(out buffer); + Assert.SdkRequiresNotNull(Entries); + Assert.SdkRequiresNotNull(ref buffer); - protected override int DoGetFreeSize() - { - lock (Locker) + UnsafeHelpers.SkipParamInit(out buffer); + + // Find the lower bound for the entry. + for (int i = 0; i < EntryCount; i++) { - return GetFreeSizeImpl(); + if (Entries[i].GetHandle() == handle) + { + UnregisterCore(out buffer, ref Entries[i]); + return true; + } } + + return false; } - private int GetFreeSizeImpl() + // ReSharper disable UnusedParameter.Local + public bool UnregisterOldest(out Buffer buffer, BufferAttribute attr, int requiredSize = 0) + // ReSharper restore UnusedParameter.Local { - return (int)BuddyHeap.GetTotalFreeSize(); - } + // Validate pre-conditions. + Unsafe.SkipInit(out buffer); + Assert.SdkRequiresNotNull(Entries); + Assert.SdkRequiresNotNull(ref buffer); - protected override int DoGetTotalAllocatableSize() - { - lock (Locker) + UnsafeHelpers.SkipParamInit(out buffer); + + // If we have no entries, we can't unregister any. + if (EntryCount == 0) { - return GetTotalAllocatableSizeImpl(); + return false; } - } - private int GetTotalAllocatableSizeImpl() - { - return GetFreeSizeImpl() + CacheTable.GetTotalCacheSize(); - } - - protected override int DoGetFreeSizePeak() - { - lock (Locker) + static bool CanUnregister(CacheHandleTable table, ref Entry entry) { - return GetFreeSizePeakImpl(); + ref AttrInfo attrInfo = ref table.FindAttrInfo(entry.GetBufferAttribute()); + Assert.SdkNotNull(ref attrInfo); + + int ccm = table.GetCacheCountMin(entry.GetBufferAttribute()); + int csm = table.GetCacheSizeMin(entry.GetBufferAttribute()); + + return ccm < attrInfo.GetCacheCount() && csm + entry.GetSize() <= attrInfo.GetCacheSize(); } - } - private int GetFreeSizePeakImpl() - { - return PeakFreeSize; - } - - protected override int DoGetTotalAllocatableSizePeak() - { - lock (Locker) + // Find an entry, falling back to the first entry. + ref Entry entry = ref Unsafe.NullRef(); + for (int i = 0; i < EntryCount; i++) { - return GetTotalAllocatableSizePeakImpl(); + if (CanUnregister(this, ref Entries[i])) + { + entry = ref Entries[i]; + break; + } } - } - private int GetTotalAllocatableSizePeakImpl() - { - return PeakTotalAllocatableSize; - } - - protected override int DoGetRetriedCount() - { - lock (Locker) + if (Unsafe.IsNullRef(ref entry)) { - return GetRetriedCountImpl(); + entry = ref Entries[0]; } + + Assert.SdkNotNull(ref entry); + UnregisterCore(out buffer, ref entry); + return true; } - private int GetRetriedCountImpl() + private void UnregisterCore(out Buffer buffer, ref Entry entry) { - return RetriedCount; + // Validate pre-conditions. + Unsafe.SkipInit(out buffer); + Assert.SdkRequiresNotNull(Entries); + Assert.SdkRequiresNotNull(ref buffer); + Assert.SdkRequiresNotNull(ref entry); + + UnsafeHelpers.SkipParamInit(out buffer); + + // Get the attribute info. + ref AttrInfo attrInfo = ref FindAttrInfo(entry.GetBufferAttribute()); + Assert.SdkNotNull(ref attrInfo); + Assert.SdkGreater(attrInfo.GetCacheCount(), 0); + Assert.SdkGreaterEqual(attrInfo.GetCacheSize(), entry.GetSize()); + + // Release from the attr info. + attrInfo.DecrementCacheCount(); + attrInfo.SubtractCacheSize(entry.GetSize()); + + // Release from cached size. + Assert.SdkGreaterEqual(TotalCacheSize, entry.GetSize()); + TotalCacheSize -= entry.GetSize(); + + // Release the entry. + buffer = entry.GetBuffer(); + ReleaseEntry(ref entry); } - protected override void DoClearPeak() + public CacheHandle PublishCacheHandle() { - lock (Locker) + Assert.SdkRequires(Entries != null); + return ++CurrentHandle; + } + + public int GetTotalCacheSize() + { + return TotalCacheSize; + } + + private ref Entry AcquireEntry(Buffer buffer, BufferAttribute attr) + { + // Validate pre-conditions. + Assert.SdkRequiresNotNull(Entries); + + ref Entry entry = ref Unsafe.NullRef(); + if (EntryCount < EntryCountMax) { - ClearPeakImpl(); + entry = ref Entries[EntryCount]; + entry.Initialize(PublishCacheHandle(), buffer, attr); + EntryCount++; + Assert.SdkAssert(EntryCount == 1 || Entries[EntryCount - 2].GetHandle() < entry.GetHandle()); } + + return ref entry; } - private void ClearPeakImpl() + private void ReleaseEntry(ref Entry entry) { - PeakFreeSize = GetFreeSizeImpl(); - PeakTotalAllocatableSize = GetTotalAllocatableSizeImpl(); - RetriedCount = 0; + // Validate pre-conditions. + Assert.SdkRequiresNotNull(Entries); + Assert.SdkRequiresNotNull(ref entry); + + // Ensure the entry is valid. + Span entryBuffer = Entries; + Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref entry, ref MemoryMarshal.GetReference(entryBuffer))); + Assert.SdkAssert(Unsafe.IsAddressLessThan(ref entry, + ref Unsafe.Add(ref MemoryMarshal.GetReference(entryBuffer), entryBuffer.Length))); + + // Get the index of the entry. + int index = Unsafe.ByteOffset(ref MemoryMarshal.GetReference(entryBuffer), ref entry).ToInt32() / + Unsafe.SizeOf(); + + // Copy the entries back by one. + Span source = entryBuffer.Slice(index + 1, EntryCount - (index + 1)); + Span dest = entryBuffer.Slice(index); + source.CopyTo(dest); + + // Decrement our entry count. + EntryCount--; + } + + private ref AttrInfo FindAttrInfo(BufferAttribute attr) + { + LinkedListNode curNode = AttrList.First; + + while (curNode != null) + { + if (curNode.ValueRef.GetLevel() == attr.Level) + { + return ref curNode.ValueRef; + } + + curNode = curNode.Next; + } + + return ref Unsafe.NullRef(); } } -} \ No newline at end of file + + private FileSystemBuddyHeap BuddyHeap { get; } = new(); + private CacheHandleTable CacheTable { get; } = new(); + private int TotalSize { get; set; } + private int PeakFreeSize { get; set; } + private int PeakTotalAllocatableSize { get; set; } + private int RetriedCount { get; set; } + private object Locker { get; } = new(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BuddyHeap.Dispose(); + } + + base.Dispose(disposing); + } + + public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize) + { + Result rc = CacheTable.Initialize(maxCacheCount); + if (rc.IsFailure()) return rc; + + rc = BuddyHeap.Initialize(heapBuffer, blockSize); + if (rc.IsFailure()) return rc; + + TotalSize = (int)BuddyHeap.GetTotalFreeSize(); + PeakFreeSize = TotalSize; + PeakTotalAllocatableSize = TotalSize; + + return Result.Success; + } + + public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, int maxOrder) + { + Result rc = CacheTable.Initialize(maxCacheCount); + if (rc.IsFailure()) return rc; + + rc = BuddyHeap.Initialize(heapBuffer, blockSize, maxOrder); + if (rc.IsFailure()) return rc; + + TotalSize = (int)BuddyHeap.GetTotalFreeSize(); + PeakFreeSize = TotalSize; + PeakTotalAllocatableSize = TotalSize; + + return Result.Success; + } + + public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, Memory workBuffer) + { + // Note: We can't use an external buffer for the cache handle table since it contains managed pointers, + // so pass the work buffer directly to the buddy heap. + + Result rc = CacheTable.Initialize(maxCacheCount); + if (rc.IsFailure()) return rc; + + rc = BuddyHeap.Initialize(heapBuffer, blockSize, workBuffer); + if (rc.IsFailure()) return rc; + + TotalSize = (int)BuddyHeap.GetTotalFreeSize(); + PeakFreeSize = TotalSize; + PeakTotalAllocatableSize = TotalSize; + + return Result.Success; + } + + public Result Initialize(int maxCacheCount, Memory heapBuffer, int blockSize, int maxOrder, + Memory workBuffer) + { + // Note: We can't use an external buffer for the cache handle table since it contains managed pointers, + // so pass the work buffer directly to the buddy heap. + + Result rc = CacheTable.Initialize(maxCacheCount); + if (rc.IsFailure()) return rc; + + rc = BuddyHeap.Initialize(heapBuffer, blockSize, maxOrder, workBuffer); + if (rc.IsFailure()) return rc; + + TotalSize = (int)BuddyHeap.GetTotalFreeSize(); + PeakFreeSize = TotalSize; + PeakTotalAllocatableSize = TotalSize; + + return Result.Success; + } + + protected override Buffer DoAllocateBuffer(int size, BufferAttribute attribute) + { + lock (Locker) + { + return AllocateBufferImpl(size, attribute); + } + } + + private Buffer AllocateBufferImpl(int size, BufferAttribute attribute) + { + int order = BuddyHeap.GetOrderFromBytes((nuint)size); + Assert.SdkAssert(order >= 0); + + // Allocate space on the heap + Buffer buffer; + while ((buffer = BuddyHeap.AllocateBufferByOrder(order)).IsNull) + { + // Not enough space in heap. Deallocate cached buffer and try again. + RetriedCount++; + + if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute, size)) + { + // No cached buffers left to deallocate. + return Buffer.Empty; + } + + DeallocateBufferImpl(deallocateBuffer); + } + + // Successfully allocated a buffer. + int allocatedSize = (int)BuddyHeap.GetBytesFromOrder(order); + Assert.SdkAssert(size <= allocatedSize); + + // Update heap stats + int freeSize = (int)BuddyHeap.GetTotalFreeSize(); + PeakFreeSize = Math.Min(PeakFreeSize, freeSize); + + int totalAllocatableSize = freeSize + CacheTable.GetTotalCacheSize(); + PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize); + + return buffer; + } + + protected override void DoDeallocateBuffer(Buffer buffer) + { + lock (Locker) + { + DeallocateBufferImpl(buffer); + } + } + + private void DeallocateBufferImpl(Buffer buffer) + { + Assert.SdkRequires(BitUtil.IsPowerOfTwo(buffer.Length)); + + BuddyHeap.Free(buffer); + } + + protected override CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute) + { + lock (Locker) + { + return RegisterCacheImpl(buffer, attribute); + } + } + + private CacheHandle RegisterCacheImpl(Buffer buffer, BufferAttribute attribute) + { + CacheHandle handle; + + // Try to register the handle. + while (!CacheTable.Register(out handle, buffer, attribute)) + { + // Unregister a buffer and try registering again. + RetriedCount++; + if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute)) + { + // Can't unregister any existing buffers. + // Register the input buffer to /dev/null. + DeallocateBufferImpl(buffer); + return CacheTable.PublishCacheHandle(); + } + + // Deallocate the unregistered buffer. + DeallocateBufferImpl(deallocateBuffer); + } + + return handle; + } + + protected override Buffer DoAcquireCache(CacheHandle handle) + { + lock (Locker) + { + return AcquireCacheImpl(handle); + } + } + + private Buffer AcquireCacheImpl(CacheHandle handle) + { + if (CacheTable.Unregister(out Buffer range, handle)) + { + int totalAllocatableSize = (int)BuddyHeap.GetTotalFreeSize() + CacheTable.GetTotalCacheSize(); + PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize); + } + else + { + range = Buffer.Empty; + } + + return range; + } + + protected override int DoGetTotalSize() + { + return TotalSize; + } + + protected override int DoGetFreeSize() + { + lock (Locker) + { + return GetFreeSizeImpl(); + } + } + + private int GetFreeSizeImpl() + { + return (int)BuddyHeap.GetTotalFreeSize(); + } + + protected override int DoGetTotalAllocatableSize() + { + lock (Locker) + { + return GetTotalAllocatableSizeImpl(); + } + } + + private int GetTotalAllocatableSizeImpl() + { + return GetFreeSizeImpl() + CacheTable.GetTotalCacheSize(); + } + + protected override int DoGetFreeSizePeak() + { + lock (Locker) + { + return GetFreeSizePeakImpl(); + } + } + + private int GetFreeSizePeakImpl() + { + return PeakFreeSize; + } + + protected override int DoGetTotalAllocatableSizePeak() + { + lock (Locker) + { + return GetTotalAllocatableSizePeakImpl(); + } + } + + private int GetTotalAllocatableSizePeakImpl() + { + return PeakTotalAllocatableSize; + } + + protected override int DoGetRetriedCount() + { + lock (Locker) + { + return GetRetriedCountImpl(); + } + } + + private int GetRetriedCountImpl() + { + return RetriedCount; + } + + protected override void DoClearPeak() + { + lock (Locker) + { + ClearPeakImpl(); + } + } + + private void ClearPeakImpl() + { + PeakFreeSize = GetFreeSizeImpl(); + PeakTotalAllocatableSize = GetTotalAllocatableSizeImpl(); + RetriedCount = 0; + } +} diff --git a/src/LibHac/FsSystem/CachedStorage.cs b/src/LibHac/FsSystem/CachedStorage.cs index 773445c6..22029840 100644 --- a/src/LibHac/FsSystem/CachedStorage.cs +++ b/src/LibHac/FsSystem/CachedStorage.cs @@ -2,208 +2,207 @@ using System.Collections.Generic; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class CachedStorage : IStorage { - public class CachedStorage : IStorage + private IStorage BaseStorage { get; } + private int BlockSize { get; } + private long Length { get; set; } + private bool LeaveOpen { get; } + + private LinkedList Blocks { get; } = new LinkedList(); + private Dictionary> BlockDict { get; } = new Dictionary>(); + + public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, bool leaveOpen) { - private IStorage BaseStorage { get; } - private int BlockSize { get; } - private long Length { get; set; } - private bool LeaveOpen { get; } + BaseStorage = baseStorage; + BlockSize = blockSize; + LeaveOpen = leaveOpen; - private LinkedList Blocks { get; } = new LinkedList(); - private Dictionary> BlockDict { get; } = new Dictionary>(); + BaseStorage.GetSize(out long baseSize).ThrowIfFailure(); + Length = baseSize; - public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, bool leaveOpen) + for (int i = 0; i < cacheSize; i++) { - BaseStorage = baseStorage; - BlockSize = blockSize; - LeaveOpen = leaveOpen; - - BaseStorage.GetSize(out long baseSize).ThrowIfFailure(); - Length = baseSize; - - for (int i = 0; i < cacheSize; i++) - { - var block = new CacheBlock { Buffer = new byte[blockSize], Index = -1 }; - Blocks.AddLast(block); - } - } - - public CachedStorage(SectorStorage baseStorage, int cacheSize, bool leaveOpen) - : this(baseStorage, baseStorage.SectorSize, cacheSize, leaveOpen) { } - - protected override Result DoRead(long offset, Span destination) - { - long remaining = destination.Length; - long inOffset = offset; - int outOffset = 0; - - if (!CheckAccessRange(offset, destination.Length, Length)) - return ResultFs.OutOfRange.Log(); - - lock (Blocks) - { - while (remaining > 0) - { - long blockIndex = inOffset / BlockSize; - int blockPos = (int)(inOffset % BlockSize); - CacheBlock block = GetBlock(blockIndex); - - int bytesToRead = (int)Math.Min(remaining, BlockSize - blockPos); - - block.Buffer.AsSpan(blockPos, bytesToRead).CopyTo(destination.Slice(outOffset)); - - outOffset += bytesToRead; - inOffset += bytesToRead; - remaining -= bytesToRead; - } - } - - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - long remaining = source.Length; - long inOffset = offset; - int outOffset = 0; - - if (!CheckAccessRange(offset, source.Length, Length)) - return ResultFs.OutOfRange.Log(); - - lock (Blocks) - { - while (remaining > 0) - { - long blockIndex = inOffset / BlockSize; - int blockPos = (int)(inOffset % BlockSize); - CacheBlock block = GetBlock(blockIndex); - - int bytesToWrite = (int)Math.Min(remaining, BlockSize - blockPos); - - source.Slice(outOffset, bytesToWrite).CopyTo(block.Buffer.AsSpan(blockPos)); - - block.Dirty = true; - - outOffset += bytesToWrite; - inOffset += bytesToWrite; - remaining -= bytesToWrite; - } - } - - return Result.Success; - } - - protected override Result DoFlush() - { - lock (Blocks) - { - foreach (CacheBlock cacheItem in Blocks) - { - FlushBlock(cacheItem); - } - } - - return BaseStorage.Flush(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - Result rc = BaseStorage.SetSize(size); - if (rc.IsFailure()) return rc; - - rc = BaseStorage.GetSize(out long newSize); - if (rc.IsFailure()) return rc; - - Length = newSize; - - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); - } - - base.Dispose(); - } - - private CacheBlock GetBlock(long blockIndex) - { - if (BlockDict.TryGetValue(blockIndex, out LinkedListNode node)) - { - if (Blocks.First != node) - { - Blocks.Remove(node); - Blocks.AddFirst(node); - } - - return node!.Value; - } - - // An inactive node shouldn't be null, but we'll fix it if it is anyway - node = Blocks.Last ?? - new LinkedListNode(new CacheBlock { Buffer = new byte[BlockSize], Index = -1 }); - - FlushBlock(node.Value); - - CacheBlock block = node.Value; - Blocks.RemoveLast(); - - if (block.Index != -1) - { - BlockDict.Remove(block.Index); - } - - FlushBlock(block); - ReadBlock(block, blockIndex); - - Blocks.AddFirst(node); - BlockDict.Add(blockIndex, node); - - return block; - } - - private void ReadBlock(CacheBlock block, long index) - { - long offset = index * BlockSize; - int length = BlockSize; - - if (Length != -1) - { - length = (int)Math.Min(Length - offset, length); - } - - BaseStorage.Read(offset, block.Buffer.AsSpan(0, length)).ThrowIfFailure(); - block.Length = length; - block.Index = index; - block.Dirty = false; - } - - private void FlushBlock(CacheBlock block) - { - if (!block.Dirty) return; - - long offset = block.Index * BlockSize; - BaseStorage.Write(offset, block.Buffer.AsSpan(0, block.Length)).ThrowIfFailure(); - block.Dirty = false; - } - - private class CacheBlock - { - public long Index { get; set; } - public byte[] Buffer { get; set; } - public int Length { get; set; } - public bool Dirty { get; set; } + var block = new CacheBlock { Buffer = new byte[blockSize], Index = -1 }; + Blocks.AddLast(block); } } + + public CachedStorage(SectorStorage baseStorage, int cacheSize, bool leaveOpen) + : this(baseStorage, baseStorage.SectorSize, cacheSize, leaveOpen) { } + + protected override Result DoRead(long offset, Span destination) + { + long remaining = destination.Length; + long inOffset = offset; + int outOffset = 0; + + if (!CheckAccessRange(offset, destination.Length, Length)) + return ResultFs.OutOfRange.Log(); + + lock (Blocks) + { + while (remaining > 0) + { + long blockIndex = inOffset / BlockSize; + int blockPos = (int)(inOffset % BlockSize); + CacheBlock block = GetBlock(blockIndex); + + int bytesToRead = (int)Math.Min(remaining, BlockSize - blockPos); + + block.Buffer.AsSpan(blockPos, bytesToRead).CopyTo(destination.Slice(outOffset)); + + outOffset += bytesToRead; + inOffset += bytesToRead; + remaining -= bytesToRead; + } + } + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + long remaining = source.Length; + long inOffset = offset; + int outOffset = 0; + + if (!CheckAccessRange(offset, source.Length, Length)) + return ResultFs.OutOfRange.Log(); + + lock (Blocks) + { + while (remaining > 0) + { + long blockIndex = inOffset / BlockSize; + int blockPos = (int)(inOffset % BlockSize); + CacheBlock block = GetBlock(blockIndex); + + int bytesToWrite = (int)Math.Min(remaining, BlockSize - blockPos); + + source.Slice(outOffset, bytesToWrite).CopyTo(block.Buffer.AsSpan(blockPos)); + + block.Dirty = true; + + outOffset += bytesToWrite; + inOffset += bytesToWrite; + remaining -= bytesToWrite; + } + } + + return Result.Success; + } + + protected override Result DoFlush() + { + lock (Blocks) + { + foreach (CacheBlock cacheItem in Blocks) + { + FlushBlock(cacheItem); + } + } + + return BaseStorage.Flush(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + Result rc = BaseStorage.SetSize(size); + if (rc.IsFailure()) return rc; + + rc = BaseStorage.GetSize(out long newSize); + if (rc.IsFailure()) return rc; + + Length = newSize; + + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) + { + BaseStorage?.Dispose(); + } + + base.Dispose(); + } + + private CacheBlock GetBlock(long blockIndex) + { + if (BlockDict.TryGetValue(blockIndex, out LinkedListNode node)) + { + if (Blocks.First != node) + { + Blocks.Remove(node); + Blocks.AddFirst(node); + } + + return node!.Value; + } + + // An inactive node shouldn't be null, but we'll fix it if it is anyway + node = Blocks.Last ?? + new LinkedListNode(new CacheBlock { Buffer = new byte[BlockSize], Index = -1 }); + + FlushBlock(node.Value); + + CacheBlock block = node.Value; + Blocks.RemoveLast(); + + if (block.Index != -1) + { + BlockDict.Remove(block.Index); + } + + FlushBlock(block); + ReadBlock(block, blockIndex); + + Blocks.AddFirst(node); + BlockDict.Add(blockIndex, node); + + return block; + } + + private void ReadBlock(CacheBlock block, long index) + { + long offset = index * BlockSize; + int length = BlockSize; + + if (Length != -1) + { + length = (int)Math.Min(Length - offset, length); + } + + BaseStorage.Read(offset, block.Buffer.AsSpan(0, length)).ThrowIfFailure(); + block.Length = length; + block.Index = index; + block.Dirty = false; + } + + private void FlushBlock(CacheBlock block) + { + if (!block.Dirty) return; + + long offset = block.Index * BlockSize; + BaseStorage.Write(offset, block.Buffer.AsSpan(0, block.Length)).ThrowIfFailure(); + block.Dirty = false; + } + + private class CacheBlock + { + public long Index { get; set; } + public byte[] Buffer { get; set; } + public int Length { get; set; } + public bool Dirty { get; set; } + } } diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs index 0f3c9167..2212404c 100644 --- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs +++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs @@ -10,957 +10,956 @@ using LibHac.Fs.Fsa; using LibHac.Util; using static LibHac.FsSystem.Utility; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// An that stores large files as smaller, separate sub-files. +/// +/// +/// This filesystem is mainly used to allow storing large files on filesystems that have low +/// limits on file size such as FAT filesystems. The underlying base filesystem must have +/// support for the "Archive" file attribute found in FAT or NTFS filesystems.
+///
+/// A may contain both standard files or Concatenation files. +/// If a directory has the archive attribute set, its contents will be concatenated and treated +/// as a single file. These sub-files must follow the naming scheme "00", "01", "02", ... +/// Each sub-file except the final one must have the size that was specified +/// at the creation of the . +///
Based on FS 12.1.0 (nnSdk 12.3.1) +///
+public class ConcatenationFileSystem : IFileSystem { - /// - /// An that stores large files as smaller, separate sub-files. - /// - /// - /// This filesystem is mainly used to allow storing large files on filesystems that have low - /// limits on file size such as FAT filesystems. The underlying base filesystem must have - /// support for the "Archive" file attribute found in FAT or NTFS filesystems.
- ///
- /// A may contain both standard files or Concatenation files. - /// If a directory has the archive attribute set, its contents will be concatenated and treated - /// as a single file. These sub-files must follow the naming scheme "00", "01", "02", ... - /// Each sub-file except the final one must have the size that was specified - /// at the creation of the . - ///
Based on FS 12.1.0 (nnSdk 12.3.1) - ///
- public class ConcatenationFileSystem : IFileSystem + private class ConcatenationFile : IFile { - private class ConcatenationFile : IFile - { - private OpenMode _mode; - private List _files; - private long _internalFileSize; - private IFileSystem _baseFileSystem; - private Path.Stored _path; - - public ConcatenationFile(OpenMode mode, ref List internalFiles, long internalFileSize, IFileSystem baseFileSystem) - { - _mode = mode; - _files = Shared.Move(ref internalFiles); - _internalFileSize = internalFileSize; - _baseFileSystem = baseFileSystem; - _path = new Path.Stored(); - } - - public override void Dispose() - { - _path.Dispose(); - - foreach (IFile file in _files) - { - file?.Dispose(); - } - - _files.Clear(); - - base.Dispose(); - } - - public Result Initialize(in Path path) - { - return _path.Initialize(in path); - } - - private int GetInternalFileIndex(long offset) - { - return (int)(offset / _internalFileSize); - } - - private int GetInternalFileCount(long size) - { - if (size == 0) - return 1; - - return (int)BitUtil.DivideUp(size, _internalFileSize); - } - - private long GetInternalFileSize(long offset, int tailIndex) - { - int index = GetInternalFileIndex(offset); - - if (tailIndex < index) - return _internalFileSize; - - Assert.SdkAssert(index == tailIndex); - - return offset % _internalFileSize; - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); - - long fileOffset = offset; - int bufferOffset = 0; - - Result rc = DryRead(out long remaining, offset, destination.Length, in option, _mode); - if (rc.IsFailure()) return rc; - - while (remaining > 0) - { - int fileIndex = GetInternalFileIndex(fileOffset); - long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); - long internalFileOffset = fileOffset - _internalFileSize * fileIndex; - - int bytesToRead = (int)Math.Min(remaining, internalFileRemaining); - - Assert.SdkAssert(fileIndex < _files.Count); - - rc = _files[fileIndex].Read(out long internalFileBytesRead, internalFileOffset, - destination.Slice(bufferOffset, bytesToRead), in option); - if (rc.IsFailure()) return rc; - - remaining -= internalFileBytesRead; - bufferOffset += (int)internalFileBytesRead; - fileOffset += internalFileBytesRead; - - if (internalFileBytesRead < bytesToRead) - break; - } - - bytesRead = bufferOffset; - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - Result rc = DryWrite(out bool needsAppend, offset, source.Length, in option, _mode); - if (rc.IsFailure()) return rc; - - if (source.Length > 0 && needsAppend) - { - rc = SetSize(offset + source.Length); - if (rc.IsFailure()) return rc; - } - - int remaining = source.Length; - int bufferOffset = 0; - long fileOffset = offset; - - // No need to send the flush option to the internal files. We'll flush them after all the writes are done. - var internalFileOption = new WriteOption(option.Flags & ~WriteOptionFlag.Flush); - - while (remaining > 0) - { - int fileIndex = GetInternalFileIndex(fileOffset); - long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); - long internalFileOffset = fileOffset - _internalFileSize * fileIndex; - - int bytesToWrite = (int)Math.Min(remaining, internalFileRemaining); - - Assert.SdkAssert(fileIndex < _files.Count); - - rc = _files[fileIndex].Write(internalFileOffset, source.Slice(bufferOffset, bytesToWrite), - in internalFileOption); - if (rc.IsFailure()) return rc; - - remaining -= bytesToWrite; - bufferOffset += bytesToWrite; - fileOffset += bytesToWrite; - } - - if (option.HasFlushFlag()) - { - rc = Flush(); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - protected override Result DoFlush() - { - if (!_mode.HasFlag(OpenMode.Write)) - return Result.Success; - - foreach (IFile file in _files) - { - Result rc = file.Flush(); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - Result rc = DrySetSize(size, _mode); - if (rc.IsFailure()) return rc; - - rc = GetSize(out long currentSize); - if (rc.IsFailure()) return rc; - - if (currentSize == size) return Result.Success; - - int currentTailIndex = GetInternalFileCount(currentSize) - 1; - int newTailIndex = GetInternalFileCount(size) - 1; - - using var internalFilePath = new Path(); - rc = internalFilePath.Initialize(in _path); - if (rc.IsFailure()) return rc; - - if (size > currentSize) - { - rc = _files[currentTailIndex].SetSize(GetInternalFileSize(size, currentTailIndex)); - if (rc.IsFailure()) return rc; - - for (int i = currentTailIndex + 1; i < newTailIndex; i++) - { - rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.CreateFile(in internalFilePath, GetInternalFileSize(size, i), - CreateFileOptions.None); - if (rc.IsFailure()) return rc; - - using var newInternalFile = new UniqueRef(); - rc = _baseFileSystem.OpenFile(ref newInternalFile.Ref(), in internalFilePath, _mode); - if (rc.IsFailure()) return rc; - - _files.Add(newInternalFile.Release()); - - rc = internalFilePath.RemoveChild(); - if (rc.IsFailure()) return rc; - } - } - else - { - for (int i = currentTailIndex - 1; i > newTailIndex; i--) - { - _files[i].Dispose(); - _files.RemoveAt(i); - - rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.DeleteFile(in internalFilePath); - if (rc.IsFailure()) return rc; - - rc = internalFilePath.RemoveChild(); - if (rc.IsFailure()) return rc; - } - - rc = _files[newTailIndex].SetSize(GetInternalFileSize(size, newTailIndex)); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - long totalSize = 0; - - foreach (IFile file in _files) - { - Result rc = file.GetSize(out long internalFileSize); - if (rc.IsFailure()) return rc; - - totalSize += internalFileSize; - } - - size = totalSize; - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - if (operationId == OperationId.InvalidateCache) - { - if (!_mode.HasFlag(OpenMode.Read)) - return ResultFs.ReadUnpermitted.Log(); - - var closure = new OperateRangeClosure(); - closure.OutBuffer = outBuffer; - closure.InBuffer = inBuffer; - closure.OperationId = operationId; - - Result rc = DoOperateRangeImpl(offset, size, InvalidateCacheImpl, ref closure); - if (rc.IsFailure()) return rc; - } - else if (operationId == OperationId.QueryRange) - { - if (outBuffer.Length != Unsafe.SizeOf()) - return ResultFs.InvalidSize.Log(); - - var closure = new OperateRangeClosure(); - closure.InBuffer = inBuffer; - closure.OperationId = operationId; - closure.InfoMerged.Clear(); - - Result rc = DoOperateRangeImpl(offset, size, QueryRangeImpl, ref closure); - if (rc.IsFailure()) return rc; - - SpanHelpers.AsByteSpan(ref closure.InfoMerged).CopyTo(outBuffer); - } - else - { - return ResultFs.UnsupportedOperateRangeForConcatenationFile.Log(); - } - - return Result.Success; - - static Result InvalidateCacheImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) - { - return file.OperateRange(closure.OutBuffer, closure.OperationId, offset, size, closure.InBuffer); - } - - static Result QueryRangeImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) - { - Unsafe.SkipInit(out QueryRangeInfo infoEntry); - - Result rc = file.OperateRange(SpanHelpers.AsByteSpan(ref infoEntry), closure.OperationId, offset, size, - closure.InBuffer); - if (rc.IsFailure()) return rc; - - closure.InfoMerged.Merge(in infoEntry); - return Result.Success; - } - } - - private Result DoOperateRangeImpl(long offset, long size, OperateRangeTask func, - ref OperateRangeClosure closure) - { - if (offset < 0) - return ResultFs.OutOfRange.Log(); - - Result rc = GetSize(out long currentSize); - if (rc.IsFailure()) return rc; - - if (offset > currentSize) - return ResultFs.OutOfRange.Log(); - - long currentOffset = offset; - long availableSize = currentSize - offset; - long remaining = Math.Min(size, availableSize); - - while (remaining > 0) - { - int fileIndex = GetInternalFileIndex(currentOffset); - long internalFileRemaining = _internalFileSize - GetInternalFileSize(currentOffset, fileIndex); - long internalFileOffset = currentOffset - _internalFileSize * fileIndex; - - long sizeToOperate = Math.Min(remaining, internalFileRemaining); - - Assert.SdkAssert(fileIndex < _files.Count); - - rc = func(_files[fileIndex], internalFileOffset, sizeToOperate, ref closure); - if (rc.IsFailure()) return rc; - - remaining -= sizeToOperate; - currentOffset += sizeToOperate; - } - - return Result.Success; - } - - private delegate Result OperateRangeTask(IFile file, long offset, long size, ref OperateRangeClosure closure); - - private ref struct OperateRangeClosure - { - public Span OutBuffer; - public ReadOnlySpan InBuffer; - public OperationId OperationId; - public QueryRangeInfo InfoMerged; - } - } - - private class ConcatenationDirectory : IDirectory - { - private OpenDirectoryMode _mode; - private UniqueRef _baseDirectory; - private Path.Stored _path; - private IFileSystem _baseFileSystem; - private ConcatenationFileSystem _concatenationFileSystem; - - public ConcatenationDirectory(OpenDirectoryMode mode, ref UniqueRef baseDirectory, - ConcatenationFileSystem concatFileSystem, IFileSystem baseFileSystem) - { - _mode = mode; - _baseDirectory = new UniqueRef(ref baseDirectory); - _baseFileSystem = baseFileSystem; - _concatenationFileSystem = concatFileSystem; - } - - public override void Dispose() - { - _path.Dispose(); - _baseDirectory.Destroy(); - - base.Dispose(); - } - - public Result Initialize(in Path path) - { - Result rc = _path.Initialize(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - UnsafeHelpers.SkipParamInit(out entriesRead); - - Unsafe.SkipInit(out DirectoryEntry entry); - int readCountTotal = 0; - - while (readCountTotal < entryBuffer.Length) - { - Result rc = _baseDirectory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); - if (rc.IsFailure()) return rc; - - if (readCount == 0) - break; - - if (!IsReadTarget(in entry)) - continue; - - if (IsConcatenationFileAttribute(entry.Attributes)) - { - entry.Type = DirectoryEntryType.File; - - if (!_mode.HasFlag(OpenDirectoryMode.NoFileSize)) - { - using var internalFilePath = new Path(); - rc = internalFilePath.Initialize(in _path); - if (rc.IsFailure()) return rc; - - rc = internalFilePath.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - rc = _concatenationFileSystem.GetFileSize(out entry.Size, in internalFilePath); - if (rc.IsFailure()) return rc; - } - } - - entry.Attributes = NxFileAttributes.None; - entryBuffer[readCountTotal++] = entry; - } - - entriesRead = readCountTotal; - return Result.Success; - } - - protected override Result DoGetEntryCount(out long entryCount) - { - UnsafeHelpers.SkipParamInit(out entryCount); - - Unsafe.SkipInit(out DirectoryEntry entry); - using var directory = new UniqueRef(); - - using Path path = _path.DangerousGetPath(); - - Result rc = _baseFileSystem.OpenDirectory(ref directory.Ref(), in path, - OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize); - if (rc.IsFailure()) return rc; - - long entryCountTotal = 0; - - while (true) - { - directory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); - if (rc.IsFailure()) return rc; - - if (readCount == 0) - break; - - if (IsReadTarget(in entry)) - entryCountTotal++; - } - - entryCount = entryCountTotal; - return Result.Success; - } - - private bool IsReadTarget(in DirectoryEntry entry) - { - bool hasConcatAttribute = IsConcatenationFileAttribute(entry.Attributes); - - return _mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || hasConcatAttribute) || - _mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !hasConcatAttribute; - } - } - - public static readonly long DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS - - private IAttributeFileSystem _baseFileSystem; - private long _InternalFileSize; - - /// - /// Initializes a new with an internal file size of . - /// - /// The base for the - /// new . - public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultInternalFileSize) { } - - /// - /// Initializes a new . - /// - /// The base for the - /// new . - /// The size of each internal file. Once a file exceeds this size, a new internal file will be created - public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long internalFileSize) + private OpenMode _mode; + private List _files; + private long _internalFileSize; + private IFileSystem _baseFileSystem; + private Path.Stored _path; + + public ConcatenationFile(OpenMode mode, ref List internalFiles, long internalFileSize, IFileSystem baseFileSystem) { + _mode = mode; + _files = Shared.Move(ref internalFiles); + _internalFileSize = internalFileSize; _baseFileSystem = baseFileSystem; - _InternalFileSize = internalFileSize; + _path = new Path.Stored(); } public override void Dispose() { - _baseFileSystem?.Dispose(); - _baseFileSystem = null; + _path.Dispose(); + + foreach (IFile file in _files) + { + file?.Dispose(); + } + + _files.Clear(); base.Dispose(); } - private static ReadOnlySpan RootPath => new[] { (byte)'/' }; - - private static Result AppendInternalFilePath(ref Path path, int index) + public Result Initialize(in Path path) { - // Use an int as the buffer instead of a stackalloc byte[3] to workaround CS8350. - // Path.AppendChild will not save the span passed to it so this should be safe. - int bufferInt = 0; - Utf8Formatter.TryFormat(index, SpanHelpers.AsByteSpan(ref bufferInt), out _, new StandardFormat('d', 2)); - - return path.AppendChild(SpanHelpers.AsByteSpan(ref bufferInt)); + return _path.Initialize(in path); } - private static Result GenerateInternalFilePath(ref Path outPath, int index, in Path basePath) + private int GetInternalFileIndex(long offset) { - Result rc = outPath.Initialize(in basePath); + return (int)(offset / _internalFileSize); + } + + private int GetInternalFileCount(long size) + { + if (size == 0) + return 1; + + return (int)BitUtil.DivideUp(size, _internalFileSize); + } + + private long GetInternalFileSize(long offset, int tailIndex) + { + int index = GetInternalFileIndex(offset); + + if (tailIndex < index) + return _internalFileSize; + + Assert.SdkAssert(index == tailIndex); + + return offset % _internalFileSize; + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + long fileOffset = offset; + int bufferOffset = 0; + + Result rc = DryRead(out long remaining, offset, destination.Length, in option, _mode); if (rc.IsFailure()) return rc; - rc = AppendInternalFilePath(ref outPath, index); - if (rc.IsFailure()) return rc; + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(fileOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); + long internalFileOffset = fileOffset - _internalFileSize * fileIndex; + int bytesToRead = (int)Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = _files[fileIndex].Read(out long internalFileBytesRead, internalFileOffset, + destination.Slice(bufferOffset, bytesToRead), in option); + if (rc.IsFailure()) return rc; + + remaining -= internalFileBytesRead; + bufferOffset += (int)internalFileBytesRead; + fileOffset += internalFileBytesRead; + + if (internalFileBytesRead < bytesToRead) + break; + } + + bytesRead = bufferOffset; return Result.Success; } - private static Result GenerateParentPath(ref Path outParentPath, in Path path) + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) { - if (path == RootPath) - return ResultFs.PathNotFound.Log(); - - Result rc = outParentPath.Initialize(in path); + Result rc = DryWrite(out bool needsAppend, offset, source.Length, in option, _mode); if (rc.IsFailure()) return rc; - rc = outParentPath.RemoveChild(); - if (rc.IsFailure()) return rc; + if (source.Length > 0 && needsAppend) + { + rc = SetSize(offset + source.Length); + if (rc.IsFailure()) return rc; + } + + int remaining = source.Length; + int bufferOffset = 0; + long fileOffset = offset; + + // No need to send the flush option to the internal files. We'll flush them after all the writes are done. + var internalFileOption = new WriteOption(option.Flags & ~WriteOptionFlag.Flush); + + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(fileOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(fileOffset, fileIndex); + long internalFileOffset = fileOffset - _internalFileSize * fileIndex; + + int bytesToWrite = (int)Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = _files[fileIndex].Write(internalFileOffset, source.Slice(bufferOffset, bytesToWrite), + in internalFileOption); + if (rc.IsFailure()) return rc; + + remaining -= bytesToWrite; + bufferOffset += bytesToWrite; + fileOffset += bytesToWrite; + } + + if (option.HasFlushFlag()) + { + rc = Flush(); + if (rc.IsFailure()) return rc; + } return Result.Success; } - private static bool IsConcatenationFileAttribute(NxFileAttributes attribute) - { - return attribute.HasFlag(NxFileAttributes.Directory | NxFileAttributes.Archive); - } - - private bool IsConcatenationFile(in Path path) - { - Result rc = _baseFileSystem.GetFileAttributes(out NxFileAttributes attribute, in path); - if (rc.IsFailure()) - return false; - - return IsConcatenationFileAttribute(attribute); - } - - private Result GetInternalFileCount(out int count, in Path path) - { - UnsafeHelpers.SkipParamInit(out count); - - using var internalFilePath = new Path(); - Result rc = internalFilePath.Initialize(in path); - if (rc.IsFailure()) return rc; - - for (int i = 0; ; i++) - { - rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.GetEntryType(out _, in internalFilePath); - if (rc.IsFailure()) - { - // We've passed the last internal file of the concatenation file - // once the next internal file doesn't exist. - if (ResultFs.PathNotFound.Includes(rc)) - { - count = i; - return Result.Success; - } - - return rc; - } - - rc = internalFilePath.RemoveChild(); - if (rc.IsFailure()) return rc; - } - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - if (IsConcatenationFile(in path)) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } - - return _baseFileSystem.GetEntryType(out entryType, path); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path); - } - protected override Result DoFlush() { - return _baseFileSystem.Flush(); - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - if (!IsConcatenationFile(in path)) - { - return _baseFileSystem.OpenFile(ref outFile, in path, mode); - } - - Result rc = GetInternalFileCount(out int fileCount, in path); - if (rc.IsFailure()) return rc; - - var internalFiles = new List(fileCount); - - using var filePath = new Path(); - filePath.Initialize(in path); - if (rc.IsFailure()) return rc; - - try - { - for (int i = 0; i < fileCount; i++) - { - rc = AppendInternalFilePath(ref filePath.Ref(), i); - if (rc.IsFailure()) return rc; - - using var internalFile = new UniqueRef(); - rc = _baseFileSystem.OpenFile(ref internalFile.Ref(), in filePath, mode); - if (rc.IsFailure()) return rc; - - internalFiles.Add(internalFile.Release()); - - rc = filePath.RemoveChild(); - if (rc.IsFailure()) return rc; - } - - using var concatFile = new UniqueRef( - new ConcatenationFile(mode, ref internalFiles, _InternalFileSize, _baseFileSystem)); - - rc = concatFile.Get.Initialize(in path); - if (rc.IsFailure()) return rc; - - outFile.Set(ref concatFile.Ref()); + if (!_mode.HasFlag(OpenMode.Write)) return Result.Success; - } - finally + + foreach (IFile file in _files) { - if (internalFiles is not null) + Result rc = file.Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + Result rc = DrySetSize(size, _mode); + if (rc.IsFailure()) return rc; + + rc = GetSize(out long currentSize); + if (rc.IsFailure()) return rc; + + if (currentSize == size) return Result.Success; + + int currentTailIndex = GetInternalFileCount(currentSize) - 1; + int newTailIndex = GetInternalFileCount(size) - 1; + + using var internalFilePath = new Path(); + rc = internalFilePath.Initialize(in _path); + if (rc.IsFailure()) return rc; + + if (size > currentSize) + { + rc = _files[currentTailIndex].SetSize(GetInternalFileSize(size, currentTailIndex)); + if (rc.IsFailure()) return rc; + + for (int i = currentTailIndex + 1; i < newTailIndex; i++) { - foreach (IFile internalFile in internalFiles) - { - internalFile?.Dispose(); - } + rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.CreateFile(in internalFilePath, GetInternalFileSize(size, i), + CreateFileOptions.None); + if (rc.IsFailure()) return rc; + + using var newInternalFile = new UniqueRef(); + rc = _baseFileSystem.OpenFile(ref newInternalFile.Ref(), in internalFilePath, _mode); + if (rc.IsFailure()) return rc; + + _files.Add(newInternalFile.Release()); + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; } } - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - if (IsConcatenationFile(path)) + else { - return ResultFs.PathNotFound.Log(); - } - - using var baseDirectory = new UniqueRef(); - Result rc = _baseFileSystem.OpenDirectory(ref baseDirectory.Ref(), path, OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; - - using var concatDirectory = new UniqueRef( - new ConcatenationDirectory(mode, ref baseDirectory.Ref(), this, _baseFileSystem)); - rc = concatDirectory.Get.Initialize(in path); - if (rc.IsFailure()) return rc; - - outDirectory.Set(ref concatDirectory.Ref()); - return Result.Success; - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - CreateFileOptions newOption = option & ~CreateFileOptions.CreateConcatenationFile; - - // Create a normal file if the concatenation file flag isn't set - if (!option.HasFlag(CreateFileOptions.CreateConcatenationFile)) - { - return _baseFileSystem.CreateFile(path, size, newOption); - } - - using var parentPath = new Path(); - Result rc = GenerateParentPath(ref parentPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - if (IsConcatenationFile(in parentPath)) - { - // Cannot create a file inside of a concatenation file - return ResultFs.PathNotFound.Log(); - } - - rc = _baseFileSystem.CreateDirectory(in path, NxFileAttributes.Archive); - if (rc.IsFailure()) return rc; - - // Handle the empty file case by manually creating a single empty internal file - if (size == 0) - { - using var emptyFilePath = new Path(); - rc = GenerateInternalFilePath(ref emptyFilePath.Ref(), 0, in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.CreateFile(in emptyFilePath, 0, newOption); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - long remaining = size; - using var filePath = new Path(); - filePath.Initialize(in path); - if (rc.IsFailure()) return rc; - - for (int i = 0; remaining > 0; i++) - { - rc = AppendInternalFilePath(ref filePath.Ref(), i); - if (rc.IsFailure()) return rc; - - long fileSize = Math.Min(remaining, _InternalFileSize); - Result createInternalFileResult = _baseFileSystem.CreateFile(in filePath, fileSize, newOption); - - // If something goes wrong when creating an internal file, delete all the - // internal files we've created so far and delete the directory. - // This will allow results like insufficient space results to be returned properly. - if (createInternalFileResult.IsFailure()) + for (int i = currentTailIndex - 1; i > newTailIndex; i--) { - for (int index = i - 1; index >= 0; index--) - { - rc = GenerateInternalFilePath(ref filePath.Ref(), index, in path); - if (rc.IsFailure()) return rc; + _files[i].Dispose(); + _files.RemoveAt(i); - rc = _baseFileSystem.DeleteFile(in filePath); + rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); + if (rc.IsFailure()) return rc; - if (rc.IsFailure()) - break; - } + rc = _baseFileSystem.DeleteFile(in internalFilePath); + if (rc.IsFailure()) return rc; - _baseFileSystem.DeleteDirectoryRecursively(in path).IgnoreResult(); - return createInternalFileResult; + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; } - rc = filePath.RemoveChild(); - if (rc.IsFailure()) return rc; - - remaining -= fileSize; - } - - return Result.Success; - } - - protected override Result DoDeleteFile(in Path path) - { - if (!IsConcatenationFile(in path)) - { - return _baseFileSystem.DeleteFile(in path); - } - - Result rc = GetInternalFileCount(out int count, path); - if (rc.IsFailure()) return rc; - - using var filePath = new Path(); - rc = filePath.Initialize(in path); - if (rc.IsFailure()) return rc; - - for (int i = count - 1; i >= 0; i--) - { - rc = AppendInternalFilePath(ref filePath.Ref(), i); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.DeleteFile(in filePath); - if (rc.IsFailure()) return rc; - - rc = filePath.RemoveChild(); + rc = _files[newTailIndex].SetSize(GetInternalFileSize(size, newTailIndex)); if (rc.IsFailure()) return rc; } - rc = _baseFileSystem.DeleteDirectoryRecursively(in path); - if (rc.IsFailure()) return rc; - return Result.Success; } - protected override Result DoCreateDirectory(in Path path) - { - // Check if the parent path is a concatenation file because we can't create a directory inside one. - using var parentPath = new Path(); - Result rc = GenerateParentPath(ref parentPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - if (IsConcatenationFile(in parentPath)) - return ResultFs.PathNotFound.Log(); - - rc = _baseFileSystem.CreateDirectory(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoDeleteDirectory(in Path path) - { - // Make sure the directory isn't a concatenation file. - if (IsConcatenationFile(path)) - return ResultFs.PathNotFound.Log(); - - return _baseFileSystem.DeleteDirectory(path); - } - - private Result CleanDirectoryRecursivelyImpl(in Path path) - { - static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => - Result.Success; - - static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => - closure.SourceFileSystem.DeleteDirectory(in path); - - static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => - closure.SourceFileSystem.DeleteFile(in path); - - var closure = new FsIterationTaskClosure(); - closure.SourceFileSystem = this; - - var directoryEntry = new DirectoryEntry(); - return CleanupDirectoryRecursively(this, in path, ref directoryEntry, OnEnterDir, OnExitDir, OnFile, - ref closure); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - if (IsConcatenationFile(in path)) - return ResultFs.PathNotFound.Log(); - - Result rc = CleanDirectoryRecursivelyImpl(in path); - if (rc.IsFailure()) return rc; - - rc = _baseFileSystem.DeleteDirectory(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - if (IsConcatenationFile(in path)) - return ResultFs.PathNotFound.Log(); - - Result rc = CleanDirectoryRecursivelyImpl(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - if (IsConcatenationFile(in currentPath)) - { - return _baseFileSystem.RenameDirectory(in currentPath, in newPath); - } - - return _baseFileSystem.RenameFile(in currentPath, in newPath); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - if (IsConcatenationFile(in currentPath)) - return ResultFs.PathNotFound.Log(); - - return _baseFileSystem.RenameDirectory(in currentPath, in newPath); - } - - public Result GetFileSize(out long size, in Path path) + protected override Result DoGetSize(out long size) { UnsafeHelpers.SkipParamInit(out size); - using var internalFilePath = new Path(); - Result rc = internalFilePath.Initialize(in path); - if (rc.IsFailure()) return rc; + long totalSize = 0; - long sizeTotal = 0; - - for (int i = 0; ; i++) + foreach (IFile file in _files) { - rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); + Result rc = file.GetSize(out long internalFileSize); if (rc.IsFailure()) return rc; - rc = _baseFileSystem.GetFileSize(out long internalFileSize, in internalFilePath); - if (rc.IsFailure()) - { - // We've passed the last internal file of the concatenation file - // once the next internal file doesn't exist. - if (ResultFs.PathNotFound.Includes(rc)) - { - size = sizeTotal; - return Result.Success; - } + totalSize += internalFileSize; + } - return rc; - } + size = totalSize; + return Result.Success; + } - rc = internalFilePath.RemoveChild(); + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + if (operationId == OperationId.InvalidateCache) + { + if (!_mode.HasFlag(OpenMode.Read)) + return ResultFs.ReadUnpermitted.Log(); + + var closure = new OperateRangeClosure(); + closure.OutBuffer = outBuffer; + closure.InBuffer = inBuffer; + closure.OperationId = operationId; + + Result rc = DoOperateRangeImpl(offset, size, InvalidateCacheImpl, ref closure); + if (rc.IsFailure()) return rc; + } + else if (operationId == OperationId.QueryRange) + { + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + var closure = new OperateRangeClosure(); + closure.InBuffer = inBuffer; + closure.OperationId = operationId; + closure.InfoMerged.Clear(); + + Result rc = DoOperateRangeImpl(offset, size, QueryRangeImpl, ref closure); if (rc.IsFailure()) return rc; - sizeTotal += internalFileSize; + SpanHelpers.AsByteSpan(ref closure.InfoMerged).CopyTo(outBuffer); + } + else + { + return ResultFs.UnsupportedOperateRangeForConcatenationFile.Log(); + } + + return Result.Success; + + static Result InvalidateCacheImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) + { + return file.OperateRange(closure.OutBuffer, closure.OperationId, offset, size, closure.InBuffer); + } + + static Result QueryRangeImpl(IFile file, long offset, long size, ref OperateRangeClosure closure) + { + Unsafe.SkipInit(out QueryRangeInfo infoEntry); + + Result rc = file.OperateRange(SpanHelpers.AsByteSpan(ref infoEntry), closure.OperationId, offset, size, + closure.InBuffer); + if (rc.IsFailure()) return rc; + + closure.InfoMerged.Merge(in infoEntry); + return Result.Success; } } - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) + private Result DoOperateRangeImpl(long offset, long size, OperateRangeTask func, + ref OperateRangeClosure closure) { - if (queryId != QueryId.SetConcatenationFileAttribute) - return ResultFs.UnsupportedQueryEntryForConcatenationFileSystem.Log(); + if (offset < 0) + return ResultFs.OutOfRange.Log(); - return _baseFileSystem.SetFileAttributes(in path, NxFileAttributes.Archive); + Result rc = GetSize(out long currentSize); + if (rc.IsFailure()) return rc; + + if (offset > currentSize) + return ResultFs.OutOfRange.Log(); + + long currentOffset = offset; + long availableSize = currentSize - offset; + long remaining = Math.Min(size, availableSize); + + while (remaining > 0) + { + int fileIndex = GetInternalFileIndex(currentOffset); + long internalFileRemaining = _internalFileSize - GetInternalFileSize(currentOffset, fileIndex); + long internalFileOffset = currentOffset - _internalFileSize * fileIndex; + + long sizeToOperate = Math.Min(remaining, internalFileRemaining); + + Assert.SdkAssert(fileIndex < _files.Count); + + rc = func(_files[fileIndex], internalFileOffset, sizeToOperate, ref closure); + if (rc.IsFailure()) return rc; + + remaining -= sizeToOperate; + currentOffset += sizeToOperate; + } + + return Result.Success; } - protected override Result DoCommit() - { - return _baseFileSystem.Commit(); - } + private delegate Result OperateRangeTask(IFile file, long offset, long size, ref OperateRangeClosure closure); - protected override Result DoCommitProvisionally(long counter) + private ref struct OperateRangeClosure { - return _baseFileSystem.CommitProvisionally(counter); + public Span OutBuffer; + public ReadOnlySpan InBuffer; + public OperationId OperationId; + public QueryRangeInfo InfoMerged; } } + + private class ConcatenationDirectory : IDirectory + { + private OpenDirectoryMode _mode; + private UniqueRef _baseDirectory; + private Path.Stored _path; + private IFileSystem _baseFileSystem; + private ConcatenationFileSystem _concatenationFileSystem; + + public ConcatenationDirectory(OpenDirectoryMode mode, ref UniqueRef baseDirectory, + ConcatenationFileSystem concatFileSystem, IFileSystem baseFileSystem) + { + _mode = mode; + _baseDirectory = new UniqueRef(ref baseDirectory); + _baseFileSystem = baseFileSystem; + _concatenationFileSystem = concatFileSystem; + } + + public override void Dispose() + { + _path.Dispose(); + _baseDirectory.Destroy(); + + base.Dispose(); + } + + public Result Initialize(in Path path) + { + Result rc = _path.Initialize(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + UnsafeHelpers.SkipParamInit(out entriesRead); + + Unsafe.SkipInit(out DirectoryEntry entry); + int readCountTotal = 0; + + while (readCountTotal < entryBuffer.Length) + { + Result rc = _baseDirectory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); + if (rc.IsFailure()) return rc; + + if (readCount == 0) + break; + + if (!IsReadTarget(in entry)) + continue; + + if (IsConcatenationFileAttribute(entry.Attributes)) + { + entry.Type = DirectoryEntryType.File; + + if (!_mode.HasFlag(OpenDirectoryMode.NoFileSize)) + { + using var internalFilePath = new Path(); + rc = internalFilePath.Initialize(in _path); + if (rc.IsFailure()) return rc; + + rc = internalFilePath.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = _concatenationFileSystem.GetFileSize(out entry.Size, in internalFilePath); + if (rc.IsFailure()) return rc; + } + } + + entry.Attributes = NxFileAttributes.None; + entryBuffer[readCountTotal++] = entry; + } + + entriesRead = readCountTotal; + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + UnsafeHelpers.SkipParamInit(out entryCount); + + Unsafe.SkipInit(out DirectoryEntry entry); + using var directory = new UniqueRef(); + + using Path path = _path.DangerousGetPath(); + + Result rc = _baseFileSystem.OpenDirectory(ref directory.Ref(), in path, + OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize); + if (rc.IsFailure()) return rc; + + long entryCountTotal = 0; + + while (true) + { + directory.Get.Read(out long readCount, SpanHelpers.AsSpan(ref entry)); + if (rc.IsFailure()) return rc; + + if (readCount == 0) + break; + + if (IsReadTarget(in entry)) + entryCountTotal++; + } + + entryCount = entryCountTotal; + return Result.Success; + } + + private bool IsReadTarget(in DirectoryEntry entry) + { + bool hasConcatAttribute = IsConcatenationFileAttribute(entry.Attributes); + + return _mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || hasConcatAttribute) || + _mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !hasConcatAttribute; + } + } + + public static readonly long DefaultInternalFileSize = 0xFFFF0000; // Hard-coded value used by FS + + private IAttributeFileSystem _baseFileSystem; + private long _InternalFileSize; + + /// + /// Initializes a new with an internal file size of . + /// + /// The base for the + /// new . + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultInternalFileSize) { } + + /// + /// Initializes a new . + /// + /// The base for the + /// new . + /// The size of each internal file. Once a file exceeds this size, a new internal file will be created + public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long internalFileSize) + { + _baseFileSystem = baseFileSystem; + _InternalFileSize = internalFileSize; + } + + public override void Dispose() + { + _baseFileSystem?.Dispose(); + _baseFileSystem = null; + + base.Dispose(); + } + + private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + + private static Result AppendInternalFilePath(ref Path path, int index) + { + // Use an int as the buffer instead of a stackalloc byte[3] to workaround CS8350. + // Path.AppendChild will not save the span passed to it so this should be safe. + int bufferInt = 0; + Utf8Formatter.TryFormat(index, SpanHelpers.AsByteSpan(ref bufferInt), out _, new StandardFormat('d', 2)); + + return path.AppendChild(SpanHelpers.AsByteSpan(ref bufferInt)); + } + + private static Result GenerateInternalFilePath(ref Path outPath, int index, in Path basePath) + { + Result rc = outPath.Initialize(in basePath); + if (rc.IsFailure()) return rc; + + rc = AppendInternalFilePath(ref outPath, index); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private static Result GenerateParentPath(ref Path outParentPath, in Path path) + { + if (path == RootPath) + return ResultFs.PathNotFound.Log(); + + Result rc = outParentPath.Initialize(in path); + if (rc.IsFailure()) return rc; + + rc = outParentPath.RemoveChild(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private static bool IsConcatenationFileAttribute(NxFileAttributes attribute) + { + return attribute.HasFlag(NxFileAttributes.Directory | NxFileAttributes.Archive); + } + + private bool IsConcatenationFile(in Path path) + { + Result rc = _baseFileSystem.GetFileAttributes(out NxFileAttributes attribute, in path); + if (rc.IsFailure()) + return false; + + return IsConcatenationFileAttribute(attribute); + } + + private Result GetInternalFileCount(out int count, in Path path) + { + UnsafeHelpers.SkipParamInit(out count); + + using var internalFilePath = new Path(); + Result rc = internalFilePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = 0; ; i++) + { + rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.GetEntryType(out _, in internalFilePath); + if (rc.IsFailure()) + { + // We've passed the last internal file of the concatenation file + // once the next internal file doesn't exist. + if (ResultFs.PathNotFound.Includes(rc)) + { + count = i; + return Result.Success; + } + + return rc; + } + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + if (IsConcatenationFile(in path)) + { + entryType = DirectoryEntryType.File; + return Result.Success; + } + + return _baseFileSystem.GetEntryType(out entryType, path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.GetTotalSpaceSize(out totalSpace, path); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return _baseFileSystem.GetFileTimeStampRaw(out timeStamp, path); + } + + protected override Result DoFlush() + { + return _baseFileSystem.Flush(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + if (!IsConcatenationFile(in path)) + { + return _baseFileSystem.OpenFile(ref outFile, in path, mode); + } + + Result rc = GetInternalFileCount(out int fileCount, in path); + if (rc.IsFailure()) return rc; + + var internalFiles = new List(fileCount); + + using var filePath = new Path(); + filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + try + { + for (int i = 0; i < fileCount; i++) + { + rc = AppendInternalFilePath(ref filePath.Ref(), i); + if (rc.IsFailure()) return rc; + + using var internalFile = new UniqueRef(); + rc = _baseFileSystem.OpenFile(ref internalFile.Ref(), in filePath, mode); + if (rc.IsFailure()) return rc; + + internalFiles.Add(internalFile.Release()); + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + using var concatFile = new UniqueRef( + new ConcatenationFile(mode, ref internalFiles, _InternalFileSize, _baseFileSystem)); + + rc = concatFile.Get.Initialize(in path); + if (rc.IsFailure()) return rc; + + outFile.Set(ref concatFile.Ref()); + return Result.Success; + } + finally + { + if (internalFiles is not null) + { + foreach (IFile internalFile in internalFiles) + { + internalFile?.Dispose(); + } + } + } + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + if (IsConcatenationFile(path)) + { + return ResultFs.PathNotFound.Log(); + } + + using var baseDirectory = new UniqueRef(); + Result rc = _baseFileSystem.OpenDirectory(ref baseDirectory.Ref(), path, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + using var concatDirectory = new UniqueRef( + new ConcatenationDirectory(mode, ref baseDirectory.Ref(), this, _baseFileSystem)); + rc = concatDirectory.Get.Initialize(in path); + if (rc.IsFailure()) return rc; + + outDirectory.Set(ref concatDirectory.Ref()); + return Result.Success; + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + CreateFileOptions newOption = option & ~CreateFileOptions.CreateConcatenationFile; + + // Create a normal file if the concatenation file flag isn't set + if (!option.HasFlag(CreateFileOptions.CreateConcatenationFile)) + { + return _baseFileSystem.CreateFile(path, size, newOption); + } + + using var parentPath = new Path(); + Result rc = GenerateParentPath(ref parentPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + if (IsConcatenationFile(in parentPath)) + { + // Cannot create a file inside of a concatenation file + return ResultFs.PathNotFound.Log(); + } + + rc = _baseFileSystem.CreateDirectory(in path, NxFileAttributes.Archive); + if (rc.IsFailure()) return rc; + + // Handle the empty file case by manually creating a single empty internal file + if (size == 0) + { + using var emptyFilePath = new Path(); + rc = GenerateInternalFilePath(ref emptyFilePath.Ref(), 0, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.CreateFile(in emptyFilePath, 0, newOption); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + long remaining = size; + using var filePath = new Path(); + filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = 0; remaining > 0; i++) + { + rc = AppendInternalFilePath(ref filePath.Ref(), i); + if (rc.IsFailure()) return rc; + + long fileSize = Math.Min(remaining, _InternalFileSize); + Result createInternalFileResult = _baseFileSystem.CreateFile(in filePath, fileSize, newOption); + + // If something goes wrong when creating an internal file, delete all the + // internal files we've created so far and delete the directory. + // This will allow results like insufficient space results to be returned properly. + if (createInternalFileResult.IsFailure()) + { + for (int index = i - 1; index >= 0; index--) + { + rc = GenerateInternalFilePath(ref filePath.Ref(), index, in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteFile(in filePath); + + if (rc.IsFailure()) + break; + } + + _baseFileSystem.DeleteDirectoryRecursively(in path).IgnoreResult(); + return createInternalFileResult; + } + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + + remaining -= fileSize; + } + + return Result.Success; + } + + protected override Result DoDeleteFile(in Path path) + { + if (!IsConcatenationFile(in path)) + { + return _baseFileSystem.DeleteFile(in path); + } + + Result rc = GetInternalFileCount(out int count, path); + if (rc.IsFailure()) return rc; + + using var filePath = new Path(); + rc = filePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + for (int i = count - 1; i >= 0; i--) + { + rc = AppendInternalFilePath(ref filePath.Ref(), i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteFile(in filePath); + if (rc.IsFailure()) return rc; + + rc = filePath.RemoveChild(); + if (rc.IsFailure()) return rc; + } + + rc = _baseFileSystem.DeleteDirectoryRecursively(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) + { + // Check if the parent path is a concatenation file because we can't create a directory inside one. + using var parentPath = new Path(); + Result rc = GenerateParentPath(ref parentPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + if (IsConcatenationFile(in parentPath)) + return ResultFs.PathNotFound.Log(); + + rc = _baseFileSystem.CreateDirectory(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoDeleteDirectory(in Path path) + { + // Make sure the directory isn't a concatenation file. + if (IsConcatenationFile(path)) + return ResultFs.PathNotFound.Log(); + + return _baseFileSystem.DeleteDirectory(path); + } + + private Result CleanDirectoryRecursivelyImpl(in Path path) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + Result.Success; + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + closure.SourceFileSystem.DeleteDirectory(in path); + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + closure.SourceFileSystem.DeleteFile(in path); + + var closure = new FsIterationTaskClosure(); + closure.SourceFileSystem = this; + + var directoryEntry = new DirectoryEntry(); + return CleanupDirectoryRecursively(this, in path, ref directoryEntry, OnEnterDir, OnExitDir, OnFile, + ref closure); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + if (IsConcatenationFile(in path)) + return ResultFs.PathNotFound.Log(); + + Result rc = CleanDirectoryRecursivelyImpl(in path); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.DeleteDirectory(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + if (IsConcatenationFile(in path)) + return ResultFs.PathNotFound.Log(); + + Result rc = CleanDirectoryRecursivelyImpl(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + if (IsConcatenationFile(in currentPath)) + { + return _baseFileSystem.RenameDirectory(in currentPath, in newPath); + } + + return _baseFileSystem.RenameFile(in currentPath, in newPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + if (IsConcatenationFile(in currentPath)) + return ResultFs.PathNotFound.Log(); + + return _baseFileSystem.RenameDirectory(in currentPath, in newPath); + } + + public Result GetFileSize(out long size, in Path path) + { + UnsafeHelpers.SkipParamInit(out size); + + using var internalFilePath = new Path(); + Result rc = internalFilePath.Initialize(in path); + if (rc.IsFailure()) return rc; + + long sizeTotal = 0; + + for (int i = 0; ; i++) + { + rc = AppendInternalFilePath(ref internalFilePath.Ref(), i); + if (rc.IsFailure()) return rc; + + rc = _baseFileSystem.GetFileSize(out long internalFileSize, in internalFilePath); + if (rc.IsFailure()) + { + // We've passed the last internal file of the concatenation file + // once the next internal file doesn't exist. + if (ResultFs.PathNotFound.Includes(rc)) + { + size = sizeTotal; + return Result.Success; + } + + return rc; + } + + rc = internalFilePath.RemoveChild(); + if (rc.IsFailure()) return rc; + + sizeTotal += internalFileSize; + } + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + if (queryId != QueryId.SetConcatenationFileAttribute) + return ResultFs.UnsupportedQueryEntryForConcatenationFileSystem.Log(); + + return _baseFileSystem.SetFileAttributes(in path, NxFileAttributes.Archive); + } + + protected override Result DoCommit() + { + return _baseFileSystem.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + return _baseFileSystem.CommitProvisionally(counter); + } } diff --git a/src/LibHac/FsSystem/ConcatenationStorage.cs b/src/LibHac/FsSystem/ConcatenationStorage.cs index 1f9c639d..0cf86224 100644 --- a/src/LibHac/FsSystem/ConcatenationStorage.cs +++ b/src/LibHac/FsSystem/ConcatenationStorage.cs @@ -2,170 +2,169 @@ using System.Collections.Generic; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ConcatenationStorage : IStorage { - public class ConcatenationStorage : IStorage + private ConcatSource[] Sources { get; } + private long Length { get; } + private bool LeaveOpen { get; } + + public ConcatenationStorage(IList sources, bool leaveOpen) { - private ConcatSource[] Sources { get; } - private long Length { get; } - private bool LeaveOpen { get; } + Sources = new ConcatSource[sources.Count]; + LeaveOpen = leaveOpen; - public ConcatenationStorage(IList sources, bool leaveOpen) + long length = 0; + for (int i = 0; i < sources.Count; i++) { - Sources = new ConcatSource[sources.Count]; - LeaveOpen = leaveOpen; + sources[i].GetSize(out long sourceSize).ThrowIfFailure(); - long length = 0; - for (int i = 0; i < sources.Count; i++) - { - sources[i].GetSize(out long sourceSize).ThrowIfFailure(); - - if (sourceSize < 0) throw new ArgumentException("Sources must have an explicit length."); - Sources[i] = new ConcatSource(sources[i], length, sourceSize); - length += sourceSize; - } - - Length = length; + if (sourceSize < 0) throw new ArgumentException("Sources must have an explicit length."); + Sources[i] = new ConcatSource(sources[i], length, sourceSize); + length += sourceSize; } - protected override Result DoRead(long offset, Span destination) + Length = length; + } + + protected override Result DoRead(long offset, Span destination) + { + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; + + if (!CheckAccessRange(offset, destination.Length, Length)) + return ResultFs.OutOfRange.Log(); + + int sourceIndex = FindSource(inPos); + + while (remaining > 0) { - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; + ConcatSource entry = Sources[sourceIndex]; + long entryPos = inPos - entry.StartOffset; + long entryRemain = entry.StartOffset + entry.Size - inPos; - if (!CheckAccessRange(offset, destination.Length, Length)) - return ResultFs.OutOfRange.Log(); + int bytesToRead = (int)Math.Min(entryRemain, remaining); - int sourceIndex = FindSource(inPos); + Result rc = entry.Storage.Read(entryPos, destination.Slice(outPos, bytesToRead)); + if (rc.IsFailure()) return rc; - while (remaining > 0) - { - ConcatSource entry = Sources[sourceIndex]; - long entryPos = inPos - entry.StartOffset; - long entryRemain = entry.StartOffset + entry.Size - inPos; - - int bytesToRead = (int)Math.Min(entryRemain, remaining); - - Result rc = entry.Storage.Read(entryPos, destination.Slice(outPos, bytesToRead)); - if (rc.IsFailure()) return rc; - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; - sourceIndex++; - } - - return Result.Success; + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; + sourceIndex++; } - protected override Result DoWrite(long offset, ReadOnlySpan source) + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + if (!CheckAccessRange(offset, source.Length, Length)) + return ResultFs.OutOfRange.Log(); + + int sourceIndex = FindSource(inPos); + + while (remaining > 0) { - long inPos = offset; - int outPos = 0; - int remaining = source.Length; + ConcatSource entry = Sources[sourceIndex]; + long entryPos = inPos - entry.StartOffset; + long entryRemain = entry.StartOffset + entry.Size - inPos; - if (!CheckAccessRange(offset, source.Length, Length)) - return ResultFs.OutOfRange.Log(); + int bytesToWrite = (int)Math.Min(entryRemain, remaining); - int sourceIndex = FindSource(inPos); + Result rc = entry.Storage.Write(entryPos, source.Slice(outPos, bytesToWrite)); + if (rc.IsFailure()) return rc; - while (remaining > 0) - { - ConcatSource entry = Sources[sourceIndex]; - long entryPos = inPos - entry.StartOffset; - long entryRemain = entry.StartOffset + entry.Size - inPos; - - int bytesToWrite = (int)Math.Min(entryRemain, remaining); - - Result rc = entry.Storage.Write(entryPos, source.Slice(outPos, bytesToWrite)); - if (rc.IsFailure()) return rc; - - outPos += bytesToWrite; - inPos += bytesToWrite; - remaining -= bytesToWrite; - sourceIndex++; - } - - return Result.Success; + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; + sourceIndex++; } - protected override Result DoFlush() + return Result.Success; + } + + protected override Result DoFlush() + { + foreach (ConcatSource source in Sources) + { + Result rc = source.Storage.Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen && Sources != null) { foreach (ConcatSource source in Sources) { - Result rc = source.Storage.Flush(); - if (rc.IsFailure()) return rc; + source?.Storage?.Dispose(); } - - return Result.Success; } - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } + base.Dispose(); + } - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } + private int FindSource(long offset) + { + if (offset < 0 || offset >= Length) + throw new ArgumentOutOfRangeException(nameof(offset), offset, "The Storage does not contain this offset."); - public override void Dispose() + int lo = 0; + int hi = Sources.Length - 1; + + while (lo <= hi) { - if (!LeaveOpen && Sources != null) + int mid = lo + ((hi - lo) >> 1); + + long val = Sources[mid].StartOffset; + + if (val == offset) return mid; + + if (val < offset) { - foreach (ConcatSource source in Sources) - { - source?.Storage?.Dispose(); - } + lo = mid + 1; + } + else + { + hi = mid - 1; } - - base.Dispose(); } - private int FindSource(long offset) + return lo - 1; + } + + private class ConcatSource + { + public IStorage Storage { get; } + public long StartOffset { get; } + public long Size { get; } + + public ConcatSource(IStorage storage, long startOffset, long length) { - 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) - { - 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; - } - } - - return lo - 1; - } - - private class ConcatSource - { - public IStorage Storage { get; } - public long StartOffset { get; } - public long Size { get; } - - public ConcatSource(IStorage storage, long startOffset, long length) - { - Storage = storage; - StartOffset = startOffset; - Size = length; - } + Storage = storage; + StartOffset = startOffset; + Size = length; } } } diff --git a/src/LibHac/FsSystem/ConcatenationStorageBuilder.cs b/src/LibHac/FsSystem/ConcatenationStorageBuilder.cs index afe464bd..fa606aa2 100644 --- a/src/LibHac/FsSystem/ConcatenationStorageBuilder.cs +++ b/src/LibHac/FsSystem/ConcatenationStorageBuilder.cs @@ -3,64 +3,63 @@ using System.IO; using System.Linq; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ConcatenationStorageBuilder { - public class ConcatenationStorageBuilder + private List Segments { get; } + + public ConcatenationStorageBuilder() { - private List Segments { get; } + Segments = new List(); + } - public ConcatenationStorageBuilder() + public ConcatenationStorageBuilder(IEnumerable segments) + { + Segments = segments.ToList(); + } + + public void Add(IStorage storage, long offset) + { + Segments.Add(new ConcatenationStorageSegment(storage, offset)); + } + + public ConcatenationStorage Build() + { + List segments = Segments.OrderBy(x => x.Offset).ToList(); + var sources = new List(); + + long offset = 0; + + foreach (ConcatenationStorageSegment segment in segments) { - Segments = new List(); - } + long paddingNeeded = segment.Offset - offset; - public ConcatenationStorageBuilder(IEnumerable segments) - { - Segments = segments.ToList(); - } + if (paddingNeeded < 0) throw new InvalidDataException("Builder has segments that overlap."); - public void Add(IStorage storage, long offset) - { - Segments.Add(new ConcatenationStorageSegment(storage, offset)); - } - - public ConcatenationStorage Build() - { - List segments = Segments.OrderBy(x => x.Offset).ToList(); - var sources = new List(); - - long offset = 0; - - foreach (ConcatenationStorageSegment segment in segments) + if (paddingNeeded > 0) { - long paddingNeeded = segment.Offset - offset; - - if (paddingNeeded < 0) throw new InvalidDataException("Builder has segments that overlap."); - - if (paddingNeeded > 0) - { - sources.Add(new NullStorage(paddingNeeded)); - } - - segment.Storage.GetSize(out long segmentSize).ThrowIfFailure(); - - sources.Add(segment.Storage); - offset = segment.Offset + segmentSize; + sources.Add(new NullStorage(paddingNeeded)); } - return new ConcatenationStorage(sources, true); - } - } + segment.Storage.GetSize(out long segmentSize).ThrowIfFailure(); - public class ConcatenationStorageSegment - { - public IStorage Storage { get; } - public long Offset { get; } - - public ConcatenationStorageSegment(IStorage storage, long offset) - { - Storage = storage; - Offset = offset; + sources.Add(segment.Storage); + offset = segment.Offset + segmentSize; } + + return new ConcatenationStorage(sources, true); + } +} + +public class ConcatenationStorageSegment +{ + public IStorage Storage { get; } + public long Offset { get; } + + public ConcatenationStorageSegment(IStorage storage, long offset) + { + Storage = storage; + Offset = offset; } } diff --git a/src/LibHac/FsSystem/Delta.cs b/src/LibHac/FsSystem/Delta.cs index f8bc168a..5150120a 100644 --- a/src/LibHac/FsSystem/Delta.cs +++ b/src/LibHac/FsSystem/Delta.cs @@ -4,161 +4,160 @@ using System.IO; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class Delta { - public class Delta + private const string Ndv0Magic = "NDV0"; + private IStorage OriginalStorage { get; set; } + private IStorage DeltaStorage { get; } + public DeltaHeader Header { get; } + private List Segments { get; } = new List(); + + public Delta(IStorage deltaStorage, IStorage originalData) : this(deltaStorage) { - private const string Ndv0Magic = "NDV0"; - private IStorage OriginalStorage { get; set; } - private IStorage DeltaStorage { get; } - public DeltaHeader Header { get; } - private List Segments { get; } = new List(); + SetBaseStorage(originalData); + } - public Delta(IStorage deltaStorage, IStorage originalData) : this(deltaStorage) + public Delta(IStorage deltaStorage) + { + DeltaStorage = deltaStorage; + deltaStorage.GetSize(out long deltaSize).ThrowIfFailure(); + + if (deltaSize < 0x40) throw new InvalidDataException("Delta file is too small."); + + Header = new DeltaHeader(deltaStorage.AsFile(OpenMode.Read)); + + if (Header.Magic != Ndv0Magic) throw new InvalidDataException("NDV0 magic value is missing."); + + long fragmentSize = Header.HeaderSize + Header.BodySize; + if (deltaSize < fragmentSize) { - SetBaseStorage(originalData); + throw new InvalidDataException($"Delta file is smaller than the header indicates. (0x{fragmentSize} bytes)"); } - public Delta(IStorage deltaStorage) + ParseDeltaStructure(); + } + + public void SetBaseStorage(IStorage baseStorage) + { + OriginalStorage = baseStorage; + baseStorage.GetSize(out long storageSize).ThrowIfFailure(); + + if (storageSize != Header.OriginalSize) { - DeltaStorage = deltaStorage; - deltaStorage.GetSize(out long deltaSize).ThrowIfFailure(); - - if (deltaSize < 0x40) throw new InvalidDataException("Delta file is too small."); - - Header = new DeltaHeader(deltaStorage.AsFile(OpenMode.Read)); - - if (Header.Magic != Ndv0Magic) throw new InvalidDataException("NDV0 magic value is missing."); - - long fragmentSize = Header.HeaderSize + Header.BodySize; - if (deltaSize < fragmentSize) - { - throw new InvalidDataException($"Delta file is smaller than the header indicates. (0x{fragmentSize} bytes)"); - } - - ParseDeltaStructure(); - } - - public void SetBaseStorage(IStorage baseStorage) - { - OriginalStorage = baseStorage; - baseStorage.GetSize(out long storageSize).ThrowIfFailure(); - - if (storageSize != Header.OriginalSize) - { - throw new InvalidDataException($"Original file size does not match the size in the delta header. (0x{Header.OriginalSize} bytes)"); - } - } - - public IStorage GetPatchedStorage() - { - if (OriginalStorage == null) throw new InvalidOperationException("Cannot apply a delta patch without a base file."); - - var storages = new List(); - - foreach (DeltaSegment segment in Segments) - { - IStorage source = segment.IsInOriginal ? OriginalStorage : DeltaStorage; - - // todo Do this without tons of SubStorages - IStorage sub = source.Slice(segment.SourceOffset, segment.Size); - - storages.Add(sub); - } - - return new ConcatenationStorage(storages, true); - } - - private void ParseDeltaStructure() - { - var reader = new FileReader(DeltaStorage.AsFile(OpenMode.Read)); - - reader.Position = Header.HeaderSize; - - long offset = 0; - - while (offset < Header.NewSize) - { - ReadSegmentHeader(reader, out int size, out int seek); - - if (seek > 0) - { - var segment = new DeltaSegment() - { - SourceOffset = offset, - Size = seek, - IsInOriginal = true - }; - - Segments.Add(segment); - offset += seek; - } - - if (size > 0) - { - var segment = new DeltaSegment() - { - SourceOffset = reader.Position, - Size = size, - IsInOriginal = false - }; - - Segments.Add(segment); - offset += size; - } - - reader.Position += size; - } - } - - private static void ReadSegmentHeader(FileReader reader, out int size, out int seek) - { - byte type = reader.ReadUInt8(); - - int seekBytes = (type & 3) + 1; - int sizeBytes = ((type >> 3) & 3) + 1; - - size = ReadInt(reader, sizeBytes); - seek = ReadInt(reader, seekBytes); - } - - private static int ReadInt(FileReader reader, int bytes) - { - switch (bytes) - { - case 1: return reader.ReadUInt8(); - case 2: return reader.ReadUInt16(); - case 3: return reader.ReadUInt24(); - case 4: return reader.ReadInt32(); - default: return 0; - } + throw new InvalidDataException($"Original file size does not match the size in the delta header. (0x{Header.OriginalSize} bytes)"); } } - internal class DeltaSegment + public IStorage GetPatchedStorage() { - public long SourceOffset { get; set; } - public int Size { get; set; } - public bool IsInOriginal { get; set; } + if (OriginalStorage == null) throw new InvalidOperationException("Cannot apply a delta patch without a base file."); + + var storages = new List(); + + foreach (DeltaSegment segment in Segments) + { + IStorage source = segment.IsInOriginal ? OriginalStorage : DeltaStorage; + + // todo Do this without tons of SubStorages + IStorage sub = source.Slice(segment.SourceOffset, segment.Size); + + storages.Add(sub); + } + + return new ConcatenationStorage(storages, true); } - public class DeltaHeader + private void ParseDeltaStructure() { - public string Magic { get; } - public long OriginalSize { get; } - public long NewSize { get; } - public long HeaderSize { get; } - public long BodySize { get; } + var reader = new FileReader(DeltaStorage.AsFile(OpenMode.Read)); - public DeltaHeader(IFile header) + reader.Position = Header.HeaderSize; + + long offset = 0; + + while (offset < Header.NewSize) { - var reader = new FileReader(header); + ReadSegmentHeader(reader, out int size, out int seek); - Magic = reader.ReadAscii(4); - OriginalSize = reader.ReadInt64(8); - NewSize = reader.ReadInt64(); - HeaderSize = reader.ReadInt64(); - BodySize = reader.ReadInt64(); + if (seek > 0) + { + var segment = new DeltaSegment() + { + SourceOffset = offset, + Size = seek, + IsInOriginal = true + }; + + Segments.Add(segment); + offset += seek; + } + + if (size > 0) + { + var segment = new DeltaSegment() + { + SourceOffset = reader.Position, + Size = size, + IsInOriginal = false + }; + + Segments.Add(segment); + offset += size; + } + + reader.Position += size; + } + } + + private static void ReadSegmentHeader(FileReader reader, out int size, out int seek) + { + byte type = reader.ReadUInt8(); + + int seekBytes = (type & 3) + 1; + int sizeBytes = ((type >> 3) & 3) + 1; + + size = ReadInt(reader, sizeBytes); + seek = ReadInt(reader, seekBytes); + } + + private static int ReadInt(FileReader reader, int bytes) + { + switch (bytes) + { + case 1: return reader.ReadUInt8(); + case 2: return reader.ReadUInt16(); + case 3: return reader.ReadUInt24(); + case 4: return reader.ReadInt32(); + default: return 0; } } } + +internal class DeltaSegment +{ + public long SourceOffset { get; set; } + public int Size { get; set; } + public bool IsInOriginal { get; set; } +} + +public class DeltaHeader +{ + public string Magic { get; } + public long OriginalSize { get; } + public long NewSize { get; } + public long HeaderSize { get; } + public long BodySize { get; } + + public DeltaHeader(IFile header) + { + var reader = new FileReader(header); + + Magic = reader.ReadAscii(4); + OriginalSize = reader.ReadInt64(8); + NewSize = reader.ReadInt64(); + HeaderSize = reader.ReadInt64(); + BodySize = reader.ReadInt64(); + } +} diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index 1988f951..8ba03e08 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -7,1006 +7,1005 @@ using LibHac.Fs.Fsa; using LibHac.FsSrv; using LibHac.Os; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +internal struct DirectorySaveDataFileSystemGlobals { - internal struct DirectorySaveDataFileSystemGlobals - { - public SdkMutexType SynchronizeDirectoryMutex; + public SdkMutexType SynchronizeDirectoryMutex; - public void Initialize(FileSystemClient fsClient) - { - SynchronizeDirectoryMutex.Initialize(); - } + public void Initialize(FileSystemClient fsClient) + { + SynchronizeDirectoryMutex.Initialize(); } +} - /// - /// An that provides transactional commits for savedata on top of another base IFileSystem. - /// - /// - /// Transactional commits should be atomic as long as the function of the - /// underlying is atomic. - /// Based on FS 12.1.0 (nnSdk 12.3.1) - /// - public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor +/// +/// An that provides transactional commits for savedata on top of another base IFileSystem. +/// +/// +/// Transactional commits should be atomic as long as the function of the +/// underlying is atomic. +/// Based on FS 12.1.0 (nnSdk 12.3.1) +/// +public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor +{ + private const int IdealWorkBufferSize = 0x100000; // 1 MiB + + private static ReadOnlySpan CommittedDirectoryName => new[] { (byte)'/', (byte)'0' }; + private static ReadOnlySpan ModifiedDirectoryName => new[] { (byte)'/', (byte)'1' }; + private static ReadOnlySpan SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' }; + private static ReadOnlySpan LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; + + private FileSystemClient _fsClient; + private IFileSystem _baseFs; + private SdkMutexType _mutex; + private UniqueRef _uniqueBaseFs; + + private int _openWritableFileCount; + private bool _isJournalingSupported; + private bool _isMultiCommitSupported; + private bool _isJournalingEnabled; + + // Additions to support extra data + private ISaveDataCommitTimeStampGetter _timeStampGetter; + private RandomDataGenerator _randomGenerator; + + // Additions to support caching + private ISaveDataExtraDataAccessorCacheObserver _cacheObserver; + private SaveDataSpaceId _spaceId; + private ulong _saveDataId; + + // Additions to ensure only one directory save data fs is opened at a time + private UniqueRef _lockFile; + + private class DirectorySaveDataFile : IFile { - private const int IdealWorkBufferSize = 0x100000; // 1 MiB + private UniqueRef _baseFile; + private DirectorySaveDataFileSystem _parentFs; + private OpenMode _mode; - private static ReadOnlySpan CommittedDirectoryName => new[] { (byte)'/', (byte)'0' }; - private static ReadOnlySpan ModifiedDirectoryName => new[] { (byte)'/', (byte)'1' }; - private static ReadOnlySpan SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' }; - private static ReadOnlySpan LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; - - private FileSystemClient _fsClient; - private IFileSystem _baseFs; - private SdkMutexType _mutex; - private UniqueRef _uniqueBaseFs; - - private int _openWritableFileCount; - private bool _isJournalingSupported; - private bool _isMultiCommitSupported; - private bool _isJournalingEnabled; - - // Additions to support extra data - private ISaveDataCommitTimeStampGetter _timeStampGetter; - private RandomDataGenerator _randomGenerator; - - // Additions to support caching - private ISaveDataExtraDataAccessorCacheObserver _cacheObserver; - private SaveDataSpaceId _spaceId; - private ulong _saveDataId; - - // Additions to ensure only one directory save data fs is opened at a time - private UniqueRef _lockFile; - - private class DirectorySaveDataFile : IFile + public DirectorySaveDataFile(ref UniqueRef baseFile, DirectorySaveDataFileSystem parentFs, OpenMode mode) { - private UniqueRef _baseFile; - private DirectorySaveDataFileSystem _parentFs; - private OpenMode _mode; - - public DirectorySaveDataFile(ref UniqueRef baseFile, DirectorySaveDataFileSystem parentFs, OpenMode mode) - { - _baseFile = new UniqueRef(ref baseFile); - _parentFs = parentFs; - _mode = mode; - } - - public override void Dispose() - { - _baseFile.Destroy(); - - if (_mode.HasFlag(OpenMode.Write)) - { - _parentFs.DecrementWriteOpenFileCount(); - _mode = default; - } - - base.Dispose(); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - return _baseFile.Get.Read(out bytesRead, offset, destination, in option); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return _baseFile.Get.Write(offset, source, in option); - } - - protected override Result DoFlush() - { - return _baseFile.Get.Flush(); - } - - protected override Result DoGetSize(out long size) - { - return _baseFile.Get.GetSize(out size); - } - - protected override Result DoSetSize(long size) - { - return _baseFile.Get.SetSize(size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, - long size, ReadOnlySpan inBuffer) - { - return _baseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } - } - - /// - /// Create an uninitialized . - /// - /// The base to use. - public DirectorySaveDataFileSystem(IFileSystem baseFileSystem) - { - _baseFs = baseFileSystem; - _mutex.Initialize(); - } - - /// - /// Create an uninitialized . - /// - /// The base to use. - public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem) - { - _baseFs = baseFileSystem.Get; - _mutex.Initialize(); - _uniqueBaseFs = new UniqueRef(ref baseFileSystem); - } - - /// - /// Create an uninitialized . - /// If a is provided a global mutex will be used when synchronizing directories. - /// Running outside of a Horizon context doesn't require this mutex, - /// and null can be passed to . - /// - /// The base to use. - /// The to use. May be null. - public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient) - { - _baseFs = baseFileSystem; - _mutex.Initialize(); - _fsClient = fsClient; - } - - /// - /// Create an uninitialized . - /// If a is provided a global mutex will be used when synchronizing directories. - /// Running outside of a Horizon context doesn't require this mutex, - /// and null can be passed to . - /// - /// The base to use. - /// The to use. May be null. - public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem, FileSystemClient fsClient) - { - _baseFs = baseFileSystem.Get; - _mutex.Initialize(); - _uniqueBaseFs = new UniqueRef(ref baseFileSystem); - _fsClient = fsClient; + _baseFile = new UniqueRef(ref baseFile); + _parentFs = parentFs; + _mode = mode; } public override void Dispose() { - _lockFile.Destroy(); + _baseFile.Destroy(); + + if (_mode.HasFlag(OpenMode.Write)) + { + _parentFs.DecrementWriteOpenFileCount(); + _mode = default; + } - _cacheObserver?.Unregister(_spaceId, _saveDataId); - _uniqueBaseFs.Destroy(); base.Dispose(); } - [NonCopyable] - private ref struct RetryClosure + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) { - public DirectorySaveDataFileSystem This; - public Path CommittedPath; - public Path ModifiedPath; - public Path SynchronizingPath; - - public void Dispose() - { - CommittedPath.Dispose(); - ModifiedPath.Dispose(); - SynchronizingPath.Dispose(); - } + return _baseFile.Get.Read(out bytesRead, offset, destination, in option); } - private delegate Result RetryDelegate(in RetryClosure closure); - - private Result RetryFinitelyForTargetLocked(RetryDelegate function, in RetryClosure closure) + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) { - const int maxRetryCount = 10; - const int retryWaitTimeMs = 100; - - int remainingRetries = maxRetryCount; - - while (true) - { - Result rc = function(in closure); - - if (rc.IsSuccess()) - return rc; - - if (!ResultFs.TargetLocked.Includes(rc)) - return rc; - - if (remainingRetries <= 0) - return rc; - - remainingRetries--; - - if (_fsClient is not null) - { - _fsClient.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryWaitTimeMs)); - } - else - { - System.Threading.Thread.Sleep(retryWaitTimeMs); - } - } + return _baseFile.Get.Write(offset, source, in option); } - public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + protected override Result DoFlush() { - return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled); + return _baseFile.Get.Flush(); } - public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, - bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + protected override Result DoGetSize(out long size) { - _isJournalingSupported = isJournalingSupported; - _isMultiCommitSupported = isMultiCommitSupported; - _isJournalingEnabled = isJournalingEnabled; - _timeStampGetter = timeStampGetter ?? _timeStampGetter; - _randomGenerator = randomGenerator ?? _randomGenerator; - - // Open the lock file - Result rc = GetFileSystemLock(); - if (rc.IsFailure()) return rc; - - using var pathModifiedDirectory = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); - if (rc.IsFailure()) return rc; - - using var pathCommittedDirectory = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathCommittedDirectory.Ref(), CommittedDirectoryName); - if (rc.IsFailure()) return rc; - - using var pathSynchronizingDirectory = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingDirectory.Ref(), SynchronizingDirectoryName); - if (rc.IsFailure()) return rc; - - // Ensure the working directory exists - rc = _baseFs.GetEntryType(out _, in pathModifiedDirectory); - - if (rc.IsFailure()) - { - if (!ResultFs.PathNotFound.Includes(rc)) - return rc; - - rc = _baseFs.CreateDirectory(in pathModifiedDirectory); - if (rc.IsFailure()) return rc; - - if (_isJournalingSupported) - { - rc = _baseFs.CreateDirectory(in pathCommittedDirectory); - - // Nintendo returns on all failures, but we'll keep going if committed already exists - // to avoid confusing people manually creating savedata in emulators - if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) - return rc; - } - } - - // Only the working directory is needed for non-journaling savedata - if (_isJournalingSupported) - { - rc = _baseFs.GetEntryType(out _, in pathCommittedDirectory); - - if (rc.IsSuccess()) - { - // The previous commit successfully completed. Copy the committed dir to the working dir. - if (_isJournalingEnabled) - { - rc = SynchronizeDirectory(in pathModifiedDirectory, in pathCommittedDirectory); - if (rc.IsFailure()) return rc; - } - } - else if (ResultFs.PathNotFound.Includes(rc)) - { - // If a previous commit failed, the committed dir may be missing. - // Finish that commit by copying the working dir to the committed dir - rc = SynchronizeDirectory(in pathSynchronizingDirectory, in pathModifiedDirectory); - if (rc.IsFailure()) return rc; - - rc = _baseFs.RenameDirectory(in pathSynchronizingDirectory, in pathCommittedDirectory); - if (rc.IsFailure()) return rc; - } - else - { - return rc; - } - } - - rc = InitializeExtraData(); - if (rc.IsFailure()) return rc; - - return Result.Success; + return _baseFile.Get.GetSize(out size); } - private Result GetFileSystemLock() + protected override Result DoSetSize(long size) { - // Having an open lock file means we already have the lock for the file system. - if (_lockFile.HasValue) - return Result.Success; - - using var pathLockFile = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName); - if (rc.IsFailure()) return rc; - - rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); - - if (rc.IsFailure()) - { - if (ResultFs.PathNotFound.Includes(rc)) - { - rc = _baseFs.CreateFile(in pathLockFile, 0); - if (rc.IsFailure()) return rc; - - rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - } - else - { - return rc; - } - } - - return Result.Success; + return _baseFile.Get.SetSize(size); } - private Result ResolvePath(ref Path outFullPath, in Path path) + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, + long size, ReadOnlySpan inBuffer) { - using var pathDirectoryName = new Path(); - - // Use the committed directory directly if journaling is supported but not enabled - ReadOnlySpan directoryName = _isJournalingSupported && !_isJournalingEnabled - ? CommittedDirectoryName - : ModifiedDirectoryName; - - Result rc = PathFunctions.SetUpFixedPath(ref pathDirectoryName.Ref(), directoryName); - if (rc.IsFailure()) return rc; - - rc = outFullPath.Combine(in pathDirectoryName, in path); - if (rc.IsFailure()) return rc; - - return Result.Success; + return _baseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); } + } - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + /// + /// Create an uninitialized . + /// + /// The base to use. + public DirectorySaveDataFileSystem(IFileSystem baseFileSystem) + { + _baseFs = baseFileSystem; + _mutex.Initialize(); + } + + /// + /// Create an uninitialized . + /// + /// The base to use. + public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem) + { + _baseFs = baseFileSystem.Get; + _mutex.Initialize(); + _uniqueBaseFs = new UniqueRef(ref baseFileSystem); + } + + /// + /// Create an uninitialized . + /// If a is provided a global mutex will be used when synchronizing directories. + /// Running outside of a Horizon context doesn't require this mutex, + /// and null can be passed to . + /// + /// The base to use. + /// The to use. May be null. + public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient) + { + _baseFs = baseFileSystem; + _mutex.Initialize(); + _fsClient = fsClient; + } + + /// + /// Create an uninitialized . + /// If a is provided a global mutex will be used when synchronizing directories. + /// Running outside of a Horizon context doesn't require this mutex, + /// and null can be passed to . + /// + /// The base to use. + /// The to use. May be null. + public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem, FileSystemClient fsClient) + { + _baseFs = baseFileSystem.Get; + _mutex.Initialize(); + _uniqueBaseFs = new UniqueRef(ref baseFileSystem); + _fsClient = fsClient; + } + + public override void Dispose() + { + _lockFile.Destroy(); + + _cacheObserver?.Unregister(_spaceId, _saveDataId); + _uniqueBaseFs.Destroy(); + base.Dispose(); + } + + [NonCopyable] + private ref struct RetryClosure + { + public DirectorySaveDataFileSystem This; + public Path CommittedPath; + public Path ModifiedPath; + public Path SynchronizingPath; + + public void Dispose() { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.CreateFile(in fullPath, size, option); - if (rc.IsFailure()) return rc; - - return Result.Success; + CommittedPath.Dispose(); + ModifiedPath.Dispose(); + SynchronizingPath.Dispose(); } + } - protected override Result DoDeleteFile(in Path path) + private delegate Result RetryDelegate(in RetryClosure closure); + + private Result RetryFinitelyForTargetLocked(RetryDelegate function, in RetryClosure closure) + { + const int maxRetryCount = 10; + const int retryWaitTimeMs = 100; + + int remainingRetries = maxRetryCount; + + while (true) { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + Result rc = function(in closure); - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + if (rc.IsSuccess()) + return rc; - rc = _baseFs.DeleteFile(in fullPath); - if (rc.IsFailure()) return rc; + if (!ResultFs.TargetLocked.Includes(rc)) + return rc; - return Result.Success; - } + if (remainingRetries <= 0) + return rc; - protected override Result DoCreateDirectory(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + remainingRetries--; - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.CreateDirectory(in fullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoDeleteDirectory(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.DeleteDirectory(in fullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.DeleteDirectoryRecursively(in fullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.CleanDirectoryRecursively(in fullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - using var currentFullPath = new Path(); - using var newFullPath = new Path(); - - Result rc = ResolvePath(ref currentFullPath.Ref(), in currentPath); - if (rc.IsFailure()) return rc; - - rc = ResolvePath(ref newFullPath.Ref(), in newPath); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.RenameFile(in currentFullPath, in newFullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - using var currentFullPath = new Path(); - using var newFullPath = new Path(); - - Result rc = ResolvePath(ref currentFullPath.Ref(), in currentPath); - if (rc.IsFailure()) return rc; - - rc = ResolvePath(ref newFullPath.Ref(), in newPath); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.RenameDirectory(in currentFullPath, in newFullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.GetEntryType(out entryType, in fullPath); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - using var baseFile = new UniqueRef(); - rc = _baseFs.OpenFile(ref baseFile.Ref(), in fullPath, mode); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(new DirectorySaveDataFile(ref baseFile.Ref(), this, mode)); - - if (mode.HasFlag(OpenMode.Write)) - { - _openWritableFileCount++; - } - - outFile.Set(ref file.Ref()); - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var fullPath = new Path(); - Result rc = ResolvePath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - rc = _baseFs.OpenDirectory(ref outDirectory, in fullPath, mode); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - /// - /// Creates the destination directory if needed and copies the source directory to it. - /// - /// The path of the destination directory. - /// The path of the source directory. - /// The of the operation. - private Result SynchronizeDirectory(in Path destPath, in Path sourcePath) - { - // Delete destination dir and recreate it. - Result rc = _baseFs.DeleteDirectoryRecursively(destPath); - - // Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations - // where a PathNotFound error would mean the save directory was in an invalid state. - // We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally - // put the save directory in an invalid state. - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; - - rc = _baseFs.CreateDirectory(destPath); - if (rc.IsFailure()) return rc; - - var directoryEntry = new DirectoryEntry(); - - // Lock only if initialized with a client if (_fsClient is not null) { - using ScopedLock scopedLock = - ScopedLock.Lock(ref _fsClient.Globals.DirectorySaveDataFileSystem.SynchronizeDirectoryMutex); - - using (var buffer = new RentedArray(IdealWorkBufferSize)) - { - return Utility.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, - buffer.Span); - } + _fsClient.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryWaitTimeMs)); } else { - using (var buffer = new RentedArray(IdealWorkBufferSize)) - { - return Utility.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, - buffer.Span); - } + System.Threading.Thread.Sleep(retryWaitTimeMs); } } + } - protected override Result DoCommit() + public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + { + return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled); + } + + public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, + bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + { + _isJournalingSupported = isJournalingSupported; + _isMultiCommitSupported = isMultiCommitSupported; + _isJournalingEnabled = isJournalingEnabled; + _timeStampGetter = timeStampGetter ?? _timeStampGetter; + _randomGenerator = randomGenerator ?? _randomGenerator; + + // Open the lock file + Result rc = GetFileSystemLock(); + if (rc.IsFailure()) return rc; + + using var pathModifiedDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); + if (rc.IsFailure()) return rc; + + using var pathCommittedDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathCommittedDirectory.Ref(), CommittedDirectoryName); + if (rc.IsFailure()) return rc; + + using var pathSynchronizingDirectory = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingDirectory.Ref(), SynchronizingDirectoryName); + if (rc.IsFailure()) return rc; + + // Ensure the working directory exists + rc = _baseFs.GetEntryType(out _, in pathModifiedDirectory); + + if (rc.IsFailure()) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; - if (!_isJournalingEnabled || !_isJournalingSupported) - return Result.Success; - - var closure = new RetryClosure(); - closure.This = this; - - Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName); + rc = _baseFs.CreateDirectory(in pathModifiedDirectory); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedDirectoryName); - if (rc.IsFailure()) return rc; - - rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingDirectoryName); - if (rc.IsFailure()) return rc; - - if (_openWritableFileCount > 0) - { - // All files must be closed before commiting save data. - return ResultFs.WriteModeFileNotClosed.Log(); - } - - static Result RenameCommittedDir(in RetryClosure closure) - { - return closure.This._baseFs.RenameDirectory(in closure.CommittedPath, - in closure.SynchronizingPath); - } - - static Result SynchronizeWorkingDir(in RetryClosure closure) - { - return closure.This.SynchronizeDirectory(in closure.SynchronizingPath, - in closure.ModifiedPath); - } - - static Result RenameSynchronizingDir(in RetryClosure closure) - { - return closure.This._baseFs.RenameDirectory(in closure.SynchronizingPath, - in closure.CommittedPath); - } - - // Get rid of the previous commit by renaming the folder. - rc = RetryFinitelyForTargetLocked(RenameCommittedDir, in closure); - if (rc.IsFailure()) return rc; - - // If something goes wrong beyond this point, the commit will be - // completed the next time the savedata is opened. - - rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir, in closure); - if (rc.IsFailure()) return rc; - - rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir, in closure); - if (rc.IsFailure()) return rc; - - closure.Dispose(); - return Result.Success; - } - - protected override Result DoCommitProvisionally(long counter) - { - if (!_isMultiCommitSupported) - return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log(); - - return Result.Success; - } - - protected override Result DoRollback() - { - // No old data is kept for non-journaling save data, so there's nothing to rollback to - if (!_isJournalingSupported) - return Result.Success; - - return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - using var pathModifiedDirectory = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); - if (rc.IsFailure()) return rc; - - rc = _baseFs.GetFreeSpaceSize(out freeSpace, in pathModifiedDirectory); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - using var pathModifiedDirectory = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); - if (rc.IsFailure()) return rc; - - rc = _baseFs.GetTotalSpaceSize(out totalSpace, in pathModifiedDirectory); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private void DecrementWriteOpenFileCount() - { - // Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile - // will try to lock this mutex a second time - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - _openWritableFileCount--; - } - - // The original class doesn't support extra data. - // Everything below this point is a LibHac extension. - - private static ReadOnlySpan CommittedExtraDataName => // "/ExtraData0" - new[] - { - (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', - (byte)'t', (byte)'a', (byte)'0' - }; - - private static ReadOnlySpan ModifiedExtraDataName => // "/ExtraData1" - new[] - { - (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', - (byte)'t', (byte)'a', (byte)'1' - }; - - private static ReadOnlySpan SynchronizingExtraDataName => // "/ExtraData_" - new[] - { - (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', - (byte)'t', (byte)'a', (byte)'_' - }; - - private Result InitializeExtraData() - { - using var pathModifiedExtraData = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedExtraData.Ref(), ModifiedExtraDataName); - if (rc.IsFailure()) return rc; - - using var pathCommittedExtraData = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathCommittedExtraData.Ref(), CommittedExtraDataName); - if (rc.IsFailure()) return rc; - - using var pathSynchronizingExtraData = new Path(); - rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingExtraData.Ref(), SynchronizingExtraDataName); - if (rc.IsFailure()) return rc; - - // Ensure the extra data files exist - rc = _baseFs.GetEntryType(out _, in pathModifiedExtraData); - - if (rc.IsFailure()) - { - if (!ResultFs.PathNotFound.Includes(rc)) - return rc; - - rc = _baseFs.CreateFile(in pathModifiedExtraData, Unsafe.SizeOf()); - if (rc.IsFailure()) return rc; - - if (_isJournalingSupported) - { - rc = _baseFs.CreateFile(in pathCommittedExtraData, Unsafe.SizeOf()); - if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) - return rc; - } - } - else - { - // If the working file exists make sure it's the right size - rc = EnsureExtraDataSize(in pathModifiedExtraData); - if (rc.IsFailure()) return rc; - } - - // Only the working extra data is needed for non-journaling savedata if (_isJournalingSupported) { - rc = _baseFs.GetEntryType(out _, in pathCommittedExtraData); + rc = _baseFs.CreateDirectory(in pathCommittedDirectory); - if (rc.IsSuccess()) - { - rc = EnsureExtraDataSize(in pathCommittedExtraData); - if (rc.IsFailure()) return rc; - - if (_isJournalingEnabled) - { - rc = SynchronizeExtraData(in pathModifiedExtraData, in pathCommittedExtraData); - if (rc.IsFailure()) return rc; - } - } - else if (ResultFs.PathNotFound.Includes(rc)) - { - // If a previous commit failed, the committed extra data may be missing. - // Finish that commit by copying the working extra data to the committed extra data - rc = SynchronizeExtraData(in pathSynchronizingExtraData, in pathModifiedExtraData); - if (rc.IsFailure()) return rc; - - rc = _baseFs.RenameFile(in pathSynchronizingExtraData, in pathCommittedExtraData); - if (rc.IsFailure()) return rc; - } - else - { + // Nintendo returns on all failures, but we'll keep going if committed already exists + // to avoid confusing people manually creating savedata in emulators + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) return rc; + } + } + + // Only the working directory is needed for non-journaling savedata + if (_isJournalingSupported) + { + rc = _baseFs.GetEntryType(out _, in pathCommittedDirectory); + + if (rc.IsSuccess()) + { + // The previous commit successfully completed. Copy the committed dir to the working dir. + if (_isJournalingEnabled) + { + rc = SynchronizeDirectory(in pathModifiedDirectory, in pathCommittedDirectory); + if (rc.IsFailure()) return rc; } } + else if (ResultFs.PathNotFound.Includes(rc)) + { + // If a previous commit failed, the committed dir may be missing. + // Finish that commit by copying the working dir to the committed dir + rc = SynchronizeDirectory(in pathSynchronizingDirectory, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; + rc = _baseFs.RenameDirectory(in pathSynchronizingDirectory, in pathCommittedDirectory); + if (rc.IsFailure()) return rc; + } + else + { + return rc; + } + } + + rc = InitializeExtraData(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result GetFileSystemLock() + { + // Having an open lock file means we already have the lock for the file system. + if (_lockFile.HasValue) return Result.Success; - } - private Result EnsureExtraDataSize(in Path path) + using var pathLockFile = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName); + if (rc.IsFailure()) return rc; + + rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); + + if (rc.IsFailure()) { - using var file = new UniqueRef(); - Result rc = _baseFs.OpenFile(ref file.Ref(), in path, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - - rc = file.Get.GetSize(out long fileSize); - if (rc.IsFailure()) return rc; - - if (fileSize == Unsafe.SizeOf()) - return Result.Success; - - return file.Get.SetSize(Unsafe.SizeOf()); - } - - private Result SynchronizeExtraData(in Path destPath, in Path sourcePath) - { - Span workBuffer = stackalloc byte[Unsafe.SizeOf()]; - - using (var sourceFile = new UniqueRef()) + if (ResultFs.PathNotFound.Includes(rc)) { - Result rc = _baseFs.OpenFile(ref sourceFile.Ref(), in sourcePath, OpenMode.Read); + rc = _baseFs.CreateFile(in pathLockFile, 0); if (rc.IsFailure()) return rc; - rc = sourceFile.Get.Read(out long bytesRead, 0, workBuffer); - if (rc.IsFailure()) return rc; - - Assert.SdkEqual(bytesRead, Unsafe.SizeOf()); - } - - using (var destFile = new UniqueRef()) - { - Result rc = _baseFs.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); - if (rc.IsFailure()) return rc; - - rc = destFile.Get.Write(0, workBuffer, WriteOption.Flush); + rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; } + else + { + return rc; + } + } + return Result.Success; + } + + private Result ResolvePath(ref Path outFullPath, in Path path) + { + using var pathDirectoryName = new Path(); + + // Use the committed directory directly if journaling is supported but not enabled + ReadOnlySpan directoryName = _isJournalingSupported && !_isJournalingEnabled + ? CommittedDirectoryName + : ModifiedDirectoryName; + + Result rc = PathFunctions.SetUpFixedPath(ref pathDirectoryName.Ref(), directoryName); + if (rc.IsFailure()) return rc; + + rc = outFullPath.Combine(in pathDirectoryName, in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.CreateFile(in fullPath, size, option); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoDeleteFile(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.DeleteFile(in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.CreateDirectory(in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoDeleteDirectory(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.DeleteDirectory(in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.DeleteDirectoryRecursively(in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.CleanDirectoryRecursively(in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + using var currentFullPath = new Path(); + using var newFullPath = new Path(); + + Result rc = ResolvePath(ref currentFullPath.Ref(), in currentPath); + if (rc.IsFailure()) return rc; + + rc = ResolvePath(ref newFullPath.Ref(), in newPath); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.RenameFile(in currentFullPath, in newFullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + using var currentFullPath = new Path(); + using var newFullPath = new Path(); + + Result rc = ResolvePath(ref currentFullPath.Ref(), in currentPath); + if (rc.IsFailure()) return rc; + + rc = ResolvePath(ref newFullPath.Ref(), in newPath); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.RenameDirectory(in currentFullPath, in newFullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.GetEntryType(out entryType, in fullPath); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + using var baseFile = new UniqueRef(); + rc = _baseFs.OpenFile(ref baseFile.Ref(), in fullPath, mode); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(new DirectorySaveDataFile(ref baseFile.Ref(), this, mode)); + + if (mode.HasFlag(OpenMode.Write)) + { + _openWritableFileCount++; + } + + outFile.Set(ref file.Ref()); + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var fullPath = new Path(); + Result rc = ResolvePath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + rc = _baseFs.OpenDirectory(ref outDirectory, in fullPath, mode); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + /// + /// Creates the destination directory if needed and copies the source directory to it. + /// + /// The path of the destination directory. + /// The path of the source directory. + /// The of the operation. + private Result SynchronizeDirectory(in Path destPath, in Path sourcePath) + { + // Delete destination dir and recreate it. + Result rc = _baseFs.DeleteDirectoryRecursively(destPath); + + // Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations + // where a PathNotFound error would mean the save directory was in an invalid state. + // We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally + // put the save directory in an invalid state. + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; + + rc = _baseFs.CreateDirectory(destPath); + if (rc.IsFailure()) return rc; + + var directoryEntry = new DirectoryEntry(); + + // Lock only if initialized with a client + if (_fsClient is not null) + { + using ScopedLock scopedLock = + ScopedLock.Lock(ref _fsClient.Globals.DirectorySaveDataFileSystem.SynchronizeDirectoryMutex); + + using (var buffer = new RentedArray(IdealWorkBufferSize)) + { + return Utility.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, + buffer.Span); + } + } + else + { + using (var buffer = new RentedArray(IdealWorkBufferSize)) + { + return Utility.CopyDirectoryRecursively(_baseFs, in destPath, in sourcePath, ref directoryEntry, + buffer.Span); + } + } + } + + protected override Result DoCommit() + { + using ScopedLock lk = ScopedLock.Lock(ref _mutex); + + if (!_isJournalingEnabled || !_isJournalingSupported) return Result.Success; + + var closure = new RetryClosure(); + closure.This = this; + + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingDirectoryName); + if (rc.IsFailure()) return rc; + + if (_openWritableFileCount > 0) + { + // All files must be closed before commiting save data. + return ResultFs.WriteModeFileNotClosed.Log(); } - private Result GetExtraDataPath(ref Path path) + static Result RenameCommittedDir(in RetryClosure closure) { - ReadOnlySpan extraDataName = _isJournalingSupported && !_isJournalingEnabled - ? CommittedExtraDataName - : ModifiedExtraDataName; - - return PathFunctions.SetUpFixedPath(ref path, extraDataName); + return closure.This._baseFs.RenameDirectory(in closure.CommittedPath, + in closure.SynchronizingPath); } - public Result WriteExtraData(in SaveDataExtraData extraData) + static Result SynchronizeWorkingDir(in RetryClosure closure) { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - return WriteExtraDataImpl(in extraData); + return closure.This.SynchronizeDirectory(in closure.SynchronizingPath, + in closure.ModifiedPath); } - public Result CommitExtraData(bool updateTimeStamp) + static Result RenameSynchronizingDir(in RetryClosure closure) { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + return closure.This._baseFs.RenameDirectory(in closure.SynchronizingPath, + in closure.CommittedPath); + } - if (updateTimeStamp && _timeStampGetter is not null && _randomGenerator is not null) + // Get rid of the previous commit by renaming the folder. + rc = RetryFinitelyForTargetLocked(RenameCommittedDir, in closure); + if (rc.IsFailure()) return rc; + + // If something goes wrong beyond this point, the commit will be + // completed the next time the savedata is opened. + + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir, in closure); + if (rc.IsFailure()) return rc; + + rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir, in closure); + if (rc.IsFailure()) return rc; + + closure.Dispose(); + return Result.Success; + } + + protected override Result DoCommitProvisionally(long counter) + { + if (!_isMultiCommitSupported) + return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log(); + + return Result.Success; + } + + protected override Result DoRollback() + { + // No old data is kept for non-journaling save data, so there's nothing to rollback to + if (!_isJournalingSupported) + return Result.Success; + + return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + using var pathModifiedDirectory = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = _baseFs.GetFreeSpaceSize(out freeSpace, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + using var pathModifiedDirectory = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedDirectory.Ref(), ModifiedDirectoryName); + if (rc.IsFailure()) return rc; + + rc = _baseFs.GetTotalSpaceSize(out totalSpace, in pathModifiedDirectory); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private void DecrementWriteOpenFileCount() + { + // Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile + // will try to lock this mutex a second time + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _openWritableFileCount--; + } + + // The original class doesn't support extra data. + // Everything below this point is a LibHac extension. + + private static ReadOnlySpan CommittedExtraDataName => // "/ExtraData0" + new[] + { + (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', + (byte)'t', (byte)'a', (byte)'0' + }; + + private static ReadOnlySpan ModifiedExtraDataName => // "/ExtraData1" + new[] + { + (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', + (byte)'t', (byte)'a', (byte)'1' + }; + + private static ReadOnlySpan SynchronizingExtraDataName => // "/ExtraData_" + new[] + { + (byte)'/', (byte)'E', (byte)'x', (byte)'t', (byte)'r', (byte)'a', (byte)'D', (byte)'a', + (byte)'t', (byte)'a', (byte)'_' + }; + + private Result InitializeExtraData() + { + using var pathModifiedExtraData = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref pathModifiedExtraData.Ref(), ModifiedExtraDataName); + if (rc.IsFailure()) return rc; + + using var pathCommittedExtraData = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathCommittedExtraData.Ref(), CommittedExtraDataName); + if (rc.IsFailure()) return rc; + + using var pathSynchronizingExtraData = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathSynchronizingExtraData.Ref(), SynchronizingExtraDataName); + if (rc.IsFailure()) return rc; + + // Ensure the extra data files exist + rc = _baseFs.GetEntryType(out _, in pathModifiedExtraData); + + if (rc.IsFailure()) + { + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + rc = _baseFs.CreateFile(in pathModifiedExtraData, Unsafe.SizeOf()); + if (rc.IsFailure()) return rc; + + if (_isJournalingSupported) { - Result rc = UpdateExtraDataTimeStamp(); + rc = _baseFs.CreateFile(in pathCommittedExtraData, Unsafe.SizeOf()); + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; + } + } + else + { + // If the working file exists make sure it's the right size + rc = EnsureExtraDataSize(in pathModifiedExtraData); + if (rc.IsFailure()) return rc; + } + + // Only the working extra data is needed for non-journaling savedata + if (_isJournalingSupported) + { + rc = _baseFs.GetEntryType(out _, in pathCommittedExtraData); + + if (rc.IsSuccess()) + { + rc = EnsureExtraDataSize(in pathCommittedExtraData); + if (rc.IsFailure()) return rc; + + if (_isJournalingEnabled) + { + rc = SynchronizeExtraData(in pathModifiedExtraData, in pathCommittedExtraData); + if (rc.IsFailure()) return rc; + } + } + else if (ResultFs.PathNotFound.Includes(rc)) + { + // If a previous commit failed, the committed extra data may be missing. + // Finish that commit by copying the working extra data to the committed extra data + rc = SynchronizeExtraData(in pathSynchronizingExtraData, in pathModifiedExtraData); + if (rc.IsFailure()) return rc; + + rc = _baseFs.RenameFile(in pathSynchronizingExtraData, in pathCommittedExtraData); if (rc.IsFailure()) return rc; } - - return CommitExtraDataImpl(); - } - - public Result ReadExtraData(out SaveDataExtraData extraData) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - return ReadExtraDataImpl(out extraData); - } - - private Result UpdateExtraDataTimeStamp() - { - Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - - Result rc = ReadExtraDataImpl(out SaveDataExtraData extraData); - if (rc.IsFailure()) return rc; - - if (_timeStampGetter.Get(out long timeStamp).IsSuccess()) + else { - extraData.TimeStamp = timeStamp; + return rc; } - - long commitId = 0; - - do - { - _randomGenerator(SpanHelpers.AsByteSpan(ref commitId)); - } while (commitId == 0 || commitId == extraData.CommitId); - - extraData.CommitId = commitId; - - return WriteExtraDataImpl(in extraData); } - private Result WriteExtraDataImpl(in SaveDataExtraData extraData) - { - Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + return Result.Success; + } - using var pathExtraData = new Path(); - Result rc = GetExtraDataPath(ref pathExtraData.Ref()); - if (rc.IsFailure()) return rc; + private Result EnsureExtraDataSize(in Path path) + { + using var file = new UniqueRef(); + Result rc = _baseFs.OpenFile(ref file.Ref(), in path, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc; - using var file = new UniqueRef(); - rc = _baseFs.OpenFile(ref file.Ref(), in pathExtraData, OpenMode.Write); - if (rc.IsFailure()) return rc; - - rc = file.Get.Write(0, SpanHelpers.AsReadOnlyByteSpan(in extraData), WriteOption.Flush); - if (rc.IsFailure()) return rc; + rc = file.Get.GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + if (fileSize == Unsafe.SizeOf()) return Result.Success; - } - private Result CommitExtraDataImpl() + return file.Get.SetSize(Unsafe.SizeOf()); + } + + private Result SynchronizeExtraData(in Path destPath, in Path sourcePath) + { + Span workBuffer = stackalloc byte[Unsafe.SizeOf()]; + + using (var sourceFile = new UniqueRef()) { - Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - - if (!_isJournalingSupported || !_isJournalingEnabled) - return Result.Success; - - var closure = new RetryClosure(); - closure.This = this; - - Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName); + Result rc = _baseFs.OpenFile(ref sourceFile.Ref(), in sourcePath, OpenMode.Read); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedExtraDataName); - if (rc.IsFailure()) return rc; - - rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingExtraDataName); - if (rc.IsFailure()) return rc; - - static Result RenameCommittedFile(in RetryClosure closure) - { - return closure.This._baseFs.RenameFile(in closure.CommittedPath, - in closure.SynchronizingPath); - } - - static Result SynchronizeWorkingFile(in RetryClosure closure) - { - return closure.This.SynchronizeExtraData(in closure.SynchronizingPath, - in closure.ModifiedPath); - } - - static Result RenameSynchronizingFile(in RetryClosure closure) - { - return closure.This._baseFs.RenameFile(in closure.SynchronizingPath, - in closure.CommittedPath); - } - - // Get rid of the previous commit by renaming the file. - rc = RetryFinitelyForTargetLocked(RenameCommittedFile, in closure); - if (rc.IsFailure()) return rc; - - // If something goes wrong beyond this point, the commit will be - // completed the next time the savedata is opened. - - rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile, in closure); - if (rc.IsFailure()) return rc; - - rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile, in closure); - if (rc.IsFailure()) return rc; - - closure.Dispose(); - return Result.Success; - } - - private Result ReadExtraDataImpl(out SaveDataExtraData extraData) - { - UnsafeHelpers.SkipParamInit(out extraData); - - Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - - using var pathExtraData = new Path(); - Result rc = GetExtraDataPath(ref pathExtraData.Ref()); - if (rc.IsFailure()) return rc; - - using var file = new UniqueRef(); - rc = _baseFs.OpenFile(ref file.Ref(), in pathExtraData, OpenMode.Read); - if (rc.IsFailure()) return rc; - - rc = file.Get.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref extraData)); + rc = sourceFile.Get.Read(out long bytesRead, 0, workBuffer); if (rc.IsFailure()) return rc; Assert.SdkEqual(bytesRead, Unsafe.SizeOf()); - - return Result.Success; } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, - ulong saveDataId) + using (var destFile = new UniqueRef()) { - _cacheObserver = observer; - _spaceId = spaceId; - _saveDataId = saveDataId; + Result rc = _baseFs.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); + if (rc.IsFailure()) return rc; + + rc = destFile.Get.Write(0, workBuffer, WriteOption.Flush); + if (rc.IsFailure()) return rc; } - public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; - public ulong GetSaveDataId() => _saveDataId; + return Result.Success; } + + private Result GetExtraDataPath(ref Path path) + { + ReadOnlySpan extraDataName = _isJournalingSupported && !_isJournalingEnabled + ? CommittedExtraDataName + : ModifiedExtraDataName; + + return PathFunctions.SetUpFixedPath(ref path, extraDataName); + } + + public Result WriteExtraData(in SaveDataExtraData extraData) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return WriteExtraDataImpl(in extraData); + } + + public Result CommitExtraData(bool updateTimeStamp) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (updateTimeStamp && _timeStampGetter is not null && _randomGenerator is not null) + { + Result rc = UpdateExtraDataTimeStamp(); + if (rc.IsFailure()) return rc; + } + + return CommitExtraDataImpl(); + } + + public Result ReadExtraData(out SaveDataExtraData extraData) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return ReadExtraDataImpl(out extraData); + } + + private Result UpdateExtraDataTimeStamp() + { + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + Result rc = ReadExtraDataImpl(out SaveDataExtraData extraData); + if (rc.IsFailure()) return rc; + + if (_timeStampGetter.Get(out long timeStamp).IsSuccess()) + { + extraData.TimeStamp = timeStamp; + } + + long commitId = 0; + + do + { + _randomGenerator(SpanHelpers.AsByteSpan(ref commitId)); + } while (commitId == 0 || commitId == extraData.CommitId); + + extraData.CommitId = commitId; + + return WriteExtraDataImpl(in extraData); + } + + private Result WriteExtraDataImpl(in SaveDataExtraData extraData) + { + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + using var pathExtraData = new Path(); + Result rc = GetExtraDataPath(ref pathExtraData.Ref()); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(); + rc = _baseFs.OpenFile(ref file.Ref(), in pathExtraData, OpenMode.Write); + if (rc.IsFailure()) return rc; + + rc = file.Get.Write(0, SpanHelpers.AsReadOnlyByteSpan(in extraData), WriteOption.Flush); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result CommitExtraDataImpl() + { + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + if (!_isJournalingSupported || !_isJournalingEnabled) + return Result.Success; + + var closure = new RetryClosure(); + closure.This = this; + + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedExtraDataName); + if (rc.IsFailure()) return rc; + + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingExtraDataName); + if (rc.IsFailure()) return rc; + + static Result RenameCommittedFile(in RetryClosure closure) + { + return closure.This._baseFs.RenameFile(in closure.CommittedPath, + in closure.SynchronizingPath); + } + + static Result SynchronizeWorkingFile(in RetryClosure closure) + { + return closure.This.SynchronizeExtraData(in closure.SynchronizingPath, + in closure.ModifiedPath); + } + + static Result RenameSynchronizingFile(in RetryClosure closure) + { + return closure.This._baseFs.RenameFile(in closure.SynchronizingPath, + in closure.CommittedPath); + } + + // Get rid of the previous commit by renaming the file. + rc = RetryFinitelyForTargetLocked(RenameCommittedFile, in closure); + if (rc.IsFailure()) return rc; + + // If something goes wrong beyond this point, the commit will be + // completed the next time the savedata is opened. + + rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile, in closure); + if (rc.IsFailure()) return rc; + + rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile, in closure); + if (rc.IsFailure()) return rc; + + closure.Dispose(); + return Result.Success; + } + + private Result ReadExtraDataImpl(out SaveDataExtraData extraData) + { + UnsafeHelpers.SkipParamInit(out extraData); + + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + using var pathExtraData = new Path(); + Result rc = GetExtraDataPath(ref pathExtraData.Ref()); + if (rc.IsFailure()) return rc; + + using var file = new UniqueRef(); + rc = _baseFs.OpenFile(ref file.Ref(), in pathExtraData, OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = file.Get.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref extraData)); + if (rc.IsFailure()) return rc; + + Assert.SdkEqual(bytesRead, Unsafe.SizeOf()); + + return Result.Success; + } + + public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, + ulong saveDataId) + { + _cacheObserver = observer; + _spaceId = spaceId; + _saveDataId = saveDataId; + } + + public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; + public ulong GetSaveDataId() => _saveDataId; } diff --git a/src/LibHac/FsSystem/FileReader.cs b/src/LibHac/FsSystem/FileReader.cs index 0a3ae64c..74456dd4 100644 --- a/src/LibHac/FsSystem/FileReader.cs +++ b/src/LibHac/FsSystem/FileReader.cs @@ -4,177 +4,176 @@ using System.Runtime.InteropServices; using System.Text; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class FileReader { - public class FileReader + private const int BufferSize = 0x10; + private IFile _file; + private byte[] _buffer; + private long _start; + + public long Position { get; set; } + + public FileReader(IFile file) { - private const int BufferSize = 0x10; - private IFile _file; - private byte[] _buffer; - private long _start; - - public long Position { get; set; } - - public FileReader(IFile file) - { - _file = file; - _buffer = new byte[BufferSize]; - } - - public FileReader(IFile file, long start) - { - _file = file; - _start = start; - _buffer = new byte[BufferSize]; - } - - private void FillBuffer(long offset, int count, bool updatePosition) - { - Debug.Assert(count <= BufferSize); - - _file.Read(out long _, _start + offset, _buffer.AsSpan(0, count)).ThrowIfFailure(); - if (updatePosition) Position = offset + count; - } - - public byte ReadUInt8(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(byte), updatePosition); - - return _buffer[0]; - } - - public sbyte ReadInt8(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(sbyte), updatePosition); - - return (sbyte)_buffer[0]; - } - - public ushort ReadUInt16(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(ushort), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public short ReadInt16(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(short), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public int ReadUInt24(long offset, bool updatePosition) - { - FillBuffer(offset, 3, updatePosition); - - return MemoryMarshal.Read(_buffer) & 0xFFFFFF; - } - - public int ReadInt24(long offset, bool updatePosition) - { - FillBuffer(offset, 3, updatePosition); - - return BitTools.SignExtend32(MemoryMarshal.Read(_buffer), 24); - } - - public uint ReadUInt32(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(uint), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public int ReadInt32(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(int), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public ulong ReadUInt64(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(ulong), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public long ReadInt64(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(long), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public float ReadSingle(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(float), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public double ReadDouble(long offset, bool updatePosition) - { - FillBuffer(offset, sizeof(double), updatePosition); - - return MemoryMarshal.Read(_buffer); - } - - public byte[] ReadBytes(long offset, int length, bool updatePosition) - { - byte[] bytes = new byte[length]; - _file.Read(out long _, offset, bytes).ThrowIfFailure(); - - if (updatePosition) Position = offset + length; - return bytes; - } - - public void ReadBytes(Span destination, long offset, bool updatePosition) - { - _file.Read(out long _, offset, destination).ThrowIfFailure(); - - if (updatePosition) Position = offset + destination.Length; - } - - public string ReadAscii(long offset, int length, bool updatePosition) - { - byte[] bytes = new byte[length]; - _file.Read(out long _, offset, bytes).ThrowIfFailure(); - - if (updatePosition) Position = offset + length; - return Encoding.ASCII.GetString(bytes); - } - - public byte ReadUInt8(long offset) => ReadUInt8(offset, true); - public sbyte ReadInt8(long offset) => ReadInt8(offset, true); - public ushort ReadUInt16(long offset) => ReadUInt16(offset, true); - public short ReadInt16(long offset) => ReadInt16(offset, true); - public int ReadUInt24(long offset) => ReadUInt24(offset, true); - public int ReadInt24(long offset) => ReadInt24(offset, true); - public uint ReadUInt32(long offset) => ReadUInt32(offset, true); - public int ReadInt32(long offset) => ReadInt32(offset, true); - public ulong ReadUInt64(long offset) => ReadUInt64(offset, true); - public long ReadInt64(long offset) => ReadInt64(offset, true); - public float ReadSingle(long offset) => ReadSingle(offset, true); - public double ReadDouble(long offset) => ReadDouble(offset, true); - public byte[] ReadBytes(long offset, int length) => ReadBytes(offset, length, true); - public void ReadBytes(Span destination, long offset) => ReadBytes(destination, offset, true); - public string ReadAscii(long offset, int length) => ReadAscii(offset, length, true); - - public byte ReadUInt8() => ReadUInt8(Position, true); - public sbyte ReadInt8() => ReadInt8(Position, true); - public ushort ReadUInt16() => ReadUInt16(Position, true); - public short ReadInt16() => ReadInt16(Position, true); - public int ReadUInt24() => ReadUInt24(Position, true); - public int ReadInt24() => ReadInt24(Position, true); - public uint ReadUInt32() => ReadUInt32(Position, true); - public int ReadInt32() => ReadInt32(Position, true); - public ulong ReadUInt64() => ReadUInt64(Position, true); - public long ReadInt64() => ReadInt64(Position, true); - public float ReadSingle() => ReadSingle(Position, true); - public double ReadDouble() => ReadDouble(Position, true); - public byte[] ReadBytes(int length) => ReadBytes(Position, length, true); - public void ReadBytes(Span destination) => ReadBytes(destination, Position, true); - public string ReadAscii(int length) => ReadAscii(Position, length, true); + _file = file; + _buffer = new byte[BufferSize]; } + + public FileReader(IFile file, long start) + { + _file = file; + _start = start; + _buffer = new byte[BufferSize]; + } + + private void FillBuffer(long offset, int count, bool updatePosition) + { + Debug.Assert(count <= BufferSize); + + _file.Read(out long _, _start + offset, _buffer.AsSpan(0, count)).ThrowIfFailure(); + if (updatePosition) Position = offset + count; + } + + public byte ReadUInt8(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(byte), updatePosition); + + return _buffer[0]; + } + + public sbyte ReadInt8(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(sbyte), updatePosition); + + return (sbyte)_buffer[0]; + } + + public ushort ReadUInt16(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(ushort), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public short ReadInt16(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(short), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public int ReadUInt24(long offset, bool updatePosition) + { + FillBuffer(offset, 3, updatePosition); + + return MemoryMarshal.Read(_buffer) & 0xFFFFFF; + } + + public int ReadInt24(long offset, bool updatePosition) + { + FillBuffer(offset, 3, updatePosition); + + return BitTools.SignExtend32(MemoryMarshal.Read(_buffer), 24); + } + + public uint ReadUInt32(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(uint), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public int ReadInt32(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(int), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public ulong ReadUInt64(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(ulong), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public long ReadInt64(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(long), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public float ReadSingle(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(float), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public double ReadDouble(long offset, bool updatePosition) + { + FillBuffer(offset, sizeof(double), updatePosition); + + return MemoryMarshal.Read(_buffer); + } + + public byte[] ReadBytes(long offset, int length, bool updatePosition) + { + byte[] bytes = new byte[length]; + _file.Read(out long _, offset, bytes).ThrowIfFailure(); + + if (updatePosition) Position = offset + length; + return bytes; + } + + public void ReadBytes(Span destination, long offset, bool updatePosition) + { + _file.Read(out long _, offset, destination).ThrowIfFailure(); + + if (updatePosition) Position = offset + destination.Length; + } + + public string ReadAscii(long offset, int length, bool updatePosition) + { + byte[] bytes = new byte[length]; + _file.Read(out long _, offset, bytes).ThrowIfFailure(); + + if (updatePosition) Position = offset + length; + return Encoding.ASCII.GetString(bytes); + } + + public byte ReadUInt8(long offset) => ReadUInt8(offset, true); + public sbyte ReadInt8(long offset) => ReadInt8(offset, true); + public ushort ReadUInt16(long offset) => ReadUInt16(offset, true); + public short ReadInt16(long offset) => ReadInt16(offset, true); + public int ReadUInt24(long offset) => ReadUInt24(offset, true); + public int ReadInt24(long offset) => ReadInt24(offset, true); + public uint ReadUInt32(long offset) => ReadUInt32(offset, true); + public int ReadInt32(long offset) => ReadInt32(offset, true); + public ulong ReadUInt64(long offset) => ReadUInt64(offset, true); + public long ReadInt64(long offset) => ReadInt64(offset, true); + public float ReadSingle(long offset) => ReadSingle(offset, true); + public double ReadDouble(long offset) => ReadDouble(offset, true); + public byte[] ReadBytes(long offset, int length) => ReadBytes(offset, length, true); + public void ReadBytes(Span destination, long offset) => ReadBytes(destination, offset, true); + public string ReadAscii(long offset, int length) => ReadAscii(offset, length, true); + + public byte ReadUInt8() => ReadUInt8(Position, true); + public sbyte ReadInt8() => ReadInt8(Position, true); + public ushort ReadUInt16() => ReadUInt16(Position, true); + public short ReadInt16() => ReadInt16(Position, true); + public int ReadUInt24() => ReadUInt24(Position, true); + public int ReadInt24() => ReadInt24(Position, true); + public uint ReadUInt32() => ReadUInt32(Position, true); + public int ReadInt32() => ReadInt32(Position, true); + public ulong ReadUInt64() => ReadUInt64(Position, true); + public long ReadInt64() => ReadInt64(Position, true); + public float ReadSingle() => ReadSingle(Position, true); + public double ReadDouble() => ReadDouble(Position, true); + public byte[] ReadBytes(int length) => ReadBytes(Position, length, true); + public void ReadBytes(Span destination) => ReadBytes(destination, Position, true); + public string ReadAscii(int length) => ReadAscii(Position, length, true); } diff --git a/src/LibHac/FsSystem/FileSystemExtensions.cs b/src/LibHac/FsSystem/FileSystemExtensions.cs index a93a399f..87ff3289 100644 --- a/src/LibHac/FsSystem/FileSystemExtensions.cs +++ b/src/LibHac/FsSystem/FileSystemExtensions.cs @@ -9,386 +9,385 @@ using LibHac.Fs.Fsa; using LibHac.Util; using Path = LibHac.Fs.Path; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public static class FileSystemExtensions { - public static class FileSystemExtensions + public static Result CopyDirectory(this IFileSystem sourceFs, IFileSystem destFs, string sourcePath, string destPath, + IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None) { - public static Result CopyDirectory(this IFileSystem sourceFs, IFileSystem destFs, string sourcePath, string destPath, - IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None) + const int bufferSize = 0x100000; + + var directoryEntryBuffer = new DirectoryEntry(); + + using var sourcePathNormalized = new Path(); + Result rc = InitializeFromString(ref sourcePathNormalized.Ref(), sourcePath); + if (rc.IsFailure()) return rc; + + using var destPathNormalized = new Path(); + rc = InitializeFromString(ref destPathNormalized.Ref(), destPath); + if (rc.IsFailure()) return rc; + + byte[] workBuffer = ArrayPool.Shared.Rent(bufferSize); + try { - const int bufferSize = 0x100000; + return CopyDirectoryRecursively(destFs, sourceFs, in destPathNormalized, in sourcePathNormalized, + ref directoryEntryBuffer, workBuffer, logger, options); + } + finally + { + ArrayPool.Shared.Return(workBuffer); + logger?.SetTotal(0); + } + } - var directoryEntryBuffer = new DirectoryEntry(); - - using var sourcePathNormalized = new Path(); - Result rc = InitializeFromString(ref sourcePathNormalized.Ref(), sourcePath); + public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, + in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer, + IProgressReport logger = null, CreateFileOptions option = CreateFileOptions.None) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, + ref Utility.FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); if (rc.IsFailure()) return rc; - using var destPathNormalized = new Path(); - rc = InitializeFromString(ref destPathNormalized.Ref(), destPath); + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref Utility.FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + Result OnFile(in Path path, in DirectoryEntry entry, ref Utility.FsIterationTaskClosure closure) + { + logger?.LogMessage(path.ToString()); + + Result result = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (result.IsFailure()) return result; + + result = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer, logger, option); + if (result.IsFailure()) return result; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + var taskClosure = new Utility.FsIterationTaskClosure(); + taskClosure.Buffer = workBuffer; + taskClosure.SourceFileSystem = sourceFileSystem; + taskClosure.DestFileSystem = destinationFileSystem; + + Result rc = taskClosure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + rc = Utility.IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, + OnExitDir, OnFile, ref taskClosure); + + taskClosure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, + in Path sourcePath, Span workBuffer, IProgressReport logger = null, + CreateFileOptions option = CreateFileOptions.None) + { + logger?.LogMessage(sourcePath.ToString()); + + // Open source file. + using var sourceFile = new UniqueRef(); + Result rc = sourceFileSystem.OpenFile(ref sourceFile.Ref(), sourcePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = sourceFile.Get.GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + rc = CreateOrOverwriteFile(destFileSystem, in destPath, fileSize, option); + if (rc.IsFailure()) return rc; + + using var destFile = new UniqueRef(); + rc = destFileSystem.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); + if (rc.IsFailure()) return rc; + + // Read/Write file in work buffer sized chunks. + long remaining = fileSize; + long offset = 0; + + logger?.SetTotal(fileSize); + + while (remaining > 0) + { + rc = sourceFile.Get.Read(out long bytesRead, offset, workBuffer, ReadOption.None); if (rc.IsFailure()) return rc; - byte[] workBuffer = ArrayPool.Shared.Rent(bufferSize); - try - { - return CopyDirectoryRecursively(destFs, sourceFs, in destPathNormalized, in sourcePathNormalized, - ref directoryEntryBuffer, workBuffer, logger, options); - } - finally - { - ArrayPool.Shared.Return(workBuffer); - logger?.SetTotal(0); - } - } - - public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, - in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer, - IProgressReport logger = null, CreateFileOptions option = CreateFileOptions.None) - { - static Result OnEnterDir(in Path path, in DirectoryEntry entry, - ref Utility.FsIterationTaskClosure closure) - { - Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); - } - - static Result OnExitDir(in Path path, in DirectoryEntry entry, ref Utility.FsIterationTaskClosure closure) - { - return closure.DestinationPathBuffer.RemoveChild(); - } - - Result OnFile(in Path path, in DirectoryEntry entry, ref Utility.FsIterationTaskClosure closure) - { - logger?.LogMessage(path.ToString()); - - Result result = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (result.IsFailure()) return result; - - result = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, - in path, closure.Buffer, logger, option); - if (result.IsFailure()) return result; - - return closure.DestinationPathBuffer.RemoveChild(); - } - - var taskClosure = new Utility.FsIterationTaskClosure(); - taskClosure.Buffer = workBuffer; - taskClosure.SourceFileSystem = sourceFileSystem; - taskClosure.DestFileSystem = destinationFileSystem; - - Result rc = taskClosure.DestinationPathBuffer.Initialize(destinationPath); + rc = destFile.Get.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); if (rc.IsFailure()) return rc; - rc = Utility.IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, - OnExitDir, OnFile, ref taskClosure); + remaining -= bytesRead; + offset += bytesRead; - taskClosure.DestinationPathBuffer.Dispose(); - return rc; + logger?.ReportAdd(bytesRead); } - public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, - in Path sourcePath, Span workBuffer, IProgressReport logger = null, - CreateFileOptions option = CreateFileOptions.None) + return Result.Success; + } + + public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null) + { + var destFs = new LocalFileSystem(destinationPath); + + source.CopyDirectory(destFs, "/", "/", logger); + } + + public static IEnumerable EnumerateEntries(this IFileSystem fileSystem) + { + return fileSystem.EnumerateEntries("/", "*"); + } + + public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern) + { + return fileSystem.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories); + } + + public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions) + { + return EnumerateEntries(fileSystem, "/", searchPattern, searchOptions); + } + + public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern, SearchOptions searchOptions) + { + bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive); + bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories); + + using var directory = new UniqueRef(); + + using (var pathNormalized = new Path()) { - logger?.LogMessage(sourcePath.ToString()); - - // Open source file. - using var sourceFile = new UniqueRef(); - Result rc = sourceFileSystem.OpenFile(ref sourceFile.Ref(), sourcePath, OpenMode.Read); - if (rc.IsFailure()) return rc; - - rc = sourceFile.Get.GetSize(out long fileSize); - if (rc.IsFailure()) return rc; - - rc = CreateOrOverwriteFile(destFileSystem, in destPath, fileSize, option); - if (rc.IsFailure()) return rc; - - using var destFile = new UniqueRef(); - rc = destFileSystem.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); - if (rc.IsFailure()) return rc; - - // Read/Write file in work buffer sized chunks. - long remaining = fileSize; - long offset = 0; - - logger?.SetTotal(fileSize); - - while (remaining > 0) - { - rc = sourceFile.Get.Read(out long bytesRead, offset, workBuffer, ReadOption.None); - if (rc.IsFailure()) return rc; - - rc = destFile.Get.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); - if (rc.IsFailure()) return rc; - - remaining -= bytesRead; - offset += bytesRead; - - logger?.ReportAdd(bytesRead); - } - - return Result.Success; - } - - public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null) - { - var destFs = new LocalFileSystem(destinationPath); - - source.CopyDirectory(destFs, "/", "/", logger); - } - - public static IEnumerable EnumerateEntries(this IFileSystem fileSystem) - { - return fileSystem.EnumerateEntries("/", "*"); - } - - public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern) - { - return fileSystem.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories); - } - - public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions) - { - return EnumerateEntries(fileSystem, "/", searchPattern, searchOptions); - } - - public static IEnumerable EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern, SearchOptions searchOptions) - { - bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive); - bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories); - - using var directory = new UniqueRef(); - - using (var pathNormalized = new Path()) - { - InitializeFromString(ref pathNormalized.Ref(), path).ThrowIfFailure(); - - fileSystem.OpenDirectory(ref directory.Ref(), in pathNormalized, OpenDirectoryMode.All) - .ThrowIfFailure(); - } - - while (true) - { - Unsafe.SkipInit(out DirectoryEntry dirEntry); - - directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)).ThrowIfFailure(); - if (entriesRead == 0) break; - - DirectoryEntryEx entry = GetDirectoryEntryEx(ref dirEntry, path); - - if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) - { - yield return entry; - } - - if (entry.Type != DirectoryEntryType.Directory || !recurse) continue; - - IEnumerable subEntries = - fileSystem.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, - searchOptions); - - foreach (DirectoryEntryEx subEntry in subEntries) - { - yield return subEntry; - } - } - } - - internal static DirectoryEntryEx GetDirectoryEntryEx(ref DirectoryEntry entry, string parentPath) - { - string name = StringUtils.Utf8ZToString(entry.Name); - string path = PathTools.Combine(parentPath, name); - - var entryEx = new DirectoryEntryEx(name, path, entry.Type, entry.Size); - entryEx.Attributes = entry.Attributes; - - return entryEx; - } - - public static void CopyTo(this IFile file, IFile dest, IProgressReport logger = null) - { - const int bufferSize = 0x8000; - - file.GetSize(out long fileSize).ThrowIfFailure(); - - logger?.SetTotal(fileSize); - - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - long inOffset = 0; - - // todo: use result for loop condition - while (true) - { - file.Read(out long bytesRead, inOffset, buffer).ThrowIfFailure(); - if (bytesRead == 0) break; - - dest.Write(inOffset, buffer.AsSpan(0, (int)bytesRead)).ThrowIfFailure(); - inOffset += bytesRead; - logger?.ReportAdd(bytesRead); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - logger?.SetTotal(0); - } - } - - public static IStorage AsStorage(this IFile file) => new FileStorage(file); - public static Stream AsStream(this IFile file) => new NxFileStream(file, true); - public static Stream AsStream(this IFile file, OpenMode mode, bool keepOpen) => new NxFileStream(file, mode, keepOpen); - - public static IFile AsIFile(this Stream stream, OpenMode mode) => new StreamFile(stream, mode); - - public static int GetEntryCount(this IFileSystem fs, OpenDirectoryMode mode) - { - return GetEntryCountRecursive(fs, "/", mode); - } - - public static int GetEntryCountRecursive(this IFileSystem fs, string path, OpenDirectoryMode mode) - { - int count = 0; - - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path, "*")) - { - if (entry.Type == DirectoryEntryType.Directory && (mode & OpenDirectoryMode.Directory) != 0 || - entry.Type == DirectoryEntryType.File && (mode & OpenDirectoryMode.File) != 0) - { - count++; - } - } - - return count; - } - - public static NxFileAttributes ToNxAttributes(this FileAttributes attributes) - { - return (NxFileAttributes)(((int)attributes >> 4) & 3); - } - - public static FileAttributes ToFatAttributes(this NxFileAttributes attributes) - { - return (FileAttributes)(((int)attributes & 3) << 4); - } - - public static FileAttributes ApplyNxAttributes(this FileAttributes attributes, NxFileAttributes nxAttributes) - { - // The only 2 bits from FileAttributes that are used in NxFileAttributes - const int mask = 3 << 4; - - FileAttributes oldAttributes = attributes & (FileAttributes)mask; - return oldAttributes | nxAttributes.ToFatAttributes(); - } - - public static void SetConcatenationFileAttribute(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); InitializeFromString(ref pathNormalized.Ref(), path).ThrowIfFailure(); - fs.QueryEntry(Span.Empty, Span.Empty, QueryId.SetConcatenationFileAttribute, in pathNormalized); + fileSystem.OpenDirectory(ref directory.Ref(), in pathNormalized, OpenDirectoryMode.All) + .ThrowIfFailure(); } - public static void CleanDirectoryRecursivelyGeneric(IFileSystem fileSystem, string path) + while (true) { - IFileSystem fs = fileSystem; + Unsafe.SkipInit(out DirectoryEntry dirEntry); - foreach (DirectoryEntryEx entry in fileSystem.EnumerateEntries(path, "*", SearchOptions.Default)) + directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)).ThrowIfFailure(); + if (entriesRead == 0) break; + + DirectoryEntryEx entry = GetDirectoryEntryEx(ref dirEntry, path); + + if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase)) { - string subPath = PathTools.Combine(path, entry.Name); - - using var subPathNormalized = new Path(); - InitializeFromString(ref subPathNormalized.Ref(), subPath).ThrowIfFailure(); - - if (entry.Type == DirectoryEntryType.Directory) - { - CleanDirectoryRecursivelyGeneric(fileSystem, subPath); - fs.DeleteDirectory(in subPathNormalized); - } - else if (entry.Type == DirectoryEntryType.File) - { - fs.DeleteFile(in subPathNormalized); - } - } - } - - public static Result Read(this IFile file, out long bytesRead, long offset, Span destination) - { - return file.Read(out bytesRead, offset, destination, ReadOption.None); - } - - public static Result Write(this IFile file, long offset, ReadOnlySpan source) - { - return file.Write(offset, source, WriteOption.None); - } - - public static bool DirectoryExists(this IFileSystem fs, string path) - { - Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); - - return (rc.IsSuccess() && type == DirectoryEntryType.Directory); - } - - public static bool FileExists(this IFileSystem fs, string path) - { - Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); - - return (rc.IsSuccess() && type == DirectoryEntryType.File); - } - - public static Result EnsureDirectoryExists(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = InitializeFromString(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; - - return Utility.EnsureDirectory(fs, in pathNormalized); - } - - public static Result CreateOrOverwriteFile(IFileSystem fileSystem, in Path path, long size, - CreateFileOptions option = CreateFileOptions.None) - { - Result rc = fileSystem.CreateFile(in path, size, option); - - if (rc.IsFailure()) - { - if (!ResultFs.PathAlreadyExists.Includes(rc)) - return rc; - - rc = fileSystem.DeleteFile(in path); - if (rc.IsFailure()) return rc; - - rc = fileSystem.CreateFile(in path, size, option); - if (rc.IsFailure()) return rc; + yield return entry; } - return Result.Success; - } + if (entry.Type != DirectoryEntryType.Directory || !recurse) continue; - private static Result InitializeFromString(ref Path outPath, string path) - { - ReadOnlySpan utf8Path = StringUtils.StringToUtf8(path); + IEnumerable subEntries = + fileSystem.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, + searchOptions); - Result rc = outPath.Initialize(utf8Path); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowEmptyPath(); - outPath.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - return Result.Success; + foreach (DirectoryEntryEx subEntry in subEntries) + { + yield return subEntry; + } } } - [Flags] - public enum SearchOptions + internal static DirectoryEntryEx GetDirectoryEntryEx(ref DirectoryEntry entry, string parentPath) { - Default = 0, - RecurseSubdirectories = 1 << 0, - CaseInsensitive = 1 << 1 + string name = StringUtils.Utf8ZToString(entry.Name); + string path = PathTools.Combine(parentPath, name); + + var entryEx = new DirectoryEntryEx(name, path, entry.Type, entry.Size); + entryEx.Attributes = entry.Attributes; + + return entryEx; + } + + public static void CopyTo(this IFile file, IFile dest, IProgressReport logger = null) + { + const int bufferSize = 0x8000; + + file.GetSize(out long fileSize).ThrowIfFailure(); + + logger?.SetTotal(fileSize); + + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + long inOffset = 0; + + // todo: use result for loop condition + while (true) + { + file.Read(out long bytesRead, inOffset, buffer).ThrowIfFailure(); + if (bytesRead == 0) break; + + dest.Write(inOffset, buffer.AsSpan(0, (int)bytesRead)).ThrowIfFailure(); + inOffset += bytesRead; + logger?.ReportAdd(bytesRead); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + logger?.SetTotal(0); + } + } + + public static IStorage AsStorage(this IFile file) => new FileStorage(file); + public static Stream AsStream(this IFile file) => new NxFileStream(file, true); + public static Stream AsStream(this IFile file, OpenMode mode, bool keepOpen) => new NxFileStream(file, mode, keepOpen); + + public static IFile AsIFile(this Stream stream, OpenMode mode) => new StreamFile(stream, mode); + + public static int GetEntryCount(this IFileSystem fs, OpenDirectoryMode mode) + { + return GetEntryCountRecursive(fs, "/", mode); + } + + public static int GetEntryCountRecursive(this IFileSystem fs, string path, OpenDirectoryMode mode) + { + int count = 0; + + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path, "*")) + { + if (entry.Type == DirectoryEntryType.Directory && (mode & OpenDirectoryMode.Directory) != 0 || + entry.Type == DirectoryEntryType.File && (mode & OpenDirectoryMode.File) != 0) + { + count++; + } + } + + return count; + } + + public static NxFileAttributes ToNxAttributes(this FileAttributes attributes) + { + return (NxFileAttributes)(((int)attributes >> 4) & 3); + } + + public static FileAttributes ToFatAttributes(this NxFileAttributes attributes) + { + return (FileAttributes)(((int)attributes & 3) << 4); + } + + public static FileAttributes ApplyNxAttributes(this FileAttributes attributes, NxFileAttributes nxAttributes) + { + // The only 2 bits from FileAttributes that are used in NxFileAttributes + const int mask = 3 << 4; + + FileAttributes oldAttributes = attributes & (FileAttributes)mask; + return oldAttributes | nxAttributes.ToFatAttributes(); + } + + public static void SetConcatenationFileAttribute(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + InitializeFromString(ref pathNormalized.Ref(), path).ThrowIfFailure(); + + fs.QueryEntry(Span.Empty, Span.Empty, QueryId.SetConcatenationFileAttribute, in pathNormalized); + } + + public static void CleanDirectoryRecursivelyGeneric(IFileSystem fileSystem, string path) + { + IFileSystem fs = fileSystem; + + foreach (DirectoryEntryEx entry in fileSystem.EnumerateEntries(path, "*", SearchOptions.Default)) + { + string subPath = PathTools.Combine(path, entry.Name); + + using var subPathNormalized = new Path(); + InitializeFromString(ref subPathNormalized.Ref(), subPath).ThrowIfFailure(); + + if (entry.Type == DirectoryEntryType.Directory) + { + CleanDirectoryRecursivelyGeneric(fileSystem, subPath); + fs.DeleteDirectory(in subPathNormalized); + } + else if (entry.Type == DirectoryEntryType.File) + { + fs.DeleteFile(in subPathNormalized); + } + } + } + + public static Result Read(this IFile file, out long bytesRead, long offset, Span destination) + { + return file.Read(out bytesRead, offset, destination, ReadOption.None); + } + + public static Result Write(this IFile file, long offset, ReadOnlySpan source) + { + return file.Write(offset, source, WriteOption.None); + } + + public static bool DirectoryExists(this IFileSystem fs, string path) + { + Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); + + return (rc.IsSuccess() && type == DirectoryEntryType.Directory); + } + + public static bool FileExists(this IFileSystem fs, string path) + { + Result rc = fs.GetEntryType(out DirectoryEntryType type, path.ToU8Span()); + + return (rc.IsSuccess() && type == DirectoryEntryType.File); + } + + public static Result EnsureDirectoryExists(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = InitializeFromString(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; + + return Utility.EnsureDirectory(fs, in pathNormalized); + } + + public static Result CreateOrOverwriteFile(IFileSystem fileSystem, in Path path, long size, + CreateFileOptions option = CreateFileOptions.None) + { + Result rc = fileSystem.CreateFile(in path, size, option); + + if (rc.IsFailure()) + { + if (!ResultFs.PathAlreadyExists.Includes(rc)) + return rc; + + rc = fileSystem.DeleteFile(in path); + if (rc.IsFailure()) return rc; + + rc = fileSystem.CreateFile(in path, size, option); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + private static Result InitializeFromString(ref Path outPath, string path) + { + ReadOnlySpan utf8Path = StringUtils.StringToUtf8(path); + + Result rc = outPath.Initialize(utf8Path); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + outPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + return Result.Success; } } + +[Flags] +public enum SearchOptions +{ + Default = 0, + RecurseSubdirectories = 1 << 0, + CaseInsensitive = 1 << 1 +} diff --git a/src/LibHac/FsSystem/ForwardingFileSystem.cs b/src/LibHac/FsSystem/ForwardingFileSystem.cs index 1af9a98f..ea0b18ca 100644 --- a/src/LibHac/FsSystem/ForwardingFileSystem.cs +++ b/src/LibHac/FsSystem/ForwardingFileSystem.cs @@ -3,73 +3,72 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ForwardingFileSystem : IFileSystem { - public class ForwardingFileSystem : IFileSystem + protected SharedRef BaseFileSystem; + + public ForwardingFileSystem(ref SharedRef baseFileSystem) { - protected SharedRef BaseFileSystem; - - public ForwardingFileSystem(ref SharedRef baseFileSystem) - { - BaseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - } - - public override void Dispose() - { - BaseFileSystem.Destroy(); - base.Dispose(); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => - BaseFileSystem.Get.CreateFile(in path, size, option); - - protected override Result DoDeleteFile(in Path path) => BaseFileSystem.Get.DeleteFile(in path); - - protected override Result DoCreateDirectory(in Path path) => BaseFileSystem.Get.CreateDirectory(in path); - - protected override Result DoDeleteDirectory(in Path path) => BaseFileSystem.Get.DeleteDirectory(in path); - - protected override Result DoDeleteDirectoryRecursively(in Path path) => - BaseFileSystem.Get.DeleteDirectoryRecursively(in path); - - protected override Result DoCleanDirectoryRecursively(in Path path) => - BaseFileSystem.Get.CleanDirectoryRecursively(in path); - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => - BaseFileSystem.Get.RenameFile(in currentPath, in newPath); - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => - BaseFileSystem.Get.RenameDirectory(in currentPath, in newPath); - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) => - BaseFileSystem.Get.GetEntryType(out entryType, in path); - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) => - BaseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) => - BaseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) => - BaseFileSystem.Get.OpenFile(ref outFile, in path, mode); - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) => - BaseFileSystem.Get.OpenDirectory(ref outDirectory, in path, mode); - - protected override Result DoCommit() => BaseFileSystem.Get.Commit(); - - protected override Result DoCommitProvisionally(long counter) => - BaseFileSystem.Get.CommitProvisionally(counter); - - protected override Result DoRollback() => BaseFileSystem.Get.Rollback(); - - protected override Result DoFlush() => BaseFileSystem.Get.Flush(); - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) => - BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path); - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) => BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in path); + BaseFileSystem = SharedRef.CreateMove(ref baseFileSystem); } + + public override void Dispose() + { + BaseFileSystem.Destroy(); + base.Dispose(); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => + BaseFileSystem.Get.CreateFile(in path, size, option); + + protected override Result DoDeleteFile(in Path path) => BaseFileSystem.Get.DeleteFile(in path); + + protected override Result DoCreateDirectory(in Path path) => BaseFileSystem.Get.CreateDirectory(in path); + + protected override Result DoDeleteDirectory(in Path path) => BaseFileSystem.Get.DeleteDirectory(in path); + + protected override Result DoDeleteDirectoryRecursively(in Path path) => + BaseFileSystem.Get.DeleteDirectoryRecursively(in path); + + protected override Result DoCleanDirectoryRecursively(in Path path) => + BaseFileSystem.Get.CleanDirectoryRecursively(in path); + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => + BaseFileSystem.Get.RenameFile(in currentPath, in newPath); + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => + BaseFileSystem.Get.RenameDirectory(in currentPath, in newPath); + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) => + BaseFileSystem.Get.GetEntryType(out entryType, in path); + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) => + BaseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) => + BaseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) => + BaseFileSystem.Get.OpenFile(ref outFile, in path, mode); + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) => + BaseFileSystem.Get.OpenDirectory(ref outDirectory, in path, mode); + + protected override Result DoCommit() => BaseFileSystem.Get.Commit(); + + protected override Result DoCommitProvisionally(long counter) => + BaseFileSystem.Get.CommitProvisionally(counter); + + protected override Result DoRollback() => BaseFileSystem.Get.Rollback(); + + protected override Result DoFlush() => BaseFileSystem.Get.Flush(); + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) => + BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path); + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) => BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in path); } diff --git a/src/LibHac/FsSystem/FsPath.cs b/src/LibHac/FsSystem/FsPath.cs index 433f681e..3d6434a6 100644 --- a/src/LibHac/FsSystem/FsPath.cs +++ b/src/LibHac/FsSystem/FsPath.cs @@ -6,39 +6,38 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] +public struct FsPath { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] - public struct FsPath - { - internal const int MaxLength = 0x300; + internal const int MaxLength = 0x300; #if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; #endif - public Span Str => SpanHelpers.AsByteSpan(ref this); + public Span Str => SpanHelpers.AsByteSpan(ref this); - public static Result FromSpan(out FsPath fsPath, ReadOnlySpan path) - { - UnsafeHelpers.SkipParamInit(out fsPath); + public static Result FromSpan(out FsPath fsPath, ReadOnlySpan path) + { + UnsafeHelpers.SkipParamInit(out fsPath); - // Ensure null terminator even if the creation fails for safety - fsPath.Str[MaxLength] = 0; + // Ensure null terminator even if the creation fails for safety + fsPath.Str[MaxLength] = 0; - var sb = new U8StringBuilder(fsPath.Str); - bool overflowed = sb.Append(path).Overflowed; + var sb = new U8StringBuilder(fsPath.Str); + bool overflowed = sb.Append(path).Overflowed; - return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator U8Span(in FsPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); - - public override string ToString() => StringUtils.Utf8ZToString(Str); + return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator U8Span(in FsPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); + + public override string ToString() => StringUtils.Utf8ZToString(Str); } diff --git a/src/LibHac/FsSystem/Hash.cs b/src/LibHac/FsSystem/Hash.cs index f4e0503f..2cd0a907 100644 --- a/src/LibHac/FsSystem/Hash.cs +++ b/src/LibHac/FsSystem/Hash.cs @@ -3,15 +3,14 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.FsSystem -{ - [StructLayout(LayoutKind.Sequential, Size = 0x20)] - public struct Hash - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; +namespace LibHac.FsSystem; - public readonly ReadOnlySpan Bytes => SpanHelpers.AsReadOnlyByteSpan(in this); - public Span BytesMutable => SpanHelpers.AsByteSpan(ref this); - } +[StructLayout(LayoutKind.Sequential, Size = 0x20)] +public struct Hash +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + + public readonly ReadOnlySpan Bytes => SpanHelpers.AsReadOnlyByteSpan(in this); + public Span BytesMutable => SpanHelpers.AsByteSpan(ref this); } diff --git a/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs b/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs index 3cf07989..c6eebebc 100644 --- a/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs +++ b/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs @@ -6,185 +6,185 @@ using System.Text; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class HierarchicalIntegrityVerificationStorage : IStorage { - public class HierarchicalIntegrityVerificationStorage : IStorage + public IStorage[] Levels { get; } + public IStorage DataLevel { get; } + public IntegrityCheckLevel IntegrityCheckLevel { get; } + + /// + /// An array of the hash statuses of every block in each level. + /// + public Validity[][] LevelValidities { get; } + + private long Length { get; } + private bool LeaveOpen { get; } + + private IntegrityVerificationStorage[] IntegrityStorages { get; } + + public HierarchicalIntegrityVerificationStorage(IntegrityVerificationInfo[] levelInfo, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) { - public IStorage[] Levels { get; } - public IStorage DataLevel { get; } - public IntegrityCheckLevel IntegrityCheckLevel { get; } + Levels = new IStorage[levelInfo.Length]; + IntegrityCheckLevel = integrityCheckLevel; + LevelValidities = new Validity[levelInfo.Length - 1][]; + IntegrityStorages = new IntegrityVerificationStorage[levelInfo.Length - 1]; - /// - /// An array of the hash statuses of every block in each level. - /// - public Validity[][] LevelValidities { get; } + Levels[0] = levelInfo[0].Data; - private long Length { get; } - private bool LeaveOpen { get; } - - private IntegrityVerificationStorage[] IntegrityStorages { get; } - - public HierarchicalIntegrityVerificationStorage(IntegrityVerificationInfo[] levelInfo, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + for (int i = 1; i < Levels.Length; i++) { - Levels = new IStorage[levelInfo.Length]; - IntegrityCheckLevel = integrityCheckLevel; - LevelValidities = new Validity[levelInfo.Length - 1][]; - IntegrityStorages = new IntegrityVerificationStorage[levelInfo.Length - 1]; + var levelData = new IntegrityVerificationStorage(levelInfo[i], Levels[i - 1], integrityCheckLevel, leaveOpen); + levelData.GetSize(out long levelSize).ThrowIfFailure(); - Levels[0] = levelInfo[0].Data; + int cacheCount = Math.Min((int)BitUtil.DivideUp(levelSize, levelInfo[i].BlockSize), 4); - for (int i = 1; i < Levels.Length; i++) - { - var levelData = new IntegrityVerificationStorage(levelInfo[i], Levels[i - 1], integrityCheckLevel, leaveOpen); - levelData.GetSize(out long levelSize).ThrowIfFailure(); - - int cacheCount = Math.Min((int)BitUtil.DivideUp(levelSize, levelInfo[i].BlockSize), 4); - - Levels[i] = new CachedStorage(levelData, cacheCount, leaveOpen); - LevelValidities[i - 1] = levelData.BlockValidities; - IntegrityStorages[i - 1] = levelData; - } - - DataLevel = Levels[Levels.Length - 1]; - DataLevel.GetSize(out long dataSize).ThrowIfFailure(); - Length = dataSize; - - LeaveOpen = leaveOpen; + Levels[i] = new CachedStorage(levelData, cacheCount, leaveOpen); + LevelValidities[i - 1] = levelData.BlockValidities; + IntegrityStorages[i - 1] = levelData; } - public HierarchicalIntegrityVerificationStorage(IvfcHeader header, IStorage masterHash, IStorage data, - IntegrityStorageType type, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) - : this(header, ToStorageList(header, masterHash, data, leaveOpen), type, integrityCheckLevel, leaveOpen) { } + DataLevel = Levels[Levels.Length - 1]; + DataLevel.GetSize(out long dataSize).ThrowIfFailure(); + Length = dataSize; - public HierarchicalIntegrityVerificationStorage(IvfcHeader header, IList levels, - IntegrityStorageType type, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) - : this(GetIvfcInfo(header, levels, type), integrityCheckLevel, leaveOpen) { } + LeaveOpen = leaveOpen; + } - private static List ToStorageList(IvfcHeader header, IStorage masterHash, IStorage data, bool leaveOpen) + public HierarchicalIntegrityVerificationStorage(IvfcHeader header, IStorage masterHash, IStorage data, + IntegrityStorageType type, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + : this(header, ToStorageList(header, masterHash, data, leaveOpen), type, integrityCheckLevel, leaveOpen) { } + + public HierarchicalIntegrityVerificationStorage(IvfcHeader header, IList levels, + IntegrityStorageType type, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + : this(GetIvfcInfo(header, levels, type), integrityCheckLevel, leaveOpen) { } + + private static List ToStorageList(IvfcHeader header, IStorage masterHash, IStorage data, bool leaveOpen) + { + var levels = new List { masterHash }; + + for (int i = 0; i < header.NumLevels - 1; i++) { - var levels = new List { masterHash }; - - for (int i = 0; i < header.NumLevels - 1; i++) - { - IvfcLevelHeader level = header.LevelHeaders[i]; - levels.Add(data.Slice(level.Offset, level.Size, leaveOpen)); - } - - return levels; + IvfcLevelHeader level = header.LevelHeaders[i]; + levels.Add(data.Slice(level.Offset, level.Size, leaveOpen)); } - private static IntegrityVerificationInfo[] GetIvfcInfo(IvfcHeader ivfc, IList levels, IntegrityStorageType type) - { - var initInfo = new IntegrityVerificationInfo[ivfc.NumLevels]; + return levels; + } - initInfo[0] = new IntegrityVerificationInfo + private static IntegrityVerificationInfo[] GetIvfcInfo(IvfcHeader ivfc, IList levels, IntegrityStorageType type) + { + var initInfo = new IntegrityVerificationInfo[ivfc.NumLevels]; + + initInfo[0] = new IntegrityVerificationInfo + { + Data = levels[0], + BlockSize = 0 + }; + + for (int i = 1; i < ivfc.NumLevels; i++) + { + initInfo[i] = new IntegrityVerificationInfo { - Data = levels[0], - BlockSize = 0 + Data = levels[i], + BlockSize = 1 << ivfc.LevelHeaders[i - 1].BlockSizePower, + Salt = new HMACSHA256(Encoding.ASCII.GetBytes(SaltSources[i - 1])).ComputeHash(ivfc.SaltSource), + Type = type }; + } - for (int i = 1; i < ivfc.NumLevels; i++) + return initInfo; + } + + protected override Result DoRead(long offset, Span destination) + { + return DataLevel.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return DataLevel.Write(offset, source); + } + + protected override Result DoFlush() + { + return DataLevel.Flush(); + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) + { + DataLevel?.Dispose(); + } + + base.Dispose(); + } + + /// + /// Checks the hashes of any unchecked blocks and returns the of the data. + /// + /// If , return as soon as an invalid block is found. + /// An optional for reporting progress. + /// The of the data of the specified hash level. + public Validity Validate(bool returnOnError, IProgressReport logger = null) + { + Validity[] validities = LevelValidities[LevelValidities.Length - 1]; + IntegrityVerificationStorage storage = IntegrityStorages[IntegrityStorages.Length - 1]; + + long blockSize = storage.SectorSize; + int blockCount = (int)BitUtil.DivideUp(Length, blockSize); + + byte[] buffer = new byte[blockSize]; + var result = Validity.Valid; + + logger?.SetTotal(blockCount); + + for (int i = 0; i < blockCount; i++) + { + if (validities[i] == Validity.Unchecked) { - initInfo[i] = new IntegrityVerificationInfo - { - Data = levels[i], - BlockSize = 1 << ivfc.LevelHeaders[i - 1].BlockSizePower, - Salt = new HMACSHA256(Encoding.ASCII.GetBytes(SaltSources[i - 1])).ComputeHash(ivfc.SaltSource), - Type = type - }; + storage.GetSize(out long storageSize).ThrowIfFailure(); + int toRead = (int)Math.Min(storageSize - blockSize * i, buffer.Length); + + storage.Read(blockSize * i, buffer.AsSpan(0, toRead), IntegrityCheckLevel.IgnoreOnInvalid); } - return initInfo; - } - - protected override Result DoRead(long offset, Span destination) - { - return DataLevel.Read(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return DataLevel.Write(offset, source); - } - - protected override Result DoFlush() - { - return DataLevel.Flush(); - } - - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) + if (validities[i] == Validity.Invalid) { - DataLevel?.Dispose(); + result = Validity.Invalid; + if (returnOnError) break; } - base.Dispose(); + logger?.ReportAdd(1); } - /// - /// Checks the hashes of any unchecked blocks and returns the of the data. - /// - /// If , return as soon as an invalid block is found. - /// An optional for reporting progress. - /// The of the data of the specified hash level. - public Validity Validate(bool returnOnError, IProgressReport logger = null) + logger?.SetTotal(0); + return result; + } + + public void FsTrim() + { + foreach (IntegrityVerificationStorage level in IntegrityStorages) { - Validity[] validities = LevelValidities[LevelValidities.Length - 1]; - IntegrityVerificationStorage storage = IntegrityStorages[IntegrityStorages.Length - 1]; - - long blockSize = storage.SectorSize; - int blockCount = (int)BitUtil.DivideUp(Length, blockSize); - - byte[] buffer = new byte[blockSize]; - var result = Validity.Valid; - - logger?.SetTotal(blockCount); - - for (int i = 0; i < blockCount; i++) - { - if (validities[i] == Validity.Unchecked) - { - storage.GetSize(out long storageSize).ThrowIfFailure(); - int toRead = (int)Math.Min(storageSize - blockSize * i, buffer.Length); - - storage.Read(blockSize * i, buffer.AsSpan(0, toRead), IntegrityCheckLevel.IgnoreOnInvalid); - } - - if (validities[i] == Validity.Invalid) - { - result = Validity.Invalid; - if (returnOnError) break; - } - - logger?.ReportAdd(1); - } - - logger?.SetTotal(0); - return result; + level.FsTrim(); } + } - public void FsTrim() - { - foreach (IntegrityVerificationStorage level in IntegrityStorages) - { - level.FsTrim(); - } - } - - private static readonly string[] SaltSources = - { + private static readonly string[] SaltSources = + { "HierarchicalIntegrityVerificationStorage::Master", "HierarchicalIntegrityVerificationStorage::L1", "HierarchicalIntegrityVerificationStorage::L2", @@ -192,88 +192,87 @@ namespace LibHac.FsSystem "HierarchicalIntegrityVerificationStorage::L4", "HierarchicalIntegrityVerificationStorage::L5" }; - } +} - public static class HierarchicalIntegrityVerificationStorageExtensions +public static class HierarchicalIntegrityVerificationStorageExtensions +{ + internal static void SetLevelValidities(this HierarchicalIntegrityVerificationStorage stream, IvfcHeader header) { - internal static void SetLevelValidities(this HierarchicalIntegrityVerificationStorage stream, IvfcHeader header) + for (int i = 0; i < stream.Levels.Length - 1; i++) { - for (int i = 0; i < stream.Levels.Length - 1; i++) + Validity[] level = stream.LevelValidities[i]; + var levelValidity = Validity.Valid; + + foreach (Validity block in level) { - Validity[] level = stream.LevelValidities[i]; - var levelValidity = Validity.Valid; - - foreach (Validity block in level) + if (block == Validity.Invalid) { - if (block == Validity.Invalid) - { - levelValidity = Validity.Invalid; - break; - } - - if (block == Validity.Unchecked && levelValidity != Validity.Invalid) - { - levelValidity = Validity.Unchecked; - } + levelValidity = Validity.Invalid; + break; } - header.LevelHeaders[i].HashValidity = levelValidity; - } - } - } - - public class IvfcHeader - { - public string Magic; - public int Version; - public int MasterHashSize; - public int NumLevels; - public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6]; - public byte[] SaltSource; - public byte[] MasterHash; - - public IvfcHeader() { } - - public IvfcHeader(BinaryReader reader) - { - Magic = reader.ReadAscii(4); - reader.BaseStream.Position += 2; - Version = reader.ReadInt16(); - MasterHashSize = reader.ReadInt32(); - NumLevels = reader.ReadInt32(); - - for (int i = 0; i < LevelHeaders.Length; i++) - { - LevelHeaders[i] = new IvfcLevelHeader(reader); + if (block == Validity.Unchecked && levelValidity != Validity.Invalid) + { + levelValidity = Validity.Unchecked; + } } - SaltSource = reader.ReadBytes(0x20); - - if (reader.BaseStream.Position + 0x20 >= reader.BaseStream.Length) return; - - MasterHash = reader.ReadBytes(0x20); - } - - public IvfcHeader(IStorage storage) : this(new BinaryReader(storage.AsStream())) { } - } - - public class IvfcLevelHeader - { - public long Offset; - public long Size; - public int BlockSizePower; - public uint Reserved; - - public Validity HashValidity = Validity.Unchecked; - - public IvfcLevelHeader() { } - - public IvfcLevelHeader(BinaryReader reader) - { - Offset = reader.ReadInt64(); - Size = reader.ReadInt64(); - BlockSizePower = reader.ReadInt32(); - Reserved = reader.ReadUInt32(); + header.LevelHeaders[i].HashValidity = levelValidity; } } } + +public class IvfcHeader +{ + public string Magic; + public int Version; + public int MasterHashSize; + public int NumLevels; + public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6]; + public byte[] SaltSource; + public byte[] MasterHash; + + public IvfcHeader() { } + + public IvfcHeader(BinaryReader reader) + { + Magic = reader.ReadAscii(4); + reader.BaseStream.Position += 2; + Version = reader.ReadInt16(); + MasterHashSize = reader.ReadInt32(); + NumLevels = reader.ReadInt32(); + + for (int i = 0; i < LevelHeaders.Length; i++) + { + LevelHeaders[i] = new IvfcLevelHeader(reader); + } + + SaltSource = reader.ReadBytes(0x20); + + if (reader.BaseStream.Position + 0x20 >= reader.BaseStream.Length) return; + + MasterHash = reader.ReadBytes(0x20); + } + + public IvfcHeader(IStorage storage) : this(new BinaryReader(storage.AsStream())) { } +} + +public class IvfcLevelHeader +{ + public long Offset; + public long Size; + public int BlockSizePower; + public uint Reserved; + + public Validity HashValidity = Validity.Unchecked; + + public IvfcLevelHeader() { } + + public IvfcLevelHeader(BinaryReader reader) + { + Offset = reader.ReadInt64(); + Size = reader.ReadInt64(); + BlockSizePower = reader.ReadInt32(); + Reserved = reader.ReadUInt32(); + } +} diff --git a/src/LibHac/FsSystem/ISaveDataCommitTimeStampGetter.cs b/src/LibHac/FsSystem/ISaveDataCommitTimeStampGetter.cs index 0738177c..cb10a64c 100644 --- a/src/LibHac/FsSystem/ISaveDataCommitTimeStampGetter.cs +++ b/src/LibHac/FsSystem/ISaveDataCommitTimeStampGetter.cs @@ -1,7 +1,6 @@ -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public interface ISaveDataCommitTimeStampGetter { - public interface ISaveDataCommitTimeStampGetter - { - Result Get(out long timeStamp); - } -} \ No newline at end of file + Result Get(out long timeStamp); +} diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs index 9b05ec5e..801404dd 100644 --- a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs @@ -1,13 +1,12 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public interface ISaveDataExtraDataAccessor : IDisposable { - public interface ISaveDataExtraDataAccessor : IDisposable - { - Result WriteExtraData(in SaveDataExtraData extraData); - Result CommitExtraData(bool updateTimeStamp); - Result ReadExtraData(out SaveDataExtraData extraData); - void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); - } -} \ No newline at end of file + Result WriteExtraData(in SaveDataExtraData extraData); + Result CommitExtraData(bool updateTimeStamp); + Result ReadExtraData(out SaveDataExtraData extraData); + void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); +} diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs index 433940ef..23d0f073 100644 --- a/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs @@ -1,10 +1,9 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable { - public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable - { - void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); - } -} \ No newline at end of file + void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); +} diff --git a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs index fa2f6cb2..087b1f3f 100644 --- a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs +++ b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs @@ -2,13 +2,12 @@ using LibHac.Common; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public interface ISaveDataFileSystemCacheManager : IDisposable { - public interface ISaveDataFileSystemCacheManager : IDisposable - { - bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId); - void Register(ref SharedRef fileSystem); - void Register(ref SharedRef fileSystem); - void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); - } -} \ No newline at end of file + bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId); + void Register(ref SharedRef fileSystem); + void Register(ref SharedRef fileSystem); + void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); +} diff --git a/src/LibHac/FsSystem/IUniqueLock.cs b/src/LibHac/FsSystem/IUniqueLock.cs index 4453f476..4f98579f 100644 --- a/src/LibHac/FsSystem/IUniqueLock.cs +++ b/src/LibHac/FsSystem/IUniqueLock.cs @@ -2,30 +2,29 @@ using LibHac.Common; using LibHac.Os; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public interface IUniqueLock : IDisposable { } + +public class UniqueLockWithPin : IUniqueLock where T : class, IDisposable { - public interface IUniqueLock : IDisposable { } + private UniqueLock _semaphore; + private SharedRef _pinnedObject; - public class UniqueLockWithPin : IUniqueLock where T : class, IDisposable + public UniqueLockWithPin(ref UniqueLock semaphore, ref SharedRef pinnedObject) { - private UniqueLock _semaphore; - private SharedRef _pinnedObject; + _semaphore = new UniqueLock(ref semaphore); + _pinnedObject = SharedRef.CreateMove(ref pinnedObject); + } - public UniqueLockWithPin(ref UniqueLock semaphore, ref SharedRef pinnedObject) + public void Dispose() + { + using (var emptyLock = new UniqueLock()) { - _semaphore = new UniqueLock(ref semaphore); - _pinnedObject = SharedRef.CreateMove(ref pinnedObject); + _semaphore.Set(ref emptyLock.Ref()); } - public void Dispose() - { - using (var emptyLock = new UniqueLock()) - { - _semaphore.Set(ref emptyLock.Ref()); - } - - _pinnedObject.Destroy(); - _semaphore.Dispose(); - } + _pinnedObject.Destroy(); + _semaphore.Dispose(); } } diff --git a/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs b/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs index e60c6714..13fbcdc8 100644 --- a/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs +++ b/src/LibHac/FsSystem/Impl/PartitionFileSystemFormats.cs @@ -1,39 +1,38 @@ using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.FsSystem.Impl +namespace LibHac.FsSystem.Impl; + +public interface IPartitionFileSystemEntry { - public interface IPartitionFileSystemEntry - { - long Offset { get; } - long Size { get; } - int NameOffset { get; } - } - - [StructLayout(LayoutKind.Sequential, Size = 0x18)] - public struct StandardEntry : IPartitionFileSystemEntry - { - public long Offset; - public long Size; - public int NameOffset; - - long IPartitionFileSystemEntry.Offset => Offset; - long IPartitionFileSystemEntry.Size => Size; - int IPartitionFileSystemEntry.NameOffset => NameOffset; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x40)] - public struct HashedEntry : IPartitionFileSystemEntry - { - public long Offset; - public long Size; - public int NameOffset; - public int HashSize; - public long HashOffset; - public Buffer32 Hash; - - long IPartitionFileSystemEntry.Offset => Offset; - long IPartitionFileSystemEntry.Size => Size; - int IPartitionFileSystemEntry.NameOffset => NameOffset; - } + long Offset { get; } + long Size { get; } + int NameOffset { get; } +} + +[StructLayout(LayoutKind.Sequential, Size = 0x18)] +public struct StandardEntry : IPartitionFileSystemEntry +{ + public long Offset; + public long Size; + public int NameOffset; + + long IPartitionFileSystemEntry.Offset => Offset; + long IPartitionFileSystemEntry.Size => Size; + int IPartitionFileSystemEntry.NameOffset => NameOffset; +} + +[StructLayout(LayoutKind.Sequential, Size = 0x40)] +public struct HashedEntry : IPartitionFileSystemEntry +{ + public long Offset; + public long Size; + public int NameOffset; + public int HashSize; + public long HashOffset; + public Buffer32 Hash; + + long IPartitionFileSystemEntry.Offset => Offset; + long IPartitionFileSystemEntry.Size => Size; + int IPartitionFileSystemEntry.NameOffset => NameOffset; } diff --git a/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs b/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs index 5a9cfc7f..840f9b57 100644 --- a/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs +++ b/src/LibHac/FsSystem/Impl/TargetLockedAvoidance.cs @@ -3,33 +3,32 @@ using System; using LibHac.Fs; using LibHac.Os; -namespace LibHac.FsSystem.Impl +namespace LibHac.FsSystem.Impl; + +internal static class TargetLockedAvoidance { - internal static class TargetLockedAvoidance + private const int RetryCount = 2; + private const int SleepTimeMs = 2; + + // Allow usage outside of a Horizon context by using standard .NET APIs + public static Result RetryToAvoidTargetLocked(Func func, FileSystemClient? fs = null) { - private const int RetryCount = 2; - private const int SleepTimeMs = 2; + Result rc = func(); - // Allow usage outside of a Horizon context by using standard .NET APIs - public static Result RetryToAvoidTargetLocked(Func func, FileSystemClient? fs = null) + for (int i = 0; i < RetryCount && ResultFs.TargetLocked.Includes(rc); i++) { - Result rc = func(); - - for (int i = 0; i < RetryCount && ResultFs.TargetLocked.Includes(rc); i++) + if (fs is null) { - if (fs is null) - { - System.Threading.Thread.Sleep(SleepTimeMs); - } - else - { - fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(SleepTimeMs)); - } - - rc = func(); + System.Threading.Thread.Sleep(SleepTimeMs); + } + else + { + fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(SleepTimeMs)); } - return rc; + rc = func(); } + + return rc; } } diff --git a/src/LibHac/FsSystem/IndirectStorage.cs b/src/LibHac/FsSystem/IndirectStorage.cs index ed07578c..dcb1036d 100644 --- a/src/LibHac/FsSystem/IndirectStorage.cs +++ b/src/LibHac/FsSystem/IndirectStorage.cs @@ -5,306 +5,305 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class IndirectStorage : IStorage { - public class IndirectStorage : IStorage + public static readonly int StorageCount = 2; + public static readonly int NodeSize = 1024 * 16; + + private BucketTree Table { get; } = new BucketTree(); + private SubStorage[] DataStorage { get; } = new SubStorage[StorageCount]; + + [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 4)] + public struct Entry { - public static readonly int StorageCount = 2; - public static readonly int NodeSize = 1024 * 16; + private long VirtualOffset; + private long PhysicalOffset; + public int StorageIndex; - private BucketTree Table { get; } = new BucketTree(); - private SubStorage[] DataStorage { get; } = new SubStorage[StorageCount]; + public void SetVirtualOffset(long offset) => VirtualOffset = offset; + public long GetVirtualOffset() => VirtualOffset; - [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 4)] - public struct Entry + public void SetPhysicalOffset(long offset) => PhysicalOffset = offset; + public long GetPhysicalOffset() => PhysicalOffset; + } + + public static long QueryHeaderStorageSize() => BucketTree.QueryHeaderStorageSize(); + + public static long QueryNodeStorageSize(int entryCount) => + BucketTree.QueryNodeStorageSize(NodeSize, Unsafe.SizeOf(), entryCount); + + public static long QueryEntryStorageSize(int entryCount) => + BucketTree.QueryEntryStorageSize(NodeSize, Unsafe.SizeOf(), entryCount); + + public bool IsInitialized() => Table.IsInitialized(); + + public Result Initialize(SubStorage tableStorage) + { + // Read and verify the bucket tree header. + // note: skip init + var header = new BucketTree.Header(); + + Result rc = tableStorage.Read(0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + rc = header.Verify(); + if (rc.IsFailure()) return rc; + + // Determine extents. + long nodeStorageSize = QueryNodeStorageSize(header.EntryCount); + long entryStorageSize = QueryEntryStorageSize(header.EntryCount); + long nodeStorageOffset = QueryHeaderStorageSize(); + long entryStorageOffset = nodeStorageOffset + nodeStorageSize; + + // Initialize. + var nodeStorage = new SubStorage(tableStorage, nodeStorageOffset, nodeStorageSize); + var entryStorage = new SubStorage(tableStorage, entryStorageOffset, entryStorageSize); + + return Initialize(nodeStorage, entryStorage, header.EntryCount); + } + + public Result Initialize(SubStorage nodeStorage, SubStorage entryStorage, int entryCount) + { + return Table.Initialize(nodeStorage, entryStorage, NodeSize, Unsafe.SizeOf(), entryCount); + } + + public void SetStorage(int index, SubStorage storage) + { + Assert.SdkRequiresInRange(index, 0, StorageCount); + DataStorage[index] = storage; + } + + public void SetStorage(int index, IStorage storage, long offset, long size) + { + Assert.SdkRequiresInRange(index, 0, StorageCount); + DataStorage[index] = new SubStorage(storage, offset, size); + } + + public Result GetEntryList(Span entryBuffer, out int outputEntryCount, long offset, long size) + { + // Validate pre-conditions + Assert.SdkRequiresLessEqual(0, offset); + Assert.SdkRequiresLessEqual(0, size); + Assert.SdkRequires(IsInitialized()); + + // Clear the out count + outputEntryCount = 0; + + // Succeed if there's no range + if (size == 0) + return Result.Success; + + // Check that our range is valid + if (!Table.Includes(offset, size)) + return ResultFs.OutOfRange.Log(); + + // Find the offset in our tree + var visitor = new BucketTree.Visitor(); + + try { - private long VirtualOffset; - private long PhysicalOffset; - public int StorageIndex; - - public void SetVirtualOffset(long offset) => VirtualOffset = offset; - public long GetVirtualOffset() => VirtualOffset; - - public void SetPhysicalOffset(long offset) => PhysicalOffset = offset; - public long GetPhysicalOffset() => PhysicalOffset; - } - - public static long QueryHeaderStorageSize() => BucketTree.QueryHeaderStorageSize(); - - public static long QueryNodeStorageSize(int entryCount) => - BucketTree.QueryNodeStorageSize(NodeSize, Unsafe.SizeOf(), entryCount); - - public static long QueryEntryStorageSize(int entryCount) => - BucketTree.QueryEntryStorageSize(NodeSize, Unsafe.SizeOf(), entryCount); - - public bool IsInitialized() => Table.IsInitialized(); - - public Result Initialize(SubStorage tableStorage) - { - // Read and verify the bucket tree header. - // note: skip init - var header = new BucketTree.Header(); - - Result rc = tableStorage.Read(0, SpanHelpers.AsByteSpan(ref header)); + Result rc = Table.Find(ref visitor, offset); if (rc.IsFailure()) return rc; - rc = header.Verify(); - if (rc.IsFailure()) return rc; + long entryOffset = visitor.Get().GetVirtualOffset(); + if (entryOffset > 0 || !Table.Includes(entryOffset)) + return ResultFs.InvalidIndirectEntryOffset.Log(); - // Determine extents. - long nodeStorageSize = QueryNodeStorageSize(header.EntryCount); - long entryStorageSize = QueryEntryStorageSize(header.EntryCount); - long nodeStorageOffset = QueryHeaderStorageSize(); - long entryStorageOffset = nodeStorageOffset + nodeStorageSize; + // Prepare to loop over entries + long endOffset = offset + size; + int count = 0; - // Initialize. - var nodeStorage = new SubStorage(tableStorage, nodeStorageOffset, nodeStorageSize); - var entryStorage = new SubStorage(tableStorage, entryStorageOffset, entryStorageSize); - - return Initialize(nodeStorage, entryStorage, header.EntryCount); - } - - public Result Initialize(SubStorage nodeStorage, SubStorage entryStorage, int entryCount) - { - return Table.Initialize(nodeStorage, entryStorage, NodeSize, Unsafe.SizeOf(), entryCount); - } - - public void SetStorage(int index, SubStorage storage) - { - Assert.SdkRequiresInRange(index, 0, StorageCount); - DataStorage[index] = storage; - } - - public void SetStorage(int index, IStorage storage, long offset, long size) - { - Assert.SdkRequiresInRange(index, 0, StorageCount); - DataStorage[index] = new SubStorage(storage, offset, size); - } - - public Result GetEntryList(Span entryBuffer, out int outputEntryCount, long offset, long size) - { - // Validate pre-conditions - Assert.SdkRequiresLessEqual(0, offset); - Assert.SdkRequiresLessEqual(0, size); - Assert.SdkRequires(IsInitialized()); - - // Clear the out count - outputEntryCount = 0; - - // Succeed if there's no range - if (size == 0) - return Result.Success; - - // Check that our range is valid - if (!Table.Includes(offset, size)) - return ResultFs.OutOfRange.Log(); - - // Find the offset in our tree - var visitor = new BucketTree.Visitor(); - - try + ref Entry currentEntry = ref visitor.Get(); + while (currentEntry.GetVirtualOffset() < endOffset) { - Result rc = Table.Find(ref visitor, offset); - if (rc.IsFailure()) return rc; - - long entryOffset = visitor.Get().GetVirtualOffset(); - if (entryOffset > 0 || !Table.Includes(entryOffset)) - return ResultFs.InvalidIndirectEntryOffset.Log(); - - // Prepare to loop over entries - long endOffset = offset + size; - int count = 0; - - ref Entry currentEntry = ref visitor.Get(); - while (currentEntry.GetVirtualOffset() < endOffset) + // Try to write the entry to the out list + if (entryBuffer.Length != 0) { - // Try to write the entry to the out list - if (entryBuffer.Length != 0) - { - if (count >= entryBuffer.Length) - break; - - entryBuffer[count] = currentEntry; - } - - count++; - - // Advance - if (visitor.CanMoveNext()) - { - rc = visitor.MoveNext(); - if (rc.IsFailure()) return rc; - - currentEntry = ref visitor.Get(); - } - else - { + if (count >= entryBuffer.Length) break; - } + + entryBuffer[count] = currentEntry; } - // Write the entry count - outputEntryCount = count; - return Result.Success; - } - finally { visitor.Dispose(); } - } + count++; - protected override unsafe Result DoRead(long offset, Span destination) - { - // Validate pre-conditions - Assert.SdkRequiresLessEqual(0, offset); - Assert.SdkRequires(IsInitialized()); - - // Succeed if there's nothing to read - if (destination.Length == 0) - return Result.Success; - - // Pin and recreate the span because C# can't use byref-like types in a closure - int bufferSize = destination.Length; - fixed (byte* pBuffer = destination) - { - // Copy the pointer to workaround CS1764. - // OperatePerEntry won't store the delegate anywhere, so it should be safe - byte* pBuffer2 = pBuffer; - - Result Operate(IStorage storage, long dataOffset, long currentOffset, long currentSize) + // Advance + if (visitor.CanMoveNext()) { - var buffer = new Span(pBuffer2, bufferSize); + rc = visitor.MoveNext(); + if (rc.IsFailure()) return rc; - return storage.Read(dataOffset, - buffer.Slice((int)(currentOffset - offset), (int)currentSize)); + currentEntry = ref visitor.Get(); } - - return OperatePerEntry(offset, destination.Length, Operate); - } - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return ResultFs.UnsupportedWriteForIndirectStorage.Log(); - } - - protected override Result DoFlush() - { - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedSetSizeForIndirectStorage.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Table.GetEnd(); - return Result.Success; - } - - private delegate Result OperateFunc(IStorage storage, long dataOffset, long currentOffset, long currentSize); - - private Result OperatePerEntry(long offset, long size, OperateFunc func) - { - // Validate preconditions - Assert.SdkRequiresLessEqual(0, offset); - Assert.SdkRequiresLessEqual(0, size); - Assert.SdkRequires(IsInitialized()); - - // Succeed if there's nothing to operate on - if (size == 0) - return Result.Success; - - // Validate arguments - if (!Table.Includes(offset, size)) - return ResultFs.OutOfRange.Log(); - - // Find the offset in our tree - var visitor = new BucketTree.Visitor(); - - try - { - Result rc = Table.Find(ref visitor, offset); - if (rc.IsFailure()) return rc; - - long entryOffset = visitor.Get().GetVirtualOffset(); - if (entryOffset < 0 || !Table.Includes(entryOffset)) - return ResultFs.InvalidIndirectEntryStorageIndex.Log(); - - // Prepare to operate in chunks - long currentOffset = offset; - long endOffset = offset + size; - - while (currentOffset < endOffset) + else { - // Get the current entry - var currentEntry = visitor.Get(); - - // Get and validate the entry's offset - long currentEntryOffset = currentEntry.GetVirtualOffset(); - if (currentEntryOffset > currentOffset) - return ResultFs.InvalidIndirectEntryOffset.Log(); - - // Validate the storage index - if (currentEntry.StorageIndex < 0 || currentEntry.StorageIndex >= StorageCount) - return ResultFs.InvalidIndirectEntryStorageIndex.Log(); - - // todo: Implement continuous reading - - // Get and validate the next entry offset - long nextEntryOffset; - if (visitor.CanMoveNext()) - { - rc = visitor.MoveNext(); - if (rc.IsFailure()) return rc; - - nextEntryOffset = visitor.Get().GetVirtualOffset(); - if (!Table.Includes(nextEntryOffset)) - return ResultFs.InvalidIndirectEntryOffset.Log(); - } - else - { - nextEntryOffset = Table.GetEnd(); - } - - if (currentOffset >= nextEntryOffset) - return ResultFs.InvalidIndirectEntryOffset.Log(); - - // Get the offset of the entry in the data we read - long dataOffset = currentOffset - currentEntryOffset; - long dataSize = nextEntryOffset - currentEntryOffset - dataOffset; - Assert.SdkLess(0, dataSize); - - // Determine how much is left - long remainingSize = endOffset - currentOffset; - long currentSize = Math.Min(remainingSize, dataSize); - Assert.SdkLessEqual(currentSize, size); - - { - SubStorage currentStorage = DataStorage[currentEntry.StorageIndex]; - - // Get the current data storage's size. - rc = currentStorage.GetSize(out long currentDataStorageSize); - if (rc.IsFailure()) return rc; - - // Ensure that we remain within range. - long currentEntryPhysicalOffset = currentEntry.GetPhysicalOffset(); - - if (currentEntryPhysicalOffset < 0 || currentEntryPhysicalOffset > currentDataStorageSize) - return ResultFs.IndirectStorageCorrupted.Log(); - - if (currentDataStorageSize < currentEntryPhysicalOffset + dataOffset + currentSize) - return ResultFs.IndirectStorageCorrupted.Log(); - - rc = func(currentStorage, currentEntryPhysicalOffset + dataOffset, currentOffset, currentSize); - if (rc.IsFailure()) return rc; - } - - currentOffset += currentSize; + break; } } - finally { visitor.Dispose(); } + // Write the entry count + outputEntryCount = count; return Result.Success; } + finally { visitor.Dispose(); } + } + + protected override unsafe Result DoRead(long offset, Span destination) + { + // Validate pre-conditions + Assert.SdkRequiresLessEqual(0, offset); + Assert.SdkRequires(IsInitialized()); + + // Succeed if there's nothing to read + if (destination.Length == 0) + return Result.Success; + + // Pin and recreate the span because C# can't use byref-like types in a closure + int bufferSize = destination.Length; + fixed (byte* pBuffer = destination) + { + // Copy the pointer to workaround CS1764. + // OperatePerEntry won't store the delegate anywhere, so it should be safe + byte* pBuffer2 = pBuffer; + + Result Operate(IStorage storage, long dataOffset, long currentOffset, long currentSize) + { + var buffer = new Span(pBuffer2, bufferSize); + + return storage.Read(dataOffset, + buffer.Slice((int)(currentOffset - offset), (int)currentSize)); + } + + return OperatePerEntry(offset, destination.Length, Operate); + } + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedWriteForIndirectStorage.Log(); + } + + protected override Result DoFlush() + { + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedSetSizeForIndirectStorage.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Table.GetEnd(); + return Result.Success; + } + + private delegate Result OperateFunc(IStorage storage, long dataOffset, long currentOffset, long currentSize); + + private Result OperatePerEntry(long offset, long size, OperateFunc func) + { + // Validate preconditions + Assert.SdkRequiresLessEqual(0, offset); + Assert.SdkRequiresLessEqual(0, size); + Assert.SdkRequires(IsInitialized()); + + // Succeed if there's nothing to operate on + if (size == 0) + return Result.Success; + + // Validate arguments + if (!Table.Includes(offset, size)) + return ResultFs.OutOfRange.Log(); + + // Find the offset in our tree + var visitor = new BucketTree.Visitor(); + + try + { + Result rc = Table.Find(ref visitor, offset); + if (rc.IsFailure()) return rc; + + long entryOffset = visitor.Get().GetVirtualOffset(); + if (entryOffset < 0 || !Table.Includes(entryOffset)) + return ResultFs.InvalidIndirectEntryStorageIndex.Log(); + + // Prepare to operate in chunks + long currentOffset = offset; + long endOffset = offset + size; + + while (currentOffset < endOffset) + { + // Get the current entry + var currentEntry = visitor.Get(); + + // Get and validate the entry's offset + long currentEntryOffset = currentEntry.GetVirtualOffset(); + if (currentEntryOffset > currentOffset) + return ResultFs.InvalidIndirectEntryOffset.Log(); + + // Validate the storage index + if (currentEntry.StorageIndex < 0 || currentEntry.StorageIndex >= StorageCount) + return ResultFs.InvalidIndirectEntryStorageIndex.Log(); + + // todo: Implement continuous reading + + // Get and validate the next entry offset + long nextEntryOffset; + if (visitor.CanMoveNext()) + { + rc = visitor.MoveNext(); + if (rc.IsFailure()) return rc; + + nextEntryOffset = visitor.Get().GetVirtualOffset(); + if (!Table.Includes(nextEntryOffset)) + return ResultFs.InvalidIndirectEntryOffset.Log(); + } + else + { + nextEntryOffset = Table.GetEnd(); + } + + if (currentOffset >= nextEntryOffset) + return ResultFs.InvalidIndirectEntryOffset.Log(); + + // Get the offset of the entry in the data we read + long dataOffset = currentOffset - currentEntryOffset; + long dataSize = nextEntryOffset - currentEntryOffset - dataOffset; + Assert.SdkLess(0, dataSize); + + // Determine how much is left + long remainingSize = endOffset - currentOffset; + long currentSize = Math.Min(remainingSize, dataSize); + Assert.SdkLessEqual(currentSize, size); + + { + SubStorage currentStorage = DataStorage[currentEntry.StorageIndex]; + + // Get the current data storage's size. + rc = currentStorage.GetSize(out long currentDataStorageSize); + if (rc.IsFailure()) return rc; + + // Ensure that we remain within range. + long currentEntryPhysicalOffset = currentEntry.GetPhysicalOffset(); + + if (currentEntryPhysicalOffset < 0 || currentEntryPhysicalOffset > currentDataStorageSize) + return ResultFs.IndirectStorageCorrupted.Log(); + + if (currentDataStorageSize < currentEntryPhysicalOffset + dataOffset + currentSize) + return ResultFs.IndirectStorageCorrupted.Log(); + + rc = func(currentStorage, currentEntryPhysicalOffset + dataOffset, currentOffset, currentSize); + if (rc.IsFailure()) return rc; + } + + currentOffset += currentSize; + } + } + finally { visitor.Dispose(); } + + return Result.Success; } } diff --git a/src/LibHac/FsSystem/IntegrityVerificationStorage.cs b/src/LibHac/FsSystem/IntegrityVerificationStorage.cs index 2b07770c..c3129884 100644 --- a/src/LibHac/FsSystem/IntegrityVerificationStorage.cs +++ b/src/LibHac/FsSystem/IntegrityVerificationStorage.cs @@ -5,250 +5,249 @@ using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSystem.Save; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class IntegrityVerificationStorage : SectorStorage { - public class IntegrityVerificationStorage : SectorStorage + private const int DigestSize = 0x20; + + private IStorage HashStorage { get; } + public IntegrityCheckLevel IntegrityCheckLevel { get; } + public Validity[] BlockValidities { get; } + + private byte[] Salt { get; } + private IntegrityStorageType Type { get; } + + private readonly IHash _hash = Sha256.CreateSha256Generator(); + private readonly object _locker = new object(); + + public IntegrityVerificationStorage(IntegrityVerificationInfo info, IStorage hashStorage, + IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + : base(info.Data, info.BlockSize, leaveOpen) { - private const int DigestSize = 0x20; + HashStorage = hashStorage; + IntegrityCheckLevel = integrityCheckLevel; + Salt = info.Salt; + Type = info.Type; - private IStorage HashStorage { get; } - public IntegrityCheckLevel IntegrityCheckLevel { get; } - public Validity[] BlockValidities { get; } + BlockValidities = new Validity[SectorCount]; + } - private byte[] Salt { get; } - private IntegrityStorageType Type { get; } + private Result ReadImpl(long offset, Span destination, IntegrityCheckLevel integrityCheckLevel) + { + int count = destination.Length; - private readonly IHash _hash = Sha256.CreateSha256Generator(); - private readonly object _locker = new object(); + if (count < 0 || count > SectorSize) + throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid."); - public IntegrityVerificationStorage(IntegrityVerificationInfo info, IStorage hashStorage, - IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) - : base(info.Data, info.BlockSize, leaveOpen) + long blockIndex = offset / SectorSize; + + if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) { - HashStorage = hashStorage; - IntegrityCheckLevel = integrityCheckLevel; - Salt = info.Salt; - Type = info.Type; - - BlockValidities = new Validity[SectorCount]; + // Todo: Differentiate between the top and lower layers + ThrowHelper.ThrowResult(ResultFs.NonRealDataVerificationFailed.Value, "Hash error!"); } - private Result ReadImpl(long offset, Span destination, IntegrityCheckLevel integrityCheckLevel) + bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None && + BlockValidities[blockIndex] == Validity.Unchecked; + + if (Type != IntegrityStorageType.Save && !needsHashCheck) { - int count = destination.Length; + BaseStorage.Read(offset, destination); + return Result.Success; + } - if (count < 0 || count > SectorSize) - throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid."); + Span hashBuffer = stackalloc byte[DigestSize]; + long hashPos = blockIndex * DigestSize; + HashStorage.Read(hashPos, hashBuffer); - long blockIndex = offset / SectorSize; - - if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) + if (Type == IntegrityStorageType.Save) + { + if (Utilities.IsZeros(hashBuffer)) { - // Todo: Differentiate between the top and lower layers - ThrowHelper.ThrowResult(ResultFs.NonRealDataVerificationFailed.Value, "Hash error!"); + destination.Clear(); + BlockValidities[blockIndex] = Validity.Valid; + return Result.Success; } - bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None && - BlockValidities[blockIndex] == Validity.Unchecked; - - if (Type != IntegrityStorageType.Save && !needsHashCheck) + if (!needsHashCheck) { BaseStorage.Read(offset, destination); return Result.Success; } + } - Span hashBuffer = stackalloc byte[DigestSize]; - long hashPos = blockIndex * DigestSize; - HashStorage.Read(hashPos, hashBuffer); + byte[] dataBuffer = ArrayPool.Shared.Rent(SectorSize); + try + { + BaseStorage.Read(offset, destination); + destination.CopyTo(dataBuffer); + + if (BlockValidities[blockIndex] != Validity.Unchecked) return Result.Success; + + int bytesToHash = SectorSize; + + if (count < SectorSize) + { + // Pad out unused portion of block + Array.Clear(dataBuffer, count, SectorSize - count); + + // Partition FS hashes don't pad out an incomplete block + if (Type == IntegrityStorageType.PartitionFs) + { + bytesToHash = count; + } + } + + byte[] hash = DoHash(dataBuffer, 0, bytesToHash); + + Validity validity = Utilities.SpansEqual(hashBuffer, hash) ? Validity.Valid : Validity.Invalid; + BlockValidities[blockIndex] = validity; + + if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) + { + ThrowHelper.ThrowResult(ResultFs.NonRealDataVerificationFailed.Value, "Hash error!"); + } + } + finally + { + ArrayPool.Shared.Return(dataBuffer); + } + + return Result.Success; + } + + protected override Result DoRead(long offset, Span destination) + { + return ReadImpl(offset, destination, IntegrityCheckLevel); + } + + public Result Read(long offset, Span destination, IntegrityCheckLevel integrityCheckLevel) + { + // ValidateParameters(destination, offset); + return ReadImpl(offset, destination, integrityCheckLevel); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + long blockIndex = offset / SectorSize; + long hashPos = blockIndex * DigestSize; + + Result rc = GetSize(out long storageSize); + if (rc.IsFailure()) return rc; + + int toWrite = (int)Math.Min(source.Length, storageSize - offset); + + byte[] dataBuffer = ArrayPool.Shared.Rent(SectorSize); + try + { + source.CopyTo(dataBuffer); + byte[] hash = DoHash(dataBuffer, 0, toWrite); + + if (Type == IntegrityStorageType.Save && source.IsZeros()) + { + Array.Clear(hash, 0, DigestSize); + } + + BaseStorage.Write(offset, source); + + HashStorage.Write(hashPos, hash); + BlockValidities[blockIndex] = Validity.Unchecked; + } + finally + { + ArrayPool.Shared.Return(dataBuffer); + } + + return Result.Success; + } + + private byte[] DoHash(byte[] buffer, int offset, int count) + { + lock (_locker) + { + _hash.Initialize(); if (Type == IntegrityStorageType.Save) { - if (Utilities.IsZeros(hashBuffer)) - { - destination.Clear(); - BlockValidities[blockIndex] = Validity.Valid; - return Result.Success; - } - - if (!needsHashCheck) - { - BaseStorage.Read(offset, destination); - return Result.Success; - } + _hash.Update(Salt); } - byte[] dataBuffer = ArrayPool.Shared.Rent(SectorSize); - try + _hash.Update(buffer.AsSpan(offset, count)); + + byte[] hash = new byte[Sha256.DigestSize]; + _hash.GetHash(hash); + + if (Type == IntegrityStorageType.Save) { - BaseStorage.Read(offset, destination); - destination.CopyTo(dataBuffer); - - if (BlockValidities[blockIndex] != Validity.Unchecked) return Result.Success; - - int bytesToHash = SectorSize; - - if (count < SectorSize) - { - // Pad out unused portion of block - Array.Clear(dataBuffer, count, SectorSize - count); - - // Partition FS hashes don't pad out an incomplete block - if (Type == IntegrityStorageType.PartitionFs) - { - bytesToHash = count; - } - } - - byte[] hash = DoHash(dataBuffer, 0, bytesToHash); - - Validity validity = Utilities.SpansEqual(hashBuffer, hash) ? Validity.Valid : Validity.Invalid; - BlockValidities[blockIndex] = validity; - - if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) - { - ThrowHelper.ThrowResult(ResultFs.NonRealDataVerificationFailed.Value, "Hash error!"); - } - } - finally - { - ArrayPool.Shared.Return(dataBuffer); + // This bit is set on all save hashes + hash[0x1F] |= 0b10000000; } - return Result.Success; - } - - protected override Result DoRead(long offset, Span destination) - { - return ReadImpl(offset, destination, IntegrityCheckLevel); - } - - public Result Read(long offset, Span destination, IntegrityCheckLevel integrityCheckLevel) - { - // ValidateParameters(destination, offset); - return ReadImpl(offset, destination, integrityCheckLevel); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - long blockIndex = offset / SectorSize; - long hashPos = blockIndex * DigestSize; - - Result rc = GetSize(out long storageSize); - if (rc.IsFailure()) return rc; - - int toWrite = (int)Math.Min(source.Length, storageSize - offset); - - byte[] dataBuffer = ArrayPool.Shared.Rent(SectorSize); - try - { - source.CopyTo(dataBuffer); - byte[] hash = DoHash(dataBuffer, 0, toWrite); - - if (Type == IntegrityStorageType.Save && source.IsZeros()) - { - Array.Clear(hash, 0, DigestSize); - } - - BaseStorage.Write(offset, source); - - HashStorage.Write(hashPos, hash); - BlockValidities[blockIndex] = Validity.Unchecked; - } - finally - { - ArrayPool.Shared.Return(dataBuffer); - } - - return Result.Success; - } - - private byte[] DoHash(byte[] buffer, int offset, int count) - { - lock (_locker) - { - _hash.Initialize(); - - if (Type == IntegrityStorageType.Save) - { - _hash.Update(Salt); - } - - _hash.Update(buffer.AsSpan(offset, count)); - - byte[] hash = new byte[Sha256.DigestSize]; - _hash.GetHash(hash); - - if (Type == IntegrityStorageType.Save) - { - // This bit is set on all save hashes - hash[0x1F] |= 0b10000000; - } - - return hash; - } - } - - protected override Result DoFlush() - { - Result rc = HashStorage.Flush(); - if (rc.IsFailure()) return rc; - - return base.DoFlush(); - } - - public void FsTrim() - { - if (Type != IntegrityStorageType.Save) return; - - Span digest = stackalloc byte[DigestSize]; - - for (int i = 0; i < SectorCount; i++) - { - long hashPos = i * DigestSize; - HashStorage.Read(hashPos, digest).ThrowIfFailure(); - - if (!Utilities.IsZeros(digest)) continue; - - int dataOffset = i * SectorSize; - BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, dataOffset, SectorSize); - } + return hash; } } - /// - /// Information for creating an - /// - public class IntegrityVerificationInfo + protected override Result DoFlush() { - public IStorage Data { get; set; } - public int BlockSize { get; set; } - public byte[] Salt { get; set; } - public IntegrityStorageType Type { get; set; } + Result rc = HashStorage.Flush(); + if (rc.IsFailure()) return rc; + + return base.DoFlush(); } - public enum IntegrityStorageType + public void FsTrim() { - Save, - RomFs, - PartitionFs - } + if (Type != IntegrityStorageType.Save) return; - /// - /// Represents the level of integrity checks to be performed. - /// - public enum IntegrityCheckLevel - { - /// - /// No integrity checks will be performed. - /// - None, - /// - /// Invalid blocks will be marked as invalid when read, and will not cause an error. - /// - IgnoreOnInvalid, - /// - /// An will be thrown if an integrity check fails. - /// - ErrorOnInvalid + Span digest = stackalloc byte[DigestSize]; + + for (int i = 0; i < SectorCount; i++) + { + long hashPos = i * DigestSize; + HashStorage.Read(hashPos, digest).ThrowIfFailure(); + + if (!Utilities.IsZeros(digest)) continue; + + int dataOffset = i * SectorSize; + BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, dataOffset, SectorSize); + } } } + +/// +/// Information for creating an +/// +public class IntegrityVerificationInfo +{ + public IStorage Data { get; set; } + public int BlockSize { get; set; } + public byte[] Salt { get; set; } + public IntegrityStorageType Type { get; set; } +} + +public enum IntegrityStorageType +{ + Save, + RomFs, + PartitionFs +} + +/// +/// Represents the level of integrity checks to be performed. +/// +public enum IntegrityCheckLevel +{ + /// + /// No integrity checks will be performed. + /// + None, + /// + /// Invalid blocks will be marked as invalid when read, and will not cause an error. + /// + IgnoreOnInvalid, + /// + /// An will be thrown if an integrity check fails. + /// + ErrorOnInvalid +} diff --git a/src/LibHac/FsSystem/LayeredFileSystem.cs b/src/LibHac/FsSystem/LayeredFileSystem.cs index 1435e211..9070d4fb 100644 --- a/src/LibHac/FsSystem/LayeredFileSystem.cs +++ b/src/LibHac/FsSystem/LayeredFileSystem.cs @@ -5,296 +5,295 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class LayeredFileSystem : IFileSystem { - public class LayeredFileSystem : IFileSystem + /// + /// List of source s. + /// Filesystems at the beginning of the list will take precedence over those later in the list. + /// + private List Sources { get; } = new List(); + + /// + /// Creates a new from the input objects. + /// + /// The base . + /// The to be layered on top of the . + public LayeredFileSystem(IFileSystem lowerFileSystem, IFileSystem upperFileSystem) { - /// - /// List of source s. - /// Filesystems at the beginning of the list will take precedence over those later in the list. - /// - private List Sources { get; } = new List(); + Sources.Add(upperFileSystem); + Sources.Add(lowerFileSystem); + } - /// - /// Creates a new from the input objects. - /// - /// The base . - /// The to be layered on top of the . - public LayeredFileSystem(IFileSystem lowerFileSystem, IFileSystem upperFileSystem) + /// + /// Creates a new from the input objects. + /// + /// An containing the s + /// used to create the . Filesystems at the beginning of the list will take + /// precedence over those later in the list. + public LayeredFileSystem(IList sourceFileSystems) + { + Sources.AddRange(sourceFileSystems); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + // Open directories from all layers so they can be merged + // Only allocate the list for multiple sources if needed + List multipleSources = null; + IFileSystem singleSource = null; + + foreach (IFileSystem fs in Sources) { - Sources.Add(upperFileSystem); - Sources.Add(lowerFileSystem); - } + Result rc = fs.GetEntryType(out DirectoryEntryType entryType, in path); - /// - /// Creates a new from the input objects. - /// - /// An containing the s - /// used to create the . Filesystems at the beginning of the list will take - /// precedence over those later in the list. - public LayeredFileSystem(IList sourceFileSystems) - { - Sources.AddRange(sourceFileSystems); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - // Open directories from all layers so they can be merged - // Only allocate the list for multiple sources if needed - List multipleSources = null; - IFileSystem singleSource = null; - - foreach (IFileSystem fs in Sources) + if (rc.IsSuccess()) { - Result rc = fs.GetEntryType(out DirectoryEntryType entryType, in path); - - if (rc.IsSuccess()) + // There were no directories with this path in higher levels, so the entry is a file + if (entryType == DirectoryEntryType.File && singleSource is null) { - // There were no directories with this path in higher levels, so the entry is a file - if (entryType == DirectoryEntryType.File && singleSource is null) - { - return ResultFs.PathNotFound.Log(); - } - - if (entryType == DirectoryEntryType.Directory) - { - if (singleSource is null) - { - singleSource = fs; - } - else if (multipleSources is null) - { - multipleSources = new List { singleSource, fs }; - } - else - { - multipleSources.Add(fs); - } - } + return ResultFs.PathNotFound.Log(); } - else if (!ResultFs.PathNotFound.Includes(rc)) + + if (entryType == DirectoryEntryType.Directory) { - return rc; + if (singleSource is null) + { + singleSource = fs; + } + else if (multipleSources is null) + { + multipleSources = new List { singleSource, fs }; + } + else + { + multipleSources.Add(fs); + } } } - - if (!(multipleSources is null)) + else if (!ResultFs.PathNotFound.Includes(rc)) { - using var dir = new UniqueRef(new MergedDirectory(multipleSources, mode)); - Result rc = dir.Get.Initialize(in path); - - if (rc.IsSuccess()) - { - outDirectory.Set(ref dir.Ref()); - } - return rc; } + } - if (!(singleSource is null)) + if (!(multipleSources is null)) + { + using var dir = new UniqueRef(new MergedDirectory(multipleSources, mode)); + Result rc = dir.Get.Initialize(in path); + + if (rc.IsSuccess()) { - using var dir = new UniqueRef(); - Result rc = singleSource.OpenDirectory(ref dir.Ref(), in path, mode); + outDirectory.Set(ref dir.Ref()); + } - if (rc.IsSuccess()) + return rc; + } + + if (!(singleSource is null)) + { + using var dir = new UniqueRef(); + Result rc = singleSource.OpenDirectory(ref dir.Ref(), in path, mode); + + if (rc.IsSuccess()) + { + outDirectory.Set(ref dir.Ref()); + } + + return rc; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + foreach (IFileSystem fs in Sources) + { + Result rc = fs.GetEntryType(out DirectoryEntryType type, path); + + if (rc.IsSuccess()) + { + if (type == DirectoryEntryType.File) { - outDirectory.Set(ref dir.Ref()); + return fs.OpenFile(ref outFile, path, mode); } + if (type == DirectoryEntryType.Directory) + { + return ResultFs.PathNotFound.Log(); + } + } + else if (!ResultFs.PathNotFound.Includes(rc)) + { return rc; } - - return ResultFs.PathNotFound.Log(); } - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + foreach (IFileSystem fs in Sources) { - foreach (IFileSystem fs in Sources) + Result getEntryResult = fs.GetEntryType(out DirectoryEntryType type, path); + + if (getEntryResult.IsSuccess()) { - Result rc = fs.GetEntryType(out DirectoryEntryType type, path); + entryType = type; + return Result.Success; + } + } - if (rc.IsSuccess()) - { - if (type == DirectoryEntryType.File) - { - return fs.OpenFile(ref outFile, path, mode); - } + return ResultFs.PathNotFound.Log(); + } - if (type == DirectoryEntryType.Directory) - { - return ResultFs.PathNotFound.Log(); - } - } - else if (!ResultFs.PathNotFound.Includes(rc)) - { - return rc; - } + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + foreach (IFileSystem fs in Sources) + { + Result getEntryResult = fs.GetEntryType(out _, path); + + if (getEntryResult.IsSuccess()) + { + return fs.GetFileTimeStampRaw(out timeStamp, path); + } + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + foreach (IFileSystem fs in Sources) + { + Result getEntryResult = fs.GetEntryType(out _, path); + + if (getEntryResult.IsSuccess()) + { + return fs.QueryEntry(outBuffer, inBuffer, queryId, path); + } + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log(); + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log(); + + private class MergedDirectory : IDirectory + { + // Needed to open new directories for GetEntryCount + private List SourceFileSystems { get; } + private List SourceDirs { get; } + private Path.Stored _path; + private OpenDirectoryMode Mode { get; } + + // todo: Efficient way to remove duplicates + private HashSet Names { get; } = new HashSet(); + + public MergedDirectory(List sourceFileSystems, OpenDirectoryMode mode) + { + SourceFileSystems = sourceFileSystems; + SourceDirs = new List(sourceFileSystems.Count); + Mode = mode; + } + + public Result Initialize(in Path path) + { + Result rc = _path.Initialize(in path); + if (rc.IsFailure()) return rc; + + using var dir = new UniqueRef(); + + foreach (IFileSystem fs in SourceFileSystems) + { + rc = fs.OpenDirectory(ref dir.Ref(), in path, Mode); + if (rc.IsFailure()) return rc; + + SourceDirs.Add(dir.Release()); } - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - foreach (IFileSystem fs in Sources) - { - Result getEntryResult = fs.GetEntryType(out DirectoryEntryType type, path); - - if (getEntryResult.IsSuccess()) - { - entryType = type; - return Result.Success; - } - } - - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); - - foreach (IFileSystem fs in Sources) - { - Result getEntryResult = fs.GetEntryType(out _, path); - - if (getEntryResult.IsSuccess()) - { - return fs.GetFileTimeStampRaw(out timeStamp, path); - } - } - - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - foreach (IFileSystem fs in Sources) - { - Result getEntryResult = fs.GetEntryType(out _, path); - - if (getEntryResult.IsSuccess()) - { - return fs.QueryEntry(outBuffer, inBuffer, queryId, path); - } - } - - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoCommit() - { return Result.Success; } - protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log(); - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedOperation.Log(); - - private class MergedDirectory : IDirectory + protected override Result DoRead(out long entriesRead, Span entryBuffer) { - // Needed to open new directories for GetEntryCount - private List SourceFileSystems { get; } - private List SourceDirs { get; } - private Path.Stored _path; - private OpenDirectoryMode Mode { get; } + entriesRead = 0; + int entryIndex = 0; + + for (int i = 0; i < SourceDirs.Count && entryIndex < entryBuffer.Length; i++) + { + long subEntriesRead; + + do + { + Result rs = SourceDirs[i].Read(out subEntriesRead, entryBuffer.Slice(entryIndex, 1)); + if (rs.IsFailure()) return rs; + + if (subEntriesRead == 1 && Names.Add(StringUtils.Utf8ZToString(entryBuffer[entryIndex].Name))) + { + entryIndex++; + } + } while (subEntriesRead != 0 && entryIndex < entryBuffer.Length); + } + + entriesRead = entryIndex; + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + entryCount = 0; + long totalEntryCount = 0; + var entry = new DirectoryEntry(); // todo: Efficient way to remove duplicates - private HashSet Names { get; } = new HashSet(); + var names = new HashSet(); - public MergedDirectory(List sourceFileSystems, OpenDirectoryMode mode) - { - SourceFileSystems = sourceFileSystems; - SourceDirs = new List(sourceFileSystems.Count); - Mode = mode; - } + using Path path = _path.DangerousGetPath(); + using var dir = new UniqueRef(); - public Result Initialize(in Path path) + // Open new directories for each source because we need to remove duplicate entries + foreach (IFileSystem fs in SourceFileSystems) { - Result rc = _path.Initialize(in path); + Result rc = fs.OpenDirectory(ref dir.Ref(), in path, Mode); if (rc.IsFailure()) return rc; - using var dir = new UniqueRef(); - - foreach (IFileSystem fs in SourceFileSystems) + long entriesRead; + do { - rc = fs.OpenDirectory(ref dir.Ref(), in path, Mode); + rc = dir.Get.Read(out entriesRead, SpanHelpers.AsSpan(ref entry)); if (rc.IsFailure()) return rc; - SourceDirs.Add(dir.Release()); - } - - return Result.Success; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - entriesRead = 0; - int entryIndex = 0; - - for (int i = 0; i < SourceDirs.Count && entryIndex < entryBuffer.Length; i++) - { - long subEntriesRead; - - do + if (entriesRead == 1 && names.Add(StringUtils.Utf8ZToString(entry.Name))) { - Result rs = SourceDirs[i].Read(out subEntriesRead, entryBuffer.Slice(entryIndex, 1)); - if (rs.IsFailure()) return rs; - - if (subEntriesRead == 1 && Names.Add(StringUtils.Utf8ZToString(entryBuffer[entryIndex].Name))) - { - entryIndex++; - } - } while (subEntriesRead != 0 && entryIndex < entryBuffer.Length); - } - - entriesRead = entryIndex; - return Result.Success; + totalEntryCount++; + } + } while (entriesRead != 0); } - protected override Result DoGetEntryCount(out long entryCount) - { - entryCount = 0; - long totalEntryCount = 0; - var entry = new DirectoryEntry(); - - // todo: Efficient way to remove duplicates - var names = new HashSet(); - - using Path path = _path.DangerousGetPath(); - using var dir = new UniqueRef(); - - // Open new directories for each source because we need to remove duplicate entries - foreach (IFileSystem fs in SourceFileSystems) - { - Result rc = fs.OpenDirectory(ref dir.Ref(), in path, Mode); - if (rc.IsFailure()) return rc; - - long entriesRead; - do - { - rc = dir.Get.Read(out entriesRead, SpanHelpers.AsSpan(ref entry)); - if (rc.IsFailure()) return rc; - - if (entriesRead == 1 && names.Add(StringUtils.Utf8ZToString(entry.Name))) - { - totalEntryCount++; - } - } while (entriesRead != 0); - } - - entryCount = totalEntryCount; - return Result.Success; - } + entryCount = totalEntryCount; + return Result.Success; } } } diff --git a/src/LibHac/FsSystem/LocalDirectory.cs b/src/LibHac/FsSystem/LocalDirectory.cs index 9afe2bd3..462ffd4c 100644 --- a/src/LibHac/FsSystem/LocalDirectory.cs +++ b/src/LibHac/FsSystem/LocalDirectory.cs @@ -6,73 +6,72 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class LocalDirectory : IDirectory { - public class LocalDirectory : IDirectory + private OpenDirectoryMode Mode { get; } + private DirectoryInfo DirInfo { get; } + private IEnumerator EntryEnumerator { get; } + + public LocalDirectory(IEnumerator entryEnumerator, DirectoryInfo dirInfo, + OpenDirectoryMode mode) { - private OpenDirectoryMode Mode { get; } - private DirectoryInfo DirInfo { get; } - private IEnumerator EntryEnumerator { get; } + EntryEnumerator = entryEnumerator; + DirInfo = dirInfo; + Mode = mode; + } - public LocalDirectory(IEnumerator entryEnumerator, DirectoryInfo dirInfo, - OpenDirectoryMode mode) + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + int i = 0; + + while (i < entryBuffer.Length && EntryEnumerator.MoveNext()) { - EntryEnumerator = entryEnumerator; - DirInfo = dirInfo; - Mode = mode; + FileSystemInfo localEntry = EntryEnumerator.Current; + if (localEntry == null) break; + + bool isDir = localEntry.Attributes.HasFlag(FileAttributes.Directory); + + if (!CanReturnEntry(isDir, Mode)) continue; + + ReadOnlySpan name = StringUtils.StringToUtf8(localEntry.Name); + DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File; + long length = isDir ? 0 : ((FileInfo)localEntry).Length; + + StringUtils.Copy(entryBuffer[i].Name, name); + entryBuffer[i].Name[PathTools.MaxPathLength] = 0; + + entryBuffer[i].Attributes = localEntry.Attributes.ToNxAttributes(); + entryBuffer[i].Type = type; + entryBuffer[i].Size = length; + + i++; } - protected override Result DoRead(out long entriesRead, Span entryBuffer) + entriesRead = i; + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + int count = 0; + + foreach (FileSystemInfo entry in DirInfo.EnumerateFileSystemInfos()) { - int i = 0; + bool isDir = (entry.Attributes & FileAttributes.Directory) != 0; - while (i < entryBuffer.Length && EntryEnumerator.MoveNext()) - { - FileSystemInfo localEntry = EntryEnumerator.Current; - if (localEntry == null) break; - - bool isDir = localEntry.Attributes.HasFlag(FileAttributes.Directory); - - if (!CanReturnEntry(isDir, Mode)) continue; - - ReadOnlySpan name = StringUtils.StringToUtf8(localEntry.Name); - DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File; - long length = isDir ? 0 : ((FileInfo)localEntry).Length; - - StringUtils.Copy(entryBuffer[i].Name, name); - entryBuffer[i].Name[PathTools.MaxPathLength] = 0; - - entryBuffer[i].Attributes = localEntry.Attributes.ToNxAttributes(); - entryBuffer[i].Type = type; - entryBuffer[i].Size = length; - - i++; - } - - entriesRead = i; - return Result.Success; + if (CanReturnEntry(isDir, Mode)) count++; } - protected override Result DoGetEntryCount(out long entryCount) - { - int count = 0; + entryCount = count; + return Result.Success; + } - foreach (FileSystemInfo entry in DirInfo.EnumerateFileSystemInfos()) - { - bool isDir = (entry.Attributes & FileAttributes.Directory) != 0; - - if (CanReturnEntry(isDir, Mode)) count++; - } - - entryCount = count; - return Result.Success; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool CanReturnEntry(bool isDir, OpenDirectoryMode mode) - { - return isDir && (mode & OpenDirectoryMode.Directory) != 0 || - !isDir && (mode & OpenDirectoryMode.File) != 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CanReturnEntry(bool isDir, OpenDirectoryMode mode) + { + return isDir && (mode & OpenDirectoryMode.Directory) != 0 || + !isDir && (mode & OpenDirectoryMode.File) != 0; } } diff --git a/src/LibHac/FsSystem/LocalFile.cs b/src/LibHac/FsSystem/LocalFile.cs index a524c5f1..86c7793e 100644 --- a/src/LibHac/FsSystem/LocalFile.cs +++ b/src/LibHac/FsSystem/LocalFile.cs @@ -4,100 +4,99 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class LocalFile : IFile { - public class LocalFile : IFile + private FileStream Stream { get; } + private StreamFile File { get; } + private OpenMode Mode { get; } + + public LocalFile(string path, OpenMode mode) { - private FileStream Stream { get; } - private StreamFile File { get; } - private OpenMode Mode { get; } + LocalFileSystem.OpenFileInternal(out FileStream stream, path, mode).ThrowIfFailure(); - public LocalFile(string path, OpenMode mode) + Mode = mode; + Stream = stream; + File = new StreamFile(Stream, mode); + } + + public LocalFile(FileStream stream, OpenMode mode) + { + Mode = mode; + Stream = stream; + File = new StreamFile(Stream, mode); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + bytesRead = 0; + + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + return File.Read(out bytesRead, offset, destination.Slice(0, (int)toRead), option); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out _, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + return File.Write(offset, source, option); + } + + protected override Result DoFlush() + { + try { - LocalFileSystem.OpenFileInternal(out FileStream stream, path, mode).ThrowIfFailure(); - - Mode = mode; - Stream = stream; - File = new StreamFile(Stream, mode); + return File.Flush(); } - - public LocalFile(FileStream stream, OpenMode mode) + catch (Exception ex) when (ex.HResult < 0) { - Mode = mode; - Stream = stream; - File = new StreamFile(Stream, mode); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - bytesRead = 0; - - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - return File.Read(out bytesRead, offset, destination.Slice(0, (int)toRead), option); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - Result rc = DryWrite(out _, offset, source.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - return File.Write(offset, source, option); - } - - protected override Result DoFlush() - { - try - { - return File.Flush(); - } - catch (Exception ex) when (ex.HResult < 0) - { - return ResultFs.UnexpectedInLocalFileSystemC.Log(); - } - } - - protected override Result DoGetSize(out long size) - { - UnsafeHelpers.SkipParamInit(out size); - - try - { - return File.GetSize(out size); - } - catch (Exception ex) when (ex.HResult < 0) - { - return ResultFs.UnexpectedInLocalFileSystemD.Log(); - } - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoSetSize(long size) - { - try - { - File.SetSize(size); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - public override void Dispose() - { - File?.Dispose(); - Stream?.Dispose(); - - base.Dispose(); + return ResultFs.UnexpectedInLocalFileSystemC.Log(); } } + + protected override Result DoGetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + try + { + return File.GetSize(out size); + } + catch (Exception ex) when (ex.HResult < 0) + { + return ResultFs.UnexpectedInLocalFileSystemD.Log(); + } + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoSetSize(long size) + { + try + { + File.SetSize(size); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + public override void Dispose() + { + File?.Dispose(); + Stream?.Dispose(); + + base.Dispose(); + } } diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index 5f8a5e92..b1640801 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -12,978 +12,977 @@ using LibHac.Util; using static LibHac.Fs.StringTraits; using Path = LibHac.Fs.Path; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class LocalFileSystem : IAttributeFileSystem { - public class LocalFileSystem : IAttributeFileSystem + /// + /// Specifies the case-sensitivity of a . + /// + public enum PathMode { /// - /// Specifies the case-sensitivity of a . + /// Uses the default case-sensitivity of the underlying file system. /// - public enum PathMode - { - /// - /// Uses the default case-sensitivity of the underlying file system. - /// - DefaultCaseSensitivity, - - /// - /// Treats the file system as case-sensitive. - /// - CaseSensitive - } - - private Path.Stored _rootPath; - private string _rootPathUtf16; - private readonly FileSystemClient _fsClient; - private PathMode _mode; - private readonly bool _useUnixTime; - - public LocalFileSystem() : this(true) { } - - public LocalFileSystem(bool useUnixTimeStamps) - { - _useUnixTime = useUnixTimeStamps; - } - - public LocalFileSystem(FileSystemClient fsClient, bool useUnixTimeStamps) : this(useUnixTimeStamps) - { - _fsClient = fsClient; - } + DefaultCaseSensitivity, /// - /// Opens a directory on local storage as an . - /// The directory will be created if it does not exist. + /// Treats the file system as case-sensitive. /// - /// The path that will be the root of the . - public LocalFileSystem(string rootPath) - { - Result rc = Initialize(rootPath, PathMode.DefaultCaseSensitivity, true); - if (rc.IsFailure()) - throw new HorizonResultException(rc, "Error creating LocalFileSystem."); - } + CaseSensitive + } - public static Result Create(out LocalFileSystem fileSystem, string rootPath, - PathMode pathMode = PathMode.DefaultCaseSensitivity, bool ensurePathExists = true) - { - UnsafeHelpers.SkipParamInit(out fileSystem); + private Path.Stored _rootPath; + private string _rootPathUtf16; + private readonly FileSystemClient _fsClient; + private PathMode _mode; + private readonly bool _useUnixTime; - var localFs = new LocalFileSystem(); - Result rc = localFs.Initialize(rootPath, pathMode, ensurePathExists); + public LocalFileSystem() : this(true) { } + + public LocalFileSystem(bool useUnixTimeStamps) + { + _useUnixTime = useUnixTimeStamps; + } + + public LocalFileSystem(FileSystemClient fsClient, bool useUnixTimeStamps) : this(useUnixTimeStamps) + { + _fsClient = fsClient; + } + + /// + /// Opens a directory on local storage as an . + /// The directory will be created if it does not exist. + /// + /// The path that will be the root of the . + public LocalFileSystem(string rootPath) + { + Result rc = Initialize(rootPath, PathMode.DefaultCaseSensitivity, true); + if (rc.IsFailure()) + throw new HorizonResultException(rc, "Error creating LocalFileSystem."); + } + + public static Result Create(out LocalFileSystem fileSystem, string rootPath, + PathMode pathMode = PathMode.DefaultCaseSensitivity, bool ensurePathExists = true) + { + UnsafeHelpers.SkipParamInit(out fileSystem); + + var localFs = new LocalFileSystem(); + Result rc = localFs.Initialize(rootPath, pathMode, ensurePathExists); + if (rc.IsFailure()) return rc; + + fileSystem = localFs; + return Result.Success; + } + + public Result Initialize(string rootPath, PathMode pathMode, bool ensurePathExists) + { + Result rc; + + if (rootPath == null) + return ResultFs.NullptrArgument.Log(); + + _mode = pathMode; + + // If the root path is empty, we interpret any incoming paths as rooted paths. + if (rootPath == string.Empty) + { + using var path = new Path(); + rc = path.InitializeAsEmpty(); + if (rc.IsFailure()) return rc; + + rc = _rootPath.Initialize(in path); if (rc.IsFailure()) return rc; - fileSystem = localFs; return Result.Success; } - public Result Initialize(string rootPath, PathMode pathMode, bool ensurePathExists) + string rootPathNormalized; + + try { - Result rc; + rootPathNormalized = System.IO.Path.GetFullPath(rootPath); + } + catch (PathTooLongException) + { + return ResultFs.TooLongPath.Log(); + } + catch (Exception) + { + return ResultFs.InvalidCharacter.Log(); + } - if (rootPath == null) - return ResultFs.NullptrArgument.Log(); - - _mode = pathMode; - - // If the root path is empty, we interpret any incoming paths as rooted paths. - if (rootPath == string.Empty) - { - using var path = new Path(); - rc = path.InitializeAsEmpty(); - if (rc.IsFailure()) return rc; - - rc = _rootPath.Initialize(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - string rootPathNormalized; + if (!Directory.Exists(rootPathNormalized)) + { + if (!ensurePathExists || File.Exists(rootPathNormalized)) + return ResultFs.PathNotFound.Log(); try { - rootPathNormalized = System.IO.Path.GetFullPath(rootPath); + Directory.CreateDirectory(rootPathNormalized); } - catch (PathTooLongException) + catch (Exception ex) when (ex.HResult < 0) { - return ResultFs.TooLongPath.Log(); - } - catch (Exception) - { - return ResultFs.InvalidCharacter.Log(); - } - - if (!Directory.Exists(rootPathNormalized)) - { - if (!ensurePathExists || File.Exists(rootPathNormalized)) - return ResultFs.PathNotFound.Log(); - - try - { - Directory.CreateDirectory(rootPathNormalized); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - } - - ReadOnlySpan utf8Path = StringUtils.StringToUtf8(rootPathNormalized); - using var pathNormalized = new Path(); - - if (utf8Path.At(0) == DirectorySeparator && utf8Path.At(1) != DirectorySeparator) - { - rc = pathNormalized.Initialize(utf8Path.Slice(1)); - if (rc.IsFailure()) return rc; - } - else - { - rc = pathNormalized.InitializeWithReplaceUnc(utf8Path); - if (rc.IsFailure()) return rc; - } - - var flags = new PathFlags(); - flags.AllowWindowsPath(); - flags.AllowRelativePath(); - flags.AllowEmptyPath(); - - rc = pathNormalized.Normalize(flags); - if (rc.IsFailure()) return rc; - - rc = _rootPath.Initialize(in pathNormalized); - if (rc.IsFailure()) return rc; - - _rootPathUtf16 = _rootPath.ToString(); - - return Result.Success; - } - - private Result ResolveFullPath(out string outFullPath, in Path path, bool checkCaseSensitivity) - { - UnsafeHelpers.SkipParamInit(out outFullPath); - - // Always normalize the incoming path even if it claims to already be normalized - // because we don't want to allow access to anything outside the root path. - - using var pathNormalized = new Path(); - Result rc = pathNormalized.Initialize(path.GetString()); - if (rc.IsFailure()) return rc; - - var pathFlags = new PathFlags(); - pathFlags.AllowWindowsPath(); - pathFlags.AllowRelativePath(); - pathFlags.AllowEmptyPath(); - rc = pathNormalized.Normalize(pathFlags); - if (rc.IsFailure()) return rc; - - using Path rootPath = _rootPath.DangerousGetPath(); - - using var fullPath = new Path(); - rc = fullPath.Combine(in rootPath, in pathNormalized); - if (rc.IsFailure()) return rc; - - string utf16FullPath = fullPath.ToString(); - - if (_mode == PathMode.CaseSensitive && checkCaseSensitivity) - { - rc = CheckPathCaseSensitively(utf16FullPath); - if (rc.IsFailure()) return rc; - } - - outFullPath = utf16FullPath; - return Result.Success; - } - - protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path) - { - UnsafeHelpers.SkipParamInit(out attributes); - - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo info, fullPath); - if (rc.IsFailure()) return rc; - - if (info.Attributes == (FileAttributes)(-1)) - { - return ResultFs.PathNotFound.Log(); - } - - attributes = info.Attributes.ToNxAttributes(); - return Result.Success; - } - - protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes) - { - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo info, fullPath); - if (rc.IsFailure()) return rc; - - if (info.Attributes == (FileAttributes)(-1)) - { - return ResultFs.PathNotFound.Log(); - } - - FileAttributes attributesOld = info.Attributes; - FileAttributes attributesNew = attributesOld.ApplyNxAttributes(attributes); - - try - { - info.Attributes = attributesNew; - } - catch (IOException) - { - return ResultFs.PathNotFound.Log(); - } - - return Result.Success; - } - - protected override Result DoGetFileSize(out long fileSize, in Path path) - { - UnsafeHelpers.SkipParamInit(out fileSize); - - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo info, fullPath); - if (rc.IsFailure()) return rc; - - return GetSizeInternal(out fileSize, info); - } - - protected override Result DoCreateDirectory(in Path path) - { - return DoCreateDirectory(path, NxFileAttributes.None); - } - - protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute) - { - Result rc = ResolveFullPath(out string fullPath, in path, false); - if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dir, fullPath); - if (rc.IsFailure()) return rc; - - if (dir.Exists) - { - return ResultFs.PathAlreadyExists.Log(); - } - - if (dir.Parent?.Exists != true) - { - return ResultFs.PathNotFound.Log(); - } - - return CreateDirInternal(dir, archiveAttribute); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - Result rc = ResolveFullPath(out string fullPath, in path, false); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo file, fullPath); - if (rc.IsFailure()) return rc; - - if (file.Exists) - { - return ResultFs.PathAlreadyExists.Log(); - } - - if (file.Directory?.Exists != true) - { - return ResultFs.PathNotFound.Log(); - } - - rc = CreateFileInternal(out FileStream stream, file); - - using (stream) - { - if (rc.IsFailure()) return rc; - - return SetStreamLengthInternal(stream, size); + return HResult.HResultToHorizonResult(ex.HResult).Log(); } } - protected override Result DoDeleteDirectory(in Path path) + ReadOnlySpan utf8Path = StringUtils.StringToUtf8(rootPathNormalized); + using var pathNormalized = new Path(); + + if (utf8Path.At(0) == DirectorySeparator && utf8Path.At(1) != DirectorySeparator) { - Result rc = ResolveFullPath(out string fullPath, in path, true); + rc = pathNormalized.Initialize(utf8Path.Slice(1)); if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dir, fullPath); + } + else + { + rc = pathNormalized.InitializeWithReplaceUnc(utf8Path); if (rc.IsFailure()) return rc; - - return TargetLockedAvoidance.RetryToAvoidTargetLocked( - () => DeleteDirectoryInternal(dir, false), _fsClient); } - protected override Result DoDeleteDirectoryRecursively(in Path path) + var flags = new PathFlags(); + flags.AllowWindowsPath(); + flags.AllowRelativePath(); + flags.AllowEmptyPath(); + + rc = pathNormalized.Normalize(flags); + if (rc.IsFailure()) return rc; + + rc = _rootPath.Initialize(in pathNormalized); + if (rc.IsFailure()) return rc; + + _rootPathUtf16 = _rootPath.ToString(); + + return Result.Success; + } + + private Result ResolveFullPath(out string outFullPath, in Path path, bool checkCaseSensitivity) + { + UnsafeHelpers.SkipParamInit(out outFullPath); + + // Always normalize the incoming path even if it claims to already be normalized + // because we don't want to allow access to anything outside the root path. + + using var pathNormalized = new Path(); + Result rc = pathNormalized.Initialize(path.GetString()); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowRelativePath(); + pathFlags.AllowEmptyPath(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + using Path rootPath = _rootPath.DangerousGetPath(); + + using var fullPath = new Path(); + rc = fullPath.Combine(in rootPath, in pathNormalized); + if (rc.IsFailure()) return rc; + + string utf16FullPath = fullPath.ToString(); + + if (_mode == PathMode.CaseSensitive && checkCaseSensitivity) { - Result rc = ResolveFullPath(out string fullPath, in path, true); + rc = CheckPathCaseSensitively(utf16FullPath); if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dir, fullPath); - if (rc.IsFailure()) return rc; - - return TargetLockedAvoidance.RetryToAvoidTargetLocked( - () => DeleteDirectoryInternal(dir, true), _fsClient); } - protected override Result DoCleanDirectoryRecursively(in Path path) + outFullPath = utf16FullPath; + return Result.Success; + } + + protected override Result DoGetFileAttributes(out NxFileAttributes attributes, in Path path) + { + UnsafeHelpers.SkipParamInit(out attributes); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo info, fullPath); + if (rc.IsFailure()) return rc; + + if (info.Attributes == (FileAttributes)(-1)) { - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dir, fullPath); - if (rc.IsFailure()) return rc; - - return CleanDirectoryInternal(dir, _fsClient); - } - - protected override Result DoDeleteFile(in Path path) - { - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo file, fullPath); - if (rc.IsFailure()) return rc; - - return TargetLockedAvoidance.RetryToAvoidTargetLocked( - () => DeleteFileInternal(file), _fsClient); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dirInfo, fullPath); - if (rc.IsFailure()) return rc; - - if (!dirInfo.Attributes.HasFlag(FileAttributes.Directory)) - { - return ResultFs.PathNotFound.Log(); - } - - IDirectory dirTemp = null; - rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => - OpenDirectoryInternal(out dirTemp, mode, dirInfo), _fsClient); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(dirTemp); - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetEntryType(out DirectoryEntryType entryType, path); - if (rc.IsFailure()) return rc; - - if (entryType == DirectoryEntryType.Directory) - { - return ResultFs.PathNotFound.Log(); - } - - FileStream fileStream = null; - - rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => - OpenFileInternal(out fileStream, fullPath, mode), _fsClient); - if (rc.IsFailure()) return rc; - - outFile.Reset(new LocalFile(fileStream, mode)); - return Result.Success; - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true); - if (rc.IsFailure()) return rc; - - rc = ResolveFullPath(out string fullNewPath, in newPath, false); - if (rc.IsFailure()) return rc; - - // Official FS behavior is to do nothing in this case - if (fullCurrentPath == fullNewPath) return Result.Success; - - rc = GetDirInfo(out DirectoryInfo currentDirInfo, fullCurrentPath); - if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo newDirInfo, fullNewPath); - if (rc.IsFailure()) return rc; - - return TargetLockedAvoidance.RetryToAvoidTargetLocked( - () => RenameDirInternal(currentDirInfo, newDirInfo), _fsClient); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true); - if (rc.IsFailure()) return rc; - - rc = ResolveFullPath(out string fullNewPath, in newPath, false); - if (rc.IsFailure()) return rc; - - // Official FS behavior is to do nothing in this case - if (fullCurrentPath == fullNewPath) return Result.Success; - - rc = GetFileInfo(out FileInfo currentFileInfo, fullCurrentPath); - if (rc.IsFailure()) return rc; - - rc = GetFileInfo(out FileInfo newFileInfo, fullNewPath); - if (rc.IsFailure()) return rc; - - return TargetLockedAvoidance.RetryToAvoidTargetLocked( - () => RenameFileInternal(currentFileInfo, newFileInfo), _fsClient); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - rc = GetDirInfo(out DirectoryInfo dir, fullPath); - if (rc.IsFailure()) return rc; - - if (dir.Exists) - { - entryType = DirectoryEntryType.Directory; - return Result.Success; - } - - rc = GetFileInfo(out FileInfo file, fullPath); - if (rc.IsFailure()) return rc; - - if (file.Exists) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } - return ResultFs.PathNotFound.Log(); } - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + attributes = info.Attributes.ToNxAttributes(); + return Result.Success; + } + + protected override Result DoSetFileAttributes(in Path path, NxFileAttributes attributes) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo info, fullPath); + if (rc.IsFailure()) return rc; + + if (info.Attributes == (FileAttributes)(-1)) { - UnsafeHelpers.SkipParamInit(out timeStamp); + return ResultFs.PathNotFound.Log(); + } - Result rc = ResolveFullPath(out string fullPath, in path, true); + FileAttributes attributesOld = info.Attributes; + FileAttributes attributesNew = attributesOld.ApplyNxAttributes(attributes); + + try + { + info.Attributes = attributesNew; + } + catch (IOException) + { + return ResultFs.PathNotFound.Log(); + } + + return Result.Success; + } + + protected override Result DoGetFileSize(out long fileSize, in Path path) + { + UnsafeHelpers.SkipParamInit(out fileSize); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo info, fullPath); + if (rc.IsFailure()) return rc; + + return GetSizeInternal(out fileSize, info); + } + + protected override Result DoCreateDirectory(in Path path) + { + return DoCreateDirectory(path, NxFileAttributes.None); + } + + protected override Result DoCreateDirectory(in Path path, NxFileAttributes archiveAttribute) + { + Result rc = ResolveFullPath(out string fullPath, in path, false); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo dir, fullPath); + if (rc.IsFailure()) return rc; + + if (dir.Exists) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (dir.Parent?.Exists != true) + { + return ResultFs.PathNotFound.Log(); + } + + return CreateDirInternal(dir, archiveAttribute); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + Result rc = ResolveFullPath(out string fullPath, in path, false); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo file, fullPath); + if (rc.IsFailure()) return rc; + + if (file.Exists) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (file.Directory?.Exists != true) + { + return ResultFs.PathNotFound.Log(); + } + + rc = CreateFileInternal(out FileStream stream, file); + + using (stream) + { if (rc.IsFailure()) return rc; - rc = GetFileInfo(out FileInfo file, fullPath); - if (rc.IsFailure()) return rc; + return SetStreamLengthInternal(stream, size); + } + } - if (!file.Exists) return ResultFs.PathNotFound.Log(); + protected override Result DoDeleteDirectory(in Path path) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; - if (_useUnixTime) + rc = GetDirInfo(out DirectoryInfo dir, fullPath); + if (rc.IsFailure()) return rc; + + return TargetLockedAvoidance.RetryToAvoidTargetLocked( + () => DeleteDirectoryInternal(dir, false), _fsClient); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo dir, fullPath); + if (rc.IsFailure()) return rc; + + return TargetLockedAvoidance.RetryToAvoidTargetLocked( + () => DeleteDirectoryInternal(dir, true), _fsClient); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo dir, fullPath); + if (rc.IsFailure()) return rc; + + return CleanDirectoryInternal(dir, _fsClient); + } + + protected override Result DoDeleteFile(in Path path) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo file, fullPath); + if (rc.IsFailure()) return rc; + + return TargetLockedAvoidance.RetryToAvoidTargetLocked( + () => DeleteFileInternal(file), _fsClient); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo dirInfo, fullPath); + if (rc.IsFailure()) return rc; + + if (!dirInfo.Attributes.HasFlag(FileAttributes.Directory)) + { + return ResultFs.PathNotFound.Log(); + } + + IDirectory dirTemp = null; + rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => + OpenDirectoryInternal(out dirTemp, mode, dirInfo), _fsClient); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(dirTemp); + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetEntryType(out DirectoryEntryType entryType, path); + if (rc.IsFailure()) return rc; + + if (entryType == DirectoryEntryType.Directory) + { + return ResultFs.PathNotFound.Log(); + } + + FileStream fileStream = null; + + rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => + OpenFileInternal(out fileStream, fullPath, mode), _fsClient); + if (rc.IsFailure()) return rc; + + outFile.Reset(new LocalFile(fileStream, mode)); + return Result.Success; + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true); + if (rc.IsFailure()) return rc; + + rc = ResolveFullPath(out string fullNewPath, in newPath, false); + if (rc.IsFailure()) return rc; + + // Official FS behavior is to do nothing in this case + if (fullCurrentPath == fullNewPath) return Result.Success; + + rc = GetDirInfo(out DirectoryInfo currentDirInfo, fullCurrentPath); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo newDirInfo, fullNewPath); + if (rc.IsFailure()) return rc; + + return TargetLockedAvoidance.RetryToAvoidTargetLocked( + () => RenameDirInternal(currentDirInfo, newDirInfo), _fsClient); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + Result rc = ResolveFullPath(out string fullCurrentPath, in currentPath, true); + if (rc.IsFailure()) return rc; + + rc = ResolveFullPath(out string fullNewPath, in newPath, false); + if (rc.IsFailure()) return rc; + + // Official FS behavior is to do nothing in this case + if (fullCurrentPath == fullNewPath) return Result.Success; + + rc = GetFileInfo(out FileInfo currentFileInfo, fullCurrentPath); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo newFileInfo, fullNewPath); + if (rc.IsFailure()) return rc; + + return TargetLockedAvoidance.RetryToAvoidTargetLocked( + () => RenameFileInternal(currentFileInfo, newFileInfo), _fsClient); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetDirInfo(out DirectoryInfo dir, fullPath); + if (rc.IsFailure()) return rc; + + if (dir.Exists) + { + entryType = DirectoryEntryType.Directory; + return Result.Success; + } + + rc = GetFileInfo(out FileInfo file, fullPath); + if (rc.IsFailure()) return rc; + + if (file.Exists) + { + entryType = DirectoryEntryType.File; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + rc = GetFileInfo(out FileInfo file, fullPath); + if (rc.IsFailure()) return rc; + + if (!file.Exists) return ResultFs.PathNotFound.Log(); + + if (_useUnixTime) + { + timeStamp.Created = new DateTimeOffset(file.CreationTimeUtc).ToUnixTimeSeconds(); + timeStamp.Accessed = new DateTimeOffset(file.LastAccessTimeUtc).ToUnixTimeSeconds(); + timeStamp.Modified = new DateTimeOffset(file.LastWriteTimeUtc).ToUnixTimeSeconds(); + } + else + { + timeStamp.Created = new DateTimeOffset(file.CreationTimeUtc).ToFileTime(); + timeStamp.Accessed = new DateTimeOffset(file.LastAccessTimeUtc).ToFileTime(); + timeStamp.Modified = new DateTimeOffset(file.LastWriteTimeUtc).ToFileTime(); + } + + timeStamp.IsLocalTime = false; + + return Result.Success; + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + freeSpace = new DriveInfo(fullPath).AvailableFreeSpace; + return Result.Success; + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + + Result rc = ResolveFullPath(out string fullPath, in path, true); + if (rc.IsFailure()) return rc; + + totalSpace = new DriveInfo(fullPath).TotalSize; + return Result.Success; + } + + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + return ResultFs.UnsupportedOperation.Log(); + } + + internal static FileAccess GetFileAccess(OpenMode mode) + { + // FileAccess and OpenMode have the same flags + return (FileAccess)(mode & OpenMode.ReadWrite); + } + + internal static FileShare GetFileShare(OpenMode mode) + { + return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite; + } + + internal static Result OpenFileInternal(out FileStream stream, string path, OpenMode mode) + { + try + { + stream = new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode)); + return Result.Success; + } + catch (Exception ex) when (ex.HResult < 0) + { + UnsafeHelpers.SkipParamInit(out stream); + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + } + + private static Result OpenDirectoryInternal(out IDirectory directory, OpenDirectoryMode mode, + DirectoryInfo dirInfo) + { + try + { + IEnumerator entryEnumerator = dirInfo.EnumerateFileSystemInfos().GetEnumerator(); + + directory = new LocalDirectory(entryEnumerator, dirInfo, mode); + return Result.Success; + } + catch (Exception ex) when (ex.HResult < 0) + { + UnsafeHelpers.SkipParamInit(out directory); + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + } + + private static Result GetSizeInternal(out long fileSize, FileInfo file) + { + UnsafeHelpers.SkipParamInit(out fileSize); + + try + { + fileSize = file.Length; + return Result.Success; + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + } + + private static Result CreateFileInternal(out FileStream file, FileInfo fileInfo) + { + UnsafeHelpers.SkipParamInit(out file); + + try + { + file = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.ReadWrite); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + private static Result SetStreamLengthInternal(Stream stream, long size) + { + try + { + stream.SetLength(size); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + private static Result CleanDirectoryInternal(DirectoryInfo dir, FileSystemClient fsClient) + { + try + { + foreach (FileInfo fileInfo in dir.EnumerateFiles()) { - timeStamp.Created = new DateTimeOffset(file.CreationTimeUtc).ToUnixTimeSeconds(); - timeStamp.Accessed = new DateTimeOffset(file.LastAccessTimeUtc).ToUnixTimeSeconds(); - timeStamp.Modified = new DateTimeOffset(file.LastWriteTimeUtc).ToUnixTimeSeconds(); + Result rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => DeleteFileInternal(fileInfo), + fsClient); + if (rc.IsFailure()) return rc; + } + + foreach (DirectoryInfo dirInfo in dir.EnumerateDirectories()) + { + Result rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => DeleteDirectoryInternal(dirInfo, true), + fsClient); + if (rc.IsFailure()) return rc; + } + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + private static Result DeleteDirectoryInternal(DirectoryInfo dir, bool recursive) + { + if (!dir.Exists) + return ResultFs.PathNotFound.Log(); + + try + { + dir.Delete(recursive); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return EnsureDeleted(dir); + } + + private static Result DeleteFileInternal(FileInfo file) + { + if (!file.Exists) + return ResultFs.PathNotFound.Log(); + + try + { + file.Delete(); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return EnsureDeleted(file); + } + + private static Result CreateDirInternal(DirectoryInfo dir, NxFileAttributes attributes) + { + try + { + dir.Create(); + dir.Refresh(); + + if (attributes.HasFlag(NxFileAttributes.Archive)) + { + dir.Attributes |= FileAttributes.Archive; + } + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + private static Result RenameDirInternal(DirectoryInfo source, DirectoryInfo dest) + { + if (!source.Exists) return ResultFs.PathNotFound.Log(); + if (dest.Exists) return ResultFs.PathAlreadyExists.Log(); + + try + { + source.MoveTo(dest.FullName); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + private static Result RenameFileInternal(FileInfo source, FileInfo dest) + { + if (!source.Exists) return ResultFs.PathNotFound.Log(); + if (dest.Exists) return ResultFs.PathAlreadyExists.Log(); + + try + { + source.MoveTo(dest.FullName); + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + + return Result.Success; + } + + // GetFileInfo and GetDirInfo detect invalid paths + private static Result GetFileInfo(out FileInfo fileInfo, string path) + { + UnsafeHelpers.SkipParamInit(out fileInfo); + + try + { + fileInfo = new FileInfo(path); + return Result.Success; + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + } + + private static Result GetDirInfo(out DirectoryInfo directoryInfo, string path) + { + UnsafeHelpers.SkipParamInit(out directoryInfo); + + try + { + directoryInfo = new DirectoryInfo(path); + return Result.Success; + } + catch (Exception ex) when (ex.HResult < 0) + { + return HResult.HResultToHorizonResult(ex.HResult).Log(); + } + } + + // Delete operations on IFileSystem should be synchronous + // DeleteFile and RemoveDirectory only mark the file for deletion on Windows, + // so we need to poll the filesystem until it's actually gone + private static Result EnsureDeleted(FileSystemInfo entry) + { + const int noDelayRetryCount = 1000; + const int delayRetryCount = 100; + const int retryDelay = 10; + + // The entry is usually deleted within the first 5-10 tries + for (int i = 0; i < noDelayRetryCount; i++) + { + entry.Refresh(); + + if (!entry.Exists) + return Result.Success; + } + + for (int i = 0; i < delayRetryCount; i++) + { + Thread.Sleep(retryDelay); + entry.Refresh(); + + if (!entry.Exists) + return Result.Success; + } + + return ResultFs.TargetLocked.Log(); + } + + public static Result GetCaseSensitivePath(out int bytesWritten, Span buffer, U8Span path, + U8Span workingDirectoryPath) + { + UnsafeHelpers.SkipParamInit(out bytesWritten); + + string pathUtf16 = StringUtils.Utf8ZToString(path); + string workingDirectoryPathUtf16 = StringUtils.Utf8ZToString(workingDirectoryPath); + + Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out int rootPathLength, pathUtf16, + workingDirectoryPathUtf16); + if (rc.IsFailure()) return rc; + + OperationStatus status = Utf8.FromUtf16(caseSensitivePath.AsSpan(rootPathLength), + buffer.Slice(0, buffer.Length - 1), out _, out int utf8BytesWritten); + + if (status == OperationStatus.DestinationTooSmall) + return ResultFs.TooLongPath.Log(); + + if (status == OperationStatus.InvalidData || status == OperationStatus.NeedMoreData) + return ResultFs.InvalidCharacter.Log(); + + buffer[utf8BytesWritten] = NullTerminator; + bytesWritten = utf8BytesWritten; + + return Result.Success; + } + + private Result CheckPathCaseSensitively(string path) + { + Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPathUtf16); + if (rc.IsFailure()) return rc; + + if (path.Length != caseSensitivePath.Length) + return ResultFs.PathNotFound.Log(); + + for (int i = 0; i < path.Length; i++) + { + if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiterW(path[i]) && + WindowsPath.IsDosDelimiterW(caseSensitivePath[i]))) + { + return ResultFs.PathNotFound.Log(); + } + } + + return Result.Success; + } + + private static Result GetCaseSensitivePathFull(out string caseSensitivePath, out int rootPathLength, + string path, string workingDirectoryPath) + { + caseSensitivePath = default; + UnsafeHelpers.SkipParamInit(out rootPathLength); + + string fullPath; + int workingDirectoryPathLength; + + if (WindowsPath.IsWindowsPathW(path)) + { + fullPath = path; + workingDirectoryPathLength = 0; + } + else + { + // We only want to send back the relative part of the path starting with a '/', so + // track where the root path ends. + if (WindowsPath.IsDosDelimiterW(workingDirectoryPath[^1])) + { + workingDirectoryPathLength = workingDirectoryPath.Length - 1; } else { - timeStamp.Created = new DateTimeOffset(file.CreationTimeUtc).ToFileTime(); - timeStamp.Accessed = new DateTimeOffset(file.LastAccessTimeUtc).ToFileTime(); - timeStamp.Modified = new DateTimeOffset(file.LastWriteTimeUtc).ToFileTime(); + workingDirectoryPathLength = workingDirectoryPath.Length; } - timeStamp.IsLocalTime = false; - - return Result.Success; + fullPath = Combine(workingDirectoryPath, path); } - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + Result rc = GetCorrectCasedPath(out caseSensitivePath, fullPath); + if (rc.IsFailure()) return rc; + + rootPathLength = workingDirectoryPathLength; + return Result.Success; + } + + private static string Combine(string path1, string path2) + { + if (path1 == null || path2 == null) throw new NullReferenceException(); + + if (string.IsNullOrEmpty(path1)) return path2; + if (string.IsNullOrEmpty(path2)) return path1; + + bool path1HasSeparator = WindowsPath.IsDosDelimiterW(path1[path1.Length - 1]); + bool path2HasSeparator = WindowsPath.IsDosDelimiterW(path2[0]); + + if (!path1HasSeparator && !path2HasSeparator) { - UnsafeHelpers.SkipParamInit(out freeSpace); - - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - freeSpace = new DriveInfo(fullPath).AvailableFreeSpace; - return Result.Success; + return path1 + DirectorySeparator + path2; } - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + if (path1HasSeparator ^ path2HasSeparator) { - UnsafeHelpers.SkipParamInit(out totalSpace); - - Result rc = ResolveFullPath(out string fullPath, in path, true); - if (rc.IsFailure()) return rc; - - totalSpace = new DriveInfo(fullPath).TotalSize; - return Result.Success; + return path1 + path2; } - protected override Result DoCommit() + return path1 + path2.Substring(1); + } + + private static readonly char[] SplitChars = { (char)DirectorySeparator, (char)AltDirectorySeparator }; + + // Copyright (c) Microsoft Corporation. + // Licensed under the MIT License. + public static Result GetCorrectCasedPath(out string casedPath, string path) + { + UnsafeHelpers.SkipParamInit(out casedPath); + + string exactPath = string.Empty; + int itemsToSkip = 0; + if (WindowsPath.IsUncPathW(path)) { - return Result.Success; + // With the Split method, a UNC path like \\server\share, we need to skip + // trying to enumerate the server and share, so skip the first two empty + // strings, then server, and finally share name. + itemsToSkip = 4; } - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) + foreach (string item in path.Split(SplitChars)) { - return ResultFs.UnsupportedOperation.Log(); - } - - internal static FileAccess GetFileAccess(OpenMode mode) - { - // FileAccess and OpenMode have the same flags - return (FileAccess)(mode & OpenMode.ReadWrite); - } - - internal static FileShare GetFileShare(OpenMode mode) - { - return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite; - } - - internal static Result OpenFileInternal(out FileStream stream, string path, OpenMode mode) - { - try + if (itemsToSkip-- > 0) { - stream = new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode)); - return Result.Success; + // This handles the UNC server and share and 8.3 short path syntax + exactPath += item + (char)DirectorySeparator; } - catch (Exception ex) when (ex.HResult < 0) + else if (string.IsNullOrEmpty(exactPath)) { - UnsafeHelpers.SkipParamInit(out stream); - return HResult.HResultToHorizonResult(ex.HResult).Log(); + // This handles the drive letter or / root path start + exactPath = item + (char)DirectorySeparator; } - } - - private static Result OpenDirectoryInternal(out IDirectory directory, OpenDirectoryMode mode, - DirectoryInfo dirInfo) - { - try + else if (string.IsNullOrEmpty(item)) { - IEnumerator entryEnumerator = dirInfo.EnumerateFileSystemInfos().GetEnumerator(); - - directory = new LocalDirectory(entryEnumerator, dirInfo, mode); - return Result.Success; - } - catch (Exception ex) when (ex.HResult < 0) - { - UnsafeHelpers.SkipParamInit(out directory); - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - } - - private static Result GetSizeInternal(out long fileSize, FileInfo file) - { - UnsafeHelpers.SkipParamInit(out fileSize); - - try - { - fileSize = file.Length; - return Result.Success; - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - } - - private static Result CreateFileInternal(out FileStream file, FileInfo fileInfo) - { - UnsafeHelpers.SkipParamInit(out file); - - try - { - file = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.ReadWrite); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - private static Result SetStreamLengthInternal(Stream stream, long size) - { - try - { - stream.SetLength(size); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - private static Result CleanDirectoryInternal(DirectoryInfo dir, FileSystemClient fsClient) - { - try - { - foreach (FileInfo fileInfo in dir.EnumerateFiles()) + // This handles the trailing slash case + if (!exactPath.EndsWith((char)DirectorySeparator)) { - Result rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => DeleteFileInternal(fileInfo), - fsClient); - if (rc.IsFailure()) return rc; + exactPath += (char)DirectorySeparator; } - foreach (DirectoryInfo dirInfo in dir.EnumerateDirectories()) - { - Result rc = TargetLockedAvoidance.RetryToAvoidTargetLocked(() => DeleteDirectoryInternal(dirInfo, true), - fsClient); - if (rc.IsFailure()) return rc; - } + break; } - catch (Exception ex) when (ex.HResult < 0) + else if (item.Contains('~')) { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - private static Result DeleteDirectoryInternal(DirectoryInfo dir, bool recursive) - { - if (!dir.Exists) - return ResultFs.PathNotFound.Log(); - - try - { - dir.Delete(recursive); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return EnsureDeleted(dir); - } - - private static Result DeleteFileInternal(FileInfo file) - { - if (!file.Exists) - return ResultFs.PathNotFound.Log(); - - try - { - file.Delete(); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return EnsureDeleted(file); - } - - private static Result CreateDirInternal(DirectoryInfo dir, NxFileAttributes attributes) - { - try - { - dir.Create(); - dir.Refresh(); - - if (attributes.HasFlag(NxFileAttributes.Archive)) - { - dir.Attributes |= FileAttributes.Archive; - } - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - private static Result RenameDirInternal(DirectoryInfo source, DirectoryInfo dest) - { - if (!source.Exists) return ResultFs.PathNotFound.Log(); - if (dest.Exists) return ResultFs.PathAlreadyExists.Log(); - - try - { - source.MoveTo(dest.FullName); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - private static Result RenameFileInternal(FileInfo source, FileInfo dest) - { - if (!source.Exists) return ResultFs.PathNotFound.Log(); - if (dest.Exists) return ResultFs.PathAlreadyExists.Log(); - - try - { - source.MoveTo(dest.FullName); - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - - return Result.Success; - } - - // GetFileInfo and GetDirInfo detect invalid paths - private static Result GetFileInfo(out FileInfo fileInfo, string path) - { - UnsafeHelpers.SkipParamInit(out fileInfo); - - try - { - fileInfo = new FileInfo(path); - return Result.Success; - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - } - - private static Result GetDirInfo(out DirectoryInfo directoryInfo, string path) - { - UnsafeHelpers.SkipParamInit(out directoryInfo); - - try - { - directoryInfo = new DirectoryInfo(path); - return Result.Success; - } - catch (Exception ex) when (ex.HResult < 0) - { - return HResult.HResultToHorizonResult(ex.HResult).Log(); - } - } - - // Delete operations on IFileSystem should be synchronous - // DeleteFile and RemoveDirectory only mark the file for deletion on Windows, - // so we need to poll the filesystem until it's actually gone - private static Result EnsureDeleted(FileSystemInfo entry) - { - const int noDelayRetryCount = 1000; - const int delayRetryCount = 100; - const int retryDelay = 10; - - // The entry is usually deleted within the first 5-10 tries - for (int i = 0; i < noDelayRetryCount; i++) - { - entry.Refresh(); - - if (!entry.Exists) - return Result.Success; - } - - for (int i = 0; i < delayRetryCount; i++) - { - Thread.Sleep(retryDelay); - entry.Refresh(); - - if (!entry.Exists) - return Result.Success; - } - - return ResultFs.TargetLocked.Log(); - } - - public static Result GetCaseSensitivePath(out int bytesWritten, Span buffer, U8Span path, - U8Span workingDirectoryPath) - { - UnsafeHelpers.SkipParamInit(out bytesWritten); - - string pathUtf16 = StringUtils.Utf8ZToString(path); - string workingDirectoryPathUtf16 = StringUtils.Utf8ZToString(workingDirectoryPath); - - Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out int rootPathLength, pathUtf16, - workingDirectoryPathUtf16); - if (rc.IsFailure()) return rc; - - OperationStatus status = Utf8.FromUtf16(caseSensitivePath.AsSpan(rootPathLength), - buffer.Slice(0, buffer.Length - 1), out _, out int utf8BytesWritten); - - if (status == OperationStatus.DestinationTooSmall) - return ResultFs.TooLongPath.Log(); - - if (status == OperationStatus.InvalidData || status == OperationStatus.NeedMoreData) - return ResultFs.InvalidCharacter.Log(); - - buffer[utf8BytesWritten] = NullTerminator; - bytesWritten = utf8BytesWritten; - - return Result.Success; - } - - private Result CheckPathCaseSensitively(string path) - { - Result rc = GetCaseSensitivePathFull(out string caseSensitivePath, out _, path, _rootPathUtf16); - if (rc.IsFailure()) return rc; - - if (path.Length != caseSensitivePath.Length) - return ResultFs.PathNotFound.Log(); - - for (int i = 0; i < path.Length; i++) - { - if (!(path[i] == caseSensitivePath[i] || WindowsPath.IsDosDelimiterW(path[i]) && - WindowsPath.IsDosDelimiterW(caseSensitivePath[i]))) - { - return ResultFs.PathNotFound.Log(); - } - } - - return Result.Success; - } - - private static Result GetCaseSensitivePathFull(out string caseSensitivePath, out int rootPathLength, - string path, string workingDirectoryPath) - { - caseSensitivePath = default; - UnsafeHelpers.SkipParamInit(out rootPathLength); - - string fullPath; - int workingDirectoryPathLength; - - if (WindowsPath.IsWindowsPathW(path)) - { - fullPath = path; - workingDirectoryPathLength = 0; + // This handles short path names + exactPath += (char)DirectorySeparator + item; } else { - // We only want to send back the relative part of the path starting with a '/', so - // track where the root path ends. - if (WindowsPath.IsDosDelimiterW(workingDirectoryPath[^1])) + // Use GetFileSystemEntries to get the correct casing of this element + try { - workingDirectoryPathLength = workingDirectoryPath.Length - 1; - } - else - { - workingDirectoryPathLength = workingDirectoryPath.Length; - } - - fullPath = Combine(workingDirectoryPath, path); - } - - Result rc = GetCorrectCasedPath(out caseSensitivePath, fullPath); - if (rc.IsFailure()) return rc; - - rootPathLength = workingDirectoryPathLength; - return Result.Success; - } - - private static string Combine(string path1, string path2) - { - if (path1 == null || path2 == null) throw new NullReferenceException(); - - if (string.IsNullOrEmpty(path1)) return path2; - if (string.IsNullOrEmpty(path2)) return path1; - - bool path1HasSeparator = WindowsPath.IsDosDelimiterW(path1[path1.Length - 1]); - bool path2HasSeparator = WindowsPath.IsDosDelimiterW(path2[0]); - - if (!path1HasSeparator && !path2HasSeparator) - { - return path1 + DirectorySeparator + path2; - } - - if (path1HasSeparator ^ path2HasSeparator) - { - return path1 + path2; - } - - return path1 + path2.Substring(1); - } - - private static readonly char[] SplitChars = { (char)DirectorySeparator, (char)AltDirectorySeparator }; - - // Copyright (c) Microsoft Corporation. - // Licensed under the MIT License. - public static Result GetCorrectCasedPath(out string casedPath, string path) - { - UnsafeHelpers.SkipParamInit(out casedPath); - - string exactPath = string.Empty; - int itemsToSkip = 0; - if (WindowsPath.IsUncPathW(path)) - { - // With the Split method, a UNC path like \\server\share, we need to skip - // trying to enumerate the server and share, so skip the first two empty - // strings, then server, and finally share name. - itemsToSkip = 4; - } - - foreach (string item in path.Split(SplitChars)) - { - if (itemsToSkip-- > 0) - { - // This handles the UNC server and share and 8.3 short path syntax - exactPath += item + (char)DirectorySeparator; - } - else if (string.IsNullOrEmpty(exactPath)) - { - // This handles the drive letter or / root path start - exactPath = item + (char)DirectorySeparator; - } - else if (string.IsNullOrEmpty(item)) - { - // This handles the trailing slash case - if (!exactPath.EndsWith((char)DirectorySeparator)) + string[] entries = Directory.GetFileSystemEntries(exactPath, item); + if (entries.Length > 0) { - exactPath += (char)DirectorySeparator; - } + int itemIndex = entries[0].LastIndexOf((char)AltDirectorySeparator); - break; - } - else if (item.Contains('~')) - { - // This handles short path names - exactPath += (char)DirectorySeparator + item; - } - else - { - // Use GetFileSystemEntries to get the correct casing of this element - try - { - string[] entries = Directory.GetFileSystemEntries(exactPath, item); - if (entries.Length > 0) + // GetFileSystemEntries will return paths in the root directory in this format: C:/Foo + if (itemIndex == -1) { - int itemIndex = entries[0].LastIndexOf((char)AltDirectorySeparator); - - // GetFileSystemEntries will return paths in the root directory in this format: C:/Foo - if (itemIndex == -1) - { - itemIndex = entries[0].LastIndexOf((char)DirectorySeparator); - exactPath += entries[0].Substring(itemIndex + 1); - } - else - { - exactPath += (char)DirectorySeparator + entries[0].Substring(itemIndex + 1); - } + itemIndex = entries[0].LastIndexOf((char)DirectorySeparator); + exactPath += entries[0].Substring(itemIndex + 1); } else { - // If previous call didn't return anything, something failed so we just return the path we were given - return ResultFs.PathNotFound.Log(); + exactPath += (char)DirectorySeparator + entries[0].Substring(itemIndex + 1); } } - catch + else { - // If we can't enumerate, we stop and just return the original path + // If previous call didn't return anything, something failed so we just return the path we were given return ResultFs.PathNotFound.Log(); } } + catch + { + // If we can't enumerate, we stop and just return the original path + return ResultFs.PathNotFound.Log(); + } } - - casedPath = exactPath; - return Result.Success; } + + casedPath = exactPath; + return Result.Success; } } diff --git a/src/LibHac/FsSystem/LocalStorage.cs b/src/LibHac/FsSystem/LocalStorage.cs index 1a56e359..37dff2a9 100644 --- a/src/LibHac/FsSystem/LocalStorage.cs +++ b/src/LibHac/FsSystem/LocalStorage.cs @@ -2,52 +2,51 @@ using System.IO; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class LocalStorage : IStorage { - public class LocalStorage : IStorage + private string Path { get; } + private FileStream Stream { get; } + private StreamStorage Storage { get; } + + public LocalStorage(string path, FileAccess access) : this(path, access, FileMode.Open) { } + + public LocalStorage(string path, FileAccess access, FileMode mode) { - private string Path { get; } - private FileStream Stream { get; } - private StreamStorage Storage { get; } + Path = path; + Stream = new FileStream(Path, mode, access); + Storage = new StreamStorage(Stream, false); + } - public LocalStorage(string path, FileAccess access) : this(path, access, FileMode.Open) { } + protected override Result DoRead(long offset, Span destination) + { + return Storage.Read(offset, destination); + } - public LocalStorage(string path, FileAccess access, FileMode mode) - { - Path = path; - Stream = new FileStream(Path, mode, access); - Storage = new StreamStorage(Stream, false); - } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return Storage.Write(offset, source); + } - protected override Result DoRead(long offset, Span destination) - { - return Storage.Read(offset, destination); - } + protected override Result DoFlush() + { + return Storage.Flush(); + } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return Storage.Write(offset, source); - } + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } - protected override Result DoFlush() - { - return Storage.Flush(); - } + protected override Result DoGetSize(out long size) + { + return Storage.GetSize(out size); + } - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoGetSize(out long size) - { - return Storage.GetSize(out size); - } - - public override void Dispose() - { - Storage?.Dispose(); - Stream?.Dispose(); - } + public override void Dispose() + { + Storage?.Dispose(); + Stream?.Dispose(); } } diff --git a/src/LibHac/FsSystem/Messages.cs b/src/LibHac/FsSystem/Messages.cs index 63b58cae..8fa16108 100644 --- a/src/LibHac/FsSystem/Messages.cs +++ b/src/LibHac/FsSystem/Messages.cs @@ -1,11 +1,10 @@ -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +internal static class Messages { - internal static class Messages - { - public static string DestSpanTooSmall => "Destination array is not long enough to hold the requested data."; - public static string NcaSectionMissing => "NCA section does not exist."; - public static string DestPathIsSubPath => "The destination directory is a subdirectory of the source directory."; - public static string DestPathAlreadyExists => "Destination path already exists."; - public static string PartialPathNotFound => "Could not find a part of the path."; - } + public static string DestSpanTooSmall => "Destination array is not long enough to hold the requested data."; + public static string NcaSectionMissing => "NCA section does not exist."; + public static string DestPathIsSubPath => "The destination directory is a subdirectory of the source directory."; + public static string DestPathAlreadyExists => "Destination path already exists."; + public static string PartialPathNotFound => "Could not find a part of the path."; } diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 714595d7..026d5d97 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -11,763 +11,762 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem.RomFs; using LibHac.Spl; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public class Nca { - public class Nca + private KeySet KeySet { get; } + private bool IsEncrypted => Header.IsEncrypted; + + private byte[] Nca0KeyArea { get; set; } + private IStorage Nca0TransformedBody { get; set; } + + public IStorage BaseStorage { get; } + + public NcaHeader Header { get; } + + public Nca(KeySet keySet, IStorage storage) { - private KeySet KeySet { get; } - private bool IsEncrypted => Header.IsEncrypted; + KeySet = keySet; + BaseStorage = storage; + Header = new NcaHeader(keySet, storage); + } - private byte[] Nca0KeyArea { get; set; } - private IStorage Nca0TransformedBody { get; set; } + public byte[] GetDecryptedKey(int index) + { + if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index)); - public IStorage BaseStorage { get; } - - public NcaHeader Header { get; } - - public Nca(KeySet keySet, IStorage storage) + // Handle old NCA0s that use different key area encryption + if (Header.FormatVersion == NcaVersion.Nca0FixedKey || Header.FormatVersion == NcaVersion.Nca0RsaOaep) { - KeySet = keySet; - BaseStorage = storage; - Header = new NcaHeader(keySet, storage); + return GetDecryptedKeyAreaNca0().AsSpan(0x10 * index, 0x10).ToArray(); } - public byte[] GetDecryptedKey(int index) + int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); + byte[] keyAreaKey = KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].DataRo.ToArray(); + + if (keyAreaKey.IsZeros()) { - if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index)); - - // Handle old NCA0s that use different key area encryption - if (Header.FormatVersion == NcaVersion.Nca0FixedKey || Header.FormatVersion == NcaVersion.Nca0RsaOaep) - { - return GetDecryptedKeyAreaNca0().AsSpan(0x10 * index, 0x10).ToArray(); - } - - int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); - byte[] keyAreaKey = KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].DataRo.ToArray(); - - if (keyAreaKey.IsZeros()) - { - string keyName = $"key_area_key_{KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}"; - throw new MissingKeyException("Unable to decrypt NCA section.", keyName, KeyType.Common); - } - - byte[] encryptedKey = Header.GetEncryptedKey(index).ToArray(); - byte[] decryptedKey = new byte[Aes.KeySize128]; - - Aes.DecryptEcb128(encryptedKey, decryptedKey, keyAreaKey); - - return decryptedKey; + string keyName = $"key_area_key_{KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}"; + throw new MissingKeyException("Unable to decrypt NCA section.", keyName, KeyType.Common); } - private static readonly string[] KakNames = { "application", "ocean", "system" }; + byte[] encryptedKey = Header.GetEncryptedKey(index).ToArray(); + byte[] decryptedKey = new byte[Aes.KeySize128]; - public byte[] GetDecryptedTitleKey() + Aes.DecryptEcb128(encryptedKey, decryptedKey, keyAreaKey); + + return decryptedKey; + } + + private static readonly string[] KakNames = { "application", "ocean", "system" }; + + public byte[] GetDecryptedTitleKey() + { + int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); + byte[] titleKek = KeySet.TitleKeks[keyRevision].DataRo.ToArray(); + + var rightsId = new RightsId(Header.RightsId); + + if (KeySet.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure()) { - int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); - byte[] titleKek = KeySet.TitleKeks[keyRevision].DataRo.ToArray(); - - var rightsId = new RightsId(Header.RightsId); - - if (KeySet.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure()) - { - throw new MissingKeyException("Missing NCA title key.", rightsId.ToString(), KeyType.Title); - } - - if (titleKek.IsZeros()) - { - string keyName = $"titlekek_{keyRevision:x2}"; - throw new MissingKeyException("Unable to decrypt title key.", keyName, KeyType.Common); - } - - byte[] encryptedKey = accessKey.Value.ToArray(); - byte[] decryptedKey = new byte[Aes.KeySize128]; - - Aes.DecryptEcb128(encryptedKey, decryptedKey, titleKek); - - return decryptedKey; + throw new MissingKeyException("Missing NCA title key.", rightsId.ToString(), KeyType.Title); } - internal byte[] GetContentKey(NcaKeyType type) + if (titleKek.IsZeros()) { - return Header.HasRightsId ? GetDecryptedTitleKey() : GetDecryptedKey((int)type); + string keyName = $"titlekek_{keyRevision:x2}"; + throw new MissingKeyException("Unable to decrypt title key.", keyName, KeyType.Common); } - public bool CanOpenSection(NcaSectionType type) - { - if (!TryGetSectionIndexFromType(type, Header.ContentType, out int index)) - { - return false; - } + byte[] encryptedKey = accessKey.Value.ToArray(); + byte[] decryptedKey = new byte[Aes.KeySize128]; - return CanOpenSection(index); + Aes.DecryptEcb128(encryptedKey, decryptedKey, titleKek); + + return decryptedKey; + } + + internal byte[] GetContentKey(NcaKeyType type) + { + return Header.HasRightsId ? GetDecryptedTitleKey() : GetDecryptedKey((int)type); + } + + public bool CanOpenSection(NcaSectionType type) + { + if (!TryGetSectionIndexFromType(type, Header.ContentType, out int index)) + { + return false; } - public bool CanOpenSection(int index) + return CanOpenSection(index); + } + + public bool CanOpenSection(int index) + { + if (!SectionExists(index)) return false; + if (GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true; + + int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); + + if (Header.HasRightsId) { - if (!SectionExists(index)) return false; - if (GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true; - - int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); - - if (Header.HasRightsId) - { - return KeySet.ExternalKeySet.Contains(new RightsId(Header.RightsId)) && - !KeySet.TitleKeks[keyRevision].IsZeros(); - } - - return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsZeros(); + return KeySet.ExternalKeySet.Contains(new RightsId(Header.RightsId)) && + !KeySet.TitleKeks[keyRevision].IsZeros(); } - public bool SectionExists(NcaSectionType type) - { - if (!TryGetSectionIndexFromType(type, Header.ContentType, out int index)) - { - return false; - } + return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsZeros(); + } - return SectionExists(index); + public bool SectionExists(NcaSectionType type) + { + if (!TryGetSectionIndexFromType(type, Header.ContentType, out int index)) + { + return false; } - public bool SectionExists(int index) + return SectionExists(index); + } + + public bool SectionExists(int index) + { + return Header.IsSectionEnabled(index); + } + + public NcaFsHeader GetFsHeader(int index) + { + if (Header.IsNca0()) + return GetNca0FsHeader(index); + + return Header.GetFsHeader(index); + } + + private IStorage OpenSectionStorage(int index) + { + if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); + + long offset = Header.GetSectionStartOffset(index); + long size = Header.GetSectionSize(index); + + BaseStorage.GetSize(out long baseSize).ThrowIfFailure(); + + if (!IsSubRange(offset, size, baseSize)) { - return Header.IsSectionEnabled(index); + throw new InvalidDataException( + $"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize:x})."); } - public NcaFsHeader GetFsHeader(int index) - { - if (Header.IsNca0()) - return GetNca0FsHeader(index); + return BaseStorage.Slice(offset, size); + } - return Header.GetFsHeader(index); + private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting) + { + NcaFsHeader header = GetFsHeader(index); + + switch (header.EncryptionType) + { + case NcaEncryptionType.None: + return baseStorage; + case NcaEncryptionType.XTS: + return OpenAesXtsStorage(baseStorage, index, decrypting); + case NcaEncryptionType.AesCtr: + return OpenAesCtrStorage(baseStorage, index); + case NcaEncryptionType.AesCtrEx: + return OpenAesCtrExStorage(baseStorage, index, decrypting); + default: + throw new ArgumentOutOfRangeException(); + } + } + + // ReSharper disable UnusedParameter.Local + private IStorage OpenAesXtsStorage(IStorage baseStorage, int index, bool decrypting) + { + const int sectorSize = 0x200; + + byte[] key0 = GetContentKey(NcaKeyType.AesXts0); + byte[] key1 = GetContentKey(NcaKeyType.AesXts1); + + // todo: Handle xts for nca version 3 + return new CachedStorage(new Aes128XtsStorage(baseStorage, key0, key1, sectorSize, true, decrypting), 2, true); + } + // ReSharper restore UnusedParameter.Local + + private IStorage OpenAesCtrStorage(IStorage baseStorage, int index) + { + NcaFsHeader fsHeader = GetFsHeader(index); + byte[] key = GetContentKey(NcaKeyType.AesCtr); + byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index)); + + var aesStorage = new Aes128CtrStorage(baseStorage, key, Header.GetSectionStartOffset(index), counter, true); + return new CachedStorage(aesStorage, 0x4000, 4, true); + } + + private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting) + { + NcaFsHeader fsHeader = GetFsHeader(index); + NcaFsPatchInfo info = fsHeader.GetPatchInfo(); + + long sectionOffset = Header.GetSectionStartOffset(index); + long sectionSize = Header.GetSectionSize(index); + + long bktrOffset = info.RelocationTreeOffset; + long bktrSize = sectionSize - bktrOffset; + long dataSize = info.RelocationTreeOffset; + + byte[] key = GetContentKey(NcaKeyType.AesCtr); + byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, bktrOffset + sectionOffset); + byte[] counterEx = Aes128CtrStorage.CreateCounter(fsHeader.Counter, sectionOffset); + + IStorage bucketTreeData; + IStorage outputBucketTreeData; + + if (decrypting) + { + bucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true); + outputBucketTreeData = bucketTreeData; + } + else + { + bucketTreeData = baseStorage.Slice(bktrOffset, bktrSize); + outputBucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true); } - private IStorage OpenSectionStorage(int index) + var encryptionBucketTreeData = new SubStorage(bucketTreeData, + info.EncryptionTreeOffset - bktrOffset, sectionSize - info.EncryptionTreeOffset); + + var cachedBucketTreeData = new CachedStorage(encryptionBucketTreeData, IndirectStorage.NodeSize, 6, true); + + var treeHeader = new BucketTree.Header(); + info.EncryptionTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader)); + long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount); + long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount); + + var tableNodeStorage = new SubStorage(cachedBucketTreeData, 0, nodeStorageSize); + var tableEntryStorage = new SubStorage(cachedBucketTreeData, nodeStorageSize, entryStorageSize); + + IStorage decStorage = new Aes128CtrExStorage(baseStorage.Slice(0, dataSize), tableNodeStorage, + tableEntryStorage, treeHeader.EntryCount, key, counterEx, true); + + return new ConcatenationStorage(new[] { decStorage, outputBucketTreeData }, true); + } + + public IStorage OpenRawStorage(int index, bool openEncrypted) + { + if (Header.IsNca0()) + return OpenNca0RawStorage(index, openEncrypted); + + IStorage storage = OpenSectionStorage(index); + + if (IsEncrypted == openEncrypted) { - if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); - - long offset = Header.GetSectionStartOffset(index); - long size = Header.GetSectionSize(index); - - BaseStorage.GetSize(out long baseSize).ThrowIfFailure(); - - if (!IsSubRange(offset, size, baseSize)) - { - throw new InvalidDataException( - $"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize:x})."); - } - - return BaseStorage.Slice(offset, size); - } - - private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting) - { - NcaFsHeader header = GetFsHeader(index); - - switch (header.EncryptionType) - { - case NcaEncryptionType.None: - return baseStorage; - case NcaEncryptionType.XTS: - return OpenAesXtsStorage(baseStorage, index, decrypting); - case NcaEncryptionType.AesCtr: - return OpenAesCtrStorage(baseStorage, index); - case NcaEncryptionType.AesCtrEx: - return OpenAesCtrExStorage(baseStorage, index, decrypting); - default: - throw new ArgumentOutOfRangeException(); - } - } - - // ReSharper disable UnusedParameter.Local - private IStorage OpenAesXtsStorage(IStorage baseStorage, int index, bool decrypting) - { - const int sectorSize = 0x200; - - byte[] key0 = GetContentKey(NcaKeyType.AesXts0); - byte[] key1 = GetContentKey(NcaKeyType.AesXts1); - - // todo: Handle xts for nca version 3 - return new CachedStorage(new Aes128XtsStorage(baseStorage, key0, key1, sectorSize, true, decrypting), 2, true); - } - // ReSharper restore UnusedParameter.Local - - private IStorage OpenAesCtrStorage(IStorage baseStorage, int index) - { - NcaFsHeader fsHeader = GetFsHeader(index); - byte[] key = GetContentKey(NcaKeyType.AesCtr); - byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index)); - - var aesStorage = new Aes128CtrStorage(baseStorage, key, Header.GetSectionStartOffset(index), counter, true); - return new CachedStorage(aesStorage, 0x4000, 4, true); - } - - private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting) - { - NcaFsHeader fsHeader = GetFsHeader(index); - NcaFsPatchInfo info = fsHeader.GetPatchInfo(); - - long sectionOffset = Header.GetSectionStartOffset(index); - long sectionSize = Header.GetSectionSize(index); - - long bktrOffset = info.RelocationTreeOffset; - long bktrSize = sectionSize - bktrOffset; - long dataSize = info.RelocationTreeOffset; - - byte[] key = GetContentKey(NcaKeyType.AesCtr); - byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, bktrOffset + sectionOffset); - byte[] counterEx = Aes128CtrStorage.CreateCounter(fsHeader.Counter, sectionOffset); - - IStorage bucketTreeData; - IStorage outputBucketTreeData; - - if (decrypting) - { - bucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true); - outputBucketTreeData = bucketTreeData; - } - else - { - bucketTreeData = baseStorage.Slice(bktrOffset, bktrSize); - outputBucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true); - } - - var encryptionBucketTreeData = new SubStorage(bucketTreeData, - info.EncryptionTreeOffset - bktrOffset, sectionSize - info.EncryptionTreeOffset); - - var cachedBucketTreeData = new CachedStorage(encryptionBucketTreeData, IndirectStorage.NodeSize, 6, true); - - var treeHeader = new BucketTree.Header(); - info.EncryptionTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader)); - long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount); - long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount); - - var tableNodeStorage = new SubStorage(cachedBucketTreeData, 0, nodeStorageSize); - var tableEntryStorage = new SubStorage(cachedBucketTreeData, nodeStorageSize, entryStorageSize); - - IStorage decStorage = new Aes128CtrExStorage(baseStorage.Slice(0, dataSize), tableNodeStorage, - tableEntryStorage, treeHeader.EntryCount, key, counterEx, true); - - return new ConcatenationStorage(new[] { decStorage, outputBucketTreeData }, true); - } - - public IStorage OpenRawStorage(int index, bool openEncrypted) - { - if (Header.IsNca0()) - return OpenNca0RawStorage(index, openEncrypted); - - IStorage storage = OpenSectionStorage(index); - - if (IsEncrypted == openEncrypted) - { - return storage; - } - - IStorage decryptedStorage = OpenDecryptedStorage(storage, index, !openEncrypted); - - return decryptedStorage; - } - - public IStorage OpenRawStorage(int index) => OpenRawStorage(index, false); - - public IStorage OpenRawStorageWithPatch(Nca patchNca, int index) - { - IStorage patchStorage = patchNca.OpenRawStorage(index); - IStorage baseStorage = SectionExists(index) ? OpenRawStorage(index) : new NullStorage(); - - patchStorage.GetSize(out long patchSize).ThrowIfFailure(); - baseStorage.GetSize(out long baseSize).ThrowIfFailure(); - - NcaFsHeader header = patchNca.GetFsHeader(index); - NcaFsPatchInfo patchInfo = header.GetPatchInfo(); - - if (patchInfo.RelocationTreeSize == 0) - { - return patchStorage; - } - - var treeHeader = new BucketTree.Header(); - patchInfo.RelocationTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader)); - long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount); - long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount); - - var relocationTableStorage = new SubStorage(patchStorage, patchInfo.RelocationTreeOffset, patchInfo.RelocationTreeSize); - var cachedTableStorage = new CachedStorage(relocationTableStorage, IndirectStorage.NodeSize, 4, true); - - var tableNodeStorage = new SubStorage(cachedTableStorage, 0, nodeStorageSize); - var tableEntryStorage = new SubStorage(cachedTableStorage, nodeStorageSize, entryStorageSize); - - var storage = new IndirectStorage(); - storage.Initialize(tableNodeStorage, tableEntryStorage, treeHeader.EntryCount).ThrowIfFailure(); - - storage.SetStorage(0, baseStorage, 0, baseSize); - storage.SetStorage(1, patchStorage, 0, patchSize); - return storage; } - public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel) + IStorage decryptedStorage = OpenDecryptedStorage(storage, index, !openEncrypted); + + return decryptedStorage; + } + + public IStorage OpenRawStorage(int index) => OpenRawStorage(index, false); + + public IStorage OpenRawStorageWithPatch(Nca patchNca, int index) + { + IStorage patchStorage = patchNca.OpenRawStorage(index); + IStorage baseStorage = SectionExists(index) ? OpenRawStorage(index) : new NullStorage(); + + patchStorage.GetSize(out long patchSize).ThrowIfFailure(); + baseStorage.GetSize(out long baseSize).ThrowIfFailure(); + + NcaFsHeader header = patchNca.GetFsHeader(index); + NcaFsPatchInfo patchInfo = header.GetPatchInfo(); + + if (patchInfo.RelocationTreeSize == 0) { - IStorage rawStorage = OpenRawStorage(index); - NcaFsHeader header = GetFsHeader(index); - - if (header.EncryptionType == NcaEncryptionType.AesCtrEx) - { - return rawStorage.Slice(0, header.GetPatchInfo().RelocationTreeOffset); - } - - return CreateVerificationStorage(integrityCheckLevel, header, rawStorage); + return patchStorage; } - public IStorage OpenStorageWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel) - { - IStorage rawStorage = OpenRawStorageWithPatch(patchNca, index); - NcaFsHeader header = patchNca.GetFsHeader(index); + var treeHeader = new BucketTree.Header(); + patchInfo.RelocationTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader)); + long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount); + long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount); - return CreateVerificationStorage(integrityCheckLevel, header, rawStorage); + var relocationTableStorage = new SubStorage(patchStorage, patchInfo.RelocationTreeOffset, patchInfo.RelocationTreeSize); + var cachedTableStorage = new CachedStorage(relocationTableStorage, IndirectStorage.NodeSize, 4, true); + + var tableNodeStorage = new SubStorage(cachedTableStorage, 0, nodeStorageSize); + var tableEntryStorage = new SubStorage(cachedTableStorage, nodeStorageSize, entryStorageSize); + + var storage = new IndirectStorage(); + storage.Initialize(tableNodeStorage, tableEntryStorage, treeHeader.EntryCount).ThrowIfFailure(); + + storage.SetStorage(0, baseStorage, 0, baseSize); + storage.SetStorage(1, patchStorage, 0, patchSize); + + return storage; + } + + public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage rawStorage = OpenRawStorage(index); + NcaFsHeader header = GetFsHeader(index); + + if (header.EncryptionType == NcaEncryptionType.AesCtrEx) + { + return rawStorage.Slice(0, header.GetPatchInfo().RelocationTreeOffset); } - private IStorage CreateVerificationStorage(IntegrityCheckLevel integrityCheckLevel, NcaFsHeader header, - IStorage rawStorage) + return CreateVerificationStorage(integrityCheckLevel, header, rawStorage); + } + + public IStorage OpenStorageWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage rawStorage = OpenRawStorageWithPatch(patchNca, index); + NcaFsHeader header = patchNca.GetFsHeader(index); + + return CreateVerificationStorage(integrityCheckLevel, header, rawStorage); + } + + private IStorage CreateVerificationStorage(IntegrityCheckLevel integrityCheckLevel, NcaFsHeader header, + IStorage rawStorage) + { + switch (header.HashType) { - switch (header.HashType) - { - case NcaHashType.Sha256: - return InitIvfcForPartitionFs(header.GetIntegrityInfoSha256(), rawStorage, integrityCheckLevel, - true); - case NcaHashType.Ivfc: - // The FS header of an NCA0 section with IVFC verification must be manually skipped - if (Header.IsNca0()) - { - rawStorage = rawStorage.Slice(0x200); - } - - return InitIvfcForRomFs(header.GetIntegrityInfoIvfc(), rawStorage, integrityCheckLevel, true); - default: - throw new ArgumentOutOfRangeException(); - } - } - - public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) - { - IStorage storage = OpenStorage(index, integrityCheckLevel); - NcaFsHeader header = GetFsHeader(index); - - return OpenFileSystem(storage, header); - } - - public IFileSystem OpenFileSystemWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel) - { - IStorage storage = OpenStorageWithPatch(patchNca, index, integrityCheckLevel); - NcaFsHeader header = patchNca.GetFsHeader(index); - - return OpenFileSystem(storage, header); - } - - private IFileSystem OpenFileSystem(IStorage storage, NcaFsHeader header) - { - switch (header.FormatType) - { - case NcaFormatType.Pfs0: - return new PartitionFileSystem(storage); - case NcaFormatType.Romfs: - return new RomFsFileSystem(storage); - default: - throw new ArgumentOutOfRangeException(); - } - } - - public IFileSystem OpenFileSystem(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenFileSystem(GetSectionIndexFromType(type), integrityCheckLevel); - } - - public IFileSystem OpenFileSystemWithPatch(Nca patchNca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenFileSystemWithPatch(patchNca, GetSectionIndexFromType(type), integrityCheckLevel); - } - - public IStorage OpenRawStorage(NcaSectionType type) - { - return OpenRawStorage(GetSectionIndexFromType(type)); - } - - public IStorage OpenRawStorageWithPatch(Nca patchNca, NcaSectionType type) - { - return OpenRawStorageWithPatch(patchNca, GetSectionIndexFromType(type)); - } - - public IStorage OpenStorage(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenStorage(GetSectionIndexFromType(type), integrityCheckLevel); - } - - public IStorage OpenStorageWithPatch(Nca patchNca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenStorageWithPatch(patchNca, GetSectionIndexFromType(type), integrityCheckLevel); - } - - public IStorage OpenEncryptedNca() => OpenFullNca(true); - public IStorage OpenDecryptedNca() => OpenFullNca(false); - - public IStorage OpenFullNca(bool openEncrypted) - { - if (openEncrypted == IsEncrypted) - { - return BaseStorage; - } - - var builder = new ConcatenationStorageBuilder(); - builder.Add(OpenHeaderStorage(openEncrypted), 0); - - if (Header.IsNca0()) - { - builder.Add(OpenNca0BodyStorage(openEncrypted), 0x400); - return builder.Build(); - } - - for (int i = 0; i < NcaHeader.SectionCount; i++) - { - if (Header.IsSectionEnabled(i)) + case NcaHashType.Sha256: + return InitIvfcForPartitionFs(header.GetIntegrityInfoSha256(), rawStorage, integrityCheckLevel, + true); + case NcaHashType.Ivfc: + // The FS header of an NCA0 section with IVFC verification must be manually skipped + if (Header.IsNca0()) { - builder.Add(OpenRawStorage(i, openEncrypted), Header.GetSectionStartOffset(i)); + rawStorage = rawStorage.Slice(0x200); } - } + return InitIvfcForRomFs(header.GetIntegrityInfoIvfc(), rawStorage, integrityCheckLevel, true); + default: + throw new ArgumentOutOfRangeException(); + } + } + + public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage storage = OpenStorage(index, integrityCheckLevel); + NcaFsHeader header = GetFsHeader(index); + + return OpenFileSystem(storage, header); + } + + public IFileSystem OpenFileSystemWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel) + { + IStorage storage = OpenStorageWithPatch(patchNca, index, integrityCheckLevel); + NcaFsHeader header = patchNca.GetFsHeader(index); + + return OpenFileSystem(storage, header); + } + + private IFileSystem OpenFileSystem(IStorage storage, NcaFsHeader header) + { + switch (header.FormatType) + { + case NcaFormatType.Pfs0: + return new PartitionFileSystem(storage); + case NcaFormatType.Romfs: + return new RomFsFileSystem(storage); + default: + throw new ArgumentOutOfRangeException(); + } + } + + public IFileSystem OpenFileSystem(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenFileSystem(GetSectionIndexFromType(type), integrityCheckLevel); + } + + public IFileSystem OpenFileSystemWithPatch(Nca patchNca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenFileSystemWithPatch(patchNca, GetSectionIndexFromType(type), integrityCheckLevel); + } + + public IStorage OpenRawStorage(NcaSectionType type) + { + return OpenRawStorage(GetSectionIndexFromType(type)); + } + + public IStorage OpenRawStorageWithPatch(Nca patchNca, NcaSectionType type) + { + return OpenRawStorageWithPatch(patchNca, GetSectionIndexFromType(type)); + } + + public IStorage OpenStorage(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenStorage(GetSectionIndexFromType(type), integrityCheckLevel); + } + + public IStorage OpenStorageWithPatch(Nca patchNca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenStorageWithPatch(patchNca, GetSectionIndexFromType(type), integrityCheckLevel); + } + + public IStorage OpenEncryptedNca() => OpenFullNca(true); + public IStorage OpenDecryptedNca() => OpenFullNca(false); + + public IStorage OpenFullNca(bool openEncrypted) + { + if (openEncrypted == IsEncrypted) + { + return BaseStorage; + } + + var builder = new ConcatenationStorageBuilder(); + builder.Add(OpenHeaderStorage(openEncrypted), 0); + + if (Header.IsNca0()) + { + builder.Add(OpenNca0BodyStorage(openEncrypted), 0x400); return builder.Build(); } - private int GetSectionIndexFromType(NcaSectionType type) + for (int i = 0; i < NcaHeader.SectionCount; i++) { - return GetSectionIndexFromType(type, Header.ContentType); - } - - public static int GetSectionIndexFromType(NcaSectionType type, NcaContentType contentType) - { - if (!TryGetSectionIndexFromType(type, contentType, out int index)) + if (Header.IsSectionEnabled(i)) { - throw new ArgumentOutOfRangeException(nameof(type), "NCA does not contain this section type."); - } - - return index; - } - - public static bool TryGetSectionIndexFromType(NcaSectionType type, NcaContentType contentType, out int index) - { - switch (type) - { - case NcaSectionType.Code when contentType == NcaContentType.Program: - index = 0; - return true; - case NcaSectionType.Data when contentType == NcaContentType.Program: - index = 1; - return true; - case NcaSectionType.Logo when contentType == NcaContentType.Program: - index = 2; - return true; - case NcaSectionType.Data: - index = 0; - return true; - default: - index = 0; - return false; + builder.Add(OpenRawStorage(i, openEncrypted), Header.GetSectionStartOffset(i)); } } - public static NcaSectionType GetSectionTypeFromIndex(int index, NcaContentType contentType) - { - if (!TryGetSectionTypeFromIndex(index, contentType, out NcaSectionType type)) - { - throw new ArgumentOutOfRangeException(nameof(type), "NCA type does not contain this index."); - } + return builder.Build(); + } - return type; + private int GetSectionIndexFromType(NcaSectionType type) + { + return GetSectionIndexFromType(type, Header.ContentType); + } + + public static int GetSectionIndexFromType(NcaSectionType type, NcaContentType contentType) + { + if (!TryGetSectionIndexFromType(type, contentType, out int index)) + { + throw new ArgumentOutOfRangeException(nameof(type), "NCA does not contain this section type."); } - public static bool TryGetSectionTypeFromIndex(int index, NcaContentType contentType, out NcaSectionType type) + return index; + } + + public static bool TryGetSectionIndexFromType(NcaSectionType type, NcaContentType contentType, out int index) + { + switch (type) { - switch (index) - { - case 0 when contentType == NcaContentType.Program: - type = NcaSectionType.Code; - return true; - case 1 when contentType == NcaContentType.Program: - type = NcaSectionType.Data; - return true; - case 2 when contentType == NcaContentType.Program: - type = NcaSectionType.Logo; - return true; - case 0: - type = NcaSectionType.Data; - return true; - default: - UnsafeHelpers.SkipParamInit(out type); - return false; - } + case NcaSectionType.Code when contentType == NcaContentType.Program: + index = 0; + return true; + case NcaSectionType.Data when contentType == NcaContentType.Program: + index = 1; + return true; + case NcaSectionType.Logo when contentType == NcaContentType.Program: + index = 2; + return true; + case NcaSectionType.Data: + index = 0; + return true; + default: + index = 0; + return false; + } + } + + public static NcaSectionType GetSectionTypeFromIndex(int index, NcaContentType contentType) + { + if (!TryGetSectionTypeFromIndex(index, contentType, out NcaSectionType type)) + { + throw new ArgumentOutOfRangeException(nameof(type), "NCA type does not contain this index."); } - private static HierarchicalIntegrityVerificationStorage InitIvfcForPartitionFs(NcaFsIntegrityInfoSha256 info, - IStorage pfsStorage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + return type; + } + + public static bool TryGetSectionTypeFromIndex(int index, NcaContentType contentType, out NcaSectionType type) + { + switch (index) { - Debug.Assert(info.LevelCount == 2); + case 0 when contentType == NcaContentType.Program: + type = NcaSectionType.Code; + return true; + case 1 when contentType == NcaContentType.Program: + type = NcaSectionType.Data; + return true; + case 2 when contentType == NcaContentType.Program: + type = NcaSectionType.Logo; + return true; + case 0: + type = NcaSectionType.Data; + return true; + default: + UnsafeHelpers.SkipParamInit(out type); + return false; + } + } - IStorage hashStorage = pfsStorage.Slice(info.GetLevelOffset(0), info.GetLevelSize(0), leaveOpen); - IStorage dataStorage = pfsStorage.Slice(info.GetLevelOffset(1), info.GetLevelSize(1), leaveOpen); + private static HierarchicalIntegrityVerificationStorage InitIvfcForPartitionFs(NcaFsIntegrityInfoSha256 info, + IStorage pfsStorage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + { + Debug.Assert(info.LevelCount == 2); - var initInfo = new IntegrityVerificationInfo[3]; + IStorage hashStorage = pfsStorage.Slice(info.GetLevelOffset(0), info.GetLevelSize(0), leaveOpen); + IStorage dataStorage = pfsStorage.Slice(info.GetLevelOffset(1), info.GetLevelSize(1), leaveOpen); - // Set the master hash - initInfo[0] = new IntegrityVerificationInfo + var initInfo = new IntegrityVerificationInfo[3]; + + // Set the master hash + initInfo[0] = new IntegrityVerificationInfo + { + // todo Get hash directly from header + Data = new MemoryStorage(info.MasterHash.ToArray()), + + BlockSize = 0, + Type = IntegrityStorageType.PartitionFs + }; + + initInfo[1] = new IntegrityVerificationInfo + { + Data = hashStorage, + BlockSize = (int)info.GetLevelSize(0), + Type = IntegrityStorageType.PartitionFs + }; + + initInfo[2] = new IntegrityVerificationInfo + { + Data = dataStorage, + BlockSize = info.BlockSize, + Type = IntegrityStorageType.PartitionFs + }; + + return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); + } + + private static HierarchicalIntegrityVerificationStorage InitIvfcForRomFs(NcaFsIntegrityInfoIvfc ivfc, + IStorage dataStorage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + { + var initInfo = new IntegrityVerificationInfo[ivfc.LevelCount]; + + initInfo[0] = new IntegrityVerificationInfo + { + Data = new MemoryStorage(ivfc.MasterHash.ToArray()), + BlockSize = 0 + }; + + for (int i = 1; i < ivfc.LevelCount; i++) + { + initInfo[i] = new IntegrityVerificationInfo { - // todo Get hash directly from header - Data = new MemoryStorage(info.MasterHash.ToArray()), - - BlockSize = 0, - Type = IntegrityStorageType.PartitionFs + Data = dataStorage.Slice(ivfc.GetLevelOffset(i - 1), ivfc.GetLevelSize(i - 1)), + BlockSize = 1 << ivfc.GetLevelBlockSize(i - 1), + Type = IntegrityStorageType.RomFs }; - - initInfo[1] = new IntegrityVerificationInfo - { - Data = hashStorage, - BlockSize = (int)info.GetLevelSize(0), - Type = IntegrityStorageType.PartitionFs - }; - - initInfo[2] = new IntegrityVerificationInfo - { - Data = dataStorage, - BlockSize = info.BlockSize, - Type = IntegrityStorageType.PartitionFs - }; - - return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); } - private static HierarchicalIntegrityVerificationStorage InitIvfcForRomFs(NcaFsIntegrityInfoIvfc ivfc, - IStorage dataStorage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); + } + + public IStorage OpenDecryptedHeaderStorage() => OpenHeaderStorage(false); + + public IStorage OpenHeaderStorage(bool openEncrypted) + { + long firstSectionOffset = long.MaxValue; + bool hasEnabledSection = false; + + // Encrypted portion continues until the first section + for (int i = 0; i < NcaHeader.SectionCount; i++) { - var initInfo = new IntegrityVerificationInfo[ivfc.LevelCount]; - - initInfo[0] = new IntegrityVerificationInfo + if (Header.IsSectionEnabled(i)) { - Data = new MemoryStorage(ivfc.MasterHash.ToArray()), - BlockSize = 0 - }; - - for (int i = 1; i < ivfc.LevelCount; i++) - { - initInfo[i] = new IntegrityVerificationInfo - { - Data = dataStorage.Slice(ivfc.GetLevelOffset(i - 1), ivfc.GetLevelSize(i - 1)), - BlockSize = 1 << ivfc.GetLevelBlockSize(i - 1), - Type = IntegrityStorageType.RomFs - }; + hasEnabledSection = true; + firstSectionOffset = Math.Min(firstSectionOffset, Header.GetSectionStartOffset(i)); } - - return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); } - public IStorage OpenDecryptedHeaderStorage() => OpenHeaderStorage(false); + long headerSize = hasEnabledSection ? firstSectionOffset : NcaHeader.HeaderSize; + IStorage rawHeaderStorage = BaseStorage.Slice(0, headerSize); - public IStorage OpenHeaderStorage(bool openEncrypted) + if (openEncrypted == IsEncrypted) + return rawHeaderStorage; + + IStorage header; + + switch (Header.Version) { - long firstSectionOffset = long.MaxValue; - bool hasEnabledSection = false; - - // Encrypted portion continues until the first section - for (int i = 0; i < NcaHeader.SectionCount; i++) - { - if (Header.IsSectionEnabled(i)) - { - hasEnabledSection = true; - firstSectionOffset = Math.Min(firstSectionOffset, Header.GetSectionStartOffset(i)); - } - } - - long headerSize = hasEnabledSection ? firstSectionOffset : NcaHeader.HeaderSize; - IStorage rawHeaderStorage = BaseStorage.Slice(0, headerSize); - - if (openEncrypted == IsEncrypted) - return rawHeaderStorage; - - IStorage header; - - switch (Header.Version) - { - case 3: - header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); - break; - case 2: - header = OpenNca2Header(headerSize, !openEncrypted); - break; - case 0: - header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); - break; - default: - throw new NotSupportedException("Unsupported NCA version"); - } - - return header; + case 3: + header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); + break; + case 2: + header = OpenNca2Header(headerSize, !openEncrypted); + break; + case 0: + header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); + break; + default: + throw new NotSupportedException("Unsupported NCA version"); } - private IStorage OpenNca2Header(long size, bool decrypting) + return header; + } + + private IStorage OpenNca2Header(long size, bool decrypting) + { + const int sectorSize = NcaHeader.HeaderSectorSize; + + var sources = new List(); + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); + + for (int i = 0x400; i < size; i += sectorSize) { - const int sectorSize = NcaHeader.HeaderSectorSize; - - var sources = new List(); - sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); - - for (int i = 0x400; i < size; i += sectorSize) - { - sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); - } - - return new ConcatenationStorage(sources, true); + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); } - private byte[] GetDecryptedKeyAreaNca0() + return new ConcatenationStorage(sources, true); + } + + private byte[] GetDecryptedKeyAreaNca0() + { + if (Nca0KeyArea != null) + return Nca0KeyArea; + + if (Header.FormatVersion == NcaVersion.Nca0FixedKey) { - if (Nca0KeyArea != null) - return Nca0KeyArea; + Nca0KeyArea = Header.GetKeyArea().ToArray(); + } + else if (Header.FormatVersion == NcaVersion.Nca0RsaOaep) + { + Span keyArea = Header.GetKeyArea(); + byte[] decKeyArea = new byte[0x100]; - if (Header.FormatVersion == NcaVersion.Nca0FixedKey) + if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKeyParams, out _)) { - Nca0KeyArea = Header.GetKeyArea().ToArray(); - } - else if (Header.FormatVersion == NcaVersion.Nca0RsaOaep) - { - Span keyArea = Header.GetKeyArea(); - byte[] decKeyArea = new byte[0x100]; - - if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKeyParams, out _)) - { - Nca0KeyArea = decKeyArea; - } - else - { - throw new InvalidDataException("Unable to decrypt NCA0 key area."); - } + Nca0KeyArea = decKeyArea; } else { - throw new NotSupportedException(); + throw new InvalidDataException("Unable to decrypt NCA0 key area."); } - - return Nca0KeyArea; + } + else + { + throw new NotSupportedException(); } - private IStorage OpenNca0BodyStorage(bool openEncrypted) + return Nca0KeyArea; + } + + private IStorage OpenNca0BodyStorage(bool openEncrypted) + { + // NCA0 encrypts the entire NCA body using AES-XTS instead of + // using different encryption types and IVs for each section. + Assert.SdkEqual(0, Header.Version); + + if (openEncrypted == IsEncrypted) { - // NCA0 encrypts the entire NCA body using AES-XTS instead of - // using different encryption types and IVs for each section. - Assert.SdkEqual(0, Header.Version); + return GetRawStorage(); + } - if (openEncrypted == IsEncrypted) - { - return GetRawStorage(); - } - - if (Nca0TransformedBody != null) - return Nca0TransformedBody; - - byte[] key0 = GetContentKey(NcaKeyType.AesXts0); - byte[] key1 = GetContentKey(NcaKeyType.AesXts1); - - Nca0TransformedBody = new CachedStorage(new Aes128XtsStorage(GetRawStorage(), key0, key1, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); + if (Nca0TransformedBody != null) return Nca0TransformedBody; - IStorage GetRawStorage() - { - BaseStorage.GetSize(out long ncaSize).ThrowIfFailure(); - return BaseStorage.Slice(0x400, ncaSize - 0x400); - } - } + byte[] key0 = GetContentKey(NcaKeyType.AesXts0); + byte[] key1 = GetContentKey(NcaKeyType.AesXts1); - private IStorage OpenNca0RawStorage(int index, bool openEncrypted) + Nca0TransformedBody = new CachedStorage(new Aes128XtsStorage(GetRawStorage(), key0, key1, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); + return Nca0TransformedBody; + + IStorage GetRawStorage() { - if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); - - long offset = Header.GetSectionStartOffset(index) - 0x400; - long size = Header.GetSectionSize(index); - - IStorage bodyStorage = OpenNca0BodyStorage(openEncrypted); - - bodyStorage.GetSize(out long baseSize).ThrowIfFailure(); - - if (!IsSubRange(offset, size, baseSize)) - { - throw new InvalidDataException( - $"Section offset (0x{offset + 0x400:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize + 0x400:x})."); - } - - return new SubStorage(bodyStorage, offset, size); - } - - public NcaFsHeader GetNca0FsHeader(int index) - { - // NCA0 stores the FS header in the first block of the section instead of the header - IStorage bodyStorage = OpenNca0BodyStorage(false); - long offset = Header.GetSectionStartOffset(index) - 0x400; - - byte[] fsHeaderData = new byte[0x200]; - bodyStorage.Read(offset, fsHeaderData).ThrowIfFailure(); - - return new NcaFsHeader(fsHeaderData); - } - - public Validity VerifyHeaderSignature() - { - return Header.VerifySignature1(KeySet.NcaHeaderSigningKeyParams[0].Modulus); - } - - internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion) - { - int counterType; - int counterVersion; - - NcaFsHeader header = GetFsHeader(sectionIndex); - if (header.EncryptionType != NcaEncryptionType.AesCtr && - header.EncryptionType != NcaEncryptionType.AesCtrEx) return; - - switch (type) - { - case Ncm.ContentType.Program: - counterType = sectionIndex + 1; - break; - case Ncm.ContentType.HtmlDocument: - counterType = (int)Ncm.ContentType.HtmlDocument; - break; - case Ncm.ContentType.LegalInformation: - counterType = (int)Ncm.ContentType.LegalInformation; - break; - default: - counterType = 0; - break; - } - - // Version of firmware NCAs appears to always be 0 - // Haven't checked delta fragment NCAs - switch (Header.ContentType) - { - case NcaContentType.Program: - case NcaContentType.Manual: - counterVersion = Math.Max(minorVersion - 1, 0); - break; - case NcaContentType.PublicData: - counterVersion = minorVersion << 16; - break; - default: - counterVersion = 0; - break; - } - - header.CounterType = counterType; - header.CounterVersion = counterVersion; - } - - private static bool IsSubRange(long startIndex, long subLength, long length) - { - bool isOutOfRange = startIndex < 0 || startIndex > length || subLength < 0 || startIndex > length - subLength; - return !isOutOfRange; + BaseStorage.GetSize(out long ncaSize).ThrowIfFailure(); + return BaseStorage.Slice(0x400, ncaSize - 0x400); } } + + private IStorage OpenNca0RawStorage(int index, bool openEncrypted) + { + if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); + + long offset = Header.GetSectionStartOffset(index) - 0x400; + long size = Header.GetSectionSize(index); + + IStorage bodyStorage = OpenNca0BodyStorage(openEncrypted); + + bodyStorage.GetSize(out long baseSize).ThrowIfFailure(); + + if (!IsSubRange(offset, size, baseSize)) + { + throw new InvalidDataException( + $"Section offset (0x{offset + 0x400:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize + 0x400:x})."); + } + + return new SubStorage(bodyStorage, offset, size); + } + + public NcaFsHeader GetNca0FsHeader(int index) + { + // NCA0 stores the FS header in the first block of the section instead of the header + IStorage bodyStorage = OpenNca0BodyStorage(false); + long offset = Header.GetSectionStartOffset(index) - 0x400; + + byte[] fsHeaderData = new byte[0x200]; + bodyStorage.Read(offset, fsHeaderData).ThrowIfFailure(); + + return new NcaFsHeader(fsHeaderData); + } + + public Validity VerifyHeaderSignature() + { + return Header.VerifySignature1(KeySet.NcaHeaderSigningKeyParams[0].Modulus); + } + + internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion) + { + int counterType; + int counterVersion; + + NcaFsHeader header = GetFsHeader(sectionIndex); + if (header.EncryptionType != NcaEncryptionType.AesCtr && + header.EncryptionType != NcaEncryptionType.AesCtrEx) return; + + switch (type) + { + case Ncm.ContentType.Program: + counterType = sectionIndex + 1; + break; + case Ncm.ContentType.HtmlDocument: + counterType = (int)Ncm.ContentType.HtmlDocument; + break; + case Ncm.ContentType.LegalInformation: + counterType = (int)Ncm.ContentType.LegalInformation; + break; + default: + counterType = 0; + break; + } + + // Version of firmware NCAs appears to always be 0 + // Haven't checked delta fragment NCAs + switch (Header.ContentType) + { + case NcaContentType.Program: + case NcaContentType.Manual: + counterVersion = Math.Max(minorVersion - 1, 0); + break; + case NcaContentType.PublicData: + counterVersion = minorVersion << 16; + break; + default: + counterVersion = 0; + break; + } + + header.CounterType = counterType; + header.CounterVersion = counterVersion; + } + + private static bool IsSubRange(long startIndex, long subLength, long length) + { + bool isOutOfRange = startIndex < 0 || startIndex > length || subLength < 0 || startIndex > length - subLength; + return !isOutOfRange; + } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaExtensions.cs b/src/LibHac/FsSystem/NcaUtils/NcaExtensions.cs index a75e437f..728f4bb3 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaExtensions.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaExtensions.cs @@ -5,195 +5,194 @@ using LibHac.Crypto; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public static class NcaExtensions { - public static class NcaExtensions + public static IStorage OpenStorage(this Nca nca, int index, IntegrityCheckLevel integrityCheckLevel, + bool openRaw) { - public static IStorage OpenStorage(this Nca nca, int index, IntegrityCheckLevel integrityCheckLevel, - bool openRaw) + if (openRaw) return nca.OpenRawStorage(index); + return nca.OpenStorage(index, integrityCheckLevel); + } + + public static IStorage OpenStorage(this Nca nca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel, + bool openRaw) + { + if (openRaw) return nca.OpenRawStorage(type); + return nca.OpenStorage(type, integrityCheckLevel); + } + + public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + nca.OpenStorage(index, integrityCheckLevel, raw) + .WriteAllBytes(filename, logger); + } + + public static void ExportSection(this Nca nca, NcaSectionType type, string filename, bool raw = false, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + nca.OpenStorage(type, integrityCheckLevel, raw) + .WriteAllBytes(filename, logger); + } + + public static void ExtractSection(this Nca nca, int index, string outputDir, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + IFileSystem fs = nca.OpenFileSystem(index, integrityCheckLevel); + fs.Extract(outputDir, logger); + } + + public static void ExtractSection(this Nca nca, NcaSectionType type, string outputDir, + IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + { + IFileSystem fs = nca.OpenFileSystem(type, integrityCheckLevel); + fs.Extract(outputDir, logger); + } + + public static Validity ValidateSectionMasterHash(this Nca nca, int index) + { + if (!nca.SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); + if (!nca.CanOpenSection(index)) return Validity.MissingKey; + + NcaFsHeader header = nca.GetFsHeader(index); + + // The base data is needed to validate the hash, so use a trick involving the AES-CTR extended + // encryption table to check if the decryption is invalid. + // todo: If the patch replaces the data checked by the master hash, use that directly + if (header.IsPatchSection()) { - if (openRaw) return nca.OpenRawStorage(index); - return nca.OpenStorage(index, integrityCheckLevel); + if (header.EncryptionType != NcaEncryptionType.AesCtrEx) return Validity.Unchecked; + + Validity ctrExValidity = ValidateCtrExDecryption(nca, index); + return ctrExValidity == Validity.Invalid ? Validity.Invalid : Validity.Unchecked; } - public static IStorage OpenStorage(this Nca nca, NcaSectionType type, IntegrityCheckLevel integrityCheckLevel, - bool openRaw) + byte[] expectedHash; + long offset; + long size; + + switch (header.HashType) { - if (openRaw) return nca.OpenRawStorage(type); - return nca.OpenStorage(type, integrityCheckLevel); + case NcaHashType.Ivfc: + NcaFsIntegrityInfoIvfc ivfcInfo = header.GetIntegrityInfoIvfc(); + + expectedHash = ivfcInfo.MasterHash.ToArray(); + offset = ivfcInfo.GetLevelOffset(0); + size = 1 << ivfcInfo.GetLevelBlockSize(0); + + break; + case NcaHashType.Sha256: + NcaFsIntegrityInfoSha256 sha256Info = header.GetIntegrityInfoSha256(); + expectedHash = sha256Info.MasterHash.ToArray(); + + offset = sha256Info.GetLevelOffset(0); + size = sha256Info.GetLevelSize(0); + + break; + default: + return Validity.Unchecked; } - public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, - IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + IStorage storage = nca.OpenRawStorage(index); + + // The FS header of an NCA0 section with IVFC verification must be manually skipped + if (nca.Header.IsNca0() && header.HashType == NcaHashType.Ivfc) { - nca.OpenStorage(index, integrityCheckLevel, raw) - .WriteAllBytes(filename, logger); + offset += 0x200; } - public static void ExportSection(this Nca nca, NcaSectionType type, string filename, bool raw = false, - IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) + byte[] data = new byte[size]; + storage.Read(offset, data).ThrowIfFailure(); + + byte[] actualHash = new byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(data, actualHash); + + if (Utilities.ArraysEqual(expectedHash, actualHash)) return Validity.Valid; + + return Validity.Invalid; + } + + private static Validity ValidateCtrExDecryption(Nca nca, int index) + { + // The encryption subsection table in an AesCtrEx-encrypted partition contains the length of the entire partition. + // The encryption table is always located immediately following the partition data, so the offset value of the encryption + // table located in the NCA header should be the same as the size read from the encryption table. + + Debug.Assert(nca.CanOpenSection(index)); + + NcaFsPatchInfo header = nca.GetFsHeader(index).GetPatchInfo(); + IStorage decryptedStorage = nca.OpenRawStorage(index); + + Span buffer = stackalloc byte[sizeof(long)]; + decryptedStorage.Read(header.EncryptionTreeOffset + 8, buffer).ThrowIfFailure(); + long readDataSize = BinaryPrimitives.ReadInt64LittleEndian(buffer); + + if (header.EncryptionTreeOffset != readDataSize) return Validity.Invalid; + + return Validity.Valid; + } + + public static Validity VerifyNca(this Nca nca, IProgressReport logger = null, bool quiet = false) + { + for (int i = 0; i < 3; i++) { - nca.OpenStorage(type, integrityCheckLevel, raw) - .WriteAllBytes(filename, logger); - } - - public static void ExtractSection(this Nca nca, int index, string outputDir, - IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) - { - IFileSystem fs = nca.OpenFileSystem(index, integrityCheckLevel); - fs.Extract(outputDir, logger); - } - - public static void ExtractSection(this Nca nca, NcaSectionType type, string outputDir, - IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null) - { - IFileSystem fs = nca.OpenFileSystem(type, integrityCheckLevel); - fs.Extract(outputDir, logger); - } - - public static Validity ValidateSectionMasterHash(this Nca nca, int index) - { - if (!nca.SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing); - if (!nca.CanOpenSection(index)) return Validity.MissingKey; - - NcaFsHeader header = nca.GetFsHeader(index); - - // The base data is needed to validate the hash, so use a trick involving the AES-CTR extended - // encryption table to check if the decryption is invalid. - // todo: If the patch replaces the data checked by the master hash, use that directly - if (header.IsPatchSection()) + if (nca.CanOpenSection(i)) { - if (header.EncryptionType != NcaEncryptionType.AesCtrEx) return Validity.Unchecked; + Validity sectionValidity = VerifySection(nca, i, logger, quiet); - Validity ctrExValidity = ValidateCtrExDecryption(nca, index); - return ctrExValidity == Validity.Invalid ? Validity.Invalid : Validity.Unchecked; + if (sectionValidity == Validity.Invalid) return Validity.Invalid; } + } - byte[] expectedHash; - long offset; - long size; + return Validity.Valid; + } - switch (header.HashType) + public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false) + { + NcaFsHeader sect = nca.GetFsHeader(index); + NcaHashType hashType = sect.HashType; + if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; + + var stream = nca.OpenStorage(index, IntegrityCheckLevel.IgnoreOnInvalid) + as HierarchicalIntegrityVerificationStorage; + if (stream == null) return Validity.Unchecked; + + if (!quiet) logger?.LogMessage($"Verifying section {index}..."); + Validity validity = stream.Validate(true, logger); + + return validity; + } + + public static Validity VerifyNca(this Nca nca, Nca patchNca, IProgressReport logger = null, bool quiet = false) + { + for (int i = 0; i < 3; i++) + { + if (patchNca.CanOpenSection(i)) { - case NcaHashType.Ivfc: - NcaFsIntegrityInfoIvfc ivfcInfo = header.GetIntegrityInfoIvfc(); + Validity sectionValidity = VerifySection(nca, patchNca, i, logger, quiet); - expectedHash = ivfcInfo.MasterHash.ToArray(); - offset = ivfcInfo.GetLevelOffset(0); - size = 1 << ivfcInfo.GetLevelBlockSize(0); - - break; - case NcaHashType.Sha256: - NcaFsIntegrityInfoSha256 sha256Info = header.GetIntegrityInfoSha256(); - expectedHash = sha256Info.MasterHash.ToArray(); - - offset = sha256Info.GetLevelOffset(0); - size = sha256Info.GetLevelSize(0); - - break; - default: - return Validity.Unchecked; + if (sectionValidity == Validity.Invalid) return Validity.Invalid; } - - IStorage storage = nca.OpenRawStorage(index); - - // The FS header of an NCA0 section with IVFC verification must be manually skipped - if (nca.Header.IsNca0() && header.HashType == NcaHashType.Ivfc) - { - offset += 0x200; - } - - byte[] data = new byte[size]; - storage.Read(offset, data).ThrowIfFailure(); - - byte[] actualHash = new byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(data, actualHash); - - if (Utilities.ArraysEqual(expectedHash, actualHash)) return Validity.Valid; - - return Validity.Invalid; } - private static Validity ValidateCtrExDecryption(Nca nca, int index) - { - // The encryption subsection table in an AesCtrEx-encrypted partition contains the length of the entire partition. - // The encryption table is always located immediately following the partition data, so the offset value of the encryption - // table located in the NCA header should be the same as the size read from the encryption table. + return Validity.Valid; + } - Debug.Assert(nca.CanOpenSection(index)); + public static Validity VerifySection(this Nca nca, Nca patchNca, int index, IProgressReport logger = null, bool quiet = false) + { + NcaFsHeader sect = nca.GetFsHeader(index); + NcaHashType hashType = sect.HashType; + if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; - NcaFsPatchInfo header = nca.GetFsHeader(index).GetPatchInfo(); - IStorage decryptedStorage = nca.OpenRawStorage(index); + var stream = nca.OpenStorageWithPatch(patchNca, index, IntegrityCheckLevel.IgnoreOnInvalid) + as HierarchicalIntegrityVerificationStorage; + if (stream == null) return Validity.Unchecked; - Span buffer = stackalloc byte[sizeof(long)]; - decryptedStorage.Read(header.EncryptionTreeOffset + 8, buffer).ThrowIfFailure(); - long readDataSize = BinaryPrimitives.ReadInt64LittleEndian(buffer); + if (!quiet) logger?.LogMessage($"Verifying section {index}..."); + Validity validity = stream.Validate(true, logger); - if (header.EncryptionTreeOffset != readDataSize) return Validity.Invalid; - - return Validity.Valid; - } - - public static Validity VerifyNca(this Nca nca, IProgressReport logger = null, bool quiet = false) - { - for (int i = 0; i < 3; i++) - { - if (nca.CanOpenSection(i)) - { - Validity sectionValidity = VerifySection(nca, i, logger, quiet); - - if (sectionValidity == Validity.Invalid) return Validity.Invalid; - } - } - - return Validity.Valid; - } - - public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false) - { - NcaFsHeader sect = nca.GetFsHeader(index); - NcaHashType hashType = sect.HashType; - if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; - - var stream = nca.OpenStorage(index, IntegrityCheckLevel.IgnoreOnInvalid) - as HierarchicalIntegrityVerificationStorage; - if (stream == null) return Validity.Unchecked; - - if (!quiet) logger?.LogMessage($"Verifying section {index}..."); - Validity validity = stream.Validate(true, logger); - - return validity; - } - - public static Validity VerifyNca(this Nca nca, Nca patchNca, IProgressReport logger = null, bool quiet = false) - { - for (int i = 0; i < 3; i++) - { - if (patchNca.CanOpenSection(i)) - { - Validity sectionValidity = VerifySection(nca, patchNca, i, logger, quiet); - - if (sectionValidity == Validity.Invalid) return Validity.Invalid; - } - } - - return Validity.Valid; - } - - public static Validity VerifySection(this Nca nca, Nca patchNca, int index, IProgressReport logger = null, bool quiet = false) - { - NcaFsHeader sect = nca.GetFsHeader(index); - NcaHashType hashType = sect.HashType; - if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked; - - var stream = nca.OpenStorageWithPatch(patchNca, index, IntegrityCheckLevel.IgnoreOnInvalid) - as HierarchicalIntegrityVerificationStorage; - if (stream == null) return Validity.Unchecked; - - if (!quiet) logger?.LogMessage($"Verifying section {index}..."); - Validity validity = stream.Validate(true, logger); - - return validity; - } + return validity; } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs index a09bb783..670e0b85 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaFsHeader.cs @@ -4,96 +4,95 @@ using System.Runtime.InteropServices; // ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public struct NcaFsHeader { - public struct NcaFsHeader + private readonly Memory _header; + + public NcaFsHeader(Memory headerData) { - private readonly Memory _header; + _header = headerData; + } - public NcaFsHeader(Memory headerData) - { - _header = headerData; - } + private ref FsHeaderStruct Header => ref Unsafe.As(ref _header.Span[0]); - private ref FsHeaderStruct Header => ref Unsafe.As(ref _header.Span[0]); + public short Version + { + get => Header.Version; + set => Header.Version = value; + } - public short Version - { - get => Header.Version; - set => Header.Version = value; - } + public NcaFormatType FormatType + { + get => (NcaFormatType)Header.FormatType; + set => Header.FormatType = (byte)value; + } - public NcaFormatType FormatType - { - get => (NcaFormatType)Header.FormatType; - set => Header.FormatType = (byte)value; - } + public NcaHashType HashType + { + get => (NcaHashType)Header.HashType; + set => Header.HashType = (byte)value; + } - public NcaHashType HashType - { - get => (NcaHashType)Header.HashType; - set => Header.HashType = (byte)value; - } + public NcaEncryptionType EncryptionType + { + get => (NcaEncryptionType)Header.EncryptionType; + set => Header.EncryptionType = (byte)value; + } - public NcaEncryptionType EncryptionType - { - get => (NcaEncryptionType)Header.EncryptionType; - set => Header.EncryptionType = (byte)value; - } + public NcaFsIntegrityInfoIvfc GetIntegrityInfoIvfc() + { + return new NcaFsIntegrityInfoIvfc(_header.Slice(FsHeaderStruct.IntegrityInfoOffset, FsHeaderStruct.IntegrityInfoSize)); + } - public NcaFsIntegrityInfoIvfc GetIntegrityInfoIvfc() - { - return new NcaFsIntegrityInfoIvfc(_header.Slice(FsHeaderStruct.IntegrityInfoOffset, FsHeaderStruct.IntegrityInfoSize)); - } + public NcaFsIntegrityInfoSha256 GetIntegrityInfoSha256() + { + return new NcaFsIntegrityInfoSha256(_header.Slice(FsHeaderStruct.IntegrityInfoOffset, FsHeaderStruct.IntegrityInfoSize)); + } - public NcaFsIntegrityInfoSha256 GetIntegrityInfoSha256() - { - return new NcaFsIntegrityInfoSha256(_header.Slice(FsHeaderStruct.IntegrityInfoOffset, FsHeaderStruct.IntegrityInfoSize)); - } + public NcaFsPatchInfo GetPatchInfo() + { + return new NcaFsPatchInfo(_header.Slice(FsHeaderStruct.PatchInfoOffset, FsHeaderStruct.PatchInfoSize)); + } - public NcaFsPatchInfo GetPatchInfo() - { - return new NcaFsPatchInfo(_header.Slice(FsHeaderStruct.PatchInfoOffset, FsHeaderStruct.PatchInfoSize)); - } + public bool IsPatchSection() + { + return GetPatchInfo().RelocationTreeSize != 0; + } - public bool IsPatchSection() - { - return GetPatchInfo().RelocationTreeSize != 0; - } + public ulong Counter + { + get => Header.UpperCounter; + set => Header.UpperCounter = value; + } - public ulong Counter - { - get => Header.UpperCounter; - set => Header.UpperCounter = value; - } + public int CounterType + { + get => Header.CounterType; + set => Header.CounterType = value; + } - public int CounterType - { - get => Header.CounterType; - set => Header.CounterType = value; - } + public int CounterVersion + { + get => Header.CounterVersion; + set => Header.CounterVersion = value; + } - public int CounterVersion - { - get => Header.CounterVersion; - set => Header.CounterVersion = value; - } + [StructLayout(LayoutKind.Explicit)] + private struct FsHeaderStruct + { + public const int IntegrityInfoOffset = 8; + public const int IntegrityInfoSize = 0xF8; + public const int PatchInfoOffset = 0x100; + public const int PatchInfoSize = 0x40; - [StructLayout(LayoutKind.Explicit)] - private struct FsHeaderStruct - { - public const int IntegrityInfoOffset = 8; - public const int IntegrityInfoSize = 0xF8; - public const int PatchInfoOffset = 0x100; - public const int PatchInfoSize = 0x40; - - [FieldOffset(0)] public short Version; - [FieldOffset(2)] public byte FormatType; - [FieldOffset(3)] public byte HashType; - [FieldOffset(4)] public byte EncryptionType; - [FieldOffset(0x140)] public ulong UpperCounter; - [FieldOffset(0x140)] public int CounterType; - [FieldOffset(0x144)] public int CounterVersion; - } + [FieldOffset(0)] public short Version; + [FieldOffset(2)] public byte FormatType; + [FieldOffset(3)] public byte HashType; + [FieldOffset(4)] public byte EncryptionType; + [FieldOffset(0x140)] public ulong UpperCounter; + [FieldOffset(0x140)] public int CounterType; + [FieldOffset(0x144)] public int CounterVersion; } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoIvfc.cs b/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoIvfc.cs index 5e290c86..b30fca98 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoIvfc.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoIvfc.cs @@ -2,88 +2,87 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public struct NcaFsIntegrityInfoIvfc { - public struct NcaFsIntegrityInfoIvfc + private readonly Memory _data; + + public NcaFsIntegrityInfoIvfc(Memory data) { - private readonly Memory _data; + _data = data; + } - public NcaFsIntegrityInfoIvfc(Memory data) + private ref IvfcStruct Data => ref Unsafe.As(ref _data.Span[0]); + + private ref IvfcLevel GetLevelInfo(int index) + { + ValidateLevelIndex(index); + + int offset = IvfcStruct.IvfcLevelsOffset + IvfcLevel.IvfcLevelSize * index; + return ref Unsafe.As(ref _data.Span[offset]); + } + + public uint Magic + { + get => Data.Magic; + set => Data.Magic = value; + } + + public int Version + { + get => Data.Version; + set => Data.Version = value; + } + + public int MasterHashSize + { + get => Data.MasterHashSize; + set => Data.MasterHashSize = value; + } + + public int LevelCount + { + get => Data.LevelCount; + set => Data.LevelCount = value; + } + + public Span SaltSource => _data.Span.Slice(IvfcStruct.SaltSourceOffset, IvfcStruct.SaltSourceSize); + public Span MasterHash => _data.Span.Slice(IvfcStruct.MasterHashOffset, MasterHashSize); + + public ref long GetLevelOffset(int index) => ref GetLevelInfo(index).Offset; + public ref long GetLevelSize(int index) => ref GetLevelInfo(index).Size; + public ref int GetLevelBlockSize(int index) => ref GetLevelInfo(index).BlockSize; + + private static void ValidateLevelIndex(int index) + { + if (index < 0 || index > 6) { - _data = data; - } - - private ref IvfcStruct Data => ref Unsafe.As(ref _data.Span[0]); - - private ref IvfcLevel GetLevelInfo(int index) - { - ValidateLevelIndex(index); - - int offset = IvfcStruct.IvfcLevelsOffset + IvfcLevel.IvfcLevelSize * index; - return ref Unsafe.As(ref _data.Span[offset]); - } - - public uint Magic - { - get => Data.Magic; - set => Data.Magic = value; - } - - public int Version - { - get => Data.Version; - set => Data.Version = value; - } - - public int MasterHashSize - { - get => Data.MasterHashSize; - set => Data.MasterHashSize = value; - } - - public int LevelCount - { - get => Data.LevelCount; - set => Data.LevelCount = value; - } - - public Span SaltSource => _data.Span.Slice(IvfcStruct.SaltSourceOffset, IvfcStruct.SaltSourceSize); - public Span MasterHash => _data.Span.Slice(IvfcStruct.MasterHashOffset, MasterHashSize); - - public ref long GetLevelOffset(int index) => ref GetLevelInfo(index).Offset; - public ref long GetLevelSize(int index) => ref GetLevelInfo(index).Size; - public ref int GetLevelBlockSize(int index) => ref GetLevelInfo(index).BlockSize; - - private static void ValidateLevelIndex(int index) - { - if (index < 0 || index > 6) - { - throw new ArgumentOutOfRangeException($"IVFC level index must be between 0 and 6. Actual: {index}"); - } - } - - [StructLayout(LayoutKind.Explicit)] - private struct IvfcStruct - { - public const int IvfcLevelsOffset = 0x10; - public const int SaltSourceOffset = 0xA0; - public const int SaltSourceSize = 0x20; - public const int MasterHashOffset = 0xC0; - - [FieldOffset(0)] public uint Magic; - [FieldOffset(4)] public int Version; - [FieldOffset(8)] public int MasterHashSize; - [FieldOffset(12)] public int LevelCount; - } - - [StructLayout(LayoutKind.Explicit, Size = IvfcLevelSize)] - private struct IvfcLevel - { - public const int IvfcLevelSize = 0x18; - - [FieldOffset(0)] public long Offset; - [FieldOffset(8)] public long Size; - [FieldOffset(0x10)] public int BlockSize; + throw new ArgumentOutOfRangeException($"IVFC level index must be between 0 and 6. Actual: {index}"); } } + + [StructLayout(LayoutKind.Explicit)] + private struct IvfcStruct + { + public const int IvfcLevelsOffset = 0x10; + public const int SaltSourceOffset = 0xA0; + public const int SaltSourceSize = 0x20; + public const int MasterHashOffset = 0xC0; + + [FieldOffset(0)] public uint Magic; + [FieldOffset(4)] public int Version; + [FieldOffset(8)] public int MasterHashSize; + [FieldOffset(12)] public int LevelCount; + } + + [StructLayout(LayoutKind.Explicit, Size = IvfcLevelSize)] + private struct IvfcLevel + { + public const int IvfcLevelSize = 0x18; + + [FieldOffset(0)] public long Offset; + [FieldOffset(8)] public long Size; + [FieldOffset(0x10)] public int BlockSize; + } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoSha256.cs b/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoSha256.cs index 7a8abd4d..2b1663e1 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoSha256.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaFsIntegrityInfoSha256.cs @@ -2,70 +2,69 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public struct NcaFsIntegrityInfoSha256 { - public struct NcaFsIntegrityInfoSha256 + private readonly Memory _data; + + public NcaFsIntegrityInfoSha256(Memory data) { - private readonly Memory _data; + _data = data; + } - public NcaFsIntegrityInfoSha256(Memory data) + private ref Sha256Struct Data => ref Unsafe.As(ref _data.Span[0]); + + private ref Sha256Level GetLevelInfo(int index) + { + ValidateLevelIndex(index); + + int offset = Sha256Struct.Sha256LevelOffset + Sha256Level.Sha256LevelSize * index; + return ref Unsafe.As(ref _data.Span[offset]); + } + + public int BlockSize + { + get => Data.BlockSize; + set => Data.BlockSize = value; + } + + public int LevelCount + { + get => Data.LevelCount; + set => Data.LevelCount = value; + } + + public Span MasterHash => _data.Span.Slice(Sha256Struct.MasterHashOffset, Sha256Struct.MasterHashSize); + + public ref long GetLevelOffset(int index) => ref GetLevelInfo(index).Offset; + public ref long GetLevelSize(int index) => ref GetLevelInfo(index).Size; + + private static void ValidateLevelIndex(int index) + { + if (index < 0 || index > 5) { - _data = data; - } - - private ref Sha256Struct Data => ref Unsafe.As(ref _data.Span[0]); - - private ref Sha256Level GetLevelInfo(int index) - { - ValidateLevelIndex(index); - - int offset = Sha256Struct.Sha256LevelOffset + Sha256Level.Sha256LevelSize * index; - return ref Unsafe.As(ref _data.Span[offset]); - } - - public int BlockSize - { - get => Data.BlockSize; - set => Data.BlockSize = value; - } - - public int LevelCount - { - get => Data.LevelCount; - set => Data.LevelCount = value; - } - - public Span MasterHash => _data.Span.Slice(Sha256Struct.MasterHashOffset, Sha256Struct.MasterHashSize); - - public ref long GetLevelOffset(int index) => ref GetLevelInfo(index).Offset; - public ref long GetLevelSize(int index) => ref GetLevelInfo(index).Size; - - private static void ValidateLevelIndex(int index) - { - if (index < 0 || index > 5) - { - throw new ArgumentOutOfRangeException($"IVFC level index must be between 0 and 5. Actual: {index}"); - } - } - - [StructLayout(LayoutKind.Explicit)] - private struct Sha256Struct - { - public const int MasterHashOffset = 0; - public const int MasterHashSize = 0x20; - public const int Sha256LevelOffset = 0x28; - - [FieldOffset(0x20)] public int BlockSize; - [FieldOffset(0x24)] public int LevelCount; - } - - [StructLayout(LayoutKind.Explicit)] - private struct Sha256Level - { - public const int Sha256LevelSize = 0x10; - - [FieldOffset(0)] public long Offset; - [FieldOffset(8)] public long Size; + throw new ArgumentOutOfRangeException($"IVFC level index must be between 0 and 5. Actual: {index}"); } } + + [StructLayout(LayoutKind.Explicit)] + private struct Sha256Struct + { + public const int MasterHashOffset = 0; + public const int MasterHashSize = 0x20; + public const int Sha256LevelOffset = 0x28; + + [FieldOffset(0x20)] public int BlockSize; + [FieldOffset(0x24)] public int LevelCount; + } + + [StructLayout(LayoutKind.Explicit)] + private struct Sha256Level + { + public const int Sha256LevelSize = 0x10; + + [FieldOffset(0)] public long Offset; + [FieldOffset(8)] public long Size; + } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaFsPatchInfo.cs b/src/LibHac/FsSystem/NcaUtils/NcaFsPatchInfo.cs index ccbbdff3..b01ba7eb 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaFsPatchInfo.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaFsPatchInfo.cs @@ -2,53 +2,52 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public struct NcaFsPatchInfo { - public struct NcaFsPatchInfo + private readonly Memory _data; + + public NcaFsPatchInfo(Memory data) { - private readonly Memory _data; + _data = data; + } - public NcaFsPatchInfo(Memory data) - { - _data = data; - } + private ref PatchInfoStruct Data => ref Unsafe.As(ref _data.Span[0]); - private ref PatchInfoStruct Data => ref Unsafe.As(ref _data.Span[0]); + public long RelocationTreeOffset + { + get => Data.RelocationTreeOffset; + set => Data.RelocationTreeOffset = value; + } - public long RelocationTreeOffset - { - get => Data.RelocationTreeOffset; - set => Data.RelocationTreeOffset = value; - } + public long RelocationTreeSize + { + get => Data.RelocationTreeSize; + set => Data.RelocationTreeSize = value; + } - public long RelocationTreeSize - { - get => Data.RelocationTreeSize; - set => Data.RelocationTreeSize = value; - } + public long EncryptionTreeOffset + { + get => Data.EncryptionTreeOffset; + set => Data.EncryptionTreeOffset = value; + } - public long EncryptionTreeOffset - { - get => Data.EncryptionTreeOffset; - set => Data.EncryptionTreeOffset = value; - } + public long EncryptionTreeSize + { + get => Data.EncryptionTreeSize; + set => Data.EncryptionTreeSize = value; + } - public long EncryptionTreeSize - { - get => Data.EncryptionTreeSize; - set => Data.EncryptionTreeSize = value; - } + public Span RelocationTreeHeader => _data.Span.Slice(0x10, 0x10); + public Span EncryptionTreeHeader => _data.Span.Slice(0x30, 0x10); - public Span RelocationTreeHeader => _data.Span.Slice(0x10, 0x10); - public Span EncryptionTreeHeader => _data.Span.Slice(0x30, 0x10); - - [StructLayout(LayoutKind.Explicit)] - private struct PatchInfoStruct - { - [FieldOffset(0x00)] public long RelocationTreeOffset; - [FieldOffset(0x08)] public long RelocationTreeSize; - [FieldOffset(0x20)] public long EncryptionTreeOffset; - [FieldOffset(0x28)] public long EncryptionTreeSize; - } + [StructLayout(LayoutKind.Explicit)] + private struct PatchInfoStruct + { + [FieldOffset(0x00)] public long RelocationTreeOffset; + [FieldOffset(0x08)] public long RelocationTreeSize; + [FieldOffset(0x20)] public long EncryptionTreeOffset; + [FieldOffset(0x28)] public long EncryptionTreeSize; } } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index 92d59158..0f53f572 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -9,368 +9,367 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public struct NcaHeader { - public struct NcaHeader + internal const int HeaderSize = 0xC00; + internal const int HeaderSectorSize = 0x200; + internal const int BlockSize = 0x200; + internal const int SectionCount = 4; + + private readonly Memory _header; + + public NcaVersion FormatVersion { get; } + public bool IsEncrypted { get; } + + public NcaHeader(KeySet keySet, IStorage headerStorage) { - internal const int HeaderSize = 0xC00; - internal const int HeaderSectorSize = 0x200; - internal const int BlockSize = 0x200; - internal const int SectionCount = 4; + (byte[] header, bool isEncrypted) = DecryptHeader(keySet, headerStorage); - private readonly Memory _header; + _header = header; + IsEncrypted = isEncrypted; + FormatVersion = DetectNcaVersion(_header.Span); + } - public NcaVersion FormatVersion { get; } - public bool IsEncrypted { get; } + private ref NcaHeaderStruct Header => ref Unsafe.As(ref _header.Span[0]); - public NcaHeader(KeySet keySet, IStorage headerStorage) + public Span Signature1 => _header.Span.Slice(0, 0x100); + public Span Signature2 => _header.Span.Slice(0x100, 0x100); + + public uint Magic + { + get => Header.Magic; + set => Header.Magic = value; + } + + public int Version => _header.Span[0x203] - '0'; + + public DistributionType DistributionType + { + get => (DistributionType)Header.DistributionType; + set => Header.DistributionType = (byte)value; + } + + public NcaContentType ContentType + { + get => (NcaContentType)Header.ContentType; + set => Header.ContentType = (byte)value; + } + + public byte KeyGeneration + { + get => Math.Max(Header.KeyGeneration1, Header.KeyGeneration2); + set { - (byte[] header, bool isEncrypted) = DecryptHeader(keySet, headerStorage); - - _header = header; - IsEncrypted = isEncrypted; - FormatVersion = DetectNcaVersion(_header.Span); - } - - private ref NcaHeaderStruct Header => ref Unsafe.As(ref _header.Span[0]); - - public Span Signature1 => _header.Span.Slice(0, 0x100); - public Span Signature2 => _header.Span.Slice(0x100, 0x100); - - public uint Magic - { - get => Header.Magic; - set => Header.Magic = value; - } - - public int Version => _header.Span[0x203] - '0'; - - public DistributionType DistributionType - { - get => (DistributionType)Header.DistributionType; - set => Header.DistributionType = (byte)value; - } - - public NcaContentType ContentType - { - get => (NcaContentType)Header.ContentType; - set => Header.ContentType = (byte)value; - } - - public byte KeyGeneration - { - get => Math.Max(Header.KeyGeneration1, Header.KeyGeneration2); - set + if (value > 2) { - if (value > 2) - { - Header.KeyGeneration1 = 2; - Header.KeyGeneration2 = value; - } - else - { - Header.KeyGeneration1 = value; - Header.KeyGeneration2 = 0; - } + Header.KeyGeneration1 = 2; + Header.KeyGeneration2 = value; } - } - - public byte KeyAreaKeyIndex - { - get => Header.KeyAreaKeyIndex; - set => Header.KeyAreaKeyIndex = value; - } - - public long NcaSize - { - get => Header.NcaSize; - set => Header.NcaSize = value; - } - - public ulong TitleId - { - get => Header.TitleId; - set => Header.TitleId = value; - } - - public int ContentIndex - { - get => Header.ContentIndex; - set => Header.ContentIndex = value; - } - - public TitleVersion SdkVersion - { - get => new TitleVersion(Header.SdkVersion); - set => Header.SdkVersion = value.Version; - } - - public Span RightsId => _header.Span.Slice(NcaHeaderStruct.RightsIdOffset, NcaHeaderStruct.RightsIdSize); - - public bool HasRightsId => !Utilities.IsZeros(RightsId); - - public Span GetKeyArea() - { - return _header.Span.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize); - } - - private ref NcaSectionEntryStruct GetSectionEntry(int index) - { - ValidateSectionIndex(index); - - int offset = NcaHeaderStruct.SectionEntriesOffset + NcaSectionEntryStruct.SectionEntrySize * index; - return ref Unsafe.As(ref _header.Span[offset]); - } - - public long GetSectionStartOffset(int index) - { - return BlockToOffset(GetSectionEntry(index).StartBlock); - } - - public long GetSectionEndOffset(int index) - { - return BlockToOffset(GetSectionEntry(index).EndBlock); - } - - public long GetSectionSize(int index) - { - ref NcaSectionEntryStruct info = ref GetSectionEntry(index); - return BlockToOffset(info.EndBlock - info.StartBlock); - } - - public bool IsSectionEnabled(int index) - { - ref NcaSectionEntryStruct info = ref GetSectionEntry(index); - - int sectStart = info.StartBlock; - int sectSize = info.EndBlock - sectStart; - return sectStart != 0 || sectSize != 0; - } - - public Span GetFsHeaderHash(int index) - { - ValidateSectionIndex(index); - - int offset = NcaHeaderStruct.FsHeaderHashOffset + NcaHeaderStruct.FsHeaderHashSize * index; - return _header.Span.Slice(offset, NcaHeaderStruct.FsHeaderHashSize); - } - - public Span GetEncryptedKey(int index) - { - if (index < 0 || index >= SectionCount) + else { - throw new ArgumentOutOfRangeException($"Key index must be between 0 and 3. Actual: {index}"); + Header.KeyGeneration1 = value; + Header.KeyGeneration2 = 0; } - - int offset = NcaHeaderStruct.KeyAreaOffset + Aes.KeySize128 * index; - return _header.Span.Slice(offset, Aes.KeySize128); - } - - public NcaFsHeader GetFsHeader(int index) - { - Span expectedHash = GetFsHeaderHash(index); - - int offset = NcaHeaderStruct.FsHeadersOffset + NcaHeaderStruct.FsHeaderSize * index; - Memory headerData = _header.Slice(offset, NcaHeaderStruct.FsHeaderSize); - - Span actualHash = stackalloc byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(headerData.Span, actualHash); - - if (!Utilities.SpansEqual(expectedHash, actualHash)) - { - throw new InvalidDataException("FS header hash is invalid."); - } - - return new NcaFsHeader(headerData); - } - - private static void ValidateSectionIndex(int index) - { - if (index < 0 || index >= SectionCount) - { - throw new ArgumentOutOfRangeException($"NCA section index must be between 0 and 3. Actual: {index}"); - } - } - - private static long BlockToOffset(int blockIndex) - { - return (long)blockIndex * BlockSize; - } - - private static (byte[] header, bool isEncrypted) DecryptHeader(KeySet keySet, IStorage storage) - { - byte[] buf = new byte[HeaderSize]; - storage.Read(0, buf).ThrowIfFailure(); - - if (CheckIfDecrypted(buf)) - { - int decVersion = buf[0x203] - '0'; - - if (decVersion != 0 && decVersion != 2 && decVersion != 3) - { - throw new NotSupportedException($"NCA version {decVersion} is not supported."); - } - - return (buf, false); - } - - byte[] key1 = keySet.HeaderKey.SubKeys[0].DataRo.ToArray(); - byte[] key2 = keySet.HeaderKey.SubKeys[1].DataRo.ToArray(); - - var transform = new Aes128XtsTransform(key1, key2, true); - - transform.TransformBlock(buf, HeaderSectorSize * 0, HeaderSectorSize, 0); - transform.TransformBlock(buf, HeaderSectorSize * 1, HeaderSectorSize, 1); - - if (buf[0x200] != 'N' || buf[0x201] != 'C' || buf[0x202] != 'A') - { - throw new InvalidDataException( - "Unable to decrypt NCA header. The file is not an NCA file or the header key is incorrect."); - } - - int version = buf[0x203] - '0'; - - if (version == 3) - { - for (int sector = 2; sector < HeaderSize / HeaderSectorSize; sector++) - { - transform.TransformBlock(buf, sector * HeaderSectorSize, HeaderSectorSize, (ulong)sector); - } - } - else if (version == 2) - { - for (int i = 0x400; i < HeaderSize; i += HeaderSectorSize) - { - transform.TransformBlock(buf, i, HeaderSectorSize, 0); - } - } - else if (version != 0) - { - throw new NotSupportedException($"NCA version {version} is not supported."); - } - - return (buf, true); - } - - private static bool CheckIfDecrypted(ReadOnlySpan header) - { - Assert.SdkRequiresGreaterEqual(header.Length, 0x400); - - // Check the magic value - if (header[0x200] != 'N' || header[0x201] != 'C' || header[0x202] != 'A') - return false; - - // Check the version in the magic value - if (!StringUtils.IsDigit(header[0x203])) - return false; - - // Is the distribution type valid? - if (header[0x204] > (int)DistributionType.GameCard) - return false; - - // Is the content type valid? - if (header[0x205] > (int)NcaContentType.PublicData) - return false; - - return true; - } - - private static NcaVersion DetectNcaVersion(ReadOnlySpan header) - { - int version = header[0x203] - '0'; - - if (version == 3) return NcaVersion.Nca3; - if (version == 2) return NcaVersion.Nca2; - if (version != 0) return NcaVersion.Unknown; - - // There are multiple versions of NCA0 that each encrypt the key area differently. - // Examine the key area to detect which version this NCA is. - ReadOnlySpan keyArea = header.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize); - - // The end of the key area will only be non-zero if it's RSA-OAEP encrypted - var zeros = new Buffer16(); - if (!keyArea.Slice(0x80, 0x10).SequenceEqual(zeros)) - { - return NcaVersion.Nca0RsaOaep; - } - - // Key areas using fixed, unencrypted keys always use the same keys. - // Check for these keys by comparing the key area with the known hash of the fixed body keys. - Unsafe.SkipInit(out Buffer32 hash); - Sha256.GenerateSha256Hash(keyArea.Slice(0, 0x20), hash); - - if (Nca0FixedBodyKeySha256Hash.SequenceEqual(hash)) - { - return NcaVersion.Nca0FixedKey; - } - - // Otherwise the key area is encrypted the same as modern NCAs. - return NcaVersion.Nca0; - } - - public Validity VerifySignature1(byte[] modulus) - { - return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature1.ToArray(), modulus); - } - - public Validity VerifySignature2(byte[] modulus) - { - return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature2.ToArray(), modulus); - } - - public bool IsNca0() => FormatVersion >= NcaVersion.Nca0; - - private static ReadOnlySpan Nca0FixedBodyKeySha256Hash => new byte[] - { - 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, - 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB - }; - - [StructLayout(LayoutKind.Explicit, Size = 0xC00)] - private struct NcaHeaderStruct - { - public const int RightsIdOffset = 0x230; - public const int RightsIdSize = 0x10; - public const int SectionEntriesOffset = 0x240; - public const int FsHeaderHashOffset = 0x280; - public const int FsHeaderHashSize = 0x20; - public const int KeyAreaOffset = 0x300; - public const int KeyAreaSize = 0x100; - public const int FsHeadersOffset = 0x400; - public const int FsHeaderSize = 0x200; - - [FieldOffset(0x000)] public byte Signature1; - [FieldOffset(0x100)] public byte Signature2; - [FieldOffset(0x200)] public uint Magic; - [FieldOffset(0x204)] public byte DistributionType; - [FieldOffset(0x205)] public byte ContentType; - [FieldOffset(0x206)] public byte KeyGeneration1; - [FieldOffset(0x207)] public byte KeyAreaKeyIndex; - [FieldOffset(0x208)] public long NcaSize; - [FieldOffset(0x210)] public ulong TitleId; - [FieldOffset(0x218)] public int ContentIndex; - [FieldOffset(0x21C)] public uint SdkVersion; - [FieldOffset(0x220)] public byte KeyGeneration2; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = SectionEntrySize)] - private struct NcaSectionEntryStruct - { - public const int SectionEntrySize = 0x10; - - public int StartBlock; - public int EndBlock; - public bool IsEnabled; } } - public enum NcaVersion + public byte KeyAreaKeyIndex { - Unknown, - Nca3, - Nca2, - Nca0, - Nca0FixedKey, - Nca0RsaOaep + get => Header.KeyAreaKeyIndex; + set => Header.KeyAreaKeyIndex = value; + } + + public long NcaSize + { + get => Header.NcaSize; + set => Header.NcaSize = value; + } + + public ulong TitleId + { + get => Header.TitleId; + set => Header.TitleId = value; + } + + public int ContentIndex + { + get => Header.ContentIndex; + set => Header.ContentIndex = value; + } + + public TitleVersion SdkVersion + { + get => new TitleVersion(Header.SdkVersion); + set => Header.SdkVersion = value.Version; + } + + public Span RightsId => _header.Span.Slice(NcaHeaderStruct.RightsIdOffset, NcaHeaderStruct.RightsIdSize); + + public bool HasRightsId => !Utilities.IsZeros(RightsId); + + public Span GetKeyArea() + { + return _header.Span.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize); + } + + private ref NcaSectionEntryStruct GetSectionEntry(int index) + { + ValidateSectionIndex(index); + + int offset = NcaHeaderStruct.SectionEntriesOffset + NcaSectionEntryStruct.SectionEntrySize * index; + return ref Unsafe.As(ref _header.Span[offset]); + } + + public long GetSectionStartOffset(int index) + { + return BlockToOffset(GetSectionEntry(index).StartBlock); + } + + public long GetSectionEndOffset(int index) + { + return BlockToOffset(GetSectionEntry(index).EndBlock); + } + + public long GetSectionSize(int index) + { + ref NcaSectionEntryStruct info = ref GetSectionEntry(index); + return BlockToOffset(info.EndBlock - info.StartBlock); + } + + public bool IsSectionEnabled(int index) + { + ref NcaSectionEntryStruct info = ref GetSectionEntry(index); + + int sectStart = info.StartBlock; + int sectSize = info.EndBlock - sectStart; + return sectStart != 0 || sectSize != 0; + } + + public Span GetFsHeaderHash(int index) + { + ValidateSectionIndex(index); + + int offset = NcaHeaderStruct.FsHeaderHashOffset + NcaHeaderStruct.FsHeaderHashSize * index; + return _header.Span.Slice(offset, NcaHeaderStruct.FsHeaderHashSize); + } + + public Span GetEncryptedKey(int index) + { + if (index < 0 || index >= SectionCount) + { + throw new ArgumentOutOfRangeException($"Key index must be between 0 and 3. Actual: {index}"); + } + + int offset = NcaHeaderStruct.KeyAreaOffset + Aes.KeySize128 * index; + return _header.Span.Slice(offset, Aes.KeySize128); + } + + public NcaFsHeader GetFsHeader(int index) + { + Span expectedHash = GetFsHeaderHash(index); + + int offset = NcaHeaderStruct.FsHeadersOffset + NcaHeaderStruct.FsHeaderSize * index; + Memory headerData = _header.Slice(offset, NcaHeaderStruct.FsHeaderSize); + + Span actualHash = stackalloc byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(headerData.Span, actualHash); + + if (!Utilities.SpansEqual(expectedHash, actualHash)) + { + throw new InvalidDataException("FS header hash is invalid."); + } + + return new NcaFsHeader(headerData); + } + + private static void ValidateSectionIndex(int index) + { + if (index < 0 || index >= SectionCount) + { + throw new ArgumentOutOfRangeException($"NCA section index must be between 0 and 3. Actual: {index}"); + } + } + + private static long BlockToOffset(int blockIndex) + { + return (long)blockIndex * BlockSize; + } + + private static (byte[] header, bool isEncrypted) DecryptHeader(KeySet keySet, IStorage storage) + { + byte[] buf = new byte[HeaderSize]; + storage.Read(0, buf).ThrowIfFailure(); + + if (CheckIfDecrypted(buf)) + { + int decVersion = buf[0x203] - '0'; + + if (decVersion != 0 && decVersion != 2 && decVersion != 3) + { + throw new NotSupportedException($"NCA version {decVersion} is not supported."); + } + + return (buf, false); + } + + byte[] key1 = keySet.HeaderKey.SubKeys[0].DataRo.ToArray(); + byte[] key2 = keySet.HeaderKey.SubKeys[1].DataRo.ToArray(); + + var transform = new Aes128XtsTransform(key1, key2, true); + + transform.TransformBlock(buf, HeaderSectorSize * 0, HeaderSectorSize, 0); + transform.TransformBlock(buf, HeaderSectorSize * 1, HeaderSectorSize, 1); + + if (buf[0x200] != 'N' || buf[0x201] != 'C' || buf[0x202] != 'A') + { + throw new InvalidDataException( + "Unable to decrypt NCA header. The file is not an NCA file or the header key is incorrect."); + } + + int version = buf[0x203] - '0'; + + if (version == 3) + { + for (int sector = 2; sector < HeaderSize / HeaderSectorSize; sector++) + { + transform.TransformBlock(buf, sector * HeaderSectorSize, HeaderSectorSize, (ulong)sector); + } + } + else if (version == 2) + { + for (int i = 0x400; i < HeaderSize; i += HeaderSectorSize) + { + transform.TransformBlock(buf, i, HeaderSectorSize, 0); + } + } + else if (version != 0) + { + throw new NotSupportedException($"NCA version {version} is not supported."); + } + + return (buf, true); + } + + private static bool CheckIfDecrypted(ReadOnlySpan header) + { + Assert.SdkRequiresGreaterEqual(header.Length, 0x400); + + // Check the magic value + if (header[0x200] != 'N' || header[0x201] != 'C' || header[0x202] != 'A') + return false; + + // Check the version in the magic value + if (!StringUtils.IsDigit(header[0x203])) + return false; + + // Is the distribution type valid? + if (header[0x204] > (int)DistributionType.GameCard) + return false; + + // Is the content type valid? + if (header[0x205] > (int)NcaContentType.PublicData) + return false; + + return true; + } + + private static NcaVersion DetectNcaVersion(ReadOnlySpan header) + { + int version = header[0x203] - '0'; + + if (version == 3) return NcaVersion.Nca3; + if (version == 2) return NcaVersion.Nca2; + if (version != 0) return NcaVersion.Unknown; + + // There are multiple versions of NCA0 that each encrypt the key area differently. + // Examine the key area to detect which version this NCA is. + ReadOnlySpan keyArea = header.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize); + + // The end of the key area will only be non-zero if it's RSA-OAEP encrypted + var zeros = new Buffer16(); + if (!keyArea.Slice(0x80, 0x10).SequenceEqual(zeros)) + { + return NcaVersion.Nca0RsaOaep; + } + + // Key areas using fixed, unencrypted keys always use the same keys. + // Check for these keys by comparing the key area with the known hash of the fixed body keys. + Unsafe.SkipInit(out Buffer32 hash); + Sha256.GenerateSha256Hash(keyArea.Slice(0, 0x20), hash); + + if (Nca0FixedBodyKeySha256Hash.SequenceEqual(hash)) + { + return NcaVersion.Nca0FixedKey; + } + + // Otherwise the key area is encrypted the same as modern NCAs. + return NcaVersion.Nca0; + } + + public Validity VerifySignature1(byte[] modulus) + { + return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature1.ToArray(), modulus); + } + + public Validity VerifySignature2(byte[] modulus) + { + return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature2.ToArray(), modulus); + } + + public bool IsNca0() => FormatVersion >= NcaVersion.Nca0; + + private static ReadOnlySpan Nca0FixedBodyKeySha256Hash => new byte[] + { + 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, + 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB + }; + + [StructLayout(LayoutKind.Explicit, Size = 0xC00)] + private struct NcaHeaderStruct + { + public const int RightsIdOffset = 0x230; + public const int RightsIdSize = 0x10; + public const int SectionEntriesOffset = 0x240; + public const int FsHeaderHashOffset = 0x280; + public const int FsHeaderHashSize = 0x20; + public const int KeyAreaOffset = 0x300; + public const int KeyAreaSize = 0x100; + public const int FsHeadersOffset = 0x400; + public const int FsHeaderSize = 0x200; + + [FieldOffset(0x000)] public byte Signature1; + [FieldOffset(0x100)] public byte Signature2; + [FieldOffset(0x200)] public uint Magic; + [FieldOffset(0x204)] public byte DistributionType; + [FieldOffset(0x205)] public byte ContentType; + [FieldOffset(0x206)] public byte KeyGeneration1; + [FieldOffset(0x207)] public byte KeyAreaKeyIndex; + [FieldOffset(0x208)] public long NcaSize; + [FieldOffset(0x210)] public ulong TitleId; + [FieldOffset(0x218)] public int ContentIndex; + [FieldOffset(0x21C)] public uint SdkVersion; + [FieldOffset(0x220)] public byte KeyGeneration2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = SectionEntrySize)] + private struct NcaSectionEntryStruct + { + public const int SectionEntrySize = 0x10; + + public int StartBlock; + public int EndBlock; + public bool IsEnabled; } } + +public enum NcaVersion +{ + Unknown, + Nca3, + Nca2, + Nca0, + Nca0FixedKey, + Nca0RsaOaep +} diff --git a/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs b/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs index 589732e4..c4f75b33 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaKeyType.cs @@ -1,11 +1,10 @@ -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +internal enum NcaKeyType { - internal enum NcaKeyType - { - AesXts0, - AesXts1, - AesCtr, - Type3, - Type4 - } + AesXts0, + AesXts1, + AesCtr, + Type3, + Type4 } diff --git a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs index a5dd5c8d..621c549f 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs @@ -1,82 +1,81 @@ -namespace LibHac.FsSystem.NcaUtils +namespace LibHac.FsSystem.NcaUtils; + +public class TitleVersion { - public class TitleVersion + public uint Version { get; } + public int Major { get; } + public int Minor { get; } + public int Patch { get; } + public int Revision { get; } + + public TitleVersion(uint version, bool isSystemTitle = false) { - public uint Version { get; } - public int Major { get; } - public int Minor { get; } - public int Patch { get; } - public int Revision { get; } + Version = version; - public TitleVersion(uint version, bool isSystemTitle = false) + if (isSystemTitle) { - Version = version; - - if (isSystemTitle) - { - Revision = (int)(version & ((1 << 16) - 1)); - Patch = (int)((version >> 16) & ((1 << 4) - 1)); - Minor = (int)((version >> 20) & ((1 << 6) - 1)); - Major = (int)((version >> 26) & ((1 << 6) - 1)); - } - else - { - Revision = (byte)version; - Patch = (byte)(version >> 8); - Minor = (byte)(version >> 16); - Major = (byte)(version >> 24); - } + Revision = (int)(version & ((1 << 16) - 1)); + Patch = (int)((version >> 16) & ((1 << 4) - 1)); + Minor = (int)((version >> 20) & ((1 << 6) - 1)); + Major = (int)((version >> 26) & ((1 << 6) - 1)); } - - public override string ToString() + else { - return $"{Major}.{Minor}.{Patch}.{Revision}"; + Revision = (byte)version; + Patch = (byte)(version >> 8); + Minor = (byte)(version >> 16); + Major = (byte)(version >> 24); } } - public enum NcaSectionType + public override string ToString() { - Code, - Data, - Logo - } - - public enum NcaContentType - { - Program, - Meta, - Control, - Manual, - Data, - PublicData - } - - public enum DistributionType - { - Download, - GameCard - } - - public enum NcaEncryptionType - { - Auto, - None, - XTS, - AesCtr, - AesCtrEx - } - - public enum NcaHashType - { - Auto, - None, - Sha256, - Ivfc - } - - public enum NcaFormatType - { - Romfs, - Pfs0 + return $"{Major}.{Minor}.{Patch}.{Revision}"; } } + +public enum NcaSectionType +{ + Code, + Data, + Logo +} + +public enum NcaContentType +{ + Program, + Meta, + Control, + Manual, + Data, + PublicData +} + +public enum DistributionType +{ + Download, + GameCard +} + +public enum NcaEncryptionType +{ + Auto, + None, + XTS, + AesCtr, + AesCtrEx +} + +public enum NcaHashType +{ + Auto, + None, + Sha256, + Ivfc +} + +public enum NcaFormatType +{ + Romfs, + Pfs0 +} diff --git a/src/LibHac/FsSystem/NullFile.cs b/src/LibHac/FsSystem/NullFile.cs index 20a701c1..6d394df4 100644 --- a/src/LibHac/FsSystem/NullFile.cs +++ b/src/LibHac/FsSystem/NullFile.cs @@ -2,60 +2,59 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class NullFile : IFile { - public class NullFile : IFile + private OpenMode Mode { get; } + + public NullFile() { - private OpenMode Mode { get; } + Mode = OpenMode.ReadWrite; + } - public NullFile() - { - Mode = OpenMode.ReadWrite; - } + public NullFile(long length) : this() => Length = length; - public NullFile(long length) : this() => Length = length; + private long Length { get; } - private long Length { get; } + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + bytesRead = 0; - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - bytesRead = 0; + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; + destination.Slice(0, (int)toRead).Clear(); - destination.Slice(0, (int)toRead).Clear(); + bytesRead = toRead; + return Result.Success; + } - bytesRead = toRead; - return Result.Success; - } + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return Result.Success; + } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return Result.Success; - } + protected override Result DoFlush() + { + return Result.Success; + } - protected override Result DoFlush() - { - return Result.Success; - } + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return Result.Success; + } - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedOperation.Log(); - } + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedOperation.Log(); } } diff --git a/src/LibHac/FsSystem/NullStorage.cs b/src/LibHac/FsSystem/NullStorage.cs index d6e6cf18..4f36b14a 100644 --- a/src/LibHac/FsSystem/NullStorage.cs +++ b/src/LibHac/FsSystem/NullStorage.cs @@ -1,44 +1,43 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// An that returns all zeros when read, and does nothing on write. +/// +public class NullStorage : IStorage { - /// - /// An that returns all zeros when read, and does nothing on write. - /// - public class NullStorage : IStorage + private long Length { get; } + + public NullStorage() { } + public NullStorage(long length) => Length = length; + + + protected override Result DoRead(long offset, Span destination) { - private long Length { get; } + destination.Clear(); + return Result.Success; + } - public NullStorage() { } - public NullStorage(long length) => Length = length; + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return Result.Success; + } + protected override Result DoFlush() + { + return Result.Success; + } - protected override Result DoRead(long offset, Span destination) - { - destination.Clear(); - return Result.Success; - } + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return Result.Success; - } - - protected override Result DoFlush() - { - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; } } diff --git a/src/LibHac/FsSystem/NxFileStream.cs b/src/LibHac/FsSystem/NxFileStream.cs index 88a7e78f..f2188d37 100644 --- a/src/LibHac/FsSystem/NxFileStream.cs +++ b/src/LibHac/FsSystem/NxFileStream.cs @@ -3,81 +3,80 @@ using System.IO; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class NxFileStream : Stream { - public class NxFileStream : Stream + private IFile BaseFile { get; } + private bool LeaveOpen { get; } + private OpenMode Mode { get; } + private long _length; + + public NxFileStream(IFile baseFile, bool leaveOpen) : this(baseFile, OpenMode.ReadWrite, leaveOpen) { } + + public NxFileStream(IFile baseFile, OpenMode mode, bool leaveOpen) { - private IFile BaseFile { get; } - private bool LeaveOpen { get; } - private OpenMode Mode { get; } - private long _length; + BaseFile = baseFile; + Mode = mode; + LeaveOpen = leaveOpen; - public NxFileStream(IFile baseFile, bool leaveOpen) : this(baseFile, OpenMode.ReadWrite, leaveOpen) { } + baseFile.GetSize(out _length).ThrowIfFailure(); + } - public NxFileStream(IFile baseFile, OpenMode mode, bool leaveOpen) + public override int Read(byte[] buffer, int offset, int count) + { + BaseFile.Read(out long bytesRead, Position, buffer.AsSpan(offset, count)); + + Position += bytesRead; + return (int)bytesRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + BaseFile.Write(Position, buffer.AsSpan(offset, count)); + + Position += count; + } + + public override void Flush() + { + BaseFile.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) { - BaseFile = baseFile; - Mode = mode; - LeaveOpen = leaveOpen; - - baseFile.GetSize(out _length).ThrowIfFailure(); + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + case SeekOrigin.End: + Position = Length - offset; + break; } - public override int Read(byte[] buffer, int offset, int count) - { - BaseFile.Read(out long bytesRead, Position, buffer.AsSpan(offset, count)); + return Position; + } - Position += bytesRead; - return (int)bytesRead; - } + public override void SetLength(long value) + { + BaseFile.SetSize(value).ThrowIfFailure(); - public override void Write(byte[] buffer, int offset, int count) - { - BaseFile.Write(Position, buffer.AsSpan(offset, count)); + BaseFile.GetSize(out _length).ThrowIfFailure(); + } - Position += count; - } + public override bool CanRead => Mode.HasFlag(OpenMode.Read); + public override bool CanSeek => true; + public override bool CanWrite => Mode.HasFlag(OpenMode.Write); + public override long Length => _length; + public override long Position { get; set; } - public override void Flush() - { - BaseFile.Flush(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - case SeekOrigin.End: - Position = Length - offset; - break; - } - - return Position; - } - - public override void SetLength(long value) - { - BaseFile.SetSize(value).ThrowIfFailure(); - - BaseFile.GetSize(out _length).ThrowIfFailure(); - } - - public override bool CanRead => Mode.HasFlag(OpenMode.Read); - public override bool CanSeek => true; - public override bool CanWrite => Mode.HasFlag(OpenMode.Write); - public override long Length => _length; - public override long Position { get; set; } - - protected override void Dispose(bool disposing) - { - if (!LeaveOpen) BaseFile?.Dispose(); - base.Dispose(disposing); - } + protected override void Dispose(bool disposing) + { + if (!LeaveOpen) BaseFile?.Dispose(); + base.Dispose(disposing); } } diff --git a/src/LibHac/FsSystem/PartitionDirectory.cs b/src/LibHac/FsSystem/PartitionDirectory.cs index 65522627..e3197d9f 100644 --- a/src/LibHac/FsSystem/PartitionDirectory.cs +++ b/src/LibHac/FsSystem/PartitionDirectory.cs @@ -5,68 +5,67 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionDirectory : IDirectory { - public class PartitionDirectory : IDirectory + private PartitionFileSystem ParentFileSystem { get; } + private OpenDirectoryMode Mode { get; } + private int CurrentIndex { get; set; } + + public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode) { - private PartitionFileSystem ParentFileSystem { get; } - private OpenDirectoryMode Mode { get; } - private int CurrentIndex { get; set; } + path = PathTools.Normalize(path); - public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode) - { - path = PathTools.Normalize(path); + if (path != "/") throw new DirectoryNotFoundException(); - if (path != "/") throw new DirectoryNotFoundException(); + ParentFileSystem = fs; + Mode = mode; - ParentFileSystem = fs; - Mode = mode; - - CurrentIndex = 0; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - if (!Mode.HasFlag(OpenDirectoryMode.File)) - { - entriesRead = 0; - return Result.Success; - } - - int entriesRemaining = ParentFileSystem.Files.Length - CurrentIndex; - int toRead = Math.Min(entriesRemaining, entryBuffer.Length); - - for (int i = 0; i < toRead; i++) - { - PartitionFileEntry fileEntry = ParentFileSystem.Files[CurrentIndex]; - ref DirectoryEntry entry = ref entryBuffer[i]; - - Span nameUtf8 = Encoding.UTF8.GetBytes(fileEntry.Name); - - entry.Type = DirectoryEntryType.File; - entry.Size = fileEntry.Size; - - StringUtils.Copy(entry.Name, nameUtf8); - entry.Name[PathTools.MaxPathLength] = 0; - - CurrentIndex++; - } - - entriesRead = toRead; - return Result.Success; - } - - protected override Result DoGetEntryCount(out long entryCount) - { - int count = 0; - - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - count += ParentFileSystem.Files.Length; - } - - entryCount = count; - return Result.Success; - } + CurrentIndex = 0; } -} \ No newline at end of file + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + if (!Mode.HasFlag(OpenDirectoryMode.File)) + { + entriesRead = 0; + return Result.Success; + } + + int entriesRemaining = ParentFileSystem.Files.Length - CurrentIndex; + int toRead = Math.Min(entriesRemaining, entryBuffer.Length); + + for (int i = 0; i < toRead; i++) + { + PartitionFileEntry fileEntry = ParentFileSystem.Files[CurrentIndex]; + ref DirectoryEntry entry = ref entryBuffer[i]; + + Span nameUtf8 = Encoding.UTF8.GetBytes(fileEntry.Name); + + entry.Type = DirectoryEntryType.File; + entry.Size = fileEntry.Size; + + StringUtils.Copy(entry.Name, nameUtf8); + entry.Name[PathTools.MaxPathLength] = 0; + + CurrentIndex++; + } + + entriesRead = toRead; + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + int count = 0; + + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + count += ParentFileSystem.Files.Length; + } + + entryCount = count; + return Result.Success; + } +} diff --git a/src/LibHac/FsSystem/PartitionFile.cs b/src/LibHac/FsSystem/PartitionFile.cs index 75739e50..20acad76 100644 --- a/src/LibHac/FsSystem/PartitionFile.cs +++ b/src/LibHac/FsSystem/PartitionFile.cs @@ -2,89 +2,88 @@ using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionFile : IFile { - public class PartitionFile : IFile + private IStorage BaseStorage { get; } + private long Offset { get; } + private long Size { get; } + private OpenMode Mode { get; } + + public PartitionFile(IStorage baseStorage, long offset, long size, OpenMode mode) { - private IStorage BaseStorage { get; } - private long Offset { get; } - private long Size { get; } - private OpenMode Mode { get; } + Mode = mode; + BaseStorage = baseStorage; + Offset = offset; + Size = size; + } - public PartitionFile(IStorage baseStorage, long offset, long size, OpenMode mode) + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + bytesRead = 0; + + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + long storageOffset = Offset + offset; + BaseStorage.Read(storageOffset, destination.Slice(0, (int)toRead)); + + bytesRead = toRead; + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (isResizeNeeded) return ResultFs.UnsupportedWriteForPartitionFile.Log(); + + if (offset > Size) return ResultFs.OutOfRange.Log(); + + rc = BaseStorage.Write(offset, source); + if (rc.IsFailure()) return rc; + + // N doesn't flush if the flag is set + if (option.HasFlushFlag()) { - Mode = mode; - BaseStorage = baseStorage; - Offset = offset; - Size = size; + return BaseStorage.Flush(); } - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) + return Result.Success; + } + + protected override Result DoFlush() + { + if (!Mode.HasFlag(OpenMode.Write)) { - bytesRead = 0; - - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - long storageOffset = Offset + offset; - BaseStorage.Read(storageOffset, destination.Slice(0, (int)toRead)); - - bytesRead = toRead; - return Result.Success; + return BaseStorage.Flush(); } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + return Result.Success; + } + + protected override Result DoGetSize(out long size) + { + size = Size; + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoSetSize(long size) + { + if (!Mode.HasFlag(OpenMode.Write)) { - Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - if (isResizeNeeded) return ResultFs.UnsupportedWriteForPartitionFile.Log(); - - if (offset > Size) return ResultFs.OutOfRange.Log(); - - rc = BaseStorage.Write(offset, source); - if (rc.IsFailure()) return rc; - - // N doesn't flush if the flag is set - if (option.HasFlushFlag()) - { - return BaseStorage.Flush(); - } - - return Result.Success; + return ResultFs.WriteUnpermitted.Log(); } - protected override Result DoFlush() - { - if (!Mode.HasFlag(OpenMode.Write)) - { - return BaseStorage.Flush(); - } - - return Result.Success; - } - - protected override Result DoGetSize(out long size) - { - size = Size; - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoSetSize(long size) - { - if (!Mode.HasFlag(OpenMode.Write)) - { - return ResultFs.WriteUnpermitted.Log(); - } - - return ResultFs.UnsupportedWriteForPartitionFile.Log(); - } + return ResultFs.UnsupportedWriteForPartitionFile.Log(); } } diff --git a/src/LibHac/FsSystem/PartitionFileSystem.cs b/src/LibHac/FsSystem/PartitionFileSystem.cs index cb188981..0333dad7 100644 --- a/src/LibHac/FsSystem/PartitionFileSystem.cs +++ b/src/LibHac/FsSystem/PartitionFileSystem.cs @@ -9,184 +9,183 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Path = LibHac.Fs.Path; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionFileSystem : IFileSystem { - public class PartitionFileSystem : IFileSystem + // todo Re-add way of checking a file hash + public PartitionFileSystemHeader Header { get; } + public int HeaderSize { get; } + public PartitionFileEntry[] Files { get; } + + private Dictionary FileDict { get; } + private IStorage BaseStorage { get; } + + public PartitionFileSystem(IStorage storage) { - // todo Re-add way of checking a file hash - public PartitionFileSystemHeader Header { get; } - public int HeaderSize { get; } - public PartitionFileEntry[] Files { get; } - - private Dictionary FileDict { get; } - private IStorage BaseStorage { get; } - - public PartitionFileSystem(IStorage storage) + using (var reader = new BinaryReader(storage.AsStream(), Encoding.Default, true)) { - using (var reader = new BinaryReader(storage.AsStream(), Encoding.Default, true)) - { - Header = new PartitionFileSystemHeader(reader); - } - - HeaderSize = Header.HeaderSize; - Files = Header.Files; - FileDict = Header.Files.ToDictionary(x => x.Name, x => x); - BaseStorage = storage; + Header = new PartitionFileSystemHeader(reader); } - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) + HeaderSize = Header.HeaderSize; + Files = Header.Files; + FileDict = Header.Files.ToDictionary(x => x.Name, x => x); + BaseStorage = storage; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + outDirectory.Reset(new PartitionDirectory(this, path.ToString(), mode)); + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + string pathNormalized = PathTools.Normalize(path.ToString()).TrimStart('/'); + + if (!FileDict.TryGetValue(pathNormalized, out PartitionFileEntry entry)) { - outDirectory.Reset(new PartitionDirectory(this, path.ToString(), mode)); + ThrowHelper.ThrowResult(ResultFs.PathNotFound.Value); + } + + outFile.Reset(OpenFile(entry, mode)); + return Result.Success; + } + + public IFile OpenFile(PartitionFileEntry entry, OpenMode mode) + { + return new PartitionFile(BaseStorage, HeaderSize + entry.Offset, entry.Size, mode); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + if (path.ToString() == "/") + { + entryType = DirectoryEntryType.Directory; return Result.Success; } - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + if (FileDict.ContainsKey(path.ToString().TrimStart('/'))) { - string pathNormalized = PathTools.Normalize(path.ToString()).TrimStart('/'); - - if (!FileDict.TryGetValue(pathNormalized, out PartitionFileEntry entry)) - { - ThrowHelper.ThrowResult(ResultFs.PathNotFound.Value); - } - - outFile.Reset(OpenFile(entry, mode)); + entryType = DirectoryEntryType.File; return Result.Success; } - public IFile OpenFile(PartitionFileEntry entry, OpenMode mode) - { - return new PartitionFile(BaseStorage, HeaderSize + entry.Offset, entry.Size, mode); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - if (path.ToString() == "/") - { - entryType = DirectoryEntryType.Directory; - return Result.Success; - } - - if (FileDict.ContainsKey(path.ToString().TrimStart('/'))) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); - } - - protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - - protected override Result DoCommit() - { - return Result.Success; - } + return ResultFs.PathNotFound.Log(); } - public enum PartitionFileSystemType + protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + + protected override Result DoCommit() { - Standard, - Hashed + return Result.Success; } +} - public class PartitionFileSystemHeader +public enum PartitionFileSystemType +{ + Standard, + Hashed +} + +public class PartitionFileSystemHeader +{ + public string Magic; + public int NumFiles; + public int StringTableSize; + public long Reserved; + public PartitionFileSystemType Type; + public int HeaderSize; + public PartitionFileEntry[] Files; + + public PartitionFileSystemHeader(BinaryReader reader) { - public string Magic; - public int NumFiles; - public int StringTableSize; - public long Reserved; - public PartitionFileSystemType Type; - public int HeaderSize; - public PartitionFileEntry[] Files; + Magic = reader.ReadAscii(4); + NumFiles = reader.ReadInt32(); + StringTableSize = reader.ReadInt32(); + Reserved = reader.ReadInt32(); - public PartitionFileSystemHeader(BinaryReader reader) + switch (Magic) { - Magic = reader.ReadAscii(4); - NumFiles = reader.ReadInt32(); - StringTableSize = reader.ReadInt32(); - Reserved = reader.ReadInt32(); - - switch (Magic) - { - case "PFS0": - Type = PartitionFileSystemType.Standard; - break; - case "HFS0": - Type = PartitionFileSystemType.Hashed; - break; - default: - ThrowHelper.ThrowResult(ResultFs.PartitionSignatureVerificationFailed.Value, $"Invalid Partition FS type \"{Magic}\""); - break; - } - - int entrySize = PartitionFileEntry.GetEntrySize(Type); - int stringTableOffset = 16 + entrySize * NumFiles; - HeaderSize = stringTableOffset + StringTableSize; - - Files = new PartitionFileEntry[NumFiles]; - for (int i = 0; i < NumFiles; i++) - { - Files[i] = new PartitionFileEntry(reader, Type) { Index = i }; - } - - for (int i = 0; i < NumFiles; i++) - { - reader.BaseStream.Position = stringTableOffset + Files[i].StringTableOffset; - Files[i].Name = reader.ReadAsciiZ(); - } - } - } - - public class PartitionFileEntry - { - public int Index; - public long Offset; - public long Size; - public uint StringTableOffset; - public long HashedRegionOffset; - public int HashedRegionSize; - public byte[] Hash; - public string Name; - public Validity HashValidity = Validity.Unchecked; - - public PartitionFileEntry(BinaryReader reader, PartitionFileSystemType type) - { - Offset = reader.ReadInt64(); - Size = reader.ReadInt64(); - StringTableOffset = reader.ReadUInt32(); - if (type == PartitionFileSystemType.Hashed) - { - HashedRegionSize = reader.ReadInt32(); - HashedRegionOffset = reader.ReadInt64(); - Hash = reader.ReadBytes(Sha256.DigestSize); - } - else - { - reader.BaseStream.Position += 4; - } + case "PFS0": + Type = PartitionFileSystemType.Standard; + break; + case "HFS0": + Type = PartitionFileSystemType.Hashed; + break; + default: + ThrowHelper.ThrowResult(ResultFs.PartitionSignatureVerificationFailed.Value, $"Invalid Partition FS type \"{Magic}\""); + break; } - public static int GetEntrySize(PartitionFileSystemType type) + int entrySize = PartitionFileEntry.GetEntrySize(Type); + int stringTableOffset = 16 + entrySize * NumFiles; + HeaderSize = stringTableOffset + StringTableSize; + + Files = new PartitionFileEntry[NumFiles]; + for (int i = 0; i < NumFiles; i++) { - switch (type) - { - case PartitionFileSystemType.Standard: - return 0x18; - case PartitionFileSystemType.Hashed: - return 0x40; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } + Files[i] = new PartitionFileEntry(reader, Type) { Index = i }; + } + + for (int i = 0; i < NumFiles; i++) + { + reader.BaseStream.Position = stringTableOffset + Files[i].StringTableOffset; + Files[i].Name = reader.ReadAsciiZ(); + } + } +} + +public class PartitionFileEntry +{ + public int Index; + public long Offset; + public long Size; + public uint StringTableOffset; + public long HashedRegionOffset; + public int HashedRegionSize; + public byte[] Hash; + public string Name; + public Validity HashValidity = Validity.Unchecked; + + public PartitionFileEntry(BinaryReader reader, PartitionFileSystemType type) + { + Offset = reader.ReadInt64(); + Size = reader.ReadInt64(); + StringTableOffset = reader.ReadUInt32(); + if (type == PartitionFileSystemType.Hashed) + { + HashedRegionSize = reader.ReadInt32(); + HashedRegionOffset = reader.ReadInt64(); + Hash = reader.ReadBytes(Sha256.DigestSize); + } + else + { + reader.BaseStream.Position += 4; + } + } + + public static int GetEntrySize(PartitionFileSystemType type) + { + switch (type) + { + case PartitionFileSystemType.Standard: + return 0x18; + case PartitionFileSystemType.Hashed: + return 0x40; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); } } } diff --git a/src/LibHac/FsSystem/PartitionFileSystemBuilder.cs b/src/LibHac/FsSystem/PartitionFileSystemBuilder.cs index 17be3e78..b5257334 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemBuilder.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemBuilder.cs @@ -9,180 +9,179 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionFileSystemBuilder { - public class PartitionFileSystemBuilder + private const int HeaderSize = 0x10; + + private List Entries { get; } = new List(); + private long CurrentOffset { get; set; } + + public PartitionFileSystemBuilder() { } + + /// + /// Creates a new and populates it with all + /// the files in the root directory. + /// + public PartitionFileSystemBuilder(IFileSystem input) { - private const int HeaderSize = 0x10; + using var file = new UniqueRef(); - private List Entries { get; } = new List(); - private long CurrentOffset { get; set; } - - public PartitionFileSystemBuilder() { } - - /// - /// Creates a new and populates it with all - /// the files in the root directory. - /// - public PartitionFileSystemBuilder(IFileSystem input) + foreach (DirectoryEntryEx entry in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File) + .OrderBy(x => x.FullPath, StringComparer.Ordinal)) { - using var file = new UniqueRef(); + input.OpenFile(ref file.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - foreach (DirectoryEntryEx entry in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File) - .OrderBy(x => x.FullPath, StringComparer.Ordinal)) - { - input.OpenFile(ref file.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - AddFile(entry.FullPath.TrimStart('/'), file.Release()); - } - } - - public void AddFile(string filename, IFile file) - { - file.GetSize(out long fileSize).ThrowIfFailure(); - - var entry = new Entry - { - Name = filename, - File = file, - Length = fileSize, - Offset = CurrentOffset, - NameLength = Encoding.UTF8.GetByteCount(filename), - HashOffset = 0, - HashLength = 0x200 - }; - - CurrentOffset += entry.Length; - - Entries.Add(entry); - } - - public IStorage Build(PartitionFileSystemType type) - { - byte[] meta = BuildMetaData(type); - - var sources = new List(); - sources.Add(new MemoryStorage(meta)); - - sources.AddRange(Entries.Select(x => new FileStorage(x.File))); - - return new ConcatenationStorage(sources, true); - } - - private byte[] BuildMetaData(PartitionFileSystemType type) - { - if (type == PartitionFileSystemType.Hashed) CalculateHashes(); - - int entryTableSize = Entries.Count * PartitionFileEntry.GetEntrySize(type); - int stringTableSize = CalcStringTableSize(HeaderSize + entryTableSize, type); - int metaDataSize = HeaderSize + entryTableSize + stringTableSize; - - byte[] metaData = new byte[metaDataSize]; - var writer = new BinaryWriter(new MemoryStream(metaData)); - - writer.WriteUTF8(GetMagicValue(type)); - writer.Write(Entries.Count); - writer.Write(stringTableSize); - writer.Write(0); - - int stringOffset = 0; - - foreach (Entry entry in Entries) - { - writer.Write(entry.Offset); - writer.Write(entry.Length); - writer.Write(stringOffset); - - if (type == PartitionFileSystemType.Standard) - { - writer.Write(0); - } - else - { - writer.Write(entry.HashLength); - writer.Write(entry.HashOffset); - writer.Write(entry.Hash); - } - - stringOffset += entry.NameLength + 1; - } - - foreach (Entry entry in Entries) - { - writer.WriteUTF8Z(entry.Name); - } - - return metaData; - } - - private int CalcStringTableSize(int startOffset, PartitionFileSystemType type) - { - int size = 0; - - foreach (Entry entry in Entries) - { - size += entry.NameLength + 1; - } - - int endOffset = Alignment.AlignUpPow2(startOffset + size, GetMetaDataAlignment(type)); - return endOffset - startOffset; - } - - private string GetMagicValue(PartitionFileSystemType type) - { - switch (type) - { - case PartitionFileSystemType.Standard: return "PFS0"; - case PartitionFileSystemType.Hashed: return "HFS0"; - default: throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } - - private uint GetMetaDataAlignment(PartitionFileSystemType type) - { - switch (type) - { - case PartitionFileSystemType.Standard: return 0x20; - case PartitionFileSystemType.Hashed: return 0x200; - default: throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } - - private void CalculateHashes() - { - IHash sha = Sha256.CreateSha256Generator(); - - foreach (Entry entry in Entries) - { - if (entry.HashLength == 0) entry.HashLength = 0x200; - - byte[] data = new byte[entry.HashLength]; - entry.File.Read(out long bytesRead, entry.HashOffset, data); - - if (bytesRead != entry.HashLength) - { - throw new ArgumentOutOfRangeException(); - } - - entry.Hash = new byte[Sha256.DigestSize]; - - sha.Initialize(); - sha.Update(data); - sha.GetHash(entry.Hash); - } - } - - private class Entry - { - public string Name; - public IFile File; - public long Length; - public long Offset; - public int NameLength; - - public int HashLength; - public long HashOffset; - public byte[] Hash; + AddFile(entry.FullPath.TrimStart('/'), file.Release()); } } + + public void AddFile(string filename, IFile file) + { + file.GetSize(out long fileSize).ThrowIfFailure(); + + var entry = new Entry + { + Name = filename, + File = file, + Length = fileSize, + Offset = CurrentOffset, + NameLength = Encoding.UTF8.GetByteCount(filename), + HashOffset = 0, + HashLength = 0x200 + }; + + CurrentOffset += entry.Length; + + Entries.Add(entry); + } + + public IStorage Build(PartitionFileSystemType type) + { + byte[] meta = BuildMetaData(type); + + var sources = new List(); + sources.Add(new MemoryStorage(meta)); + + sources.AddRange(Entries.Select(x => new FileStorage(x.File))); + + return new ConcatenationStorage(sources, true); + } + + private byte[] BuildMetaData(PartitionFileSystemType type) + { + if (type == PartitionFileSystemType.Hashed) CalculateHashes(); + + int entryTableSize = Entries.Count * PartitionFileEntry.GetEntrySize(type); + int stringTableSize = CalcStringTableSize(HeaderSize + entryTableSize, type); + int metaDataSize = HeaderSize + entryTableSize + stringTableSize; + + byte[] metaData = new byte[metaDataSize]; + var writer = new BinaryWriter(new MemoryStream(metaData)); + + writer.WriteUTF8(GetMagicValue(type)); + writer.Write(Entries.Count); + writer.Write(stringTableSize); + writer.Write(0); + + int stringOffset = 0; + + foreach (Entry entry in Entries) + { + writer.Write(entry.Offset); + writer.Write(entry.Length); + writer.Write(stringOffset); + + if (type == PartitionFileSystemType.Standard) + { + writer.Write(0); + } + else + { + writer.Write(entry.HashLength); + writer.Write(entry.HashOffset); + writer.Write(entry.Hash); + } + + stringOffset += entry.NameLength + 1; + } + + foreach (Entry entry in Entries) + { + writer.WriteUTF8Z(entry.Name); + } + + return metaData; + } + + private int CalcStringTableSize(int startOffset, PartitionFileSystemType type) + { + int size = 0; + + foreach (Entry entry in Entries) + { + size += entry.NameLength + 1; + } + + int endOffset = Alignment.AlignUpPow2(startOffset + size, GetMetaDataAlignment(type)); + return endOffset - startOffset; + } + + private string GetMagicValue(PartitionFileSystemType type) + { + switch (type) + { + case PartitionFileSystemType.Standard: return "PFS0"; + case PartitionFileSystemType.Hashed: return "HFS0"; + default: throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + private uint GetMetaDataAlignment(PartitionFileSystemType type) + { + switch (type) + { + case PartitionFileSystemType.Standard: return 0x20; + case PartitionFileSystemType.Hashed: return 0x200; + default: throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + private void CalculateHashes() + { + IHash sha = Sha256.CreateSha256Generator(); + + foreach (Entry entry in Entries) + { + if (entry.HashLength == 0) entry.HashLength = 0x200; + + byte[] data = new byte[entry.HashLength]; + entry.File.Read(out long bytesRead, entry.HashOffset, data); + + if (bytesRead != entry.HashLength) + { + throw new ArgumentOutOfRangeException(); + } + + entry.Hash = new byte[Sha256.DigestSize]; + + sha.Initialize(); + sha.Update(data); + sha.GetHash(entry.Hash); + } + } + + private class Entry + { + public string Name; + public IFile File; + public long Length; + public long Offset; + public int NameLength; + + public int HashLength; + public long HashOffset; + public byte[] Hash; + } } diff --git a/src/LibHac/FsSystem/PartitionFileSystemCore.cs b/src/LibHac/FsSystem/PartitionFileSystemCore.cs index b6074534..db133b75 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemCore.cs @@ -7,386 +7,385 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem.Impl; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionFileSystemCore : IFileSystem where T : unmanaged, IPartitionFileSystemEntry { - public class PartitionFileSystemCore : IFileSystem where T : unmanaged, IPartitionFileSystemEntry + private IStorage _baseStorage; + private PartitionFileSystemMetaCore _metaData; + private bool _isInitialized; + private int _dataOffset; + private SharedRef _baseStorageShared; + + public Result Initialize(ref SharedRef baseStorage) { - private IStorage _baseStorage; - private PartitionFileSystemMetaCore _metaData; - private bool _isInitialized; - private int _dataOffset; - private SharedRef _baseStorageShared; + Result rc = Initialize(baseStorage.Get); + if (rc.IsFailure()) return rc; - public Result Initialize(ref SharedRef baseStorage) - { - Result rc = Initialize(baseStorage.Get); - if (rc.IsFailure()) return rc; + _baseStorageShared.SetByMove(ref baseStorage); + return Result.Success; + } - _baseStorageShared.SetByMove(ref baseStorage); - return Result.Success; - } + public Result Initialize(IStorage baseStorage) + { + if (_isInitialized) + return ResultFs.PreconditionViolation.Log(); - public Result Initialize(IStorage baseStorage) - { - if (_isInitialized) - return ResultFs.PreconditionViolation.Log(); + _metaData = new PartitionFileSystemMetaCore(); - _metaData = new PartitionFileSystemMetaCore(); + Result rc = _metaData.Initialize(baseStorage); + if (rc.IsFailure()) return rc; - Result rc = _metaData.Initialize(baseStorage); - if (rc.IsFailure()) return rc; + _baseStorage = baseStorage; + _dataOffset = _metaData.Size; + _isInitialized = true; - _baseStorage = baseStorage; - _dataOffset = _metaData.Size; - _isInitialized = true; + return Result.Success; + } - return Result.Success; - } + public override void Dispose() + { + _baseStorageShared.Destroy(); + base.Dispose(); + } - public override void Dispose() - { - _baseStorageShared.Destroy(); - base.Dispose(); - } + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + if (!_isInitialized) + return ResultFs.PreconditionViolation.Log(); - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - if (!_isInitialized) - return ResultFs.PreconditionViolation.Log(); - - ReadOnlySpan rootPath = new[] { (byte)'/' }; - - if (path == rootPath) - return ResultFs.PathNotFound.Log(); - - outDirectory.Reset(new PartitionDirectory(this, mode)); - - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - if (!_isInitialized) - return ResultFs.PreconditionViolation.Log(); - - if (!mode.HasFlag(OpenMode.Read) && !mode.HasFlag(OpenMode.Write)) - return ResultFs.InvalidArgument.Log(); - - int entryIndex = _metaData.FindEntry(new U8Span(path.GetString().Slice(1))); - if (entryIndex < 0) return ResultFs.PathNotFound.Log(); - - ref T entry = ref _metaData.GetEntry(entryIndex); - - outFile.Reset(new PartitionFile(this, ref entry, mode)); - - return Result.Success; - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - if (!_isInitialized) - return ResultFs.PreconditionViolation.Log(); - - ReadOnlySpan pathStr = path.GetString(); - - if (path.IsEmpty() || pathStr[0] != '/') - return ResultFs.InvalidPathFormat.Log(); - - ReadOnlySpan rootPath = new[] { (byte)'/' }; - - if (StringUtils.Compare(rootPath, pathStr, 2) == 0) - { - entryType = DirectoryEntryType.Directory; - return Result.Success; - } - - if (_metaData.FindEntry(new U8Span(pathStr.Slice(1))) >= 0) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } + ReadOnlySpan rootPath = new[] { (byte)'/' }; + if (path == rootPath) return ResultFs.PathNotFound.Log(); - } - protected override Result DoCommit() + outDirectory.Reset(new PartitionDirectory(this, mode)); + + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + if (!_isInitialized) + return ResultFs.PreconditionViolation.Log(); + + if (!mode.HasFlag(OpenMode.Read) && !mode.HasFlag(OpenMode.Write)) + return ResultFs.InvalidArgument.Log(); + + int entryIndex = _metaData.FindEntry(new U8Span(path.GetString().Slice(1))); + if (entryIndex < 0) return ResultFs.PathNotFound.Log(); + + ref T entry = ref _metaData.GetEntry(entryIndex); + + outFile.Reset(new PartitionFile(this, ref entry, mode)); + + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + if (!_isInitialized) + return ResultFs.PreconditionViolation.Log(); + + ReadOnlySpan pathStr = path.GetString(); + + if (path.IsEmpty() || pathStr[0] != '/') + return ResultFs.InvalidPathFormat.Log(); + + ReadOnlySpan rootPath = new[] { (byte)'/' }; + + if (StringUtils.Compare(rootPath, pathStr, 2) == 0) { + entryType = DirectoryEntryType.Directory; return Result.Success; } - protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); - protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForPartitionFileSystem.Log(); - - private class PartitionFile : IFile + if (_metaData.FindEntry(new U8Span(pathStr.Slice(1))) >= 0) { - private PartitionFileSystemCore ParentFs { get; } - private OpenMode Mode { get; } - private T _entry; + entryType = DirectoryEntryType.File; + return Result.Success; + } - public PartitionFile(PartitionFileSystemCore parentFs, ref T entry, OpenMode mode) + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForPartitionFileSystem.Log(); + protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForPartitionFileSystem.Log(); + + private class PartitionFile : IFile + { + private PartitionFileSystemCore ParentFs { get; } + private OpenMode Mode { get; } + private T _entry; + + public PartitionFile(PartitionFileSystemCore parentFs, ref T entry, OpenMode mode) + { + ParentFs = parentFs; + _entry = entry; + Mode = mode; + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + Result rc = DryRead(out long bytesToRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + bool hashNeeded = false; + long fileStorageOffset = ParentFs._dataOffset + _entry.Offset; + + if (typeof(T) == typeof(HashedEntry)) { - ParentFs = parentFs; - _entry = entry; - Mode = mode; + ref HashedEntry entry = ref Unsafe.As(ref _entry); + + long readEnd = offset + destination.Length; + long hashEnd = entry.HashOffset + entry.HashSize; + + // The hash must be checked if any part of the hashed region is read + hashNeeded = entry.HashOffset < readEnd && hashEnd >= offset; } - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) + if (!hashNeeded) { - UnsafeHelpers.SkipParamInit(out bytesRead); + rc = ParentFs._baseStorage.Read(fileStorageOffset + offset, destination.Slice(0, (int)bytesToRead)); + } + else + { + ref HashedEntry entry = ref Unsafe.As(ref _entry); - Result rc = DryRead(out long bytesToRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; + long readEnd = offset + destination.Length; + long hashEnd = entry.HashOffset + entry.HashSize; - bool hashNeeded = false; - long fileStorageOffset = ParentFs._dataOffset + _entry.Offset; + // Make sure the hashed region doesn't extend past the end of the file + // N's code requires that the hashed region starts at the beginning of the file + if (entry.HashOffset != 0 || hashEnd > entry.Size) + return ResultFs.InvalidSha256PartitionHashTarget.Log(); - if (typeof(T) == typeof(HashedEntry)) + long storageOffset = fileStorageOffset + offset; + + // Nintendo checks for overflow here but not in other places for some reason + if (storageOffset < 0) + return ResultFs.OutOfRange.Log(); + + IHash sha256 = Sha256.CreateSha256Generator(); + sha256.Initialize(); + + var actualHash = new Buffer32(); + + // If the area to read contains the entire hashed area + if (entry.HashOffset >= offset && hashEnd <= readEnd) { - ref HashedEntry entry = ref Unsafe.As(ref _entry); + rc = ParentFs._baseStorage.Read(storageOffset, destination.Slice(0, (int)bytesToRead)); + if (rc.IsFailure()) return rc; - long readEnd = offset + destination.Length; - long hashEnd = entry.HashOffset + entry.HashSize; - - // The hash must be checked if any part of the hashed region is read - hashNeeded = entry.HashOffset < readEnd && hashEnd >= offset; - } - - if (!hashNeeded) - { - rc = ParentFs._baseStorage.Read(fileStorageOffset + offset, destination.Slice(0, (int)bytesToRead)); + Span hashedArea = destination.Slice((int)(entry.HashOffset - offset), entry.HashSize); + sha256.Update(hashedArea); } else { - ref HashedEntry entry = ref Unsafe.As(ref _entry); - - long readEnd = offset + destination.Length; - long hashEnd = entry.HashOffset + entry.HashSize; - - // Make sure the hashed region doesn't extend past the end of the file - // N's code requires that the hashed region starts at the beginning of the file - if (entry.HashOffset != 0 || hashEnd > entry.Size) - return ResultFs.InvalidSha256PartitionHashTarget.Log(); - - long storageOffset = fileStorageOffset + offset; - - // Nintendo checks for overflow here but not in other places for some reason - if (storageOffset < 0) - return ResultFs.OutOfRange.Log(); - - IHash sha256 = Sha256.CreateSha256Generator(); - sha256.Initialize(); - - var actualHash = new Buffer32(); - - // If the area to read contains the entire hashed area - if (entry.HashOffset >= offset && hashEnd <= readEnd) + // Can't start a read in the middle of the hashed region + if (readEnd > hashEnd || entry.HashOffset > offset) { - rc = ParentFs._baseStorage.Read(storageOffset, destination.Slice(0, (int)bytesToRead)); + return ResultFs.InvalidSha256PartitionHashTarget.Log(); + } + + int hashRemaining = entry.HashSize; + int readRemaining = (int)bytesToRead; + long readPos = fileStorageOffset + entry.HashOffset; + int outBufPos = 0; + + const int hashBufferSize = 0x200; + Span hashBuffer = stackalloc byte[hashBufferSize]; + + while (hashRemaining > 0) + { + int toRead = Math.Min(hashRemaining, hashBufferSize); + Span hashBufferSliced = hashBuffer.Slice(0, toRead); + + rc = ParentFs._baseStorage.Read(readPos, hashBufferSliced); if (rc.IsFailure()) return rc; - Span hashedArea = destination.Slice((int)(entry.HashOffset - offset), entry.HashSize); - sha256.Update(hashedArea); - } - else - { - // Can't start a read in the middle of the hashed region - if (readEnd > hashEnd || entry.HashOffset > offset) + sha256.Update(hashBufferSliced); + + if (readRemaining > 0 && storageOffset <= readPos + toRead) { - return ResultFs.InvalidSha256PartitionHashTarget.Log(); + int hashBufferOffset = (int)Math.Max(storageOffset - readPos, 0); + int toCopy = Math.Min(readRemaining, toRead - hashBufferOffset); + + hashBuffer.Slice(hashBufferOffset, toCopy).CopyTo(destination.Slice(outBufPos)); + + outBufPos += toCopy; + readRemaining -= toCopy; } - int hashRemaining = entry.HashSize; - int readRemaining = (int)bytesToRead; - long readPos = fileStorageOffset + entry.HashOffset; - int outBufPos = 0; - - const int hashBufferSize = 0x200; - Span hashBuffer = stackalloc byte[hashBufferSize]; - - while (hashRemaining > 0) - { - int toRead = Math.Min(hashRemaining, hashBufferSize); - Span hashBufferSliced = hashBuffer.Slice(0, toRead); - - rc = ParentFs._baseStorage.Read(readPos, hashBufferSliced); - if (rc.IsFailure()) return rc; - - sha256.Update(hashBufferSliced); - - if (readRemaining > 0 && storageOffset <= readPos + toRead) - { - int hashBufferOffset = (int)Math.Max(storageOffset - readPos, 0); - int toCopy = Math.Min(readRemaining, toRead - hashBufferOffset); - - hashBuffer.Slice(hashBufferOffset, toCopy).CopyTo(destination.Slice(outBufPos)); - - outBufPos += toCopy; - readRemaining -= toCopy; - } - - hashRemaining -= toRead; - readPos += toRead; - } + hashRemaining -= toRead; + readPos += toRead; } - - sha256.GetHash(actualHash); - - if (!CryptoUtil.IsSameBytes(entry.Hash, actualHash, Sha256.DigestSize)) - { - destination.Slice(0, (int)bytesToRead).Clear(); - - return ResultFs.Sha256PartitionHashVerificationFailed.Log(); - } - - rc = Result.Success; } - if (rc.IsSuccess()) - bytesRead = bytesToRead; + sha256.GetHash(actualHash); - return rc; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - if (isResizeNeeded) - return ResultFs.UnsupportedWriteForPartitionFile.Log(); - - if (_entry.Size < offset) - return ResultFs.OutOfRange.Log(); - - if (_entry.Size < source.Length + offset) - return ResultFs.InvalidSize.Log(); - - return ParentFs._baseStorage.Write(ParentFs._dataOffset + _entry.Offset + offset, source); - } - - protected override Result DoFlush() - { - if (Mode.HasFlag(OpenMode.Write)) + if (!CryptoUtil.IsSameBytes(entry.Hash, actualHash, Sha256.DigestSize)) { - return ParentFs._baseStorage.Flush(); + destination.Slice(0, (int)bytesToRead).Clear(); + + return ResultFs.Sha256PartitionHashVerificationFailed.Log(); } - return Result.Success; + rc = Result.Success; } - protected override Result DoSetSize(long size) - { - if (Mode.HasFlag(OpenMode.Write)) - { - return ResultFs.UnsupportedWriteForPartitionFile.Log(); - } + if (rc.IsSuccess()) + bytesRead = bytesToRead; - return ResultFs.WriteUnpermitted.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = _entry.Size; - - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - switch (operationId) - { - case OperationId.InvalidateCache: - if (!Mode.HasFlag(OpenMode.Read)) - return ResultFs.ReadUnpermitted.Log(); - - if (Mode.HasFlag(OpenMode.Write)) - return ResultFs.UnsupportedOperateRangeForPartitionFile.Log(); - - break; - case OperationId.QueryRange: - break; - default: - return ResultFs.UnsupportedOperateRangeForPartitionFile.Log(); - } - - if (offset < 0 || offset > _entry.Size) - return ResultFs.OutOfRange.Log(); - - if (size < 0 || offset + size > _entry.Size) - return ResultFs.InvalidSize.Log(); - - long offsetInStorage = ParentFs._dataOffset + _entry.Offset + offset; - - return ParentFs._baseStorage.OperateRange(outBuffer, operationId, offsetInStorage, size, inBuffer); - } + return rc; } - private class PartitionDirectory : IDirectory + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) { - private PartitionFileSystemCore ParentFs { get; } - private int CurrentIndex { get; set; } - private OpenDirectoryMode Mode { get; } + Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; - public PartitionDirectory(PartitionFileSystemCore parentFs, OpenDirectoryMode mode) + if (isResizeNeeded) + return ResultFs.UnsupportedWriteForPartitionFile.Log(); + + if (_entry.Size < offset) + return ResultFs.OutOfRange.Log(); + + if (_entry.Size < source.Length + offset) + return ResultFs.InvalidSize.Log(); + + return ParentFs._baseStorage.Write(ParentFs._dataOffset + _entry.Offset + offset, source); + } + + protected override Result DoFlush() + { + if (Mode.HasFlag(OpenMode.Write)) { - ParentFs = parentFs; - CurrentIndex = 0; - Mode = mode; + return ParentFs._baseStorage.Flush(); } - protected override Result DoRead(out long entriesRead, Span entryBuffer) + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + if (Mode.HasFlag(OpenMode.Write)) { - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - int totalEntryCount = ParentFs._metaData.GetEntryCount(); - int toReadCount = Math.Min(totalEntryCount - CurrentIndex, entryBuffer.Length); - - for (int i = 0; i < toReadCount; i++) - { - entryBuffer[i].Type = DirectoryEntryType.File; - entryBuffer[i].Size = ParentFs._metaData.GetEntry(CurrentIndex).Size; - - U8Span name = ParentFs._metaData.GetName(CurrentIndex); - StringUtils.Copy(entryBuffer[i].Name, name); - entryBuffer[i].Name[FsPath.MaxLength] = 0; - - CurrentIndex++; - } - - entriesRead = toReadCount; - } - else - { - entriesRead = 0; - } - - return Result.Success; + return ResultFs.UnsupportedWriteForPartitionFile.Log(); } - protected override Result DoGetEntryCount(out long entryCount) + return ResultFs.WriteUnpermitted.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = _entry.Size; + + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) { - if (Mode.HasFlag(OpenDirectoryMode.File)) + case OperationId.InvalidateCache: + if (!Mode.HasFlag(OpenMode.Read)) + return ResultFs.ReadUnpermitted.Log(); + + if (Mode.HasFlag(OpenMode.Write)) + return ResultFs.UnsupportedOperateRangeForPartitionFile.Log(); + + break; + case OperationId.QueryRange: + break; + default: + return ResultFs.UnsupportedOperateRangeForPartitionFile.Log(); + } + + if (offset < 0 || offset > _entry.Size) + return ResultFs.OutOfRange.Log(); + + if (size < 0 || offset + size > _entry.Size) + return ResultFs.InvalidSize.Log(); + + long offsetInStorage = ParentFs._dataOffset + _entry.Offset + offset; + + return ParentFs._baseStorage.OperateRange(outBuffer, operationId, offsetInStorage, size, inBuffer); + } + } + + private class PartitionDirectory : IDirectory + { + private PartitionFileSystemCore ParentFs { get; } + private int CurrentIndex { get; set; } + private OpenDirectoryMode Mode { get; } + + public PartitionDirectory(PartitionFileSystemCore parentFs, OpenDirectoryMode mode) + { + ParentFs = parentFs; + CurrentIndex = 0; + Mode = mode; + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + int totalEntryCount = ParentFs._metaData.GetEntryCount(); + int toReadCount = Math.Min(totalEntryCount - CurrentIndex, entryBuffer.Length); + + for (int i = 0; i < toReadCount; i++) { - entryCount = ParentFs._metaData.GetEntryCount(); - } - else - { - entryCount = 0; + entryBuffer[i].Type = DirectoryEntryType.File; + entryBuffer[i].Size = ParentFs._metaData.GetEntry(CurrentIndex).Size; + + U8Span name = ParentFs._metaData.GetName(CurrentIndex); + StringUtils.Copy(entryBuffer[i].Name, name); + entryBuffer[i].Name[FsPath.MaxLength] = 0; + + CurrentIndex++; } - return Result.Success; + entriesRead = toReadCount; } + else + { + entriesRead = 0; + } + + return Result.Success; + } + + protected override Result DoGetEntryCount(out long entryCount) + { + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + entryCount = ParentFs._metaData.GetEntryCount(); + } + else + { + entryCount = 0; + } + + return Result.Success; } } } diff --git a/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs index e32e72eb..4fce01c7 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs @@ -7,189 +7,188 @@ using LibHac.Fs; using LibHac.FsSystem.Impl; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class PartitionFileSystemMetaCore where T : unmanaged, IPartitionFileSystemEntry { - public class PartitionFileSystemMetaCore where T : unmanaged, IPartitionFileSystemEntry + private static int HeaderSize => Unsafe.SizeOf
(); + private static int EntrySize => Unsafe.SizeOf(); + + private bool IsInitialized { get; set; } + private int EntryCount { get; set; } + private int StringTableSize { get; set; } + private int StringTableOffset { get; set; } + private byte[] Buffer { get; set; } + + public int Size { get; private set; } + + public Result Initialize(IStorage baseStorage) { - private static int HeaderSize => Unsafe.SizeOf
(); - private static int EntrySize => Unsafe.SizeOf(); + var header = new Header(); - private bool IsInitialized { get; set; } - private int EntryCount { get; set; } - private int StringTableSize { get; set; } - private int StringTableOffset { get; set; } - private byte[] Buffer { get; set; } + Result rc = baseStorage.Read(0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; - public int Size { get; private set; } + int pfsMetaSize = HeaderSize + header.EntryCount * EntrySize + header.StringTableSize; + Buffer = new byte[pfsMetaSize]; + Size = pfsMetaSize; - public Result Initialize(IStorage baseStorage) + return Initialize(baseStorage, Buffer); + } + + private Result Initialize(IStorage baseStorage, Span buffer) + { + if (buffer.Length < HeaderSize) + return ResultFs.InvalidSize.Log(); + + Result rc = baseStorage.Read(0, buffer.Slice(0, HeaderSize)); + if (rc.IsFailure()) return rc; + + ref Header header = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + if (header.Magic != GetMagicValue()) + return GetInvalidMagicResult(); + + EntryCount = header.EntryCount; + + int entryTableOffset = HeaderSize; + int entryTableSize = EntryCount * EntrySize; + + StringTableOffset = entryTableOffset + entryTableSize; + StringTableSize = header.StringTableSize; + + int pfsMetaSize = StringTableOffset + StringTableSize; + + if (buffer.Length < pfsMetaSize) + return ResultFs.InvalidSize.Log(); + + rc = baseStorage.Read(entryTableOffset, + buffer.Slice(entryTableOffset, entryTableSize + StringTableSize)); + + if (rc.IsSuccess()) { - var header = new Header(); - - Result rc = baseStorage.Read(0, SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; - - int pfsMetaSize = HeaderSize + header.EntryCount * EntrySize + header.StringTableSize; - Buffer = new byte[pfsMetaSize]; - Size = pfsMetaSize; - - return Initialize(baseStorage, Buffer); + IsInitialized = true; } - private Result Initialize(IStorage baseStorage, Span buffer) + return rc; + } + + public int GetEntryCount() + { + // FS aborts instead of returning the result value + if (!IsInitialized) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + return EntryCount; + } + + public int FindEntry(U8Span name) + { + // FS aborts instead of returning the result value + if (!IsInitialized) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + int stringTableSize = StringTableSize; + + ReadOnlySpan entries = GetEntries(); + ReadOnlySpan names = GetStringTable(); + + for (int i = 0; i < entries.Length; i++) { - if (buffer.Length < HeaderSize) - return ResultFs.InvalidSize.Log(); - - Result rc = baseStorage.Read(0, buffer.Slice(0, HeaderSize)); - if (rc.IsFailure()) return rc; - - ref Header header = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - - if (header.Magic != GetMagicValue()) - return GetInvalidMagicResult(); - - EntryCount = header.EntryCount; - - int entryTableOffset = HeaderSize; - int entryTableSize = EntryCount * EntrySize; - - StringTableOffset = entryTableOffset + entryTableSize; - StringTableSize = header.StringTableSize; - - int pfsMetaSize = StringTableOffset + StringTableSize; - - if (buffer.Length < pfsMetaSize) - return ResultFs.InvalidSize.Log(); - - rc = baseStorage.Read(entryTableOffset, - buffer.Slice(entryTableOffset, entryTableSize + StringTableSize)); - - if (rc.IsSuccess()) - { - IsInitialized = true; - } - - return rc; - } - - public int GetEntryCount() - { - // FS aborts instead of returning the result value - if (!IsInitialized) - throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); - - return EntryCount; - } - - public int FindEntry(U8Span name) - { - // FS aborts instead of returning the result value - if (!IsInitialized) - throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); - - int stringTableSize = StringTableSize; - - ReadOnlySpan entries = GetEntries(); - ReadOnlySpan names = GetStringTable(); - - for (int i = 0; i < entries.Length; i++) - { - if (stringTableSize <= entries[i].NameOffset) - { - throw new HorizonResultException(ResultFs.InvalidPartitionEntryOffset.Log()); - } - - ReadOnlySpan entryName = names.Slice(entries[i].NameOffset); - - if (StringUtils.Compare(name, entryName) == 0) - { - return i; - } - } - - return -1; - } - - public ref T GetEntry(int index) - { - if (!IsInitialized || index < 0 || index > EntryCount) - throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); - - return ref GetEntries()[index]; - } - - public U8Span GetName(int index) - { - int nameOffset = GetEntry(index).NameOffset; - ReadOnlySpan table = GetStringTable(); - - // Nintendo doesn't check the offset here like they do in FindEntry, but we will for safety - if (table.Length <= nameOffset) + if (stringTableSize <= entries[i].NameOffset) { throw new HorizonResultException(ResultFs.InvalidPartitionEntryOffset.Log()); } - return new U8Span(table.Slice(nameOffset)); - } + ReadOnlySpan entryName = names.Slice(entries[i].NameOffset); - private Span GetEntries() - { - Debug.Assert(IsInitialized); - Debug.Assert(Buffer.Length >= HeaderSize + EntryCount * EntrySize); - - Span entryBuffer = Buffer.AsSpan(HeaderSize, EntryCount * EntrySize); - return MemoryMarshal.Cast(entryBuffer); - } - - private ReadOnlySpan GetStringTable() - { - Debug.Assert(IsInitialized); - Debug.Assert(Buffer.Length >= StringTableOffset + StringTableSize); - - return Buffer.AsSpan(StringTableOffset, StringTableSize); - } - - // You can't attach constant values to interfaces in C#, so workaround that - // by getting the values based on which generic type is used - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Result GetInvalidMagicResult() - { - if (typeof(T) == typeof(StandardEntry)) + if (StringUtils.Compare(name, entryName) == 0) { - return ResultFs.PartitionSignatureVerificationFailed.Log(); + return i; } - - if (typeof(T) == typeof(HashedEntry)) - { - return ResultFs.Sha256PartitionSignatureVerificationFailed.Log(); - } - - throw new NotSupportedException(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GetMagicValue() + return -1; + } + + public ref T GetEntry(int index) + { + if (!IsInitialized || index < 0 || index > EntryCount) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + return ref GetEntries()[index]; + } + + public U8Span GetName(int index) + { + int nameOffset = GetEntry(index).NameOffset; + ReadOnlySpan table = GetStringTable(); + + // Nintendo doesn't check the offset here like they do in FindEntry, but we will for safety + if (table.Length <= nameOffset) { - if (typeof(T) == typeof(StandardEntry)) - { - return 0x30534650; // PFS0 - } - - if (typeof(T) == typeof(HashedEntry)) - { - return 0x30534648; // HFS0 - } - - throw new NotSupportedException(); + throw new HorizonResultException(ResultFs.InvalidPartitionEntryOffset.Log()); } - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - private struct Header + return new U8Span(table.Slice(nameOffset)); + } + + private Span GetEntries() + { + Debug.Assert(IsInitialized); + Debug.Assert(Buffer.Length >= HeaderSize + EntryCount * EntrySize); + + Span entryBuffer = Buffer.AsSpan(HeaderSize, EntryCount * EntrySize); + return MemoryMarshal.Cast(entryBuffer); + } + + private ReadOnlySpan GetStringTable() + { + Debug.Assert(IsInitialized); + Debug.Assert(Buffer.Length >= StringTableOffset + StringTableSize); + + return Buffer.AsSpan(StringTableOffset, StringTableSize); + } + + // You can't attach constant values to interfaces in C#, so workaround that + // by getting the values based on which generic type is used + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Result GetInvalidMagicResult() + { + if (typeof(T) == typeof(StandardEntry)) { - public uint Magic; - public int EntryCount; - public int StringTableSize; + return ResultFs.PartitionSignatureVerificationFailed.Log(); } + + if (typeof(T) == typeof(HashedEntry)) + { + return ResultFs.Sha256PartitionSignatureVerificationFailed.Log(); + } + + throw new NotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetMagicValue() + { + if (typeof(T) == typeof(StandardEntry)) + { + return 0x30534650; // PFS0 + } + + if (typeof(T) == typeof(HashedEntry)) + { + return 0x30534648; // HFS0 + } + + throw new NotSupportedException(); + } + + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + private struct Header + { + public uint Magic; + public int EntryCount; + public int StringTableSize; } } diff --git a/src/LibHac/FsSystem/PathParser.cs b/src/LibHac/FsSystem/PathParser.cs index 48db1630..76bf7524 100644 --- a/src/LibHac/FsSystem/PathParser.cs +++ b/src/LibHac/FsSystem/PathParser.cs @@ -1,86 +1,85 @@ using System; using System.Diagnostics; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// Enumerates a file or directory path one segment at a time. +/// +/// When the parser is initialized +/// will return the root directory name, i.e. an empty string. +public ref struct PathParser { - /// - /// Enumerates a file or directory path one segment at a time. - /// - /// When the parser is initialized - /// will return the root directory name, i.e. an empty string. - public ref struct PathParser + private ReadOnlySpan _path; + private int _offset; + private int _length; + private bool _finished; + + public PathParser(ReadOnlySpan path) { - private ReadOnlySpan _path; - private int _offset; - private int _length; - private bool _finished; + Debug.Assert(PathTools.IsNormalized(path)); - public PathParser(ReadOnlySpan path) + if (path.Length < 1 || path[0] != '/') { - Debug.Assert(PathTools.IsNormalized(path)); - - if (path.Length < 1 || path[0] != '/') - { - throw new ArgumentException("Path must begin with a '/'"); - } - - _path = path; - _offset = 0; - _length = 0; - _finished = path.Length == 1 || path[1] == '\0'; + throw new ArgumentException("Path must begin with a '/'"); } - /// - /// Moves the iterator to the next segment in the path and gets the name of that segment. - /// - /// When this method returns, contains the path segment's name. - /// if the was able to - /// move to the next path segment. - /// if there are no remaining path segments. - public bool TryGetNext(out ReadOnlySpan name) - { - bool success = MoveNext(); - name = GetCurrent(); - return success; - } - - /// - /// Moves the iterator to the next segment in the path. - /// - /// if the was able to - /// move to the next path segment. - /// if there are no remaining path segments. - public bool MoveNext() - { - if (_finished) return false; - - _offset = _offset + _length + 1; - int end = _offset; - - while (end < _path.Length && _path[end] != '\0' && _path[end] != '/') - { - end++; - } - - _finished = end + 1 >= _path.Length || _path[end] == '\0'; - _length = end - _offset; - - return true; - } - - /// - /// Gets the current path segment's name. - /// - /// The current path segment. - public ReadOnlySpan GetCurrent() - { - return _path.Slice(_offset, _length); - } - - /// - /// Checks if the current path segment is the final one. - /// - /// if the current path segment is the final one. - public bool IsFinished() => _finished; + _path = path; + _offset = 0; + _length = 0; + _finished = path.Length == 1 || path[1] == '\0'; } + + /// + /// Moves the iterator to the next segment in the path and gets the name of that segment. + /// + /// When this method returns, contains the path segment's name. + /// if the was able to + /// move to the next path segment. + /// if there are no remaining path segments. + public bool TryGetNext(out ReadOnlySpan name) + { + bool success = MoveNext(); + name = GetCurrent(); + return success; + } + + /// + /// Moves the iterator to the next segment in the path. + /// + /// if the was able to + /// move to the next path segment. + /// if there are no remaining path segments. + public bool MoveNext() + { + if (_finished) return false; + + _offset = _offset + _length + 1; + int end = _offset; + + while (end < _path.Length && _path[end] != '\0' && _path[end] != '/') + { + end++; + } + + _finished = end + 1 >= _path.Length || _path[end] == '\0'; + _length = end - _offset; + + return true; + } + + /// + /// Gets the current path segment's name. + /// + /// The current path segment. + public ReadOnlySpan GetCurrent() + { + return _path.Slice(_offset, _length); + } + + /// + /// Checks if the current path segment is the final one. + /// + /// if the current path segment is the final one. + public bool IsFinished() => _finished; } diff --git a/src/LibHac/FsSystem/PathTools.cs b/src/LibHac/FsSystem/PathTools.cs index 34f7ed24..37aa753d 100644 --- a/src/LibHac/FsSystem/PathTools.cs +++ b/src/LibHac/FsSystem/PathTools.cs @@ -7,181 +7,148 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public static class PathTools { - public static class PathTools + // todo: Consolidate these + internal const char DirectorySeparator = '/'; + internal const char MountSeparator = ':'; + internal const int MountNameLengthMax = 0xF; + + // Todo: Remove + internal const int MaxPathLength = 0x300; + + public static string Normalize(string inPath) { - // todo: Consolidate these - internal const char DirectorySeparator = '/'; - internal const char MountSeparator = ':'; - internal const int MountNameLengthMax = 0xF; + if (IsNormalized(inPath.AsSpan())) return inPath; - // Todo: Remove - internal const int MaxPathLength = 0x300; + Span initialBuffer = stackalloc char[0x200]; + var sb = new ValueStringBuilder(initialBuffer); - public static string Normalize(string inPath) + int rootLen = 0; + int maxMountLen = Math.Min(inPath.Length, MountNameLengthMax); + + for (int i = 0; i < maxMountLen; i++) { - if (IsNormalized(inPath.AsSpan())) return inPath; - - Span initialBuffer = stackalloc char[0x200]; - var sb = new ValueStringBuilder(initialBuffer); - - int rootLen = 0; - int maxMountLen = Math.Min(inPath.Length, MountNameLengthMax); - - for (int i = 0; i < maxMountLen; i++) + if (inPath[i] == MountSeparator) { - if (inPath[i] == MountSeparator) - { - rootLen = i + 1; - break; - } - - if (IsDirectorySeparator(inPath[i])) - { - break; - } + rootLen = i + 1; + break; } - bool isNormalized = NormalizeInternal(inPath.AsSpan(), rootLen, ref sb); - - string normalized = isNormalized ? inPath : sb.ToString(); - - sb.Dispose(); - - return normalized; + if (IsDirectorySeparator(inPath[i])) + { + break; + } } - public static Result Normalize(out U8Span normalizedPath, U8Span path) + bool isNormalized = NormalizeInternal(inPath.AsSpan(), rootLen, ref sb); + + string normalized = isNormalized ? inPath : sb.ToString(); + + sb.Dispose(); + + return normalized; + } + + public static Result Normalize(out U8Span normalizedPath, U8Span path) + { + if (path.Length == 0) { - if (path.Length == 0) - { - normalizedPath = path; - return Result.Success; - } - - // Todo: optimize - normalizedPath = new U8Span(Normalize(path.ToString())); - + normalizedPath = path; return Result.Success; } - // 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. - internal static bool NormalizeInternal(ReadOnlySpan path, int rootLength, ref ValueStringBuilder sb) + // Todo: optimize + normalizedPath = new U8Span(Normalize(path.ToString())); + + return Result.Success; + } + + // 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. + internal static bool NormalizeInternal(ReadOnlySpan path, int rootLength, ref ValueStringBuilder sb) + { + if (rootLength > 0) { - if (rootLength > 0) - { - sb.Append(path.Slice(0, rootLength)); - } + sb.Append(path.Slice(0, rootLength)); + } - bool isNormalized = true; + bool isNormalized = true; - var state = NormalizeState.Initial; + var state = NormalizeState.Initial; - for (int i = rootLength; i < path.Length; i++) - { - char c = path[i]; - - switch (state) - { - case NormalizeState.Initial when IsDirectorySeparator(c): - state = NormalizeState.Delimiter; - sb.Append(c); - break; - - case NormalizeState.Initial when c == '.': - isNormalized = false; - state = NormalizeState.Dot; - - sb.Append(DirectorySeparator); - sb.Append(c); - break; - - case NormalizeState.Initial: - isNormalized = false; - state = NormalizeState.Normal; - - sb.Append(DirectorySeparator); - sb.Append(c); - break; - - case NormalizeState.Normal when IsDirectorySeparator(c): - state = NormalizeState.Delimiter; - sb.Append(c); - break; - - case NormalizeState.Normal: - sb.Append(c); - break; - - case NormalizeState.Delimiter when IsDirectorySeparator(c): - isNormalized = false; - break; - - case NormalizeState.Delimiter when c == '.': - state = NormalizeState.Dot; - sb.Append(c); - break; - - case NormalizeState.Delimiter: - state = NormalizeState.Normal; - sb.Append(c); - break; - - case NormalizeState.Dot when IsDirectorySeparator(c): - isNormalized = false; - state = NormalizeState.Delimiter; - sb.Length -= 1; - break; - - case NormalizeState.Dot when c == '.': - state = NormalizeState.DoubleDot; - sb.Append(c); - break; - - case NormalizeState.Dot: - state = NormalizeState.Normal; - sb.Append(c); - break; - - case NormalizeState.DoubleDot when IsDirectorySeparator(c): - isNormalized = false; - state = NormalizeState.Delimiter; - - int s = sb.Length - 1; - int separators = 0; - - for (; s > rootLength; s--) - { - if (IsDirectorySeparator(sb[s])) - { - separators++; - - if (separators == 2) break; - } - } - - sb.Length = s + 1; - - break; - - case NormalizeState.DoubleDot: - state = NormalizeState.Normal; - break; - } - } + for (int i = rootLength; i < path.Length; i++) + { + char c = path[i]; switch (state) { - case NormalizeState.Dot: - isNormalized = false; - sb.Length -= 2; + case NormalizeState.Initial when IsDirectorySeparator(c): + state = NormalizeState.Delimiter; + sb.Append(c); break; - case NormalizeState.DoubleDot: + case NormalizeState.Initial when c == '.': isNormalized = false; + state = NormalizeState.Dot; + + sb.Append(DirectorySeparator); + sb.Append(c); + break; + + case NormalizeState.Initial: + isNormalized = false; + state = NormalizeState.Normal; + + sb.Append(DirectorySeparator); + sb.Append(c); + break; + + case NormalizeState.Normal when IsDirectorySeparator(c): + state = NormalizeState.Delimiter; + sb.Append(c); + break; + + case NormalizeState.Normal: + sb.Append(c); + break; + + case NormalizeState.Delimiter when IsDirectorySeparator(c): + isNormalized = false; + break; + + case NormalizeState.Delimiter when c == '.': + state = NormalizeState.Dot; + sb.Append(c); + break; + + case NormalizeState.Delimiter: + state = NormalizeState.Normal; + sb.Append(c); + break; + + case NormalizeState.Dot when IsDirectorySeparator(c): + isNormalized = false; + state = NormalizeState.Delimiter; + sb.Length -= 1; + break; + + case NormalizeState.Dot when c == '.': + state = NormalizeState.DoubleDot; + sb.Append(c); + break; + + case NormalizeState.Dot: + state = NormalizeState.Normal; + sb.Append(c); + break; + + case NormalizeState.DoubleDot when IsDirectorySeparator(c): + isNormalized = false; + state = NormalizeState.Delimiter; int s = sb.Length - 1; int separators = 0; @@ -196,327 +163,359 @@ namespace LibHac.FsSystem } } - sb.Length = s; + sb.Length = s + 1; break; - } - if (sb.Length == rootLength) - { - sb.Append(DirectorySeparator); - - return false; - } - - return isNormalized; - } - - public static string GetParentDirectory(string path) - { - Debug.Assert(IsNormalized(path.AsSpan())); - - int i = path.Length - 1; - - // Handles non-mounted root paths - if (i == 0) return string.Empty; - - // A trailing separator should be ignored - if (path[i] == '/') i--; - - // Handles mounted root paths - if (i >= 0 && path[i] == ':') return string.Empty; - - while (i >= 0 && path[i] != '/') i--; - - // Leave the '/' if the parent is the root directory - if (i == 0 || i > 0 && path[i - 1] == ':') i++; - - return path.Substring(0, i); - } - - public static ReadOnlySpan GetParentDirectory(ReadOnlySpan path) - { - Assert.SdkAssert(IsNormalized(path)); - - int i = StringUtils.GetLength(path) - 1; - - // A trailing separator should be ignored - if (path[i] == '/') i--; - - while (i >= 1 && path[i] != '/') i--; - - i = Math.Max(i, 1); - return path.Slice(0, i); - } - - /// - /// Returns the name and extension parts of the given path. The returned ReadOnlySpan - /// contains the characters of the path that follows the last separator in path. - /// - public static ReadOnlySpan GetFileName(ReadOnlySpan path) - { - Debug.Assert(IsNormalized(path)); - - int i = path.Length; - - while (i >= 1 && path[i - 1] != '/') i--; - - i = Math.Max(i, 0); - return path.Slice(i, path.Length - i); - } - - public static ReadOnlySpan GetLastSegment(ReadOnlySpan path) - { - Debug.Assert(IsNormalized(path)); - - int pathLength = StringUtils.GetLength(path); - - if (pathLength == 0) - return path; - - int endIndex = path[pathLength - 1] == DirectorySeparator ? pathLength - 1 : pathLength; - int i = endIndex; - - while (i >= 1 && path[i - 1] != '/') i--; - - i = Math.Max(i, 0); - return path.Slice(i, endIndex - i); - } - - public static bool IsNormalized(ReadOnlySpan path) - { - var state = NormalizeState.Initial; - - foreach (char c in path) - { - if (c == 0) + case NormalizeState.DoubleDot: + state = NormalizeState.Normal; break; + } + } - switch (state) + switch (state) + { + case NormalizeState.Dot: + isNormalized = false; + sb.Length -= 2; + break; + + case NormalizeState.DoubleDot: + isNormalized = false; + + int s = sb.Length - 1; + int separators = 0; + + for (; s > rootLength; s--) { - case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break; - case NormalizeState.Initial when IsValidMountNameChar(c): state = NormalizeState.MountName; break; - case NormalizeState.Initial: return false; + if (IsDirectorySeparator(sb[s])) + { + separators++; - case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break; - - case NormalizeState.Delimiter when c == '/': return false; - case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break; - case NormalizeState.Delimiter: state = NormalizeState.Normal; break; - - case NormalizeState.Dot when c == '/': return false; - case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break; - case NormalizeState.Dot: state = NormalizeState.Normal; break; - - case NormalizeState.DoubleDot when c == '/': return false; - case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; - - case NormalizeState.MountName when IsValidMountNameChar(c): break; - case NormalizeState.MountName when c == ':': state = NormalizeState.MountDelimiter; break; - case NormalizeState.MountName: return false; - - case NormalizeState.MountDelimiter when c == '/': state = NormalizeState.Delimiter; break; - case NormalizeState.MountDelimiter: return false; + if (separators == 2) break; + } } - } - return state == NormalizeState.Normal || state == NormalizeState.Delimiter; + sb.Length = s; + + break; } - public static bool IsNormalized(ReadOnlySpan path) + if (sb.Length == rootLength) { - var state = NormalizeState.Initial; + sb.Append(DirectorySeparator); - foreach (byte c in path) + return false; + } + + return isNormalized; + } + + public static string GetParentDirectory(string path) + { + Debug.Assert(IsNormalized(path.AsSpan())); + + int i = path.Length - 1; + + // Handles non-mounted root paths + if (i == 0) return string.Empty; + + // A trailing separator should be ignored + if (path[i] == '/') i--; + + // Handles mounted root paths + if (i >= 0 && path[i] == ':') return string.Empty; + + while (i >= 0 && path[i] != '/') i--; + + // Leave the '/' if the parent is the root directory + if (i == 0 || i > 0 && path[i - 1] == ':') i++; + + return path.Substring(0, i); + } + + public static ReadOnlySpan GetParentDirectory(ReadOnlySpan path) + { + Assert.SdkAssert(IsNormalized(path)); + + int i = StringUtils.GetLength(path) - 1; + + // A trailing separator should be ignored + if (path[i] == '/') i--; + + while (i >= 1 && path[i] != '/') i--; + + i = Math.Max(i, 1); + return path.Slice(0, i); + } + + /// + /// Returns the name and extension parts of the given path. The returned ReadOnlySpan + /// contains the characters of the path that follows the last separator in path. + /// + public static ReadOnlySpan GetFileName(ReadOnlySpan path) + { + Debug.Assert(IsNormalized(path)); + + int i = path.Length; + + while (i >= 1 && path[i - 1] != '/') i--; + + i = Math.Max(i, 0); + return path.Slice(i, path.Length - i); + } + + public static ReadOnlySpan GetLastSegment(ReadOnlySpan path) + { + Debug.Assert(IsNormalized(path)); + + int pathLength = StringUtils.GetLength(path); + + if (pathLength == 0) + return path; + + int endIndex = path[pathLength - 1] == DirectorySeparator ? pathLength - 1 : pathLength; + int i = endIndex; + + while (i >= 1 && path[i - 1] != '/') i--; + + i = Math.Max(i, 0); + return path.Slice(i, endIndex - i); + } + + public static bool IsNormalized(ReadOnlySpan path) + { + var state = NormalizeState.Initial; + + foreach (char c in path) + { + if (c == 0) + break; + + switch (state) { - if (c == 0) - break; + case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break; + case NormalizeState.Initial when IsValidMountNameChar(c): state = NormalizeState.MountName; break; + case NormalizeState.Initial: return false; - switch (state) - { - case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break; - case NormalizeState.Initial when IsValidMountNameChar(c): state = NormalizeState.MountName; break; - case NormalizeState.Initial: return false; + case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break; - case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break; + case NormalizeState.Delimiter when c == '/': return false; + case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break; + case NormalizeState.Delimiter: state = NormalizeState.Normal; break; - case NormalizeState.Delimiter when c == '/': return false; - case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break; - case NormalizeState.Delimiter: state = NormalizeState.Normal; break; + case NormalizeState.Dot when c == '/': return false; + case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break; + case NormalizeState.Dot: state = NormalizeState.Normal; break; - case NormalizeState.Dot when c == '/': return false; - case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break; - case NormalizeState.Dot: state = NormalizeState.Normal; break; + case NormalizeState.DoubleDot when c == '/': return false; + case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; - case NormalizeState.DoubleDot when c == '/': return false; - case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; + case NormalizeState.MountName when IsValidMountNameChar(c): break; + case NormalizeState.MountName when c == ':': state = NormalizeState.MountDelimiter; break; + case NormalizeState.MountName: return false; - case NormalizeState.MountName when IsValidMountNameChar(c): break; - case NormalizeState.MountName when c == ':': state = NormalizeState.MountDelimiter; break; - case NormalizeState.MountName: return false; - - case NormalizeState.MountDelimiter when c == '/': state = NormalizeState.Delimiter; break; - case NormalizeState.MountDelimiter: return false; - } + case NormalizeState.MountDelimiter when c == '/': state = NormalizeState.Delimiter; break; + case NormalizeState.MountDelimiter: return false; } - - return state == NormalizeState.Normal || state == NormalizeState.Delimiter; } - /// - /// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized. - /// - /// The first path to be compared. - /// The second path to be compared. - /// - public static bool IsSubPath(ReadOnlySpan path1, ReadOnlySpan path2) + return state == NormalizeState.Normal || state == NormalizeState.Delimiter; + } + + public static bool IsNormalized(ReadOnlySpan path) + { + var state = NormalizeState.Initial; + + foreach (byte c in path) { - Debug.Assert(IsNormalized(path1)); - Debug.Assert(IsNormalized(path2)); + if (c == 0) + break; - if (path1.Length == 0 || path2.Length == 0) return true; - - //Ignore any trailing slashes - if (path1[path1.Length - 1] == DirectorySeparator) + switch (state) { - path1 = path1.Slice(0, path1.Length - 1); - } + case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break; + case NormalizeState.Initial when IsValidMountNameChar(c): state = NormalizeState.MountName; break; + case NormalizeState.Initial: return false; - if (path2[path2.Length - 1] == DirectorySeparator) + case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break; + + case NormalizeState.Delimiter when c == '/': return false; + case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break; + case NormalizeState.Delimiter: state = NormalizeState.Normal; break; + + case NormalizeState.Dot when c == '/': return false; + case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break; + case NormalizeState.Dot: state = NormalizeState.Normal; break; + + case NormalizeState.DoubleDot when c == '/': return false; + case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; + + case NormalizeState.MountName when IsValidMountNameChar(c): break; + case NormalizeState.MountName when c == ':': state = NormalizeState.MountDelimiter; break; + case NormalizeState.MountName: return false; + + case NormalizeState.MountDelimiter when c == '/': state = NormalizeState.Delimiter; break; + case NormalizeState.MountDelimiter: return false; + } + } + + return state == NormalizeState.Normal || state == NormalizeState.Delimiter; + } + + /// + /// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized. + /// + /// The first path to be compared. + /// The second path to be compared. + /// + public static bool IsSubPath(ReadOnlySpan path1, ReadOnlySpan path2) + { + Debug.Assert(IsNormalized(path1)); + Debug.Assert(IsNormalized(path2)); + + if (path1.Length == 0 || path2.Length == 0) return true; + + //Ignore any trailing slashes + if (path1[path1.Length - 1] == DirectorySeparator) + { + path1 = path1.Slice(0, path1.Length - 1); + } + + if (path2[path2.Length - 1] == DirectorySeparator) + { + path2 = path2.Slice(0, path2.Length - 1); + } + + ReadOnlySpan shortPath = path1.Length < path2.Length ? path1 : path2; + ReadOnlySpan longPath = path1.Length < path2.Length ? path2 : path1; + + if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length))) + { + return false; + } + + return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator; + } + + /// + /// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized. + /// + /// The first path to be compared. + /// The second path to be compared. + /// + public static bool IsSubPath(ReadOnlySpan path1, ReadOnlySpan path2) + { + Debug.Assert(IsNormalized(path1)); + Debug.Assert(IsNormalized(path2)); + + if (path1.Length == 0 || path2.Length == 0) return true; + + //Ignore any trailing slashes + if (path1[path1.Length - 1] == DirectorySeparator) + { + path1 = path1.Slice(0, path1.Length - 1); + } + + if (path2[path2.Length - 1] == DirectorySeparator) + { + path2 = path2.Slice(0, path2.Length - 1); + } + + ReadOnlySpan shortPath = path1.Length < path2.Length ? path1 : path2; + ReadOnlySpan longPath = path1.Length < path2.Length ? path2 : path1; + + if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length))) + { + return false; + } + + return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator; + } + + public static string Combine(string path1, string path2) + { + if (path1 == null || path2 == null) throw new NullReferenceException(); + + if (string.IsNullOrEmpty(path1)) return path2; + if (string.IsNullOrEmpty(path2)) return path1; + + bool hasSeparator = IsDirectorySeparator(path1[path1.Length - 1]) || IsDirectorySeparator(path2[0]); + + if (hasSeparator) + { + return path1 + path2; + } + + return path1 + DirectorySeparator + path2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c == DirectorySeparator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(byte c) + { + return c == DirectorySeparator; + } + + public static Result GetMountName(string path, out string mountName) + { + Result rc = GetMountNameLength(path, out int length); + + if (rc.IsFailure()) + { + UnsafeHelpers.SkipParamInit(out mountName); + return rc; + } + + mountName = path.Substring(0, length); + return Result.Success; + } + + public static Result GetMountNameLength(string path, out int length) + { + UnsafeHelpers.SkipParamInit(out length); + + int maxLen = Math.Min(path.Length, MountNameLengthMax); + + for (int i = 0; i < maxLen; i++) + { + if (path[i] == MountSeparator) { - path2 = path2.Slice(0, path2.Length - 1); + length = i; + return Result.Success; } - - ReadOnlySpan shortPath = path1.Length < path2.Length ? path1 : path2; - ReadOnlySpan longPath = path1.Length < path2.Length ? path2 : path1; - - if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length))) - { - return false; - } - - return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator; } - /// - /// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized. - /// - /// The first path to be compared. - /// The second path to be compared. - /// - public static bool IsSubPath(ReadOnlySpan path1, ReadOnlySpan path2) - { - Debug.Assert(IsNormalized(path1)); - Debug.Assert(IsNormalized(path2)); + return ResultFs.InvalidMountName.Log(); + } - if (path1.Length == 0 || path2.Length == 0) return true; + public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase) + { + return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), + name.AsSpan(), ignoreCase); + } - //Ignore any trailing slashes - if (path1[path1.Length - 1] == DirectorySeparator) - { - path1 = path1.Slice(0, path1.Length - 1); - } + private static bool IsValidMountNameChar(char c) + { + c |= (char)0x20; + return c >= 'a' && c <= 'z'; + } - if (path2[path2.Length - 1] == DirectorySeparator) - { - path2 = path2.Slice(0, path2.Length - 1); - } + private static bool IsValidMountNameChar(byte c) => IsValidMountNameChar((char)c); - ReadOnlySpan shortPath = path1.Length < path2.Length ? path1 : path2; - ReadOnlySpan longPath = path1.Length < path2.Length ? path2 : path1; - - if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length))) - { - return false; - } - - return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator; - } - - public static string Combine(string path1, string path2) - { - if (path1 == null || path2 == null) throw new NullReferenceException(); - - if (string.IsNullOrEmpty(path1)) return path2; - if (string.IsNullOrEmpty(path2)) return path1; - - bool hasSeparator = IsDirectorySeparator(path1[path1.Length - 1]) || IsDirectorySeparator(path2[0]); - - if (hasSeparator) - { - return path1 + path2; - } - - return path1 + DirectorySeparator + path2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsDirectorySeparator(char c) - { - return c == DirectorySeparator; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsDirectorySeparator(byte c) - { - return c == DirectorySeparator; - } - - public static Result GetMountName(string path, out string mountName) - { - Result rc = GetMountNameLength(path, out int length); - - if (rc.IsFailure()) - { - UnsafeHelpers.SkipParamInit(out mountName); - return rc; - } - - mountName = path.Substring(0, length); - return Result.Success; - } - - public static Result GetMountNameLength(string path, out int length) - { - UnsafeHelpers.SkipParamInit(out length); - - int maxLen = Math.Min(path.Length, MountNameLengthMax); - - for (int i = 0; i < maxLen; i++) - { - if (path[i] == MountSeparator) - { - length = i; - return Result.Success; - } - } - - return ResultFs.InvalidMountName.Log(); - } - - public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase) - { - return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(), - name.AsSpan(), ignoreCase); - } - - private static bool IsValidMountNameChar(char c) - { - c |= (char)0x20; - return c >= 'a' && c <= 'z'; - } - - private static bool IsValidMountNameChar(byte c) => IsValidMountNameChar((char)c); - - private enum NormalizeState - { - Initial, - Normal, - Delimiter, - Dot, - DoubleDot, - MountName, - MountDelimiter - } + private enum NormalizeState + { + Initial, + Normal, + Delimiter, + Dot, + DoubleDot, + MountName, + MountDelimiter } } diff --git a/src/LibHac/FsSystem/PooledBuffer.cs b/src/LibHac/FsSystem/PooledBuffer.cs index c410172b..8cd3c59e 100644 --- a/src/LibHac/FsSystem/PooledBuffer.cs +++ b/src/LibHac/FsSystem/PooledBuffer.cs @@ -2,116 +2,115 @@ using System.Buffers; using LibHac.Diag; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +// Implement the PooledBuffer interface using .NET ArrayPools +public struct PooledBuffer : IDisposable { - // Implement the PooledBuffer interface using .NET ArrayPools - public struct PooledBuffer : IDisposable + // It's faster to create new smaller arrays than rent them + private const int RentThresholdBytes = 512; + + private const int HeapBlockSize = 1024 * 4; + + // Keep the max sizes that FS uses. + // A heap block is 4KB.An order is a power of two. + // This gives blocks of the order 512KB, 4MB. + private const int HeapOrderMax = 7; + private const int HeapOrderMaxForLarge = HeapOrderMax + 3; + + private const int HeapAllocatableSizeMax = HeapBlockSize * (1 << HeapOrderMax); + private const int HeapAllocatableSizeMaxForLarge = HeapBlockSize * (1 << HeapOrderMaxForLarge); + + private byte[] Array { get; set; } + private int Length { get; set; } + + public PooledBuffer(int idealSize, int requiredSize) { - // It's faster to create new smaller arrays than rent them - private const int RentThresholdBytes = 512; + Array = null; + Length = default; + Allocate(idealSize, requiredSize); + } - private const int HeapBlockSize = 1024 * 4; + public Span GetBuffer() + { + Assert.SdkRequiresNotNull(Array); + return Array.AsSpan(0, Length); + } - // Keep the max sizes that FS uses. - // A heap block is 4KB.An order is a power of two. - // This gives blocks of the order 512KB, 4MB. - private const int HeapOrderMax = 7; - private const int HeapOrderMaxForLarge = HeapOrderMax + 3; + public int GetSize() + { + Assert.SdkRequiresNotNull(Array); + return Length; + } - private const int HeapAllocatableSizeMax = HeapBlockSize * (1 << HeapOrderMax); - private const int HeapAllocatableSizeMaxForLarge = HeapBlockSize * (1 << HeapOrderMaxForLarge); + public static int GetAllocatableSizeMax() => GetAllocatableSizeMaxCore(false); + public static int GetAllocatableParticularlyLargeSizeMax => GetAllocatableSizeMaxCore(true); - private byte[] Array { get; set; } - private int Length { get; set; } + private static int GetAllocatableSizeMaxCore(bool enableLargeCapacity) + { + return enableLargeCapacity ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; + } - public PooledBuffer(int idealSize, int requiredSize) + public void Allocate(int idealSize, int requiredSize) => AllocateCore(idealSize, requiredSize, false); + public void AllocateParticularlyLarge(int idealSize, int requiredSize) => AllocateCore(idealSize, requiredSize, true); + + private void AllocateCore(int idealSize, int requiredSize, bool enableLargeCapacity) + { + Assert.SdkRequiresNull(Array); + + // Check that we can allocate this size. + Assert.SdkRequiresLessEqual(requiredSize, GetAllocatableSizeMaxCore(enableLargeCapacity)); + + int targetSize = Math.Min(Math.Max(idealSize, requiredSize), + GetAllocatableSizeMaxCore(enableLargeCapacity)); + + if (targetSize >= RentThresholdBytes) { - Array = null; - Length = default; - Allocate(idealSize, requiredSize); + Array = ArrayPool.Shared.Rent(targetSize); + } + else + { + Array = new byte[targetSize]; } - public Span GetBuffer() + Length = Array.Length; + } + + public void Deallocate() + { + // Shrink the buffer to empty. + Shrink(0); + Assert.SdkNull(Array); + } + + public void Shrink(int idealSize) + { + Assert.SdkRequiresLessEqual(idealSize, GetAllocatableSizeMaxCore(true)); + + // Check if we actually need to shrink. + if (Length > idealSize) { Assert.SdkRequiresNotNull(Array); - return Array.AsSpan(0, Length); - } - public int GetSize() - { - Assert.SdkRequiresNotNull(Array); - return Length; - } + // Pretend we shrank the buffer. + Length = idealSize; - public static int GetAllocatableSizeMax() => GetAllocatableSizeMaxCore(false); - public static int GetAllocatableParticularlyLargeSizeMax => GetAllocatableSizeMaxCore(true); - - private static int GetAllocatableSizeMaxCore(bool enableLargeCapacity) - { - return enableLargeCapacity ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; - } - - public void Allocate(int idealSize, int requiredSize) => AllocateCore(idealSize, requiredSize, false); - public void AllocateParticularlyLarge(int idealSize, int requiredSize) => AllocateCore(idealSize, requiredSize, true); - - private void AllocateCore(int idealSize, int requiredSize, bool enableLargeCapacity) - { - Assert.SdkRequiresNull(Array); - - // Check that we can allocate this size. - Assert.SdkRequiresLessEqual(requiredSize, GetAllocatableSizeMaxCore(enableLargeCapacity)); - - int targetSize = Math.Min(Math.Max(idealSize, requiredSize), - GetAllocatableSizeMaxCore(enableLargeCapacity)); - - if (targetSize >= RentThresholdBytes) + // Shrinking to zero means that we have no buffer. + if (Length == 0) { - Array = ArrayPool.Shared.Rent(targetSize); - } - else - { - Array = new byte[targetSize]; - } - - Length = Array.Length; - } - - public void Deallocate() - { - // Shrink the buffer to empty. - Shrink(0); - Assert.SdkNull(Array); - } - - public void Shrink(int idealSize) - { - Assert.SdkRequiresLessEqual(idealSize, GetAllocatableSizeMaxCore(true)); - - // Check if we actually need to shrink. - if (Length > idealSize) - { - Assert.SdkRequiresNotNull(Array); - - // Pretend we shrank the buffer. - Length = idealSize; - - // Shrinking to zero means that we have no buffer. - if (Length == 0) + // Return the array if we rented it. + if (Array?.Length >= RentThresholdBytes) { - // Return the array if we rented it. - if (Array?.Length >= RentThresholdBytes) - { - ArrayPool.Shared.Return(Array); - } - - Array = null; + ArrayPool.Shared.Return(Array); } - } - } - public void Dispose() - { - Deallocate(); + Array = null; + } } } + + public void Dispose() + { + Deallocate(); + } } diff --git a/src/LibHac/FsSystem/ReadOnlyFile.cs b/src/LibHac/FsSystem/ReadOnlyFile.cs index 80db3339..ab7a8030 100644 --- a/src/LibHac/FsSystem/ReadOnlyFile.cs +++ b/src/LibHac/FsSystem/ReadOnlyFile.cs @@ -3,53 +3,52 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ReadOnlyFile : IFile { - public class ReadOnlyFile : IFile + private UniqueRef _baseFile; + + public ReadOnlyFile(ref UniqueRef baseFile) { - private UniqueRef _baseFile; + _baseFile = new UniqueRef(ref baseFile); + } - public ReadOnlyFile(ref UniqueRef baseFile) - { - _baseFile = new UniqueRef(ref baseFile); - } + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + return _baseFile.Get.Read(out bytesRead, offset, destination, option); + } - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - return _baseFile.Get.Read(out bytesRead, offset, destination, option); - } + protected override Result DoGetSize(out long size) + { + return _baseFile.Get.GetSize(out size); + } - protected override Result DoGetSize(out long size) - { - return _baseFile.Get.GetSize(out size); - } + protected override Result DoFlush() + { + return Result.Success; + } - protected override Result DoFlush() - { - return Result.Success; - } + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return ResultFs.WriteUnpermitted.Log(); + } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return ResultFs.WriteUnpermitted.Log(); - } + protected override Result DoSetSize(long size) + { + return ResultFs.WriteUnpermitted.Log(); + } - protected override Result DoSetSize(long size) + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) { - return ResultFs.WriteUnpermitted.Log(); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - switch (operationId) - { - case OperationId.InvalidateCache: - case OperationId.QueryRange: - return _baseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - default: - return ResultFs.UnsupportedOperateRangeForReadOnlyFile.Log(); - } + case OperationId.InvalidateCache: + case OperationId.QueryRange: + return _baseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + default: + return ResultFs.UnsupportedOperateRangeForReadOnlyFile.Log(); } } } diff --git a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs index f81cd534..d868d559 100644 --- a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs +++ b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs @@ -3,86 +3,85 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class ReadOnlyFileSystem : IFileSystem { - public class ReadOnlyFileSystem : IFileSystem + private SharedRef _baseFileSystem; + + public ReadOnlyFileSystem(ref SharedRef baseFileSystem) { - private SharedRef _baseFileSystem; + _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - public ReadOnlyFileSystem(ref SharedRef baseFileSystem) - { - _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - - Assert.SdkRequires(_baseFileSystem.HasValue); - } - - public override void Dispose() - { - _baseFileSystem.Destroy(); - base.Dispose(); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - return _baseFileSystem.Get.OpenDirectory(ref outDirectory, in path, mode); - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var baseFile = new UniqueRef(); - Result rc = _baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); - if (rc.IsFailure()) return rc; - - outFile.Reset(new ReadOnlyFile(ref baseFile.Ref())); - return Result.Success; - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - return _baseFileSystem.Get.GetEntryType(out entryType, in path); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); - - // FS does: - // return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log(); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path); - - // FS does: - // return ResultFs.NotImplemented.Log(); - } - - protected override Result DoCommit() - { - return Result.Success; - } - - protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + Assert.SdkRequires(_baseFileSystem.HasValue); } + + public override void Dispose() + { + _baseFileSystem.Destroy(); + base.Dispose(); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + return _baseFileSystem.Get.OpenDirectory(ref outDirectory, in path, mode); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var baseFile = new UniqueRef(); + Result rc = _baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); + if (rc.IsFailure()) return rc; + + outFile.Reset(new ReadOnlyFile(ref baseFile.Ref())); + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + return _baseFileSystem.Get.GetEntryType(out entryType, in path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); + + // FS does: + // return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path); + + // FS does: + // return ResultFs.NotImplemented.Log(); + } + + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForReadOnlyFileSystem.Log(); } diff --git a/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs b/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs index 19a01057..76836755 100644 --- a/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs +++ b/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs @@ -4,385 +4,384 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +/// +/// Represents the file table used by the RomFS format. +/// +/// The type of the value to be stored for each file entry. +/// +/// This file table stores the structure of the file tree in a RomFS. +/// Each file or directory entry is stored in the table using its full path as a key. +/// Once added, a file or directory is assigned an ID that can also be used to retrieve it. +/// Each file entry contains a structure of type . +/// In a standard RomFS, this includes the size of the file and its offset in the RomFS. +/// Each directory entry contains the IDs for its first child file and first child directory. +/// +/// The table is represented by four byte arrays. Two of the arrays contain the hash buckets and +/// entries for the files, and the other two for the directories. +/// +/// Once all files have been added to the table, should be called +/// to optimize the size of the table. +/// +public class HierarchicalRomFileTable where T : unmanaged { + private RomFsDictionary FileTable { get; } + private RomFsDictionary DirectoryTable { get; } + /// - /// Represents the file table used by the RomFS format. + /// Initializes a from an existing table. /// - /// The type of the value to be stored for each file entry. - /// - /// This file table stores the structure of the file tree in a RomFS. - /// Each file or directory entry is stored in the table using its full path as a key. - /// Once added, a file or directory is assigned an ID that can also be used to retrieve it. - /// Each file entry contains a structure of type . - /// In a standard RomFS, this includes the size of the file and its offset in the RomFS. - /// Each directory entry contains the IDs for its first child file and first child directory. - /// - /// The table is represented by four byte arrays. Two of the arrays contain the hash buckets and - /// entries for the files, and the other two for the directories. - /// - /// Once all files have been added to the table, should be called - /// to optimize the size of the table. - /// - public class HierarchicalRomFileTable where T : unmanaged + /// + /// + /// + /// + public HierarchicalRomFileTable(IStorage dirHashTable, IStorage dirEntryTable, IStorage fileHashTable, + IStorage fileEntryTable) { - private RomFsDictionary FileTable { get; } - private RomFsDictionary DirectoryTable { get; } + FileTable = new RomFsDictionary(fileHashTable, fileEntryTable); + DirectoryTable = new RomFsDictionary(dirHashTable, dirEntryTable); + } - /// - /// Initializes a from an existing table. - /// - /// - /// - /// - /// - public HierarchicalRomFileTable(IStorage dirHashTable, IStorage dirEntryTable, IStorage fileHashTable, - IStorage fileEntryTable) + /// + /// Initializes a new that has the default initial capacity. + /// + public HierarchicalRomFileTable() : this(0, 0) { } + + /// + /// Initializes a new that has the specified initial capacity. + /// + /// The initial number of directories that the + /// can contain. + /// The initial number of files that the + /// can contain. + public HierarchicalRomFileTable(int directoryCapacity, int fileCapacity) + { + FileTable = new RomFsDictionary(fileCapacity); + DirectoryTable = new RomFsDictionary(directoryCapacity); + + CreateRootDirectory(); + } + + public byte[] GetDirectoryBuckets() + { + return MemoryMarshal.Cast(DirectoryTable.GetBucketData()).ToArray(); + } + + public byte[] GetDirectoryEntries() + { + return DirectoryTable.GetEntryData().ToArray(); + } + + public byte[] GetFileBuckets() + { + return MemoryMarshal.Cast(FileTable.GetBucketData()).ToArray(); + } + + public byte[] GetFileEntries() + { + return FileTable.GetEntryData().ToArray(); + } + + public bool TryOpenFile(string path, out T fileInfo) + { + FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); + + if (FileTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { - FileTable = new RomFsDictionary(fileHashTable, fileEntryTable); - DirectoryTable = new RomFsDictionary(dirHashTable, dirEntryTable); - } - - /// - /// Initializes a new that has the default initial capacity. - /// - public HierarchicalRomFileTable() : this(0, 0) { } - - /// - /// Initializes a new that has the specified initial capacity. - /// - /// The initial number of directories that the - /// can contain. - /// The initial number of files that the - /// can contain. - public HierarchicalRomFileTable(int directoryCapacity, int fileCapacity) - { - FileTable = new RomFsDictionary(fileCapacity); - DirectoryTable = new RomFsDictionary(directoryCapacity); - - CreateRootDirectory(); - } - - public byte[] GetDirectoryBuckets() - { - return MemoryMarshal.Cast(DirectoryTable.GetBucketData()).ToArray(); - } - - public byte[] GetDirectoryEntries() - { - return DirectoryTable.GetEntryData().ToArray(); - } - - public byte[] GetFileBuckets() - { - return MemoryMarshal.Cast(FileTable.GetBucketData()).ToArray(); - } - - public byte[] GetFileEntries() - { - return FileTable.GetEntryData().ToArray(); - } - - public bool TryOpenFile(string path, out T fileInfo) - { - FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); - - if (FileTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) - { - fileInfo = keyValuePair.Value.Info; - return true; - } - - UnsafeHelpers.SkipParamInit(out fileInfo); - return false; - } - - public bool TryOpenFile(int fileId, out T fileInfo) - { - if (FileTable.TryGetValue(fileId, out RomKeyValuePair keyValuePair)) - { - fileInfo = keyValuePair.Value.Info; - return true; - } - - UnsafeHelpers.SkipParamInit(out fileInfo); - return false; - } - - /// - /// Opens a directory for enumeration. - /// - /// The full path of the directory to open. - /// The initial position of the directory enumerator. - /// if the table contains a directory with the specified path; - /// otherwise, . - public bool TryOpenDirectory(string path, out FindPosition position) - { - FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); - - if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) - { - position = keyValuePair.Value.Pos; - return true; - } - - UnsafeHelpers.SkipParamInit(out position); - return false; - } - - /// - /// Opens a directory for enumeration. - /// - /// The ID of the directory to open. - /// When this method returns, contains the initial position of the directory enumerator. - /// if the table contains a directory with the specified path; - /// otherwise, . - public bool TryOpenDirectory(int directoryId, out FindPosition position) - { - if (DirectoryTable.TryGetValue(directoryId, out RomKeyValuePair keyValuePair)) - { - position = keyValuePair.Value.Pos; - return true; - } - - UnsafeHelpers.SkipParamInit(out position); - return false; - } - - /// - /// Returns the next file in a directory and updates the enumerator's position. - /// - /// The current position of the directory enumerator. - /// This position will be updated when the method returns. - /// When this method returns, contains the file's metadata. - /// When this method returns, contains the file's name (Not the full path). - /// if the next file was successfully returned. - /// if there are no more files to enumerate. - public bool FindNextFile(ref FindPosition position, out T info, out string name) - { - if (position.NextFile == -1) - { - UnsafeHelpers.SkipParamInit(out info, out name); - return false; - } - - ref FileRomEntry entry = ref FileTable.GetValueReference(position.NextFile, out Span nameBytes); - position.NextFile = entry.NextSibling; - info = entry.Info; - - name = StringUtils.Utf8ToString(nameBytes); - + fileInfo = keyValuePair.Value.Info; return true; } - /// - /// Returns the next child directory in a directory and updates the enumerator's position. - /// - /// The current position of the directory enumerator. - /// This position will be updated when the method returns. - /// When this method returns, contains the directory's name (Not the full path). - /// if the next directory was successfully returned. - /// if there are no more directories to enumerate. - public bool FindNextDirectory(ref FindPosition position, out string name) + UnsafeHelpers.SkipParamInit(out fileInfo); + return false; + } + + public bool TryOpenFile(int fileId, out T fileInfo) + { + if (FileTable.TryGetValue(fileId, out RomKeyValuePair keyValuePair)) { - if (position.NextDirectory == -1) - { - UnsafeHelpers.SkipParamInit(out name); - return false; - } - - ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span nameBytes); - position.NextDirectory = entry.NextSibling; - - name = StringUtils.Utf8ToString(nameBytes); - + fileInfo = keyValuePair.Value.Info; return true; } - /// - /// Adds a file to the file table. If the file already exists - /// its will be updated. - /// - /// The full path of the file to be added. - /// The file information to be stored. - public void AddFile(string path, ref T fileInfo) + UnsafeHelpers.SkipParamInit(out fileInfo); + return false; + } + + /// + /// Opens a directory for enumeration. + /// + /// The full path of the directory to open. + /// The initial position of the directory enumerator. + /// if the table contains a directory with the specified path; + /// otherwise, . + public bool TryOpenDirectory(string path, out FindPosition position) + { + FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); + + if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { - path = PathTools.Normalize(path); - ReadOnlySpan pathBytes = StringUtils.StringToUtf8(path); - - if (path == "/") throw new ArgumentException("Path cannot be empty"); - - CreateFileRecursiveInternal(pathBytes, ref fileInfo); + position = keyValuePair.Value.Pos; + return true; } - /// - /// Adds a directory to the file table. If the directory already exists, - /// no action is performed. - /// - /// The full path of the directory to be added. - public void AddDirectory(string path) - { - path = PathTools.Normalize(path); + UnsafeHelpers.SkipParamInit(out position); + return false; + } - CreateDirectoryRecursive(StringUtils.StringToUtf8(path)); + /// + /// Opens a directory for enumeration. + /// + /// The ID of the directory to open. + /// When this method returns, contains the initial position of the directory enumerator. + /// if the table contains a directory with the specified path; + /// otherwise, . + public bool TryOpenDirectory(int directoryId, out FindPosition position) + { + if (DirectoryTable.TryGetValue(directoryId, out RomKeyValuePair keyValuePair)) + { + position = keyValuePair.Value.Pos; + return true; } - /// - /// Sets the capacity of this dictionary to what it would be if - /// it had been originally initialized with all its entries. - /// - /// This method can be used to minimize the memory overhead - /// once it is known that no new elements will be added. - /// - public void TrimExcess() + UnsafeHelpers.SkipParamInit(out position); + return false; + } + + /// + /// Returns the next file in a directory and updates the enumerator's position. + /// + /// The current position of the directory enumerator. + /// This position will be updated when the method returns. + /// When this method returns, contains the file's metadata. + /// When this method returns, contains the file's name (Not the full path). + /// if the next file was successfully returned. + /// if there are no more files to enumerate. + public bool FindNextFile(ref FindPosition position, out T info, out string name) + { + if (position.NextFile == -1) { - DirectoryTable.TrimExcess(); - FileTable.TrimExcess(); + UnsafeHelpers.SkipParamInit(out info, out name); + return false; } - private void CreateRootDirectory() - { - var key = new RomEntryKey(ReadOnlySpan.Empty, 0); - var entry = new DirectoryRomEntry(); - entry.NextSibling = -1; - entry.Pos.NextDirectory = -1; - entry.Pos.NextFile = -1; + ref FileRomEntry entry = ref FileTable.GetValueReference(position.NextFile, out Span nameBytes); + position.NextFile = entry.NextSibling; + info = entry.Info; - DirectoryTable.Add(ref key, ref entry); + name = StringUtils.Utf8ToString(nameBytes); + + return true; + } + + /// + /// Returns the next child directory in a directory and updates the enumerator's position. + /// + /// The current position of the directory enumerator. + /// This position will be updated when the method returns. + /// When this method returns, contains the directory's name (Not the full path). + /// if the next directory was successfully returned. + /// if there are no more directories to enumerate. + public bool FindNextDirectory(ref FindPosition position, out string name) + { + if (position.NextDirectory == -1) + { + UnsafeHelpers.SkipParamInit(out name); + return false; } - private void CreateDirectoryRecursive(ReadOnlySpan path) + ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span nameBytes); + position.NextDirectory = entry.NextSibling; + + name = StringUtils.Utf8ToString(nameBytes); + + return true; + } + + /// + /// Adds a file to the file table. If the file already exists + /// its will be updated. + /// + /// The full path of the file to be added. + /// The file information to be stored. + public void AddFile(string path, ref T fileInfo) + { + path = PathTools.Normalize(path); + ReadOnlySpan pathBytes = StringUtils.StringToUtf8(path); + + if (path == "/") throw new ArgumentException("Path cannot be empty"); + + CreateFileRecursiveInternal(pathBytes, ref fileInfo); + } + + /// + /// Adds a directory to the file table. If the directory already exists, + /// no action is performed. + /// + /// The full path of the directory to be added. + public void AddDirectory(string path) + { + path = PathTools.Normalize(path); + + CreateDirectoryRecursive(StringUtils.StringToUtf8(path)); + } + + /// + /// Sets the capacity of this dictionary to what it would be if + /// it had been originally initialized with all its entries. + /// + /// This method can be used to minimize the memory overhead + /// once it is known that no new elements will be added. + /// + public void TrimExcess() + { + DirectoryTable.TrimExcess(); + FileTable.TrimExcess(); + } + + private void CreateRootDirectory() + { + var key = new RomEntryKey(ReadOnlySpan.Empty, 0); + var entry = new DirectoryRomEntry(); + entry.NextSibling = -1; + entry.Pos.NextDirectory = -1; + entry.Pos.NextFile = -1; + + DirectoryTable.Add(ref key, ref entry); + } + + private void CreateDirectoryRecursive(ReadOnlySpan path) + { + var parser = new PathParser(path); + var key = new RomEntryKey(); + + int prevOffset = 0; + + while (parser.TryGetNext(out key.Name)) { - var parser = new PathParser(path); - var key = new RomEntryKey(); - - int prevOffset = 0; - - while (parser.TryGetNext(out key.Name)) + int offset = DirectoryTable.GetOffsetFromKey(ref key); + if (offset < 0) { - int offset = DirectoryTable.GetOffsetFromKey(ref key); - if (offset < 0) - { - ref DirectoryRomEntry entry = ref DirectoryTable.AddOrGet(ref key, out offset, out _, out _); - entry.NextSibling = -1; - entry.Pos.NextDirectory = -1; - entry.Pos.NextFile = -1; - - ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); - - if (parent.Pos.NextDirectory == -1) - { - parent.Pos.NextDirectory = offset; - } - else - { - ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); - - while (chain.NextSibling != -1) - { - chain = ref DirectoryTable.GetValueReference(chain.NextSibling); - } - - chain.NextSibling = offset; - } - } - - prevOffset = offset; - key.Parent = offset; - } - } - - private void CreateFileRecursiveInternal(ReadOnlySpan path, ref T fileInfo) - { - var parser = new PathParser(path); - var key = new RomEntryKey(); - - parser.TryGetNext(out key.Name); - int prevOffset = 0; - - while (!parser.IsFinished()) - { - int offset = DirectoryTable.GetOffsetFromKey(ref key); - if (offset < 0) - { - ref DirectoryRomEntry entry = ref DirectoryTable.AddOrGet(ref key, out offset, out _, out _); - entry.NextSibling = -1; - entry.Pos.NextDirectory = -1; - entry.Pos.NextFile = -1; - - ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); - - if (parent.Pos.NextDirectory == -1) - { - parent.Pos.NextDirectory = offset; - } - else - { - ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); - - while (chain.NextSibling != -1) - { - chain = ref DirectoryTable.GetValueReference(chain.NextSibling); - } - - chain.NextSibling = offset; - } - } - - prevOffset = offset; - key.Parent = offset; - parser.TryGetNext(out key.Name); - } - - { - ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out bool alreadyExists, out _); - entry.Info = fileInfo; - if (!alreadyExists) entry.NextSibling = -1; + ref DirectoryRomEntry entry = ref DirectoryTable.AddOrGet(ref key, out offset, out _, out _); + entry.NextSibling = -1; + entry.Pos.NextDirectory = -1; + entry.Pos.NextFile = -1; ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); - if (parent.Pos.NextFile == -1) + if (parent.Pos.NextDirectory == -1) { - parent.Pos.NextFile = offset; + parent.Pos.NextDirectory = offset; } else { - ref FileRomEntry chain = ref FileTable.GetValueReference(parent.Pos.NextFile); + ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); while (chain.NextSibling != -1) { - chain = ref FileTable.GetValueReference(chain.NextSibling); + chain = ref DirectoryTable.GetValueReference(chain.NextSibling); } chain.NextSibling = offset; } } - } - private void FindPathRecursive(ReadOnlySpan path, out RomEntryKey key) - { - var parser = new PathParser(path); - key = new RomEntryKey(parser.GetCurrent(), 0); - - while (!parser.IsFinished()) - { - key.Parent = DirectoryTable.GetOffsetFromKey(ref key); - parser.TryGetNext(out key.Name); - } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - private struct DirectoryRomEntry - { - public int NextSibling; - public FindPosition Pos; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - private struct FileRomEntry - { - public int NextSibling; - public T Info; + prevOffset = offset; + key.Parent = offset; } } + + private void CreateFileRecursiveInternal(ReadOnlySpan path, ref T fileInfo) + { + var parser = new PathParser(path); + var key = new RomEntryKey(); + + parser.TryGetNext(out key.Name); + int prevOffset = 0; + + while (!parser.IsFinished()) + { + int offset = DirectoryTable.GetOffsetFromKey(ref key); + if (offset < 0) + { + ref DirectoryRomEntry entry = ref DirectoryTable.AddOrGet(ref key, out offset, out _, out _); + entry.NextSibling = -1; + entry.Pos.NextDirectory = -1; + entry.Pos.NextFile = -1; + + ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); + + if (parent.Pos.NextDirectory == -1) + { + parent.Pos.NextDirectory = offset; + } + else + { + ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); + + while (chain.NextSibling != -1) + { + chain = ref DirectoryTable.GetValueReference(chain.NextSibling); + } + + chain.NextSibling = offset; + } + } + + prevOffset = offset; + key.Parent = offset; + parser.TryGetNext(out key.Name); + } + + { + ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out bool alreadyExists, out _); + entry.Info = fileInfo; + if (!alreadyExists) entry.NextSibling = -1; + + ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); + + if (parent.Pos.NextFile == -1) + { + parent.Pos.NextFile = offset; + } + else + { + ref FileRomEntry chain = ref FileTable.GetValueReference(parent.Pos.NextFile); + + while (chain.NextSibling != -1) + { + chain = ref FileTable.GetValueReference(chain.NextSibling); + } + + chain.NextSibling = offset; + } + } + } + + private void FindPathRecursive(ReadOnlySpan path, out RomEntryKey key) + { + var parser = new PathParser(path); + key = new RomEntryKey(parser.GetCurrent(), 0); + + while (!parser.IsFinished()) + { + key.Parent = DirectoryTable.GetOffsetFromKey(ref key); + parser.TryGetNext(out key.Name); + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct DirectoryRomEntry + { + public int NextSibling; + public FindPosition Pos; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct FileRomEntry + { + public int NextSibling; + public T Info; + } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsBuilder.cs b/src/LibHac/FsSystem/RomFs/RomFsBuilder.cs index a4f5bb66..75f2499b 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsBuilder.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsBuilder.cs @@ -7,111 +7,110 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +/// +/// Builds a RomFS from a collection of files. +/// +/// A produces a view of a RomFS archive. +/// When doing so, it will create an instance that will +/// provide the RomFS data when read. Random seek is supported. +public class RomFsBuilder { + private const int FileAlignment = 0x10; + private const int HeaderSize = 0x50; + private const int HeaderWithPaddingSize = 0x200; + + private List Sources { get; } = new List(); + private HierarchicalRomFileTable FileTable { get; } = new HierarchicalRomFileTable(); + private long CurrentOffset { get; set; } + /// - /// Builds a RomFS from a collection of files. + /// Creates a new, empty /// - /// A produces a view of a RomFS archive. - /// When doing so, it will create an instance that will - /// provide the RomFS data when read. Random seek is supported. - public class RomFsBuilder + public RomFsBuilder() { } + + /// + /// Creates a new and populates it with all + /// the files in the specified . + /// + public RomFsBuilder(IFileSystem input) { - private const int FileAlignment = 0x10; - private const int HeaderSize = 0x50; - private const int HeaderWithPaddingSize = 0x200; - - private List Sources { get; } = new List(); - private HierarchicalRomFileTable FileTable { get; } = new HierarchicalRomFileTable(); - private long CurrentOffset { get; set; } - - /// - /// Creates a new, empty - /// - public RomFsBuilder() { } - - /// - /// Creates a new and populates it with all - /// the files in the specified . - /// - public RomFsBuilder(IFileSystem input) + foreach (DirectoryEntryEx entry in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File) + .OrderBy(x => x.FullPath, StringComparer.Ordinal)) { - foreach (DirectoryEntryEx entry in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File) - .OrderBy(x => x.FullPath, StringComparer.Ordinal)) - { - using var file = new UniqueRef(); - input.OpenFile(ref file.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + using var file = new UniqueRef(); + input.OpenFile(ref file.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - AddFile(entry.FullPath, file.Release()); - } + AddFile(entry.FullPath, file.Release()); } + } - /// - /// Adds a file to the RomFS. - /// - /// The full path in the RomFS - /// An of the file data to add. - public void AddFile(string path, IFile file) + /// + /// Adds a file to the RomFS. + /// + /// The full path in the RomFS + /// An of the file data to add. + public void AddFile(string path, IFile file) + { + var fileInfo = new RomFileInfo(); + file.GetSize(out long fileSize).ThrowIfFailure(); + + fileInfo.Offset = CurrentOffset; + fileInfo.Length = fileSize; + + IStorage fileStorage = file.AsStorage(); + Sources.Add(fileStorage); + + long newOffset = CurrentOffset + fileSize; + CurrentOffset = Alignment.AlignUp(newOffset, FileAlignment); + + var padding = new NullStorage(CurrentOffset - newOffset); + Sources.Add(padding); + + FileTable.AddFile(path, ref fileInfo); + } + + /// + /// Returns a view of a RomFS containing all the currently added files. + /// Additional files may be added and a new view produced without + /// invalidating previously built RomFS views. + /// + /// + public IStorage Build() + { + FileTable.TrimExcess(); + + byte[] header = new byte[HeaderWithPaddingSize]; + var headerWriter = new BinaryWriter(new MemoryStream(header)); + + var sources = new List(); + sources.Add(new MemoryStorage(header)); + sources.AddRange(Sources); + + long fileLength = sources.Sum(x => { - var fileInfo = new RomFileInfo(); - file.GetSize(out long fileSize).ThrowIfFailure(); + x.GetSize(out long fileSize).ThrowIfFailure(); + return fileSize; + }); - fileInfo.Offset = CurrentOffset; - fileInfo.Length = fileSize; + headerWriter.Write((long)HeaderSize); - IStorage fileStorage = file.AsStorage(); - Sources.Add(fileStorage); + AddTable(FileTable.GetDirectoryBuckets()); + AddTable(FileTable.GetDirectoryEntries()); + AddTable(FileTable.GetFileBuckets()); + AddTable(FileTable.GetFileEntries()); - long newOffset = CurrentOffset + fileSize; - CurrentOffset = Alignment.AlignUp(newOffset, FileAlignment); + headerWriter.Write((long)HeaderWithPaddingSize); - var padding = new NullStorage(CurrentOffset - newOffset); - Sources.Add(padding); + return new ConcatenationStorage(sources, true); - FileTable.AddFile(path, ref fileInfo); - } - - /// - /// Returns a view of a RomFS containing all the currently added files. - /// Additional files may be added and a new view produced without - /// invalidating previously built RomFS views. - /// - /// - public IStorage Build() + void AddTable(byte[] table) { - FileTable.TrimExcess(); - - byte[] header = new byte[HeaderWithPaddingSize]; - var headerWriter = new BinaryWriter(new MemoryStream(header)); - - var sources = new List(); - sources.Add(new MemoryStorage(header)); - sources.AddRange(Sources); - - long fileLength = sources.Sum(x => - { - x.GetSize(out long fileSize).ThrowIfFailure(); - return fileSize; - }); - - headerWriter.Write((long)HeaderSize); - - AddTable(FileTable.GetDirectoryBuckets()); - AddTable(FileTable.GetDirectoryEntries()); - AddTable(FileTable.GetFileBuckets()); - AddTable(FileTable.GetFileEntries()); - - headerWriter.Write((long)HeaderWithPaddingSize); - - return new ConcatenationStorage(sources, true); - - void AddTable(byte[] table) - { - sources.Add(new MemoryStorage(table)); - headerWriter.Write(fileLength); - headerWriter.Write((long)table.Length); - fileLength += table.Length; - } + sources.Add(new MemoryStorage(table)); + headerWriter.Write(fileLength); + headerWriter.Write((long)table.Length); + fileLength += table.Length; } } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsDictionary.cs b/src/LibHac/FsSystem/RomFs/RomFsDictionary.cs index ed4ebefc..93543799 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsDictionary.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsDictionary.cs @@ -5,296 +5,295 @@ using System.Runtime.InteropServices; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +internal class RomFsDictionary where T : unmanaged { - internal class RomFsDictionary where T : unmanaged + private int _count; + private int _length; + private int _capacity; + + private int[] Buckets { get; set; } + private byte[] Entries { get; set; } + + private readonly int _sizeOfEntry = Unsafe.SizeOf(); + + public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage) { - private int _count; - private int _length; - private int _capacity; + Buckets = bucketStorage.ToArray(); + Entries = entryStorage.ToArray(); - private int[] Buckets { get; set; } - private byte[] Entries { get; set; } + _length = Entries.Length; + _capacity = Entries.Length; - private readonly int _sizeOfEntry = Unsafe.SizeOf(); + _count = CountEntries(); + } - public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage) + public RomFsDictionary() : this(0) { } + + public RomFsDictionary(int capacity) + { + int size = HashHelpers.GetRomFsPrime(capacity); + + Buckets = new int[size]; + Buckets.AsSpan().Fill(-1); + Entries = new byte[EstimateEntryTableSize(size)]; + _capacity = Entries.Length; + } + + public ReadOnlySpan GetBucketData() => Buckets.AsSpan(); + public ReadOnlySpan GetEntryData() => Entries.AsSpan(0, _length); + + public bool TryGetValue(ref RomEntryKey key, out RomKeyValuePair value) + { + return TryGetValue(GetOffsetFromKey(ref key), out value); + } + + public bool TryGetValue(int offset, out RomKeyValuePair value) + { + if (offset < 0 || offset + _sizeOfEntry > Entries.Length) { - Buckets = bucketStorage.ToArray(); - Entries = entryStorage.ToArray(); - - _length = Entries.Length; - _capacity = Entries.Length; - - _count = CountEntries(); + value = default; + return false; } - public RomFsDictionary() : this(0) { } + value = new RomKeyValuePair(); - public RomFsDictionary(int capacity) + ref RomFsEntry entry = ref GetEntryReference(offset, out Span name); + + value.Key.Name = name; + value.Value = entry.Value; + value.Key.Parent = entry.Parent; + value.Offset = offset; + return true; + } + + public ref T GetValueReference(int offset) + { + return ref Unsafe.As(ref Entries[offset]).Value; + } + + public ref T GetValueReference(int offset, out Span name) + { + ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); + + name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength); + return ref entry.Value; + } + + public bool ContainsKey(ref RomEntryKey key) => GetOffsetFromKey(ref key) >= 0; + + public int Add(ref RomEntryKey key, ref T value) + { + ref T entry = ref AddOrGet(ref key, out int offset, out bool alreadyExists, out _); + + if (alreadyExists) { - int size = HashHelpers.GetRomFsPrime(capacity); - - Buckets = new int[size]; - Buckets.AsSpan().Fill(-1); - Entries = new byte[EstimateEntryTableSize(size)]; - _capacity = Entries.Length; + throw new ArgumentException("Key already exists in dictionary."); } - public ReadOnlySpan GetBucketData() => Buckets.AsSpan(); - public ReadOnlySpan GetEntryData() => Entries.AsSpan(0, _length); + entry = value; - public bool TryGetValue(ref RomEntryKey key, out RomKeyValuePair value) + return offset; + } + + public ref T AddOrGet(ref RomEntryKey key, out int offset, out bool alreadyExists, out Span name) + { + int oldOffset = GetOffsetFromKey(ref key); + + if (oldOffset >= 0) { - return TryGetValue(GetOffsetFromKey(ref key), out value); - } + alreadyExists = true; + offset = oldOffset; - public bool TryGetValue(int offset, out RomKeyValuePair value) - { - if (offset < 0 || offset + _sizeOfEntry > Entries.Length) - { - value = default; - return false; - } - - value = new RomKeyValuePair(); - - ref RomFsEntry entry = ref GetEntryReference(offset, out Span name); - - value.Key.Name = name; - value.Value = entry.Value; - value.Key.Parent = entry.Parent; - value.Offset = offset; - return true; - } - - public ref T GetValueReference(int offset) - { - return ref Unsafe.As(ref Entries[offset]).Value; - } - - public ref T GetValueReference(int offset, out Span name) - { - ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); - - name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength); + ref RomFsEntry entry = ref GetEntryReference(oldOffset, out name); return ref entry.Value; } - - public bool ContainsKey(ref RomEntryKey key) => GetOffsetFromKey(ref key) >= 0; - - public int Add(ref RomEntryKey key, ref T value) + else { - ref T entry = ref AddOrGet(ref key, out int offset, out bool alreadyExists, out _); + int newOffset = CreateNewEntry(key.Name.Length); + alreadyExists = false; + offset = newOffset; - if (alreadyExists) + ref RomFsEntry entry = ref GetEntryReference(newOffset, out name, key.Name.Length); + + entry.Parent = key.Parent; + entry.KeyLength = key.Name.Length; + key.Name.CopyTo(name); + + int bucket = (int)(key.GetRomHashCode() % Buckets.Length); + + entry.Next = Buckets[bucket]; + Buckets[bucket] = newOffset; + _count++; + + if (Buckets.Length < _count) { - throw new ArgumentException("Key already exists in dictionary."); + Resize(); + entry = ref GetEntryReference(newOffset, out name, key.Name.Length); } - entry = value; - - return offset; - } - - public ref T AddOrGet(ref RomEntryKey key, out int offset, out bool alreadyExists, out Span name) - { - int oldOffset = GetOffsetFromKey(ref key); - - if (oldOffset >= 0) - { - alreadyExists = true; - offset = oldOffset; - - ref RomFsEntry entry = ref GetEntryReference(oldOffset, out name); - return ref entry.Value; - } - else - { - int newOffset = CreateNewEntry(key.Name.Length); - alreadyExists = false; - offset = newOffset; - - ref RomFsEntry entry = ref GetEntryReference(newOffset, out name, key.Name.Length); - - entry.Parent = key.Parent; - entry.KeyLength = key.Name.Length; - key.Name.CopyTo(name); - - int bucket = (int)(key.GetRomHashCode() % Buckets.Length); - - entry.Next = Buckets[bucket]; - Buckets[bucket] = newOffset; - _count++; - - if (Buckets.Length < _count) - { - Resize(); - entry = ref GetEntryReference(newOffset, out name, key.Name.Length); - } - - return ref entry.Value; - } - } - - private int CreateNewEntry(int nameLength) - { - int bytesNeeded = Alignment.AlignUp(_sizeOfEntry + nameLength, 4); - - if (_length + bytesNeeded > _capacity) - { - EnsureCapacityBytes(_length + bytesNeeded); - } - - int offset = _length; - _length += bytesNeeded; - - return offset; - } - - public int GetOffsetFromKey(ref RomEntryKey key) - { - uint hashCode = key.GetRomHashCode(); - int index = (int)(hashCode % Buckets.Length); - int i = Buckets[index]; - - while (i != -1) - { - ref RomFsEntry entry = ref GetEntryReference(i, out Span name); - - if (key.Parent == entry.Parent && key.Name.SequenceEqual(name)) - { - break; - } - - i = entry.Next; - } - - return i; - } - - private void EnsureCapacityBytes(int value) - { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); - if (value <= _capacity) return; - - long newCapacity = Math.Max(value, 256); - newCapacity = Math.Max(newCapacity, _capacity * 2); - - SetCapacity((int)Math.Min(newCapacity, int.MaxValue)); - } - - private void SetCapacity(int value) - { - if (value < _length) - throw new ArgumentOutOfRangeException(nameof(value), "Capacity is smaller than the current length."); - - if (value != _capacity) - { - byte[] newBuffer = new byte[value]; - System.Buffer.BlockCopy(Entries, 0, newBuffer, 0, _length); - - Entries = newBuffer; - _capacity = value; - } - } - - private int CountEntries() - { - int count = 0; - int nextStructOffset = (sizeof(int) + Unsafe.SizeOf()) / 4; - Span data = MemoryMarshal.Cast(Entries.AsSpan()); - - for (int i = 0; i < Buckets.Length; i++) - { - int next = Buckets[i]; - - while (next != -1) - { - next = data[next / 4 + nextStructOffset]; - count++; - } - } - - return count; - } - - private void Resize() => Resize(HashHelpers.ExpandPrime(_count)); - - private void Resize(int newSize) - { - int[] newBuckets = new int[newSize]; - newBuckets.AsSpan().Fill(-1); - - List offsets = GetEntryOffsets(); - - for (int i = 0; i < offsets.Count; i++) - { - ref RomFsEntry entry = ref GetEntryReference(offsets[i], out Span name); - - uint hashCode = RomEntryKey.GetRomHashCode(entry.Parent, name); - int bucket = (int)(hashCode % newSize); - - entry.Next = newBuckets[bucket]; - newBuckets[bucket] = offsets[i]; - } - - Buckets = newBuckets; - } - - public void TrimExcess() - { - Resize(HashHelpers.GetRomFsPrime(_count)); - SetCapacity(_length); - } - - private ref RomFsEntry GetEntryReference(int offset, out Span name) - { - ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); - - name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength); - return ref entry; - } - - private ref RomFsEntry GetEntryReference(int offset, out Span name, int nameLength) - { - ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); - - name = Entries.AsSpan(offset + _sizeOfEntry, nameLength); - return ref entry; - } - - private List GetEntryOffsets() - { - var offsets = new List(_count); - - int nextStructOffset = (sizeof(int) + Unsafe.SizeOf()) / 4; - Span data = MemoryMarshal.Cast(Entries.AsSpan()); - - for (int i = 0; i < Buckets.Length; i++) - { - int next = Buckets[i]; - - while (next != -1) - { - offsets.Add(next); - next = data[next / 4 + nextStructOffset]; - } - } - - offsets.Sort(); - return offsets; - } - - private int EstimateEntryTableSize(int count) => (_sizeOfEntry + 0x10) * count; // Estimate 0x10 bytes per name - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - private struct RomFsEntry - { - public int Parent; - public T Value; - public int Next; - public int KeyLength; + return ref entry.Value; } } + + private int CreateNewEntry(int nameLength) + { + int bytesNeeded = Alignment.AlignUp(_sizeOfEntry + nameLength, 4); + + if (_length + bytesNeeded > _capacity) + { + EnsureCapacityBytes(_length + bytesNeeded); + } + + int offset = _length; + _length += bytesNeeded; + + return offset; + } + + public int GetOffsetFromKey(ref RomEntryKey key) + { + uint hashCode = key.GetRomHashCode(); + int index = (int)(hashCode % Buckets.Length); + int i = Buckets[index]; + + while (i != -1) + { + ref RomFsEntry entry = ref GetEntryReference(i, out Span name); + + if (key.Parent == entry.Parent && key.Name.SequenceEqual(name)) + { + break; + } + + i = entry.Next; + } + + return i; + } + + private void EnsureCapacityBytes(int value) + { + if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); + if (value <= _capacity) return; + + long newCapacity = Math.Max(value, 256); + newCapacity = Math.Max(newCapacity, _capacity * 2); + + SetCapacity((int)Math.Min(newCapacity, int.MaxValue)); + } + + private void SetCapacity(int value) + { + if (value < _length) + throw new ArgumentOutOfRangeException(nameof(value), "Capacity is smaller than the current length."); + + if (value != _capacity) + { + byte[] newBuffer = new byte[value]; + System.Buffer.BlockCopy(Entries, 0, newBuffer, 0, _length); + + Entries = newBuffer; + _capacity = value; + } + } + + private int CountEntries() + { + int count = 0; + int nextStructOffset = (sizeof(int) + Unsafe.SizeOf()) / 4; + Span data = MemoryMarshal.Cast(Entries.AsSpan()); + + for (int i = 0; i < Buckets.Length; i++) + { + int next = Buckets[i]; + + while (next != -1) + { + next = data[next / 4 + nextStructOffset]; + count++; + } + } + + return count; + } + + private void Resize() => Resize(HashHelpers.ExpandPrime(_count)); + + private void Resize(int newSize) + { + int[] newBuckets = new int[newSize]; + newBuckets.AsSpan().Fill(-1); + + List offsets = GetEntryOffsets(); + + for (int i = 0; i < offsets.Count; i++) + { + ref RomFsEntry entry = ref GetEntryReference(offsets[i], out Span name); + + uint hashCode = RomEntryKey.GetRomHashCode(entry.Parent, name); + int bucket = (int)(hashCode % newSize); + + entry.Next = newBuckets[bucket]; + newBuckets[bucket] = offsets[i]; + } + + Buckets = newBuckets; + } + + public void TrimExcess() + { + Resize(HashHelpers.GetRomFsPrime(_count)); + SetCapacity(_length); + } + + private ref RomFsEntry GetEntryReference(int offset, out Span name) + { + ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); + + name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength); + return ref entry; + } + + private ref RomFsEntry GetEntryReference(int offset, out Span name, int nameLength) + { + ref RomFsEntry entry = ref Unsafe.As(ref Entries[offset]); + + name = Entries.AsSpan(offset + _sizeOfEntry, nameLength); + return ref entry; + } + + private List GetEntryOffsets() + { + var offsets = new List(_count); + + int nextStructOffset = (sizeof(int) + Unsafe.SizeOf()) / 4; + Span data = MemoryMarshal.Cast(Entries.AsSpan()); + + for (int i = 0; i < Buckets.Length; i++) + { + int next = Buckets[i]; + + while (next != -1) + { + offsets.Add(next); + next = data[next / 4 + nextStructOffset]; + } + } + + offsets.Sort(); + return offsets; + } + + private int EstimateEntryTableSize(int count) => (_sizeOfEntry + 0x10) * count; // Estimate 0x10 bytes per name + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct RomFsEntry + { + public int Parent; + public T Value; + public int Next; + public int KeyLength; + } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs b/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs index 635d5047..a6658638 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs @@ -4,86 +4,85 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +public class RomFsDirectory : IDirectory { - public class RomFsDirectory : IDirectory + private RomFsFileSystem ParentFileSystem { get; } + + private OpenDirectoryMode Mode { get; } + + private FindPosition InitialPosition { get; } + private FindPosition _currentPosition; + + public RomFsDirectory(RomFsFileSystem fs, FindPosition position, OpenDirectoryMode mode) { - private RomFsFileSystem ParentFileSystem { get; } + ParentFileSystem = fs; + InitialPosition = position; + _currentPosition = position; + Mode = mode; + } - private OpenDirectoryMode Mode { get; } + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer); + } - private FindPosition InitialPosition { get; } - private FindPosition _currentPosition; + protected override Result DoGetEntryCount(out long entryCount) + { + FindPosition position = InitialPosition; - public RomFsDirectory(RomFsFileSystem fs, FindPosition position, OpenDirectoryMode mode) + return ReadImpl(out entryCount, ref position, Span.Empty); + } + + private Result ReadImpl(out long entriesRead, ref FindPosition position, Span entryBuffer) + { + HierarchicalRomFileTable tab = ParentFileSystem.FileTable; + + int i = 0; + + if (Mode.HasFlag(OpenDirectoryMode.Directory)) { - ParentFileSystem = fs; - InitialPosition = position; - _currentPosition = position; - Mode = mode; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer); - } - - protected override Result DoGetEntryCount(out long entryCount) - { - FindPosition position = InitialPosition; - - return ReadImpl(out entryCount, ref position, Span.Empty); - } - - private Result ReadImpl(out long entriesRead, ref FindPosition position, Span entryBuffer) - { - HierarchicalRomFileTable tab = ParentFileSystem.FileTable; - - int i = 0; - - if (Mode.HasFlag(OpenDirectoryMode.Directory)) + while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name)) { - while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name)) + if (!entryBuffer.IsEmpty) { - if (!entryBuffer.IsEmpty) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - Span nameUtf8 = Encoding.UTF8.GetBytes(name); + ref DirectoryEntry entry = ref entryBuffer[i]; + Span nameUtf8 = Encoding.UTF8.GetBytes(name); - StringUtils.Copy(entry.Name, nameUtf8); - entry.Name[PathTools.MaxPathLength] = 0; + StringUtils.Copy(entry.Name, nameUtf8); + entry.Name[PathTools.MaxPathLength] = 0; - entry.Type = DirectoryEntryType.Directory; - entry.Size = 0; - } - - i++; + entry.Type = DirectoryEntryType.Directory; + entry.Size = 0; } + + i++; } - - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out RomFileInfo info, out string name)) - { - if (!entryBuffer.IsEmpty) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - Span nameUtf8 = Encoding.UTF8.GetBytes(name); - - StringUtils.Copy(entry.Name, nameUtf8); - entry.Name[PathTools.MaxPathLength] = 0; - - entry.Type = DirectoryEntryType.File; - entry.Size = info.Length; - } - - i++; - } - } - - entriesRead = i; - - return Result.Success; } + + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out RomFileInfo info, out string name)) + { + if (!entryBuffer.IsEmpty) + { + ref DirectoryEntry entry = ref entryBuffer[i]; + Span nameUtf8 = Encoding.UTF8.GetBytes(name); + + StringUtils.Copy(entry.Name, nameUtf8); + entry.Name[PathTools.MaxPathLength] = 0; + + entry.Type = DirectoryEntryType.File; + entry.Size = info.Length; + } + + i++; + } + } + + entriesRead = i; + + return Result.Success; } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsEntries.cs b/src/LibHac/FsSystem/RomFs/RomFsEntries.cs index 8e6a4e7e..23fecaab 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsEntries.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsEntries.cs @@ -1,62 +1,61 @@ using System; using System.Runtime.InteropServices; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +internal ref struct RomEntryKey { - internal ref struct RomEntryKey + public ReadOnlySpan Name; + public int Parent; + + public RomEntryKey(ReadOnlySpan name, int parent) { - public ReadOnlySpan Name; - public int Parent; - - public RomEntryKey(ReadOnlySpan name, int parent) - { - Name = name; - Parent = parent; - } - - public uint GetRomHashCode() - { - return GetRomHashCode(Parent, Name); - } - - public static uint GetRomHashCode(int parent, ReadOnlySpan name) - { - uint hash = 123456789 ^ (uint)parent; - - foreach (byte c in name) - { - hash = c ^ ((hash << 27) | (hash >> 5)); - } - - return hash; - } + Name = name; + Parent = parent; } - // todo: Change constraint to "unmanaged" after updating to - // a newer SDK https://github.com/dotnet/csharplang/issues/1937 - internal ref struct RomKeyValuePair where T : struct + public uint GetRomHashCode() { - public RomEntryKey Key; - public int Offset; - public T Value; + return GetRomHashCode(Parent, Name); } - [StructLayout(LayoutKind.Sequential)] - public struct RomFileInfo + public static uint GetRomHashCode(int parent, ReadOnlySpan name) { - public long Offset; - public long Length; - } + uint hash = 123456789 ^ (uint)parent; - /// - /// Represents the current position when enumerating a directory's contents. - /// - [StructLayout(LayoutKind.Sequential)] - public struct FindPosition - { - /// The ID of the next directory to be enumerated. - public int NextDirectory; - /// The ID of the next file to be enumerated. - public int NextFile; + foreach (byte c in name) + { + hash = c ^ ((hash << 27) | (hash >> 5)); + } + + return hash; } } + +// todo: Change constraint to "unmanaged" after updating to +// a newer SDK https://github.com/dotnet/csharplang/issues/1937 +internal ref struct RomKeyValuePair where T : struct +{ + public RomEntryKey Key; + public int Offset; + public T Value; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RomFileInfo +{ + public long Offset; + public long Length; +} + +/// +/// Represents the current position when enumerating a directory's contents. +/// +[StructLayout(LayoutKind.Sequential)] +public struct FindPosition +{ + /// The ID of the next directory to be enumerated. + public int NextDirectory; + /// The ID of the next file to be enumerated. + public int NextFile; +} diff --git a/src/LibHac/FsSystem/RomFs/RomFsFile.cs b/src/LibHac/FsSystem/RomFs/RomFsFile.cs index d903c082..6fd1d9ab 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsFile.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsFile.cs @@ -3,69 +3,68 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +public class RomFsFile : IFile { - public class RomFsFile : IFile + private IStorage BaseStorage { get; } + private long Offset { get; } + private long Size { get; } + + public RomFsFile(IStorage baseStorage, long offset, long size) { - private IStorage BaseStorage { get; } - private long Offset { get; } - private long Size { get; } + BaseStorage = baseStorage; + Offset = offset; + Size = size; + } - public RomFsFile(IStorage baseStorage, long offset, long size) - { - BaseStorage = baseStorage; - Offset = offset; - Size = size; - } + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); + Result rc = DryRead(out long toRead, offset, destination.Length, in option, OpenMode.Read); + if (rc.IsFailure()) return rc; - Result rc = DryRead(out long toRead, offset, destination.Length, in option, OpenMode.Read); - if (rc.IsFailure()) return rc; + long storageOffset = Offset + offset; - long storageOffset = Offset + offset; + rc = ConvertToApplicationResult(BaseStorage.Read(storageOffset, destination.Slice(0, (int)toRead))); + if (rc.IsFailure()) return rc; - rc = ConvertToApplicationResult(BaseStorage.Read(storageOffset, destination.Slice(0, (int)toRead))); - if (rc.IsFailure()) return rc; + bytesRead = toRead; - bytesRead = toRead; + return Result.Success; + } - return Result.Success; - } + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return ResultFs.UnsupportedWriteForRomFsFile.Log(); + } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - return ResultFs.UnsupportedWriteForRomFsFile.Log(); - } + protected override Result DoFlush() + { + return Result.Success; + } - protected override Result DoFlush() - { - return Result.Success; - } + protected override Result DoGetSize(out long size) + { + size = Size; + return Result.Success; + } - protected override Result DoGetSize(out long size) - { - size = Size; - return Result.Success; - } + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedWriteForRomFsFile.Log(); + } - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedWriteForRomFsFile.Log(); - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } - - public Result ConvertToApplicationResult(Result result) - { - return RomFsFileSystem.ConvertRomFsDriverPrivateResult(result); - } + public Result ConvertToApplicationResult(Result result) + { + return RomFsFileSystem.ConvertRomFsDriverPrivateResult(result); } } diff --git a/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs b/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs index 57c55f9a..4cc7700e 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsFileSystem.cs @@ -3,319 +3,318 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem.RomFs +namespace LibHac.FsSystem.RomFs; + +public class RomFsFileSystem : IFileSystem { - public class RomFsFileSystem : IFileSystem + public RomfsHeader Header { get; } + + public HierarchicalRomFileTable FileTable { get; } + private IStorage BaseStorage { get; } + private SharedRef _baseStorageShared; + + public RomFsFileSystem(IStorage storage) { - public RomfsHeader Header { get; } + BaseStorage = storage; + Header = new RomfsHeader(storage.AsFile(OpenMode.Read)); - public HierarchicalRomFileTable FileTable { get; } - private IStorage BaseStorage { get; } - private SharedRef _baseStorageShared; + IStorage dirHashTable = storage.Slice(Header.DirHashTableOffset, Header.DirHashTableSize); + IStorage dirEntryTable = storage.Slice(Header.DirMetaTableOffset, Header.DirMetaTableSize); + IStorage fileHashTable = storage.Slice(Header.FileHashTableOffset, Header.FileHashTableSize); + IStorage fileEntryTable = storage.Slice(Header.FileMetaTableOffset, Header.FileMetaTableSize); - public RomFsFileSystem(IStorage storage) + FileTable = new HierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable); + } + + public RomFsFileSystem(ref SharedRef storage) : this(storage.Get) + { + _baseStorageShared = SharedRef.CreateMove(ref storage); + } + + public override void Dispose() + { + _baseStorageShared.Destroy(); + base.Dispose(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + if (FileTable.TryOpenFile(path.ToString(), out RomFileInfo _)) { - BaseStorage = storage; - Header = new RomfsHeader(storage.AsFile(OpenMode.Read)); - - IStorage dirHashTable = storage.Slice(Header.DirHashTableOffset, Header.DirHashTableSize); - IStorage dirEntryTable = storage.Slice(Header.DirMetaTableOffset, Header.DirMetaTableSize); - IStorage fileHashTable = storage.Slice(Header.FileHashTableOffset, Header.FileHashTableSize); - IStorage fileEntryTable = storage.Slice(Header.FileMetaTableOffset, Header.FileMetaTableSize); - - FileTable = new HierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable); + entryType = DirectoryEntryType.File; + return Result.Success; } - public RomFsFileSystem(ref SharedRef storage) : this(storage.Get) + if (FileTable.TryOpenDirectory(path.ToString(), out FindPosition _)) { - _baseStorageShared = SharedRef.CreateMove(ref storage); + entryType = DirectoryEntryType.Directory; + return Result.Success; } - public override void Dispose() + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoCommit() + { + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + if (!FileTable.TryOpenDirectory(path.ToString(), out FindPosition position)) { - _baseStorageShared.Destroy(); - base.Dispose(); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - if (FileTable.TryOpenFile(path.ToString(), out RomFileInfo _)) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } - - if (FileTable.TryOpenDirectory(path.ToString(), out FindPosition _)) - { - entryType = DirectoryEntryType.Directory; - return Result.Success; - } - return ResultFs.PathNotFound.Log(); } - protected override Result DoCommit() - { - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - if (!FileTable.TryOpenDirectory(path.ToString(), out FindPosition position)) - { - return ResultFs.PathNotFound.Log(); - } - - outDirectory.Reset(new RomFsDirectory(this, position, mode)); - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - if (!FileTable.TryOpenFile(path.ToString(), out RomFileInfo info)) - { - return ResultFs.PathNotFound.Log(); - } - - if (mode != OpenMode.Read) - { - // RomFs files must be opened read-only. - return ResultFs.InvalidArgument.Log(); - } - - outFile.Reset(new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length)); - return Result.Success; - } - - public IStorage GetBaseStorage() - { - return BaseStorage; - } - - protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); - protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForRomFsFileSystem.Log(); - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - freeSpace = 0; - return Result.Success; - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); - return ResultFs.UnsupportedGetTotalSpaceSizeForRomFsFileSystem.Log(); - } - - internal static Result ConvertRomFsDriverPrivateResult(Result result) - { - if (result.IsSuccess()) - return Result.Success; - - if (ResultFs.UnsupportedVersion.Includes(result)) - return ResultFs.UnsupportedRomVersion.LogConverted(result); - - if (ResultFs.NcaCorrupted.Includes(result) || - ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) || - ResultFs.BuiltInStorageCorrupted.Includes(result) || - ResultFs.PartitionFileSystemCorrupted.Includes(result) || - ResultFs.HostFileSystemCorrupted.Includes(result)) - { - return ConvertCorruptedResult(result); - } - - if (ResultFs.FatFileSystemCorrupted.Includes(result)) - return result; - - if (ResultFs.NotFound.Includes(result)) - return ResultFs.PathNotFound.LogConverted(result); - - if (ResultFs.InvalidOffset.Includes(result)) - return ResultFs.OutOfRange.LogConverted(result); - - if (ResultFs.FileNotFound.Includes(result) || - ResultFs.IncompatiblePath.Includes(result)) - { - return ResultFs.PathNotFound.LogConverted(result); - } - - return result; - } - - private static Result ConvertCorruptedResult(Result result) - { - if (ResultFs.NcaCorrupted.Includes(result)) - { - if (ResultFs.InvalidNcaFileSystemType.Includes(result)) - return ResultFs.InvalidRomNcaFileSystemType.LogConverted(result); - - if (ResultFs.InvalidNcaSignature.Includes(result)) - return ResultFs.InvalidRomNcaSignature.LogConverted(result); - - if (ResultFs.NcaHeaderSignature1VerificationFailed.Includes(result)) - return ResultFs.RomNcaHeaderSignature1VerificationFailed.LogConverted(result); - - if (ResultFs.NcaFsHeaderHashVerificationFailed.Includes(result)) - return ResultFs.RomNcaFsHeaderHashVerificationFailed.LogConverted(result); - - if (ResultFs.InvalidNcaKeyIndex.Includes(result)) - return ResultFs.InvalidRomNcaKeyIndex.LogConverted(result); - - if (ResultFs.InvalidNcaFsHeaderHashType.Includes(result)) - return ResultFs.InvalidRomNcaFsHeaderHashType.LogConverted(result); - - if (ResultFs.InvalidNcaFsHeaderEncryptionType.Includes(result)) - return ResultFs.InvalidRomNcaFsHeaderEncryptionType.LogConverted(result); - - if (ResultFs.InvalidNcaPatchInfoIndirectSize.Includes(result)) - return ResultFs.InvalidRomNcaPatchInfoIndirectSize.LogConverted(result); - - if (ResultFs.InvalidNcaPatchInfoAesCtrExSize.Includes(result)) - return ResultFs.InvalidRomNcaPatchInfoAesCtrExSize.LogConverted(result); - - if (ResultFs.InvalidNcaPatchInfoAesCtrExOffset.Includes(result)) - return ResultFs.InvalidRomNcaPatchInfoAesCtrExOffset.LogConverted(result); - - if (ResultFs.InvalidNcaId.Includes(result)) - return ResultFs.InvalidRomNcaId.LogConverted(result); - - if (ResultFs.InvalidNcaHeader.Includes(result)) - return ResultFs.InvalidRomNcaHeader.LogConverted(result); - - if (ResultFs.InvalidNcaFsHeader.Includes(result)) - return ResultFs.InvalidRomNcaFsHeader.LogConverted(result); - - if (ResultFs.InvalidNcaPatchInfoIndirectOffset.Includes(result)) - return ResultFs.InvalidRomNcaPatchInfoIndirectOffset.LogConverted(result); - - if (ResultFs.InvalidHierarchicalSha256BlockSize.Includes(result)) - return ResultFs.InvalidRomHierarchicalSha256BlockSize.LogConverted(result); - - if (ResultFs.InvalidHierarchicalSha256LayerCount.Includes(result)) - return ResultFs.InvalidRomHierarchicalSha256LayerCount.LogConverted(result); - - if (ResultFs.HierarchicalSha256BaseStorageTooLarge.Includes(result)) - return ResultFs.RomHierarchicalSha256BaseStorageTooLarge.LogConverted(result); - - if (ResultFs.HierarchicalSha256HashVerificationFailed.Includes(result)) - return ResultFs.RomHierarchicalSha256HashVerificationFailed.LogConverted(result); - - if (ResultFs.InvalidHierarchicalIntegrityVerificationLayerCount.Includes(result)) - return ResultFs.InvalidRomHierarchicalIntegrityVerificationLayerCount.LogConverted(result); - - if (ResultFs.NcaIndirectStorageOutOfRange.Includes(result)) - return ResultFs.RomNcaIndirectStorageOutOfRange.LogConverted(result); - } - - if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) - { - if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) - return ResultFs.IncorrectRomIntegrityVerificationMagic.LogConverted(result); - - if (ResultFs.InvalidZeroHash.Includes(result)) - return ResultFs.InvalidRomZeroSignature.LogConverted(result); - - if (ResultFs.NonRealDataVerificationFailed.Includes(result)) - return ResultFs.RomNonRealDataVerificationFailed.LogConverted(result); - - if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) - return ResultFs.ClearedRomRealDataVerificationFailed.LogConverted(result); - - if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) - return ResultFs.UnclearedRomRealDataVerificationFailed.LogConverted(result); - } - - if (ResultFs.PartitionFileSystemCorrupted.Includes(result)) - { - if (ResultFs.InvalidSha256PartitionHashTarget.Includes(result)) - return ResultFs.InvalidRomSha256PartitionHashTarget.LogConverted(result); - - if (ResultFs.Sha256PartitionHashVerificationFailed.Includes(result)) - return ResultFs.RomSha256PartitionHashVerificationFailed.LogConverted(result); - - if (ResultFs.PartitionSignatureVerificationFailed.Includes(result)) - return ResultFs.RomPartitionSignatureVerificationFailed.LogConverted(result); - - if (ResultFs.Sha256PartitionSignatureVerificationFailed.Includes(result)) - return ResultFs.RomSha256PartitionSignatureVerificationFailed.LogConverted(result); - - if (ResultFs.InvalidPartitionEntryOffset.Includes(result)) - return ResultFs.InvalidRomPartitionEntryOffset.LogConverted(result); - - if (ResultFs.InvalidSha256PartitionMetaDataSize.Includes(result)) - return ResultFs.InvalidRomSha256PartitionMetaDataSize.LogConverted(result); - } - - if (ResultFs.HostFileSystemCorrupted.Includes(result)) - { - if (ResultFs.HostEntryCorrupted.Includes(result)) - return ResultFs.RomHostEntryCorrupted.LogConverted(result); - - if (ResultFs.HostFileDataCorrupted.Includes(result)) - return ResultFs.RomHostFileDataCorrupted.LogConverted(result); - - if (ResultFs.HostFileCorrupted.Includes(result)) - return ResultFs.RomHostFileCorrupted.LogConverted(result); - - if (ResultFs.InvalidHostHandle.Includes(result)) - return ResultFs.InvalidRomHostHandle.LogConverted(result); - } - - return result; - } + outDirectory.Reset(new RomFsDirectory(this, position, mode)); + return Result.Success; } - public class RomfsHeader + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) { - public long HeaderSize { get; } - public long DirHashTableOffset { get; } - public long DirHashTableSize { get; } - public long DirMetaTableOffset { get; } - public long DirMetaTableSize { get; } - public long FileHashTableOffset { get; } - public long FileHashTableSize { get; } - public long FileMetaTableOffset { get; } - public long FileMetaTableSize { get; } - public long DataOffset { get; } - - public RomfsHeader(IFile file) + if (!FileTable.TryOpenFile(path.ToString(), out RomFileInfo info)) { - var reader = new FileReader(file); - - HeaderSize = reader.ReadInt32(); - - Func func; - - // Old pre-release romfs is exactly the same except the fields in the header are 32-bit instead of 64-bit - if (HeaderSize == 0x28) - { - func = () => reader.ReadInt32(); - } - else - { - func = reader.ReadInt64; - reader.Position += 4; - } - - DirHashTableOffset = func(); - DirHashTableSize = func(); - DirMetaTableOffset = func(); - DirMetaTableSize = func(); - FileHashTableOffset = func(); - FileHashTableSize = func(); - FileMetaTableOffset = func(); - FileMetaTableSize = func(); - DataOffset = func(); + return ResultFs.PathNotFound.Log(); } + + if (mode != OpenMode.Read) + { + // RomFs files must be opened read-only. + return ResultFs.InvalidArgument.Log(); + } + + outFile.Reset(new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length)); + return Result.Success; + } + + public IStorage GetBaseStorage() + { + return BaseStorage; + } + + protected override Result DoCreateDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoDeleteDirectory(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoDeleteDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoCleanDirectoryRecursively(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoDeleteFile(in Path path) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoRenameFile(in Path currentPath, in Path newPath) => ResultFs.UnsupportedWriteForRomFsFileSystem.Log(); + protected override Result DoCommitProvisionally(long counter) => ResultFs.UnsupportedCommitProvisionallyForRomFsFileSystem.Log(); + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + freeSpace = 0; + return Result.Success; + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); + return ResultFs.UnsupportedGetTotalSpaceSizeForRomFsFileSystem.Log(); + } + + internal static Result ConvertRomFsDriverPrivateResult(Result result) + { + if (result.IsSuccess()) + return Result.Success; + + if (ResultFs.UnsupportedVersion.Includes(result)) + return ResultFs.UnsupportedRomVersion.LogConverted(result); + + if (ResultFs.NcaCorrupted.Includes(result) || + ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) || + ResultFs.BuiltInStorageCorrupted.Includes(result) || + ResultFs.PartitionFileSystemCorrupted.Includes(result) || + ResultFs.HostFileSystemCorrupted.Includes(result)) + { + return ConvertCorruptedResult(result); + } + + if (ResultFs.FatFileSystemCorrupted.Includes(result)) + return result; + + if (ResultFs.NotFound.Includes(result)) + return ResultFs.PathNotFound.LogConverted(result); + + if (ResultFs.InvalidOffset.Includes(result)) + return ResultFs.OutOfRange.LogConverted(result); + + if (ResultFs.FileNotFound.Includes(result) || + ResultFs.IncompatiblePath.Includes(result)) + { + return ResultFs.PathNotFound.LogConverted(result); + } + + return result; + } + + private static Result ConvertCorruptedResult(Result result) + { + if (ResultFs.NcaCorrupted.Includes(result)) + { + if (ResultFs.InvalidNcaFileSystemType.Includes(result)) + return ResultFs.InvalidRomNcaFileSystemType.LogConverted(result); + + if (ResultFs.InvalidNcaSignature.Includes(result)) + return ResultFs.InvalidRomNcaSignature.LogConverted(result); + + if (ResultFs.NcaHeaderSignature1VerificationFailed.Includes(result)) + return ResultFs.RomNcaHeaderSignature1VerificationFailed.LogConverted(result); + + if (ResultFs.NcaFsHeaderHashVerificationFailed.Includes(result)) + return ResultFs.RomNcaFsHeaderHashVerificationFailed.LogConverted(result); + + if (ResultFs.InvalidNcaKeyIndex.Includes(result)) + return ResultFs.InvalidRomNcaKeyIndex.LogConverted(result); + + if (ResultFs.InvalidNcaFsHeaderHashType.Includes(result)) + return ResultFs.InvalidRomNcaFsHeaderHashType.LogConverted(result); + + if (ResultFs.InvalidNcaFsHeaderEncryptionType.Includes(result)) + return ResultFs.InvalidRomNcaFsHeaderEncryptionType.LogConverted(result); + + if (ResultFs.InvalidNcaPatchInfoIndirectSize.Includes(result)) + return ResultFs.InvalidRomNcaPatchInfoIndirectSize.LogConverted(result); + + if (ResultFs.InvalidNcaPatchInfoAesCtrExSize.Includes(result)) + return ResultFs.InvalidRomNcaPatchInfoAesCtrExSize.LogConverted(result); + + if (ResultFs.InvalidNcaPatchInfoAesCtrExOffset.Includes(result)) + return ResultFs.InvalidRomNcaPatchInfoAesCtrExOffset.LogConverted(result); + + if (ResultFs.InvalidNcaId.Includes(result)) + return ResultFs.InvalidRomNcaId.LogConverted(result); + + if (ResultFs.InvalidNcaHeader.Includes(result)) + return ResultFs.InvalidRomNcaHeader.LogConverted(result); + + if (ResultFs.InvalidNcaFsHeader.Includes(result)) + return ResultFs.InvalidRomNcaFsHeader.LogConverted(result); + + if (ResultFs.InvalidNcaPatchInfoIndirectOffset.Includes(result)) + return ResultFs.InvalidRomNcaPatchInfoIndirectOffset.LogConverted(result); + + if (ResultFs.InvalidHierarchicalSha256BlockSize.Includes(result)) + return ResultFs.InvalidRomHierarchicalSha256BlockSize.LogConverted(result); + + if (ResultFs.InvalidHierarchicalSha256LayerCount.Includes(result)) + return ResultFs.InvalidRomHierarchicalSha256LayerCount.LogConverted(result); + + if (ResultFs.HierarchicalSha256BaseStorageTooLarge.Includes(result)) + return ResultFs.RomHierarchicalSha256BaseStorageTooLarge.LogConverted(result); + + if (ResultFs.HierarchicalSha256HashVerificationFailed.Includes(result)) + return ResultFs.RomHierarchicalSha256HashVerificationFailed.LogConverted(result); + + if (ResultFs.InvalidHierarchicalIntegrityVerificationLayerCount.Includes(result)) + return ResultFs.InvalidRomHierarchicalIntegrityVerificationLayerCount.LogConverted(result); + + if (ResultFs.NcaIndirectStorageOutOfRange.Includes(result)) + return ResultFs.RomNcaIndirectStorageOutOfRange.LogConverted(result); + } + + if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) + { + if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) + return ResultFs.IncorrectRomIntegrityVerificationMagic.LogConverted(result); + + if (ResultFs.InvalidZeroHash.Includes(result)) + return ResultFs.InvalidRomZeroSignature.LogConverted(result); + + if (ResultFs.NonRealDataVerificationFailed.Includes(result)) + return ResultFs.RomNonRealDataVerificationFailed.LogConverted(result); + + if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) + return ResultFs.ClearedRomRealDataVerificationFailed.LogConverted(result); + + if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) + return ResultFs.UnclearedRomRealDataVerificationFailed.LogConverted(result); + } + + if (ResultFs.PartitionFileSystemCorrupted.Includes(result)) + { + if (ResultFs.InvalidSha256PartitionHashTarget.Includes(result)) + return ResultFs.InvalidRomSha256PartitionHashTarget.LogConverted(result); + + if (ResultFs.Sha256PartitionHashVerificationFailed.Includes(result)) + return ResultFs.RomSha256PartitionHashVerificationFailed.LogConverted(result); + + if (ResultFs.PartitionSignatureVerificationFailed.Includes(result)) + return ResultFs.RomPartitionSignatureVerificationFailed.LogConverted(result); + + if (ResultFs.Sha256PartitionSignatureVerificationFailed.Includes(result)) + return ResultFs.RomSha256PartitionSignatureVerificationFailed.LogConverted(result); + + if (ResultFs.InvalidPartitionEntryOffset.Includes(result)) + return ResultFs.InvalidRomPartitionEntryOffset.LogConverted(result); + + if (ResultFs.InvalidSha256PartitionMetaDataSize.Includes(result)) + return ResultFs.InvalidRomSha256PartitionMetaDataSize.LogConverted(result); + } + + if (ResultFs.HostFileSystemCorrupted.Includes(result)) + { + if (ResultFs.HostEntryCorrupted.Includes(result)) + return ResultFs.RomHostEntryCorrupted.LogConverted(result); + + if (ResultFs.HostFileDataCorrupted.Includes(result)) + return ResultFs.RomHostFileDataCorrupted.LogConverted(result); + + if (ResultFs.HostFileCorrupted.Includes(result)) + return ResultFs.RomHostFileCorrupted.LogConverted(result); + + if (ResultFs.InvalidHostHandle.Includes(result)) + return ResultFs.InvalidRomHostHandle.LogConverted(result); + } + + return result; + } +} + +public class RomfsHeader +{ + public long HeaderSize { get; } + public long DirHashTableOffset { get; } + public long DirHashTableSize { get; } + public long DirMetaTableOffset { get; } + public long DirMetaTableSize { get; } + public long FileHashTableOffset { get; } + public long FileHashTableSize { get; } + public long FileMetaTableOffset { get; } + public long FileMetaTableSize { get; } + public long DataOffset { get; } + + public RomfsHeader(IFile file) + { + var reader = new FileReader(file); + + HeaderSize = reader.ReadInt32(); + + Func func; + + // Old pre-release romfs is exactly the same except the fields in the header are 32-bit instead of 64-bit + if (HeaderSize == 0x28) + { + func = () => reader.ReadInt32(); + } + else + { + func = reader.ReadInt64; + reader.Position += 4; + } + + DirHashTableOffset = func(); + DirHashTableSize = func(); + DirMetaTableOffset = func(); + DirMetaTableSize = func(); + FileHashTableOffset = func(); + FileHashTableSize = func(); + FileMetaTableOffset = func(); + FileMetaTableSize = func(); + DataOffset = func(); } } diff --git a/src/LibHac/FsSystem/Save/AllocationTable.cs b/src/LibHac/FsSystem/Save/AllocationTable.cs index 79c5de15..bc920357 100644 --- a/src/LibHac/FsSystem/Save/AllocationTable.cs +++ b/src/LibHac/FsSystem/Save/AllocationTable.cs @@ -4,551 +4,550 @@ using System.IO; using System.Runtime.InteropServices; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class AllocationTable { - public class AllocationTable + private const int FreeListEntryIndex = 0; + private const int EntrySize = 8; + + private IStorage BaseStorage { get; } + private IStorage HeaderStorage { get; } + + public AllocationTableHeader Header { get; } + + public IStorage GetBaseStorage() => BaseStorage; + public IStorage GetHeaderStorage() => HeaderStorage; + + public AllocationTable(IStorage storage, IStorage header) { - private const int FreeListEntryIndex = 0; - private const int EntrySize = 8; + BaseStorage = storage; + HeaderStorage = header; + Header = new AllocationTableHeader(HeaderStorage); + } - private IStorage BaseStorage { get; } - private IStorage HeaderStorage { get; } + public void ReadEntry(int blockIndex, out int next, out int previous, out int length) + { + int entryIndex = BlockToEntryIndex(blockIndex); - public AllocationTableHeader Header { get; } + Span entries = stackalloc AllocationTableEntry[2]; + ReadEntries(entryIndex, entries); - public IStorage GetBaseStorage() => BaseStorage; - public IStorage GetHeaderStorage() => HeaderStorage; - - public AllocationTable(IStorage storage, IStorage header) + if (entries[0].IsSingleBlockSegment()) { - BaseStorage = storage; - HeaderStorage = header; - Header = new AllocationTableHeader(HeaderStorage); + length = 1; + + if (entries[0].IsRangeEntry()) + { + ThrowHelper.ThrowResult(ResultFs.AllocationTableIteratedRangeEntry.Value); + } + } + else + { + length = entries[1].Next - entryIndex + 1; } - public void ReadEntry(int blockIndex, out int next, out int previous, out int length) + if (entries[0].IsListEnd()) { - int entryIndex = BlockToEntryIndex(blockIndex); + next = -1; + } + else + { + next = EntryIndexToBlock(entries[0].GetNext()); + } - Span entries = stackalloc AllocationTableEntry[2]; - ReadEntries(entryIndex, entries); + if (entries[0].IsListStart()) + { + previous = -1; + } + else + { + previous = EntryIndexToBlock(entries[0].GetPrev()); + } + } - if (entries[0].IsSingleBlockSegment()) + public int GetFreeListBlockIndex() + { + return EntryIndexToBlock(GetFreeListEntryIndex()); + } + + public void SetFreeListBlockIndex(int headBlockIndex) + { + SetFreeListEntryIndex(BlockToEntryIndex(headBlockIndex)); + } + + public int GetFreeListEntryIndex() + { + AllocationTableEntry freeList = ReadEntry(FreeListEntryIndex); + return freeList.GetNext(); + } + + public void SetFreeListEntryIndex(int headBlockIndex) + { + var freeList = new AllocationTableEntry { Next = headBlockIndex }; + WriteEntry(FreeListEntryIndex, freeList); + } + + public int Allocate(int blockCount) + { + if (blockCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(blockCount)); + } + + int freeList = GetFreeListBlockIndex(); + + int newFreeList = Trim(freeList, blockCount); + if (newFreeList == -1) return -1; + + SetFreeListBlockIndex(newFreeList); + + return freeList; + } + + public void Free(int listBlockIndex) + { + int listEntryIndex = BlockToEntryIndex(listBlockIndex); + AllocationTableEntry listEntry = ReadEntry(listEntryIndex); + + if (!listEntry.IsListStart()) + { + throw new ArgumentOutOfRangeException(nameof(listBlockIndex), "The block to free must be the start of a list."); + } + + int freeListIndex = GetFreeListEntryIndex(); + + // Free list is empty + if (freeListIndex == 0) + { + SetFreeListEntryIndex(listEntryIndex); + return; + } + + Join(listBlockIndex, EntryIndexToBlock(freeListIndex)); + + SetFreeListBlockIndex(listBlockIndex); + } + + /// + /// Combines 2 lists into one list. The second list will be attached to the end of the first list. + /// + /// The index of the start block of the first list. + /// The index of the start block of the second list. + public void Join(int frontListBlockIndex, int backListBlockIndex) + { + int frontEntryIndex = BlockToEntryIndex(frontListBlockIndex); + int backEntryIndex = BlockToEntryIndex(backListBlockIndex); + + int frontTailIndex = GetListTail(frontEntryIndex); + + AllocationTableEntry frontTail = ReadEntry(frontTailIndex); + AllocationTableEntry backHead = ReadEntry(backEntryIndex); + + frontTail.SetNext(backEntryIndex); + backHead.SetPrev(frontTailIndex); + + WriteEntry(frontTailIndex, frontTail); + WriteEntry(backEntryIndex, backHead); + } + + /// + /// Trims an existing list to the specified length and returns the excess blocks as a new list. + /// + /// The starting block of the list to trim. + /// The length in blocks that the list will be shortened to. + /// The index of the head node of the removed blocks. + public int Trim(int listHeadBlockIndex, int newListLength) + { + int blocksRemaining = newListLength; + int nextEntry = BlockToEntryIndex(listHeadBlockIndex); + int listAIndex = -1; + int listBIndex = -1; + + while (blocksRemaining > 0) + { + if (nextEntry == 0) { - length = 1; - - if (entries[0].IsRangeEntry()) - { - ThrowHelper.ThrowResult(ResultFs.AllocationTableIteratedRangeEntry.Value); - } - } - else - { - length = entries[1].Next - entryIndex + 1; + return -1; } - if (entries[0].IsListEnd()) + int currentEntryIndex = nextEntry; + + ReadEntry(EntryIndexToBlock(currentEntryIndex), out int nextBlock, out int _, out int segmentLength); + + nextEntry = BlockToEntryIndex(nextBlock); + + if (segmentLength == blocksRemaining) { - next = -1; + listAIndex = currentEntryIndex; + listBIndex = nextEntry; } - else + else if (segmentLength > blocksRemaining) { - next = EntryIndexToBlock(entries[0].GetNext()); + Split(EntryIndexToBlock(currentEntryIndex), blocksRemaining); + + listAIndex = currentEntryIndex; + listBIndex = currentEntryIndex + blocksRemaining; } - if (entries[0].IsListStart()) + blocksRemaining -= segmentLength; + } + + if (listAIndex == -1 || listBIndex == -1) return -1; + + AllocationTableEntry listANode = ReadEntry(listAIndex); + AllocationTableEntry listBNode = ReadEntry(listBIndex); + + listANode.SetNext(0); + listBNode.MakeListStart(); + + WriteEntry(listAIndex, listANode); + WriteEntry(listBIndex, listBNode); + + return EntryIndexToBlock(listBIndex); + } + + /// + /// Splits a single list segment into 2 segments. The sequence of blocks in the full list will remain the same. + /// + /// The block index of the segment to split. + /// The length of the first subsegment. + public void Split(int segmentBlockIndex, int firstSubSegmentLength) + { + Debug.Assert(firstSubSegmentLength > 0); + + int segAIndex = BlockToEntryIndex(segmentBlockIndex); + + AllocationTableEntry segA = ReadEntry(segAIndex); + if (!segA.IsMultiBlockSegment()) throw new ArgumentException("Cannot split a single-entry segment."); + + AllocationTableEntry segARange = ReadEntry(segAIndex + 1); + int originalLength = segARange.GetNext() - segARange.GetPrev() + 1; + + if (firstSubSegmentLength >= originalLength) + { + throw new ArgumentOutOfRangeException(nameof(firstSubSegmentLength), + $"Requested sub-segment length ({firstSubSegmentLength}) must be less than the full segment length ({originalLength})"); + } + + int segBIndex = segAIndex + firstSubSegmentLength; + + int segALength = firstSubSegmentLength; + int segBLength = originalLength - segALength; + + var segB = new AllocationTableEntry(); + + // Insert segment B between segments A and C + segB.SetPrev(segAIndex); + segB.SetNext(segA.GetNext()); + segA.SetNext(segBIndex); + + if (!segB.IsListEnd()) + { + AllocationTableEntry segC = ReadEntry(segB.GetNext()); + segC.SetPrev(segBIndex); + WriteEntry(segB.GetNext(), segC); + } + + // Write the new range entries if needed + if (segBLength > 1) + { + segB.MakeMultiBlockSegment(); + + var segBRange = new AllocationTableEntry(); + segBRange.SetRange(segBIndex, segBIndex + segBLength - 1); + + WriteEntry(segBIndex + 1, segBRange); + WriteEntry(segBIndex + segBLength - 1, segBRange); + } + + WriteEntry(segBIndex, segB); + + if (segALength == 1) + { + segA.MakeSingleBlockSegment(); + } + else + { + segARange.SetRange(segAIndex, segAIndex + segALength - 1); + + WriteEntry(segAIndex + 1, segARange); + WriteEntry(segAIndex + segALength - 1, segARange); + } + + WriteEntry(segAIndex, segA); + } + + public int GetFreeListLength() + { + int freeListStart = GetFreeListBlockIndex(); + + if (freeListStart == -1) return 0; + + return GetListLength(freeListStart); + } + + public int GetListLength(int blockIndex) + { + int index = blockIndex; + int totalLength = 0; + + int tableSize = Header.AllocationTableBlockCount; + int nodesIterated = 0; + + while (index != -1) + { + ReadEntry(index, out index, out int _, out int length); + + totalLength += length; + nodesIterated++; + + if (nodesIterated > tableSize) { - previous = -1; - } - else - { - previous = EntryIndexToBlock(entries[0].GetPrev()); + throw new InvalidDataException("Cycle detected in allocation table."); } } - public int GetFreeListBlockIndex() - { - return EntryIndexToBlock(GetFreeListEntryIndex()); - } + return totalLength; + } - public void SetFreeListBlockIndex(int headBlockIndex) - { - SetFreeListEntryIndex(BlockToEntryIndex(headBlockIndex)); - } + public void FsTrimList(int blockIndex) + { + int index = blockIndex; - public int GetFreeListEntryIndex() - { - AllocationTableEntry freeList = ReadEntry(FreeListEntryIndex); - return freeList.GetNext(); - } + int tableSize = Header.AllocationTableBlockCount; + int nodesIterated = 0; - public void SetFreeListEntryIndex(int headBlockIndex) + while (index != -1) { - var freeList = new AllocationTableEntry { Next = headBlockIndex }; - WriteEntry(FreeListEntryIndex, freeList); - } + ReadEntry(index, out int next, out int _, out int length); - public int Allocate(int blockCount) - { - if (blockCount <= 0) + if (length > 3) { - throw new ArgumentOutOfRangeException(nameof(blockCount)); + int fillOffset = BlockToEntryIndex(index + 2) * EntrySize; + int fillLength = (length - 3) * EntrySize; + + BaseStorage.Slice(fillOffset, fillLength).Fill(SaveDataFileSystem.TrimFillValue); } - int freeList = GetFreeListBlockIndex(); + nodesIterated++; - int newFreeList = Trim(freeList, blockCount); - if (newFreeList == -1) return -1; - - SetFreeListBlockIndex(newFreeList); - - return freeList; - } - - public void Free(int listBlockIndex) - { - int listEntryIndex = BlockToEntryIndex(listBlockIndex); - AllocationTableEntry listEntry = ReadEntry(listEntryIndex); - - if (!listEntry.IsListStart()) + if (nodesIterated > tableSize) { - throw new ArgumentOutOfRangeException(nameof(listBlockIndex), "The block to free must be the start of a list."); - } - - int freeListIndex = GetFreeListEntryIndex(); - - // Free list is empty - if (freeListIndex == 0) - { - SetFreeListEntryIndex(listEntryIndex); return; } - Join(listBlockIndex, EntryIndexToBlock(freeListIndex)); - - SetFreeListBlockIndex(listBlockIndex); - } - - /// - /// Combines 2 lists into one list. The second list will be attached to the end of the first list. - /// - /// The index of the start block of the first list. - /// The index of the start block of the second list. - public void Join(int frontListBlockIndex, int backListBlockIndex) - { - int frontEntryIndex = BlockToEntryIndex(frontListBlockIndex); - int backEntryIndex = BlockToEntryIndex(backListBlockIndex); - - int frontTailIndex = GetListTail(frontEntryIndex); - - AllocationTableEntry frontTail = ReadEntry(frontTailIndex); - AllocationTableEntry backHead = ReadEntry(backEntryIndex); - - frontTail.SetNext(backEntryIndex); - backHead.SetPrev(frontTailIndex); - - WriteEntry(frontTailIndex, frontTail); - WriteEntry(backEntryIndex, backHead); - } - - /// - /// Trims an existing list to the specified length and returns the excess blocks as a new list. - /// - /// The starting block of the list to trim. - /// The length in blocks that the list will be shortened to. - /// The index of the head node of the removed blocks. - public int Trim(int listHeadBlockIndex, int newListLength) - { - int blocksRemaining = newListLength; - int nextEntry = BlockToEntryIndex(listHeadBlockIndex); - int listAIndex = -1; - int listBIndex = -1; - - while (blocksRemaining > 0) - { - if (nextEntry == 0) - { - return -1; - } - - int currentEntryIndex = nextEntry; - - ReadEntry(EntryIndexToBlock(currentEntryIndex), out int nextBlock, out int _, out int segmentLength); - - nextEntry = BlockToEntryIndex(nextBlock); - - if (segmentLength == blocksRemaining) - { - listAIndex = currentEntryIndex; - listBIndex = nextEntry; - } - else if (segmentLength > blocksRemaining) - { - Split(EntryIndexToBlock(currentEntryIndex), blocksRemaining); - - listAIndex = currentEntryIndex; - listBIndex = currentEntryIndex + blocksRemaining; - } - - blocksRemaining -= segmentLength; - } - - if (listAIndex == -1 || listBIndex == -1) return -1; - - AllocationTableEntry listANode = ReadEntry(listAIndex); - AllocationTableEntry listBNode = ReadEntry(listBIndex); - - listANode.SetNext(0); - listBNode.MakeListStart(); - - WriteEntry(listAIndex, listANode); - WriteEntry(listBIndex, listBNode); - - return EntryIndexToBlock(listBIndex); - } - - /// - /// Splits a single list segment into 2 segments. The sequence of blocks in the full list will remain the same. - /// - /// The block index of the segment to split. - /// The length of the first subsegment. - public void Split(int segmentBlockIndex, int firstSubSegmentLength) - { - Debug.Assert(firstSubSegmentLength > 0); - - int segAIndex = BlockToEntryIndex(segmentBlockIndex); - - AllocationTableEntry segA = ReadEntry(segAIndex); - if (!segA.IsMultiBlockSegment()) throw new ArgumentException("Cannot split a single-entry segment."); - - AllocationTableEntry segARange = ReadEntry(segAIndex + 1); - int originalLength = segARange.GetNext() - segARange.GetPrev() + 1; - - if (firstSubSegmentLength >= originalLength) - { - throw new ArgumentOutOfRangeException(nameof(firstSubSegmentLength), - $"Requested sub-segment length ({firstSubSegmentLength}) must be less than the full segment length ({originalLength})"); - } - - int segBIndex = segAIndex + firstSubSegmentLength; - - int segALength = firstSubSegmentLength; - int segBLength = originalLength - segALength; - - var segB = new AllocationTableEntry(); - - // Insert segment B between segments A and C - segB.SetPrev(segAIndex); - segB.SetNext(segA.GetNext()); - segA.SetNext(segBIndex); - - if (!segB.IsListEnd()) - { - AllocationTableEntry segC = ReadEntry(segB.GetNext()); - segC.SetPrev(segBIndex); - WriteEntry(segB.GetNext(), segC); - } - - // Write the new range entries if needed - if (segBLength > 1) - { - segB.MakeMultiBlockSegment(); - - var segBRange = new AllocationTableEntry(); - segBRange.SetRange(segBIndex, segBIndex + segBLength - 1); - - WriteEntry(segBIndex + 1, segBRange); - WriteEntry(segBIndex + segBLength - 1, segBRange); - } - - WriteEntry(segBIndex, segB); - - if (segALength == 1) - { - segA.MakeSingleBlockSegment(); - } - else - { - segARange.SetRange(segAIndex, segAIndex + segALength - 1); - - WriteEntry(segAIndex + 1, segARange); - WriteEntry(segAIndex + segALength - 1, segARange); - } - - WriteEntry(segAIndex, segA); - } - - public int GetFreeListLength() - { - int freeListStart = GetFreeListBlockIndex(); - - if (freeListStart == -1) return 0; - - return GetListLength(freeListStart); - } - - public int GetListLength(int blockIndex) - { - int index = blockIndex; - int totalLength = 0; - - int tableSize = Header.AllocationTableBlockCount; - int nodesIterated = 0; - - while (index != -1) - { - ReadEntry(index, out index, out int _, out int length); - - totalLength += length; - nodesIterated++; - - if (nodesIterated > tableSize) - { - throw new InvalidDataException("Cycle detected in allocation table."); - } - } - - return totalLength; - } - - public void FsTrimList(int blockIndex) - { - int index = blockIndex; - - int tableSize = Header.AllocationTableBlockCount; - int nodesIterated = 0; - - while (index != -1) - { - ReadEntry(index, out int next, out int _, out int length); - - if (length > 3) - { - int fillOffset = BlockToEntryIndex(index + 2) * EntrySize; - int fillLength = (length - 3) * EntrySize; - - BaseStorage.Slice(fillOffset, fillLength).Fill(SaveDataFileSystem.TrimFillValue); - } - - nodesIterated++; - - if (nodesIterated > tableSize) - { - return; - } - - index = next; - } - } - - public void FsTrim() - { - int tableSize = BlockToEntryIndex(Header.AllocationTableBlockCount) * EntrySize; - BaseStorage.Slice(tableSize).Fill(SaveDataFileSystem.TrimFillValue); - } - - private void ReadEntries(int entryIndex, Span entries) - { - Debug.Assert(entries.Length >= 2); - - bool isLastBlock = entryIndex == BlockToEntryIndex(Header.AllocationTableBlockCount) - 1; - int entriesToRead = isLastBlock ? 1 : 2; - int offset = entryIndex * EntrySize; - - Span buffer = MemoryMarshal.Cast(entries.Slice(0, entriesToRead)); - - BaseStorage.Read(offset, buffer).ThrowIfFailure(); - } - - private AllocationTableEntry ReadEntry(int entryIndex) - { - Span bytes = stackalloc byte[EntrySize]; - int offset = entryIndex * EntrySize; - - BaseStorage.Read(offset, bytes).ThrowIfFailure(); - - return GetEntryFromBytes(bytes); - } - - private void WriteEntry(int entryIndex, AllocationTableEntry entry) - { - Span bytes = stackalloc byte[EntrySize]; - int offset = entryIndex * EntrySize; - - ref AllocationTableEntry newEntry = ref GetEntryFromBytes(bytes); - newEntry = entry; - - BaseStorage.Write(offset, bytes).ThrowIfFailure(); - } - - // ReSharper disable once UnusedMember.Local - private int GetListHead(int entryIndex) - { - int headIndex = entryIndex; - int tableSize = Header.AllocationTableBlockCount; - int nodesTraversed = 0; - - AllocationTableEntry entry = ReadEntry(entryIndex); - - while (!entry.IsListStart()) - { - nodesTraversed++; - headIndex = entry.Prev & 0x7FFFFFFF; - entry = ReadEntry(headIndex); - - if (nodesTraversed > tableSize) - { - throw new InvalidDataException("Cycle detected in allocation table."); - } - } - - return headIndex; - } - - private int GetListTail(int entryIndex) - { - int tailIndex = entryIndex; - int tableSize = Header.AllocationTableBlockCount; - int nodesTraversed = 0; - - AllocationTableEntry entry = ReadEntry(entryIndex); - - while (!entry.IsListEnd()) - { - nodesTraversed++; - tailIndex = entry.Next & 0x7FFFFFFF; - entry = ReadEntry(tailIndex); - - if (nodesTraversed > tableSize) - { - throw new InvalidDataException("Cycle detected in allocation table."); - } - } - - return tailIndex; - } - - private static ref AllocationTableEntry GetEntryFromBytes(Span entry) - { - return ref MemoryMarshal.Cast(entry)[0]; - } - - private static int EntryIndexToBlock(int entryIndex) => entryIndex - 1; - private static int BlockToEntryIndex(int blockIndex) => blockIndex + 1; - } - - [StructLayout(LayoutKind.Sequential)] - public struct AllocationTableEntry - { - public int Prev; - public int Next; - - public int GetPrev() - { - return Prev & 0x7FFFFFFF; - } - - public int GetNext() - { - return Next & 0x7FFFFFFF; - } - - public bool IsListStart() - { - return Prev == unchecked((int)0x80000000); - } - - public bool IsListEnd() - { - return (Next & 0x7FFFFFFF) == 0; - } - - public bool IsMultiBlockSegment() - { - return Next < 0; - } - - public void MakeMultiBlockSegment() - { - Next |= unchecked((int)0x80000000); - } - - public void MakeSingleBlockSegment() - { - Next &= 0x7FFFFFFF; - } - - public bool IsSingleBlockSegment() - { - return Next >= 0; - } - - public void MakeListStart() - { - Prev = unchecked((int)0x80000000); - } - - public bool IsRangeEntry() - { - return Prev != unchecked((int)0x80000000) && Prev < 0; - } - - public void MakeRangeEntry() - { - Prev |= unchecked((int)0x80000000); - } - - public void SetNext(int value) - { - Debug.Assert(value >= 0); - - Next = Next & unchecked((int)0x80000000) | value; - } - - public void SetPrev(int value) - { - Debug.Assert(value >= 0); - - Prev = value; - } - - public void SetRange(int startIndex, int endIndex) - { - Debug.Assert(startIndex > 0); - Debug.Assert(endIndex > 0); - - Next = endIndex; - Prev = startIndex; - MakeRangeEntry(); + index = next; } } - public class AllocationTableHeader + public void FsTrim() { - public long BlockSize { get; } - public long AllocationTableOffset { get; } - public int AllocationTableBlockCount { get; } - public long DataOffset { get; } - public long DataBlockCount { get; } - public int DirectoryTableBlock { get; } - public int FileTableBlock { get; } + int tableSize = BlockToEntryIndex(Header.AllocationTableBlockCount) * EntrySize; + BaseStorage.Slice(tableSize).Fill(SaveDataFileSystem.TrimFillValue); + } - public AllocationTableHeader(IStorage storage) + private void ReadEntries(int entryIndex, Span entries) + { + Debug.Assert(entries.Length >= 2); + + bool isLastBlock = entryIndex == BlockToEntryIndex(Header.AllocationTableBlockCount) - 1; + int entriesToRead = isLastBlock ? 1 : 2; + int offset = entryIndex * EntrySize; + + Span buffer = MemoryMarshal.Cast(entries.Slice(0, entriesToRead)); + + BaseStorage.Read(offset, buffer).ThrowIfFailure(); + } + + private AllocationTableEntry ReadEntry(int entryIndex) + { + Span bytes = stackalloc byte[EntrySize]; + int offset = entryIndex * EntrySize; + + BaseStorage.Read(offset, bytes).ThrowIfFailure(); + + return GetEntryFromBytes(bytes); + } + + private void WriteEntry(int entryIndex, AllocationTableEntry entry) + { + Span bytes = stackalloc byte[EntrySize]; + int offset = entryIndex * EntrySize; + + ref AllocationTableEntry newEntry = ref GetEntryFromBytes(bytes); + newEntry = entry; + + BaseStorage.Write(offset, bytes).ThrowIfFailure(); + } + + // ReSharper disable once UnusedMember.Local + private int GetListHead(int entryIndex) + { + int headIndex = entryIndex; + int tableSize = Header.AllocationTableBlockCount; + int nodesTraversed = 0; + + AllocationTableEntry entry = ReadEntry(entryIndex); + + while (!entry.IsListStart()) { - var reader = new BinaryReader(storage.AsStream()); + nodesTraversed++; + headIndex = entry.Prev & 0x7FFFFFFF; + entry = ReadEntry(headIndex); - BlockSize = reader.ReadInt64(); - - AllocationTableOffset = reader.ReadInt64(); - AllocationTableBlockCount = reader.ReadInt32(); - reader.BaseStream.Position += 4; - - DataOffset = reader.ReadInt64(); - DataBlockCount = reader.ReadInt32(); - reader.BaseStream.Position += 4; - - DirectoryTableBlock = reader.ReadInt32(); - FileTableBlock = reader.ReadInt32(); + if (nodesTraversed > tableSize) + { + throw new InvalidDataException("Cycle detected in allocation table."); + } } + + return headIndex; + } + + private int GetListTail(int entryIndex) + { + int tailIndex = entryIndex; + int tableSize = Header.AllocationTableBlockCount; + int nodesTraversed = 0; + + AllocationTableEntry entry = ReadEntry(entryIndex); + + while (!entry.IsListEnd()) + { + nodesTraversed++; + tailIndex = entry.Next & 0x7FFFFFFF; + entry = ReadEntry(tailIndex); + + if (nodesTraversed > tableSize) + { + throw new InvalidDataException("Cycle detected in allocation table."); + } + } + + return tailIndex; + } + + private static ref AllocationTableEntry GetEntryFromBytes(Span entry) + { + return ref MemoryMarshal.Cast(entry)[0]; + } + + private static int EntryIndexToBlock(int entryIndex) => entryIndex - 1; + private static int BlockToEntryIndex(int blockIndex) => blockIndex + 1; +} + +[StructLayout(LayoutKind.Sequential)] +public struct AllocationTableEntry +{ + public int Prev; + public int Next; + + public int GetPrev() + { + return Prev & 0x7FFFFFFF; + } + + public int GetNext() + { + return Next & 0x7FFFFFFF; + } + + public bool IsListStart() + { + return Prev == unchecked((int)0x80000000); + } + + public bool IsListEnd() + { + return (Next & 0x7FFFFFFF) == 0; + } + + public bool IsMultiBlockSegment() + { + return Next < 0; + } + + public void MakeMultiBlockSegment() + { + Next |= unchecked((int)0x80000000); + } + + public void MakeSingleBlockSegment() + { + Next &= 0x7FFFFFFF; + } + + public bool IsSingleBlockSegment() + { + return Next >= 0; + } + + public void MakeListStart() + { + Prev = unchecked((int)0x80000000); + } + + public bool IsRangeEntry() + { + return Prev != unchecked((int)0x80000000) && Prev < 0; + } + + public void MakeRangeEntry() + { + Prev |= unchecked((int)0x80000000); + } + + public void SetNext(int value) + { + Debug.Assert(value >= 0); + + Next = Next & unchecked((int)0x80000000) | value; + } + + public void SetPrev(int value) + { + Debug.Assert(value >= 0); + + Prev = value; + } + + public void SetRange(int startIndex, int endIndex) + { + Debug.Assert(startIndex > 0); + Debug.Assert(endIndex > 0); + + Next = endIndex; + Prev = startIndex; + MakeRangeEntry(); + } +} + +public class AllocationTableHeader +{ + public long BlockSize { get; } + public long AllocationTableOffset { get; } + public int AllocationTableBlockCount { get; } + public long DataOffset { get; } + public long DataBlockCount { get; } + public int DirectoryTableBlock { get; } + public int FileTableBlock { get; } + + public AllocationTableHeader(IStorage storage) + { + var reader = new BinaryReader(storage.AsStream()); + + BlockSize = reader.ReadInt64(); + + AllocationTableOffset = reader.ReadInt64(); + AllocationTableBlockCount = reader.ReadInt32(); + reader.BaseStream.Position += 4; + + DataOffset = reader.ReadInt64(); + DataBlockCount = reader.ReadInt32(); + reader.BaseStream.Position += 4; + + DirectoryTableBlock = reader.ReadInt32(); + FileTableBlock = reader.ReadInt32(); } } diff --git a/src/LibHac/FsSystem/Save/AllocationTableIterator.cs b/src/LibHac/FsSystem/Save/AllocationTableIterator.cs index 886eb5be..dee510aa 100644 --- a/src/LibHac/FsSystem/Save/AllocationTableIterator.cs +++ b/src/LibHac/FsSystem/Save/AllocationTableIterator.cs @@ -1,78 +1,77 @@ using System; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class AllocationTableIterator { - public class AllocationTableIterator + private AllocationTable Fat { get; } + + public int VirtualBlock { get; private set; } + public int PhysicalBlock { get; private set; } + public int CurrentSegmentSize => _currentSegmentSize; + + private int _nextBlock; + private int _prevBlock; + private int _currentSegmentSize; + + public AllocationTableIterator(AllocationTable table, int initialBlock) { - private AllocationTable Fat { get; } + Fat = table; - public int VirtualBlock { get; private set; } - public int PhysicalBlock { get; private set; } - public int CurrentSegmentSize => _currentSegmentSize; - - private int _nextBlock; - private int _prevBlock; - private int _currentSegmentSize; - - public AllocationTableIterator(AllocationTable table, int initialBlock) + if (!BeginIteration(initialBlock)) { - Fat = table; + throw new ArgumentException($"Attempted to start FAT iteration from an invalid block. ({initialBlock})"); + } + } - if (!BeginIteration(initialBlock)) + public bool BeginIteration(int initialBlock) + { + PhysicalBlock = initialBlock; + Fat.ReadEntry(initialBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); + + return _prevBlock == -1; + } + + public bool MoveNext() + { + if (_nextBlock == -1) return false; + + VirtualBlock += _currentSegmentSize; + PhysicalBlock = _nextBlock; + + Fat.ReadEntry(_nextBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); + + return true; + } + + public bool MovePrevious() + { + if (_prevBlock == -1) return false; + + PhysicalBlock = _prevBlock; + + Fat.ReadEntry(_prevBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); + + VirtualBlock -= _currentSegmentSize; + + return true; + } + + public bool Seek(int block) + { + while (true) + { + if (block < VirtualBlock) { - throw new ArgumentException($"Attempted to start FAT iteration from an invalid block. ({initialBlock})"); + if (!MovePrevious()) return false; } - } - - public bool BeginIteration(int initialBlock) - { - PhysicalBlock = initialBlock; - Fat.ReadEntry(initialBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); - - return _prevBlock == -1; - } - - public bool MoveNext() - { - if (_nextBlock == -1) return false; - - VirtualBlock += _currentSegmentSize; - PhysicalBlock = _nextBlock; - - Fat.ReadEntry(_nextBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); - - return true; - } - - public bool MovePrevious() - { - if (_prevBlock == -1) return false; - - PhysicalBlock = _prevBlock; - - Fat.ReadEntry(_prevBlock, out _nextBlock, out _prevBlock, out _currentSegmentSize); - - VirtualBlock -= _currentSegmentSize; - - return true; - } - - public bool Seek(int block) - { - while (true) + else if (block >= VirtualBlock + CurrentSegmentSize) { - if (block < VirtualBlock) - { - if (!MovePrevious()) return false; - } - else if (block >= VirtualBlock + CurrentSegmentSize) - { - if (!MoveNext()) return false; - } - else - { - return true; - } + if (!MoveNext()) return false; + } + else + { + return true; } } } diff --git a/src/LibHac/FsSystem/Save/AllocationTableStorage.cs b/src/LibHac/FsSystem/Save/AllocationTableStorage.cs index 44cc3728..a893c2db 100644 --- a/src/LibHac/FsSystem/Save/AllocationTableStorage.cs +++ b/src/LibHac/FsSystem/Save/AllocationTableStorage.cs @@ -3,149 +3,148 @@ using System.IO; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class AllocationTableStorage : IStorage { - public class AllocationTableStorage : IStorage + private IStorage BaseStorage { get; } + private int BlockSize { get; } + internal int InitialBlock { get; private set; } + private AllocationTable Fat { get; } + + private long _length; + + public AllocationTableStorage(IStorage data, AllocationTable table, int blockSize, int initialBlock) { - private IStorage BaseStorage { get; } - private int BlockSize { get; } - internal int InitialBlock { get; private set; } - private AllocationTable Fat { get; } + BaseStorage = data; + BlockSize = blockSize; + Fat = table; + InitialBlock = initialBlock; - private long _length; + _length = initialBlock == -1 ? 0 : table.GetListLength(initialBlock) * blockSize; + } - public AllocationTableStorage(IStorage data, AllocationTable table, int blockSize, int initialBlock) + protected override Result DoRead(long offset, Span destination) + { + var iterator = new AllocationTableIterator(Fat, InitialBlock); + + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; + + while (remaining > 0) { - BaseStorage = data; - BlockSize = blockSize; - Fat = table; - InitialBlock = initialBlock; + int blockNum = (int)(inPos / BlockSize); - _length = initialBlock == -1 ? 0 : table.GetListLength(initialBlock) * blockSize; + if (!iterator.Seek(blockNum)) + { + return ResultFs.InvalidAllocationTableOffset.Log(); + } + + int segmentPos = (int)(inPos - (long)iterator.VirtualBlock * BlockSize); + long physicalOffset = iterator.PhysicalBlock * BlockSize + segmentPos; + + int remainingInSegment = iterator.CurrentSegmentSize * BlockSize - segmentPos; + int bytesToRead = Math.Min(remaining, remainingInSegment); + + Result rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead)); + if (rc.IsFailure()) return rc; + + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; } - protected override Result DoRead(long offset, Span destination) + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + var iterator = new AllocationTableIterator(Fat, InitialBlock); + + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + while (remaining > 0) { - var iterator = new AllocationTableIterator(Fat, InitialBlock); + int blockNum = (int)(inPos / BlockSize); - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; - - while (remaining > 0) + if (!iterator.Seek(blockNum)) { - int blockNum = (int)(inPos / BlockSize); - - if (!iterator.Seek(blockNum)) - { - return ResultFs.InvalidAllocationTableOffset.Log(); - } - - int segmentPos = (int)(inPos - (long)iterator.VirtualBlock * BlockSize); - long physicalOffset = iterator.PhysicalBlock * BlockSize + segmentPos; - - int remainingInSegment = iterator.CurrentSegmentSize * BlockSize - segmentPos; - int bytesToRead = Math.Min(remaining, remainingInSegment); - - Result rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead)); - if (rc.IsFailure()) return rc; - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; + return ResultFs.InvalidAllocationTableOffset.Log(); } - return Result.Success; + int segmentPos = (int)(inPos - (long)iterator.VirtualBlock * BlockSize); + long physicalOffset = iterator.PhysicalBlock * BlockSize + segmentPos; + + int remainingInSegment = iterator.CurrentSegmentSize * BlockSize - segmentPos; + int bytesToWrite = Math.Min(remaining, remainingInSegment); + + Result rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite)); + if (rc.IsFailure()) return rc; + + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; } - protected override Result DoWrite(long offset, ReadOnlySpan source) + return Result.Success; + } + + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } + + protected override Result DoGetSize(out long size) + { + size = _length; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + int oldBlockCount = (int)BitUtil.DivideUp(_length, BlockSize); + int newBlockCount = (int)BitUtil.DivideUp(size, BlockSize); + + if (oldBlockCount == newBlockCount) return Result.Success; + + if (oldBlockCount == 0) { - var iterator = new AllocationTableIterator(Fat, InitialBlock); - - long inPos = offset; - int outPos = 0; - int remaining = source.Length; - - while (remaining > 0) - { - int blockNum = (int)(inPos / BlockSize); - - if (!iterator.Seek(blockNum)) - { - return ResultFs.InvalidAllocationTableOffset.Log(); - } - - int segmentPos = (int)(inPos - (long)iterator.VirtualBlock * BlockSize); - long physicalOffset = iterator.PhysicalBlock * BlockSize + segmentPos; - - int remainingInSegment = iterator.CurrentSegmentSize * BlockSize - segmentPos; - int bytesToWrite = Math.Min(remaining, remainingInSegment); - - Result rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite)); - if (rc.IsFailure()) return rc; - - outPos += bytesToWrite; - inPos += bytesToWrite; - remaining -= bytesToWrite; - } - - return Result.Success; - } - - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } - - protected override Result DoGetSize(out long size) - { - size = _length; - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - int oldBlockCount = (int)BitUtil.DivideUp(_length, BlockSize); - int newBlockCount = (int)BitUtil.DivideUp(size, BlockSize); - - if (oldBlockCount == newBlockCount) return Result.Success; - - if (oldBlockCount == 0) - { - InitialBlock = Fat.Allocate(newBlockCount); - if (InitialBlock == -1) throw new IOException("Not enough space to resize file."); - - _length = newBlockCount * BlockSize; - - return Result.Success; - } - - if (newBlockCount == 0) - { - Fat.Free(InitialBlock); - - InitialBlock = int.MinValue; - _length = 0; - - return Result.Success; - } - - if (newBlockCount > oldBlockCount) - { - int newBlocks = Fat.Allocate(newBlockCount - oldBlockCount); - if (newBlocks == -1) throw new IOException("Not enough space to resize file."); - - Fat.Join(InitialBlock, newBlocks); - } - else - { - int oldBlocks = Fat.Trim(InitialBlock, newBlockCount); - Fat.Free(oldBlocks); - } + InitialBlock = Fat.Allocate(newBlockCount); + if (InitialBlock == -1) throw new IOException("Not enough space to resize file."); _length = newBlockCount * BlockSize; return Result.Success; } + + if (newBlockCount == 0) + { + Fat.Free(InitialBlock); + + InitialBlock = int.MinValue; + _length = 0; + + return Result.Success; + } + + if (newBlockCount > oldBlockCount) + { + int newBlocks = Fat.Allocate(newBlockCount - oldBlockCount); + if (newBlocks == -1) throw new IOException("Not enough space to resize file."); + + Fat.Join(InitialBlock, newBlocks); + } + else + { + int oldBlocks = Fat.Trim(InitialBlock, newBlockCount); + Fat.Free(oldBlocks); + } + + _length = newBlockCount * BlockSize; + + return Result.Success; } } diff --git a/src/LibHac/FsSystem/Save/BufferedStorage.cs b/src/LibHac/FsSystem/Save/BufferedStorage.cs index dbec32e6..91d10d16 100644 --- a/src/LibHac/FsSystem/Save/BufferedStorage.cs +++ b/src/LibHac/FsSystem/Save/BufferedStorage.cs @@ -10,1222 +10,1365 @@ using LibHac.Util; using Buffer = LibHac.Fs.Buffer; using CacheHandle = System.Int64; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +/// +/// An that provides buffered access to a base . +/// +public class BufferedStorage : IStorage { + private const long InvalidOffset = long.MaxValue; + private const int InvalidIndex = -1; + /// - /// An that provides buffered access to a base . + /// Caches a single block of data for a /// - public class BufferedStorage : IStorage + private struct Cache : IDisposable { - private const long InvalidOffset = long.MaxValue; - private const int InvalidIndex = -1; + private ref struct FetchParameter + { + public long Offset; + public Span Buffer; + } + + private BufferedStorage BufferedStorage { get; set; } + private Buffer MemoryRange { get; set; } + private CacheHandle CacheHandle { get; set; } + private long Offset { get; set; } + private bool _isValid; + private bool _isDirty; + private int ReferenceCount { get; set; } + private int Index { get; set; } + private int NextIndex { get; set; } + private int PrevIndex { get; set; } + + private ref Cache Next => ref BufferedStorage.Caches[NextIndex]; + private ref Cache Prev => ref BufferedStorage.Caches[PrevIndex]; + + public void Dispose() + { + FinalizeObject(); + } + + public void Initialize(BufferedStorage bufferedStorage, int index) + { + // Note: C# can't have default constructors on structs, so the default constructor code was + // moved into Initialize since Initialize is always called right after the constructor. + Offset = InvalidOffset; + ReferenceCount = 1; + Index = index; + NextIndex = InvalidIndex; + PrevIndex = InvalidIndex; + // End default constructor code + + Assert.SdkRequiresNotNull(bufferedStorage); + Assert.SdkRequires(BufferedStorage == null); + + BufferedStorage = bufferedStorage; + Link(); + } + + public void FinalizeObject() + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresEqual(0, ReferenceCount); + + // If we're valid, acquire our cache handle and free our buffer. + if (IsValid()) + { + IBufferManager bufferManager = BufferedStorage.BufferManager; + if (!_isDirty) + { + Assert.SdkAssert(MemoryRange.IsNull); + MemoryRange = bufferManager.AcquireCache(CacheHandle); + } + + if (!MemoryRange.IsNull) + { + bufferManager.DeallocateBuffer(MemoryRange); + MemoryRange = Buffer.Empty; + } + } + + // Clear all our members. + BufferedStorage = null; + Offset = InvalidOffset; + _isValid = false; + _isDirty = false; + NextIndex = InvalidIndex; + PrevIndex = InvalidIndex; + } /// - /// Caches a single block of data for a + /// Decrements the ref-count and adds the to its 's + /// fetch list if the has no more references, and registering the buffer with + /// the if not dirty. /// - private struct Cache : IDisposable + public void Link() { - private ref struct FetchParameter + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresLess(0, ReferenceCount); + + ReferenceCount--; + if (ReferenceCount == 0) { - public long Offset; - public Span Buffer; - } + Assert.SdkAssert(NextIndex == InvalidIndex); + Assert.SdkAssert(PrevIndex == InvalidIndex); - private BufferedStorage BufferedStorage { get; set; } - private Buffer MemoryRange { get; set; } - private CacheHandle CacheHandle { get; set; } - private long Offset { get; set; } - private bool _isValid; - private bool _isDirty; - private int ReferenceCount { get; set; } - private int Index { get; set; } - private int NextIndex { get; set; } - private int PrevIndex { get; set; } - - private ref Cache Next => ref BufferedStorage.Caches[NextIndex]; - private ref Cache Prev => ref BufferedStorage.Caches[PrevIndex]; - - public void Dispose() - { - FinalizeObject(); - } - - public void Initialize(BufferedStorage bufferedStorage, int index) - { - // Note: C# can't have default constructors on structs, so the default constructor code was - // moved into Initialize since Initialize is always called right after the constructor. - Offset = InvalidOffset; - ReferenceCount = 1; - Index = index; - NextIndex = InvalidIndex; - PrevIndex = InvalidIndex; - // End default constructor code - - Assert.SdkRequiresNotNull(bufferedStorage); - Assert.SdkRequires(BufferedStorage == null); - - BufferedStorage = bufferedStorage; - Link(); - } - - public void FinalizeObject() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresEqual(0, ReferenceCount); - - // If we're valid, acquire our cache handle and free our buffer. - if (IsValid()) + // If the fetch list is empty we can simply add it as the only cache in the list. + if (BufferedStorage.NextFetchCacheIndex == InvalidIndex) { - IBufferManager bufferManager = BufferedStorage.BufferManager; - if (!_isDirty) - { - Assert.SdkAssert(MemoryRange.IsNull); - MemoryRange = bufferManager.AcquireCache(CacheHandle); - } - - if (!MemoryRange.IsNull) - { - bufferManager.DeallocateBuffer(MemoryRange); - MemoryRange = Buffer.Empty; - } - } - - // Clear all our members. - BufferedStorage = null; - Offset = InvalidOffset; - _isValid = false; - _isDirty = false; - NextIndex = InvalidIndex; - PrevIndex = InvalidIndex; - } - - /// - /// Decrements the ref-count and adds the to its 's - /// fetch list if the has no more references, and registering the buffer with - /// the if not dirty. - /// - public void Link() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresLess(0, ReferenceCount); - - ReferenceCount--; - if (ReferenceCount == 0) - { - Assert.SdkAssert(NextIndex == InvalidIndex); - Assert.SdkAssert(PrevIndex == InvalidIndex); - - // If the fetch list is empty we can simply add it as the only cache in the list. - if (BufferedStorage.NextFetchCacheIndex == InvalidIndex) - { - BufferedStorage.NextFetchCacheIndex = Index; - NextIndex = Index; - PrevIndex = Index; - } - else - { - // Check against a cache being registered twice. - ref Cache cache = ref BufferedStorage.NextFetchCache; - do - { - if (cache.IsValid() && Hits(cache.Offset, BufferedStorage.BlockSize)) - { - _isValid = false; - break; - } - - cache = ref cache.Next; - } while (cache.Index != BufferedStorage.NextFetchCacheIndex); - - // Verify the end of the fetch list loops back to the start. - Assert.SdkAssert(BufferedStorage.NextFetchCache.PrevIndex != InvalidIndex); - Assert.SdkEqual(BufferedStorage.NextFetchCache.Prev.NextIndex, - BufferedStorage.NextFetchCacheIndex); - - // Link into the fetch list. - NextIndex = BufferedStorage.NextFetchCacheIndex; - PrevIndex = BufferedStorage.NextFetchCache.PrevIndex; - Next.PrevIndex = Index; - Prev.NextIndex = Index; - - // Insert invalid caches at the start of the list so they'll - // be used first when a fetch cache is needed. - if (!IsValid()) - BufferedStorage.NextFetchCacheIndex = Index; - } - - // If we're not valid, clear our offset. - if (!IsValid()) - { - Offset = InvalidOffset; - _isDirty = false; - } - - // Ensure our buffer state is coherent. - // We can let go of our buffer if it's not dirty, allowing the buffer to be used elsewhere if needed. - if (!MemoryRange.IsNull && !IsDirty()) - { - // If we're valid, register the buffer with the buffer manager for possible later retrieval. - // Otherwise the the data in the buffer isn't needed, so deallocate it. - if (IsValid()) - { - CacheHandle = BufferedStorage.BufferManager.RegisterCache(MemoryRange, - new IBufferManager.BufferAttribute()); - } - else - { - BufferedStorage.BufferManager.DeallocateBuffer(MemoryRange); - } - - MemoryRange = default; - } - } - } - - /// - /// Increments the ref-count and removes the from its - /// 's fetch list if needed. - /// - public void Unlink() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresGreaterEqual(ReferenceCount, 0); - - ReferenceCount++; - if (ReferenceCount == 1) - { - // If we're the first to grab this Cache, the Cache should be in the BufferedStorage's fetch list. - Assert.SdkNotEqual(NextIndex, InvalidIndex); - Assert.SdkNotEqual(PrevIndex, InvalidIndex); - Assert.SdkEqual(Next.PrevIndex, Index); - Assert.SdkEqual(Prev.NextIndex, Index); - - // Set the new fetch list head if this Cache is the current head - if (BufferedStorage.NextFetchCacheIndex == Index) - { - if (NextIndex != Index) - { - BufferedStorage.NextFetchCacheIndex = NextIndex; - } - else - { - BufferedStorage.NextFetchCacheIndex = InvalidIndex; - } - } - - BufferedStorage.NextAcquireCacheIndex = Index; - - Next.PrevIndex = PrevIndex; - Prev.NextIndex = NextIndex; - NextIndex = InvalidIndex; - PrevIndex = InvalidIndex; + BufferedStorage.NextFetchCacheIndex = Index; + NextIndex = Index; + PrevIndex = Index; } else { - Assert.SdkEqual(NextIndex, InvalidIndex); - Assert.SdkEqual(PrevIndex, InvalidIndex); - } - } - - /// - /// Reads the data from the base contained in this 's buffer. - /// The must contain valid data before calling, and the - /// must be inside the block of data held by this . - /// - /// The offset in the base to be read from. - /// The buffer in which to place the read data. - public void Read(long offset, Span buffer) - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(IsValid()); - Assert.SdkRequires(Hits(offset, 1)); - Assert.SdkRequires(!MemoryRange.IsNull); - - long readOffset = offset - Offset; - long readableOffsetMax = BufferedStorage.BlockSize - buffer.Length; - - Assert.SdkLessEqual(0, readOffset); - Assert.SdkLessEqual(readOffset, readableOffsetMax); - - Span cacheBuffer = MemoryRange.Span.Slice((int)readOffset, buffer.Length); - cacheBuffer.CopyTo(buffer); - } - - /// - /// Buffers data to be written to the base when this is flushed. - /// The must contain valid data before calling, and the - /// must be inside the block of data held by this . - /// - /// The offset in the base to be written to. - /// The buffer containing the data to be written. - public void Write(long offset, ReadOnlySpan buffer) - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(IsValid()); - Assert.SdkRequires(Hits(offset, 1)); - Assert.SdkRequires(!MemoryRange.IsNull); - - long writeOffset = offset - Offset; - long writableOffsetMax = BufferedStorage.BlockSize - buffer.Length; - - Assert.SdkLessEqual(0, writeOffset); - Assert.SdkLessEqual(writeOffset, writableOffsetMax); - - Span cacheBuffer = MemoryRange.Span.Slice((int)writeOffset, buffer.Length); - buffer.CopyTo(cacheBuffer); - _isDirty = true; - } - - /// - /// If this is dirty, flushes its data to the base . - /// The must contain valid data before calling. - /// - /// The of the operation. - public Result Flush() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(IsValid()); - - if (_isDirty) - { - Assert.SdkRequires(!MemoryRange.IsNull); - - long baseSize = BufferedStorage.BaseStorageSize; - long blockSize = BufferedStorage.BlockSize; - long flushSize = Math.Min(blockSize, baseSize - Offset); - - SubStorage baseStorage = BufferedStorage.BaseStorage; - Span cacheBuffer = MemoryRange.Span; - Assert.SdkEqual(flushSize, cacheBuffer.Length); - - Result rc = baseStorage.Write(Offset, cacheBuffer); - if (rc.IsFailure()) return rc; - - _isDirty = false; - - BufferManagerUtility.EnableBlockingBufferManagerAllocation(); - } - - return Result.Success; - } - - /// - /// Prepares this to fetch a new block from the base . - /// If the caller has the only reference to this Cache, - /// the Cache's buffer will be flushed and the Cache invalidated. While the Cache is - /// prepared to fetch, will skip it when iterating all the Caches. - /// - /// The of any attempted flush, and if the - /// is prepared to fetch; if not. - public (Result Result, bool IsPrepared) PrepareFetch() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(IsValid()); - Assert.SdkRequires(Monitor.IsEntered(BufferedStorage.Locker)); - - (Result Result, bool IsPrepared) result = (Result.Success, false); - - if (ReferenceCount == 1) - { - result.Result = Flush(); - - if (result.Result.IsSuccess()) + // Check against a cache being registered twice. + ref Cache cache = ref BufferedStorage.NextFetchCache; + do { - _isValid = false; - ReferenceCount = 0; - result.IsPrepared = true; - } - } - - return result; - } - - /// - /// Marks the as unprepared to cache a new block, - /// allowing to acquire it while iterating. - /// - public void UnprepareFetch() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(!IsValid()); - Assert.SdkRequires(!_isDirty); - Assert.SdkRequires(Monitor.IsEntered(BufferedStorage.Locker)); - - _isValid = true; - ReferenceCount = 1; - } - - /// - /// Reads the storage block containing the specified offset into this 's buffer. - /// - /// An offset in the block to fetch. - /// : The operation was successful.
- /// : A buffer could not be allocated.
- public Result Fetch(long offset) - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(!IsValid()); - Assert.SdkRequires(!_isDirty); - - Result rc; - - // Make sure this Cache has an allocated buffer - if (MemoryRange.IsNull) - { - rc = AllocateFetchBuffer(); - if (rc.IsFailure()) return rc; - } - - CalcFetchParameter(out FetchParameter fetchParam, offset); - - rc = BufferedStorage.BaseStorage.Read(fetchParam.Offset, fetchParam.Buffer); - if (rc.IsFailure()) return rc; - - Offset = fetchParam.Offset; - Assert.SdkAssert(Hits(offset, 1)); - - return Result.Success; - } - - /// - /// Fills this 's buffer from an input buffer containing a block of data - /// read from the base . - /// - /// The start offset of the block in the base - /// that the data was read from. - /// A buffer containing the data read from the base . - /// : The operation was successful.
- /// : A buffer could not be allocated.
- public Result FetchFromBuffer(long offset, ReadOnlySpan buffer) - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequiresEqual(NextIndex, InvalidIndex); - Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); - Assert.SdkRequires(!IsValid()); - Assert.SdkRequires(!_isDirty); - Assert.SdkRequiresAligned((ulong)offset, (int)BufferedStorage.BlockSize); - - // Make sure this Cache has an allocated buffer - if (MemoryRange.IsNull) - { - Result rc = AllocateFetchBuffer(); - if (rc.IsFailure()) return rc; - } - - CalcFetchParameter(out FetchParameter fetchParam, offset); - Assert.SdkEqual(fetchParam.Offset, offset); - Assert.SdkLessEqual(fetchParam.Buffer.Length, buffer.Length); - - buffer.Slice(0, fetchParam.Buffer.Length).CopyTo(fetchParam.Buffer); - Offset = fetchParam.Offset; - Assert.SdkAssert(Hits(offset, 1)); - - return Result.Success; - } - - /// - /// Tries to retrieve the cache's memory buffer from the . - /// - /// if the memory buffer was available. - /// if the buffer has been evicted from the cache. - public bool TryAcquireCache() - { - Assert.SdkRequiresNotNull(BufferedStorage); - Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); - Assert.SdkRequires(IsValid()); - - if (!MemoryRange.IsNull) - return true; - - MemoryRange = BufferedStorage.BufferManager.AcquireCache(CacheHandle); - _isValid = !MemoryRange.IsNull; - return _isValid; - } - - /// - /// Invalidates the data in this . - /// - public void Invalidate() - { - Assert.SdkRequiresNotNull(BufferedStorage); - _isValid = false; - } - - /// - /// Does this have a valid buffer or are there any references to this Cache? - /// - /// if this has a valid buffer - /// or if anybody currently has a reference to this Cache. Otherwise, . - public bool IsValid() - { - Assert.SdkRequiresNotNull(BufferedStorage); - - return _isValid || ReferenceCount > 0; - } - - /// - /// Does this have modified data that needs - /// to be flushed to the base ? - /// - /// if this has unflushed data. - /// Otherwise, . - public bool IsDirty() - { - Assert.SdkRequiresNotNull(BufferedStorage); - - return _isDirty; - } - - /// - /// Checks if the covers any of the specified range. - /// - /// The start offset of the range to check. - /// The size of the range to check. - /// if this 's range covers any of the input range. - /// Otherwise, . - public bool Hits(long offset, long size) - { - Assert.SdkRequiresNotNull(BufferedStorage); - - long blockSize = BufferedStorage.BlockSize; - return (offset < Offset + blockSize) && (Offset < offset + size); - } - - /// - /// Allocates a buffer for this . - /// Should only be called when there is not already an allocated buffer. - /// - /// : The operation was successful.
- /// : A buffer could not be allocated.
- private Result AllocateFetchBuffer() - { - IBufferManager bufferManager = BufferedStorage.BufferManager; - Assert.SdkAssert(bufferManager.AcquireCache(CacheHandle).IsNull); - - Result rc = BufferManagerUtility.AllocateBufferUsingBufferManagerContext(out Buffer bufferTemp, - bufferManager, (int)BufferedStorage.BlockSize, new IBufferManager.BufferAttribute(), - static (in Buffer buffer) => !buffer.IsNull); - - // Clear the current MemoryRange if allocation failed. - MemoryRange = rc.IsSuccess() ? bufferTemp : default; - return Result.Success; - } - - /// - /// Calculates the parameters used to fetch the block containing the - /// specified offset in the base . - /// - /// When this function returns, contains - /// the parameters that can be used to fetch the block. - /// The offset to be fetched. - private void CalcFetchParameter(out FetchParameter fetchParam, long offset) - { - long blockSize = BufferedStorage.BlockSize; - long storageOffset = Alignment.AlignDownPow2(offset, (uint)BufferedStorage.BlockSize); - long baseSize = BufferedStorage.BaseStorageSize; - long remainingSize = baseSize - storageOffset; - long cacheSize = Math.Min(blockSize, remainingSize); - Span cacheBuffer = MemoryRange.Span.Slice(0, (int)cacheSize); - - Assert.SdkLessEqual(0, offset); - Assert.SdkLess(offset, baseSize); - - fetchParam = new FetchParameter - { - Offset = storageOffset, - Buffer = cacheBuffer - }; - } - } - - /// - /// Allows iteration over the in a . - /// Several options exist for which Caches to iterate. - /// - private ref struct SharedCache - { - // ReSharper disable once MemberHidesStaticFromOuterClass - public Ref Cache { get; private set; } - private Ref StartCache { get; } - public BufferedStorage BufferedStorage { get; } - - public SharedCache(BufferedStorage bufferedStorage) - { - Assert.SdkRequiresNotNull(bufferedStorage); - Cache = default; - StartCache = new Ref(ref bufferedStorage.NextAcquireCache); - BufferedStorage = bufferedStorage; - } - - public void Dispose() - { - lock (BufferedStorage.Locker) - { - Release(); - } - } - - /// - /// Moves to the next that contains data from the specified range. - /// - /// The start offset of the range. - /// The size of the range. - /// if a from the - /// specified range was found. if no matching Caches exist, - /// or if all matching Caches have already been iterated. - public bool AcquireNextOverlappedCache(long offset, long size) - { - Assert.SdkRequiresNotNull(BufferedStorage); - - bool isFirst = Cache.IsNull; - ref Cache start = ref isFirst ? ref StartCache.Value : ref Unsafe.Add(ref Cache.Value, 1); - - // Make sure the Cache instance is in-range. - Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, - ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); - - Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), - BufferedStorage.CacheCount))); - - lock (BufferedStorage.Locker) - { - Release(); - Assert.SdkAssert(Cache.IsNull); - - for (ref Cache cache = ref start; ; cache = ref Unsafe.Add(ref cache, 1)) - { - // Wrap to the front of the list if we've reached the end. - ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), - BufferedStorage.CacheCount); - if (!Unsafe.IsAddressLessThan(ref cache, ref end)) - { - cache = ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches); - } - - // Break if we've iterated all the Caches - if (!isFirst && Unsafe.AreSame(ref cache, ref StartCache.Value)) + if (cache.IsValid() && Hits(cache.Offset, BufferedStorage.BlockSize)) { + _isValid = false; break; } - if (cache.IsValid() && cache.Hits(offset, size) && cache.TryAcquireCache()) - { - cache.Unlink(); - Cache = new Ref(ref cache); - return true; - } + cache = ref cache.Next; + } while (cache.Index != BufferedStorage.NextFetchCacheIndex); - isFirst = false; - } + // Verify the end of the fetch list loops back to the start. + Assert.SdkAssert(BufferedStorage.NextFetchCache.PrevIndex != InvalidIndex); + Assert.SdkEqual(BufferedStorage.NextFetchCache.Prev.NextIndex, + BufferedStorage.NextFetchCacheIndex); - Cache = default; - return false; + // Link into the fetch list. + NextIndex = BufferedStorage.NextFetchCacheIndex; + PrevIndex = BufferedStorage.NextFetchCache.PrevIndex; + Next.PrevIndex = Index; + Prev.NextIndex = Index; + + // Insert invalid caches at the start of the list so they'll + // be used first when a fetch cache is needed. + if (!IsValid()) + BufferedStorage.NextFetchCacheIndex = Index; } - } - /// - /// Moves to the next dirty . - /// - /// if a dirty was found. - /// if no dirty Caches exist, - /// or if all dirty Caches have already been iterated. - public bool AcquireNextDirtyCache() - { - Assert.SdkRequiresNotNull(BufferedStorage); - - ref Cache start = ref Cache.IsNull - ? ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches) - : ref Unsafe.Add(ref Cache.Value, 1); - - ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), - BufferedStorage.CacheCount); - - // Validate the range. - Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, - ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); - - Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, ref end)); - - Release(); - Assert.SdkAssert(Cache.IsNull); - - // Find the next dirty Cache - for (ref Cache cache = ref start; - Unsafe.IsAddressLessThan(ref cache, ref end); - cache = ref Unsafe.Add(ref cache, 1)) + // If we're not valid, clear our offset. + if (!IsValid()) { - if (cache.IsValid() && cache.IsDirty() && cache.TryAcquireCache()) + Offset = InvalidOffset; + _isDirty = false; + } + + // Ensure our buffer state is coherent. + // We can let go of our buffer if it's not dirty, allowing the buffer to be used elsewhere if needed. + if (!MemoryRange.IsNull && !IsDirty()) + { + // If we're valid, register the buffer with the buffer manager for possible later retrieval. + // Otherwise the the data in the buffer isn't needed, so deallocate it. + if (IsValid()) { - cache.Unlink(); - Cache = new Ref(ref cache); - return true; + CacheHandle = BufferedStorage.BufferManager.RegisterCache(MemoryRange, + new IBufferManager.BufferAttribute()); } - } - - Cache = default; - return false; - } - - /// - /// Moves to the next valid . - /// - /// if a valid was found. - /// if no valid Caches exist, - /// or if all valid Caches have already been iterated. - public bool AcquireNextValidCache() - { - Assert.SdkRequiresNotNull(BufferedStorage); - - ref Cache start = ref Cache.IsNull - ? ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches) - : ref Unsafe.Add(ref Cache.Value, 1); - - ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), - BufferedStorage.CacheCount); - - // Validate the range. - Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, - ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); - - Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, ref end)); - - Release(); - Assert.SdkAssert(Cache.IsNull); - - // Find the next valid Cache - for (ref Cache cache = ref start; - Unsafe.IsAddressLessThan(ref cache, ref end); - cache = ref Unsafe.Add(ref cache, 1)) - { - if (cache.IsValid() && cache.TryAcquireCache()) + else { - cache.Unlink(); - Cache = new Ref(ref cache); - return true; + BufferedStorage.BufferManager.DeallocateBuffer(MemoryRange); } + + MemoryRange = default; } - - Cache = default; - return false; } + } - /// - /// Moves to a that can be used for - /// fetching a new block from the base . - /// - /// if a was acquired. - /// Otherwise, . - public bool AcquireFetchableCache() + /// + /// Increments the ref-count and removes the from its + /// 's fetch list if needed. + /// + public void Unlink() + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresGreaterEqual(ReferenceCount, 0); + + ReferenceCount++; + if (ReferenceCount == 1) { - Assert.SdkRequiresNotNull(BufferedStorage); + // If we're the first to grab this Cache, the Cache should be in the BufferedStorage's fetch list. + Assert.SdkNotEqual(NextIndex, InvalidIndex); + Assert.SdkNotEqual(PrevIndex, InvalidIndex); + Assert.SdkEqual(Next.PrevIndex, Index); + Assert.SdkEqual(Prev.NextIndex, Index); - lock (BufferedStorage.Locker) + // Set the new fetch list head if this Cache is the current head + if (BufferedStorage.NextFetchCacheIndex == Index) { - Release(); - Assert.SdkAssert(Cache.IsNull); - - Cache = new Ref(ref BufferedStorage.NextFetchCache); - - if (!Cache.IsNull) + if (NextIndex != Index) { - if (Cache.Value.IsValid()) - Cache.Value.TryAcquireCache(); - - Cache.Value.Unlink(); + BufferedStorage.NextFetchCacheIndex = NextIndex; } - - return !Cache.IsNull; - } - } - - /// - /// Reads from the current 's buffer. - /// The provided must be inside the block of - /// data held by the . - /// - /// The offset in the base to be read from. - /// The buffer in which to place the read data. - public void Read(long offset, Span buffer) - { - Assert.SdkRequires(!Cache.IsNull); - Cache.Value.Read(offset, buffer); - } - - /// - /// Buffers data to be written to the base when the current - /// is flushed. The provided - /// must be contained by the block of data held by the . - /// - /// The offset in the base to be written to. - /// The buffer containing the data to be written. - public void Write(long offset, ReadOnlySpan buffer) - { - Assert.SdkRequires(!Cache.IsNull); - Cache.Value.Write(offset, buffer); - } - - /// - /// If the current is dirty, - /// flushes its data to the base . - /// - /// The of the operation. - public Result Flush() - { - Assert.SdkRequires(!Cache.IsNull); - return Cache.Value.Flush(); - } - - /// - /// Invalidates the data in the current . - /// Any dirty data will be discarded. - /// - public void Invalidate() - { - Assert.SdkRequires(!Cache.IsNull); - Cache.Value.Invalidate(); - } - - /// - /// Checks if the current covers any of the specified range. - /// - /// The start offset of the range to check. - /// The size of the range to check. - /// if the current 's range - /// covers any of the input range. Otherwise, . - public bool Hits(long offset, long size) - { - Assert.SdkRequires(!Cache.IsNull); - return Cache.Value.Hits(offset, size); - } - - /// - /// Releases the current to return to the fetch list. - /// - private void Release() - { - if (!Cache.IsNull) - { - // Make sure the Cache instance is in-range. - Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref Cache.Value, - ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); - - Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref Cache.Value, - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), - BufferedStorage.CacheCount))); - - Cache.Value.Link(); - Cache = default; - } - } - } - - /// - /// Provides exclusive access to a - /// entry in a . - /// - private ref struct UniqueCache - { - // ReSharper disable once MemberHidesStaticFromOuterClass - private Ref Cache { get; set; } - private BufferedStorage BufferedStorage { get; } - - public UniqueCache(BufferedStorage bufferedStorage) - { - Assert.SdkRequiresNotNull(bufferedStorage); - Cache = default; - BufferedStorage = bufferedStorage; - } - - /// - /// Disposes the , releasing any held . - /// - public void Dispose() - { - if (!Cache.IsNull) - { - lock (BufferedStorage.Locker) + else { - Cache.Value.UnprepareFetch(); + BufferedStorage.NextFetchCacheIndex = InvalidIndex; } } + + BufferedStorage.NextAcquireCacheIndex = Index; + + Next.PrevIndex = PrevIndex; + Prev.NextIndex = NextIndex; + NextIndex = InvalidIndex; + PrevIndex = InvalidIndex; } - - /// - /// Attempts to gain exclusive access to the held by - /// and prepare it to read a new block from the base . - /// - /// The to gain exclusive access to. - /// The of the operation, and if exclusive - /// access to the was gained; if not. - public (Result Result, bool wasUpgradeSuccessful) Upgrade(in SharedCache sharedCache) + else { - Assert.SdkRequires(BufferedStorage == sharedCache.BufferedStorage); - Assert.SdkRequires(!sharedCache.Cache.IsNull); - - lock (BufferedStorage.Locker) - { - (Result Result, bool wasUpgradeSuccessful) result = sharedCache.Cache.Value.PrepareFetch(); - - if (result.Result.IsSuccess() && result.wasUpgradeSuccessful) - Cache = sharedCache.Cache; - - return result; - } - } - - /// - /// Reads the storage block containing the specified offset into the - /// 's buffer, and sets the Cache to that offset. - /// - /// An offset in the block to fetch. - /// : The operation was successful.
- /// : A buffer could not be allocated.
- public Result Fetch(long offset) - { - Assert.SdkRequires(!Cache.IsNull); - - return Cache.Value.Fetch(offset); - } - - /// - /// Fills the 's buffer from an input buffer containing a block of data - /// read from the base , and sets the Cache to that offset. - /// - /// The start offset of the block in the base - /// that the data was read from. - /// A buffer containing the data read from the base . - /// : The operation was successful.
- /// : A buffer could not be allocated.
- public Result FetchFromBuffer(long offset, ReadOnlySpan buffer) - { - Assert.SdkRequires(!Cache.IsNull); - - return Cache.Value.FetchFromBuffer(offset, buffer); + Assert.SdkEqual(NextIndex, InvalidIndex); + Assert.SdkEqual(PrevIndex, InvalidIndex); } } - private SubStorage BaseStorage { get; set; } - private IBufferManager BufferManager { get; set; } - private long BlockSize { get; set; } - - private long _baseStorageSize; - private long BaseStorageSize - { - get => _baseStorageSize; - set => _baseStorageSize = value; - } - - private Cache[] Caches { get; set; } - private int CacheCount { get; set; } - private int NextAcquireCacheIndex { get; set; } - private int NextFetchCacheIndex { get; set; } - private object Locker { get; } = new(); - private bool BulkReadEnabled { get; set; } - /// - /// The at which new s will begin iterating. + /// Reads the data from the base contained in this 's buffer. + /// The must contain valid data before calling, and the + /// must be inside the block of data held by this . /// - private ref Cache NextAcquireCache => ref Caches[NextAcquireCacheIndex]; + /// The offset in the base to be read from. + /// The buffer in which to place the read data. + public void Read(long offset, Span buffer) + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(IsValid()); + Assert.SdkRequires(Hits(offset, 1)); + Assert.SdkRequires(!MemoryRange.IsNull); + + long readOffset = offset - Offset; + long readableOffsetMax = BufferedStorage.BlockSize - buffer.Length; + + Assert.SdkLessEqual(0, readOffset); + Assert.SdkLessEqual(readOffset, readableOffsetMax); + + Span cacheBuffer = MemoryRange.Span.Slice((int)readOffset, buffer.Length); + cacheBuffer.CopyTo(buffer); + } /// - /// A list of s that can be used for fetching - /// new blocks of data from the base . + /// Buffers data to be written to the base when this is flushed. + /// The must contain valid data before calling, and the + /// must be inside the block of data held by this . /// - private ref Cache NextFetchCache => ref Caches[NextFetchCacheIndex]; - - /// - /// Creates an uninitialized . - /// - public BufferedStorage() + /// The offset in the base to be written to. + /// The buffer containing the data to be written. + public void Write(long offset, ReadOnlySpan buffer) { - NextAcquireCacheIndex = InvalidIndex; - NextFetchCacheIndex = InvalidIndex; + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(IsValid()); + Assert.SdkRequires(Hits(offset, 1)); + Assert.SdkRequires(!MemoryRange.IsNull); + + long writeOffset = offset - Offset; + long writableOffsetMax = BufferedStorage.BlockSize - buffer.Length; + + Assert.SdkLessEqual(0, writeOffset); + Assert.SdkLessEqual(writeOffset, writableOffsetMax); + + Span cacheBuffer = MemoryRange.Span.Slice((int)writeOffset, buffer.Length); + buffer.CopyTo(cacheBuffer); + _isDirty = true; } /// - /// Disposes the , flushing any cached data. - /// - public override void Dispose() - { - FinalizeObject(); - base.Dispose(); - } - - /// - /// Initializes the . - /// Calling this method again afterwards will flush the current cache and - /// reinitialize the with the new parameters. - /// - /// The base storage to use. - /// The buffer manager used to allocate and cache memory. - /// The size of each cached block. Must be a power of 2. - /// The maximum number of blocks that can be cached at one time. - /// - public Result Initialize(SubStorage baseStorage, IBufferManager bufferManager, int blockSize, int cacheCount) - { - Assert.SdkRequiresNotNull(baseStorage); - Assert.SdkRequiresNotNull(bufferManager); - Assert.SdkRequiresLess(0, blockSize); - Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); - Assert.SdkRequiresLess(0, cacheCount); - - // Get the base storage size. - Result rc = baseStorage.GetSize(out _baseStorageSize); - if (rc.IsFailure()) return rc; - - // Set members. - BaseStorage = baseStorage; - BufferManager = bufferManager; - BlockSize = blockSize; - CacheCount = cacheCount; - - // Allocate the caches. - if (Caches != null) - { - for (int i = 0; i < Caches.Length; i++) - { - Caches[i].FinalizeObject(); - } - } - - Caches = new Cache[cacheCount]; - if (Caches == null) - { - return ResultFs.AllocationMemoryFailedInBufferedStorageA.Log(); - } - - // Initialize the caches. - for (int i = 0; i < Caches.Length; i++) - { - Caches[i].Initialize(this, i); - } - - NextAcquireCacheIndex = 0; - return Result.Success; - } - - /// - /// Finalizes this , flushing all buffers and leaving it in an uninitialized state. - /// - public void FinalizeObject() - { - BaseStorage = null; - BaseStorageSize = 0; - - foreach (Cache cache in Caches) - { - cache.Dispose(); - } - - Caches = null; - CacheCount = 0; - NextFetchCacheIndex = InvalidIndex; - } - - /// - /// Has this been initialized? - /// - /// if this is initialized. - /// Otherwise, . - public bool IsInitialized() => Caches != null; - - protected override Result DoRead(long offset, Span destination) - { - Assert.SdkRequires(IsInitialized()); - - // Succeed if zero size. - if (destination.Length == 0) - return Result.Success; - - // Do the read. - return ReadCore(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - Assert.SdkRequires(IsInitialized()); - - // Succeed if zero size. - if (source.Length == 0) - return Result.Success; - - // Do the read. - return WriteCore(offset, source); - } - - protected override Result DoGetSize(out long size) - { - Assert.SdkRequires(IsInitialized()); - - size = BaseStorageSize; - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - Assert.SdkRequires(IsInitialized()); - - Result rc; - long prevSize = BaseStorageSize; - if (prevSize < size) - { - // Prepare to expand. - if (!Alignment.IsAlignedPow2(prevSize, (uint)BlockSize)) - { - using var cache = new SharedCache(this); - long invalidateOffset = prevSize; - long invalidateSize = size - prevSize; - - if (cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)) - { - rc = cache.Flush(); - if (rc.IsFailure()) return rc; - - cache.Invalidate(); - } - - Assert.SdkAssert(!cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)); - } - } - else if (size < prevSize) - { - // Prepare to shrink. - using var cache = new SharedCache(this); - long invalidateOffset = prevSize; - long invalidateSize = size - prevSize; - bool isFragment = Alignment.IsAlignedPow2(size, (uint)BlockSize); - - while (cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)) - { - if (isFragment && cache.Hits(invalidateOffset, 1)) - { - rc = cache.Flush(); - if (rc.IsFailure()) return rc; - } - - cache.Invalidate(); - } - } - - // Set the size. - rc = BaseStorage.SetSize(size); - if (rc.IsFailure()) return rc; - - // Get our new size. - rc = BaseStorage.GetSize(out long newSize); - if (rc.IsFailure()) return rc; - - BaseStorageSize = newSize; - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - Assert.SdkRequires(IsInitialized()); - - // Invalidate caches if needed. - if (operationId == OperationId.InvalidateCache) - { - using var cache = new SharedCache(this); - - while (cache.AcquireNextOverlappedCache(offset, size)) - cache.Invalidate(); - } - - return BaseStorage.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } - - protected override Result DoFlush() - { - Assert.SdkRequires(IsInitialized()); - - // Flush caches. - using var cache = new SharedCache(this); - while (cache.AcquireNextDirtyCache()) - { - Result flushResult = cache.Flush(); - if (flushResult.IsFailure()) return flushResult; - } - - // Flush the base storage. - return BaseStorage.Flush(); - } - - /// - /// Invalidates all cached data. Any unflushed data will be discarded. - /// - public void InvalidateCaches() - { - Assert.SdkRequires(IsInitialized()); - - using var cache = new SharedCache(this); - while (cache.AcquireNextValidCache()) - cache.Invalidate(); - } - - /// - /// Gets the used by this . - /// - /// The buffer manager. - public IBufferManager GetBufferManager() => BufferManager; - - public void EnableBulkRead() => BulkReadEnabled = true; - - /// - /// Flushes the cache to the base if less than 1/8 of the - /// 's space can be used for allocation. + /// If this is dirty, flushes its data to the base . + /// The must contain valid data before calling. /// /// The of the operation. - private Result PrepareAllocation() + public Result Flush() { - uint flushThreshold = (uint)BufferManager.GetTotalSize() / 8; + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(IsValid()); - if (BufferManager.GetTotalAllocatableSize() < flushThreshold) + if (_isDirty) { - Result rc = Flush(); + Assert.SdkRequires(!MemoryRange.IsNull); + + long baseSize = BufferedStorage.BaseStorageSize; + long blockSize = BufferedStorage.BlockSize; + long flushSize = Math.Min(blockSize, baseSize - Offset); + + SubStorage baseStorage = BufferedStorage.BaseStorage; + Span cacheBuffer = MemoryRange.Span; + Assert.SdkEqual(flushSize, cacheBuffer.Length); + + Result rc = baseStorage.Write(Offset, cacheBuffer); + if (rc.IsFailure()) return rc; + + _isDirty = false; + + BufferManagerUtility.EnableBlockingBufferManagerAllocation(); + } + + return Result.Success; + } + + /// + /// Prepares this to fetch a new block from the base . + /// If the caller has the only reference to this Cache, + /// the Cache's buffer will be flushed and the Cache invalidated. While the Cache is + /// prepared to fetch, will skip it when iterating all the Caches. + /// + /// The of any attempted flush, and if the + /// is prepared to fetch; if not. + public (Result Result, bool IsPrepared) PrepareFetch() + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(IsValid()); + Assert.SdkRequires(Monitor.IsEntered(BufferedStorage.Locker)); + + (Result Result, bool IsPrepared) result = (Result.Success, false); + + if (ReferenceCount == 1) + { + result.Result = Flush(); + + if (result.Result.IsSuccess()) + { + _isValid = false; + ReferenceCount = 0; + result.IsPrepared = true; + } + } + + return result; + } + + /// + /// Marks the as unprepared to cache a new block, + /// allowing to acquire it while iterating. + /// + public void UnprepareFetch() + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(!IsValid()); + Assert.SdkRequires(!_isDirty); + Assert.SdkRequires(Monitor.IsEntered(BufferedStorage.Locker)); + + _isValid = true; + ReferenceCount = 1; + } + + /// + /// Reads the storage block containing the specified offset into this 's buffer. + /// + /// An offset in the block to fetch. + /// : The operation was successful.
+ /// : A buffer could not be allocated.
+ public Result Fetch(long offset) + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(!IsValid()); + Assert.SdkRequires(!_isDirty); + + Result rc; + + // Make sure this Cache has an allocated buffer + if (MemoryRange.IsNull) + { + rc = AllocateFetchBuffer(); if (rc.IsFailure()) return rc; } + CalcFetchParameter(out FetchParameter fetchParam, offset); + + rc = BufferedStorage.BaseStorage.Read(fetchParam.Offset, fetchParam.Buffer); + if (rc.IsFailure()) return rc; + + Offset = fetchParam.Offset; + Assert.SdkAssert(Hits(offset, 1)); + return Result.Success; } /// - /// Flushes all dirty caches if less than 25% of the space - /// in the is allocatable. + /// Fills this 's buffer from an input buffer containing a block of data + /// read from the base . /// - /// - private Result ControlDirtiness() + /// The start offset of the block in the base + /// that the data was read from. + /// A buffer containing the data read from the base . + /// : The operation was successful.
+ /// : A buffer could not be allocated.
+ public Result FetchFromBuffer(long offset, ReadOnlySpan buffer) { - uint flushThreshold = (uint)BufferManager.GetTotalSize() / 4; + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequiresEqual(NextIndex, InvalidIndex); + Assert.SdkRequiresEqual(PrevIndex, InvalidIndex); + Assert.SdkRequires(!IsValid()); + Assert.SdkRequires(!_isDirty); + Assert.SdkRequiresAligned((ulong)offset, (int)BufferedStorage.BlockSize); - if (BufferManager.GetTotalAllocatableSize() < flushThreshold) + // Make sure this Cache has an allocated buffer + if (MemoryRange.IsNull) + { + Result rc = AllocateFetchBuffer(); + if (rc.IsFailure()) return rc; + } + + CalcFetchParameter(out FetchParameter fetchParam, offset); + Assert.SdkEqual(fetchParam.Offset, offset); + Assert.SdkLessEqual(fetchParam.Buffer.Length, buffer.Length); + + buffer.Slice(0, fetchParam.Buffer.Length).CopyTo(fetchParam.Buffer); + Offset = fetchParam.Offset; + Assert.SdkAssert(Hits(offset, 1)); + + return Result.Success; + } + + /// + /// Tries to retrieve the cache's memory buffer from the . + /// + /// if the memory buffer was available. + /// if the buffer has been evicted from the cache. + public bool TryAcquireCache() + { + Assert.SdkRequiresNotNull(BufferedStorage); + Assert.SdkRequiresNotNull(BufferedStorage.BufferManager); + Assert.SdkRequires(IsValid()); + + if (!MemoryRange.IsNull) + return true; + + MemoryRange = BufferedStorage.BufferManager.AcquireCache(CacheHandle); + _isValid = !MemoryRange.IsNull; + return _isValid; + } + + /// + /// Invalidates the data in this . + /// + public void Invalidate() + { + Assert.SdkRequiresNotNull(BufferedStorage); + _isValid = false; + } + + /// + /// Does this have a valid buffer or are there any references to this Cache? + /// + /// if this has a valid buffer + /// or if anybody currently has a reference to this Cache. Otherwise, . + public bool IsValid() + { + Assert.SdkRequiresNotNull(BufferedStorage); + + return _isValid || ReferenceCount > 0; + } + + /// + /// Does this have modified data that needs + /// to be flushed to the base ? + /// + /// if this has unflushed data. + /// Otherwise, . + public bool IsDirty() + { + Assert.SdkRequiresNotNull(BufferedStorage); + + return _isDirty; + } + + /// + /// Checks if the covers any of the specified range. + /// + /// The start offset of the range to check. + /// The size of the range to check. + /// if this 's range covers any of the input range. + /// Otherwise, . + public bool Hits(long offset, long size) + { + Assert.SdkRequiresNotNull(BufferedStorage); + + long blockSize = BufferedStorage.BlockSize; + return (offset < Offset + blockSize) && (Offset < offset + size); + } + + /// + /// Allocates a buffer for this . + /// Should only be called when there is not already an allocated buffer. + /// + /// : The operation was successful.
+ /// : A buffer could not be allocated.
+ private Result AllocateFetchBuffer() + { + IBufferManager bufferManager = BufferedStorage.BufferManager; + Assert.SdkAssert(bufferManager.AcquireCache(CacheHandle).IsNull); + + Result rc = BufferManagerUtility.AllocateBufferUsingBufferManagerContext(out Buffer bufferTemp, + bufferManager, (int)BufferedStorage.BlockSize, new IBufferManager.BufferAttribute(), + static (in Buffer buffer) => !buffer.IsNull); + + // Clear the current MemoryRange if allocation failed. + MemoryRange = rc.IsSuccess() ? bufferTemp : default; + return Result.Success; + } + + /// + /// Calculates the parameters used to fetch the block containing the + /// specified offset in the base . + /// + /// When this function returns, contains + /// the parameters that can be used to fetch the block. + /// The offset to be fetched. + private void CalcFetchParameter(out FetchParameter fetchParam, long offset) + { + long blockSize = BufferedStorage.BlockSize; + long storageOffset = Alignment.AlignDownPow2(offset, (uint)BufferedStorage.BlockSize); + long baseSize = BufferedStorage.BaseStorageSize; + long remainingSize = baseSize - storageOffset; + long cacheSize = Math.Min(blockSize, remainingSize); + Span cacheBuffer = MemoryRange.Span.Slice(0, (int)cacheSize); + + Assert.SdkLessEqual(0, offset); + Assert.SdkLess(offset, baseSize); + + fetchParam = new FetchParameter + { + Offset = storageOffset, + Buffer = cacheBuffer + }; + } + } + + /// + /// Allows iteration over the in a . + /// Several options exist for which Caches to iterate. + /// + private ref struct SharedCache + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public Ref Cache { get; private set; } + private Ref StartCache { get; } + public BufferedStorage BufferedStorage { get; } + + public SharedCache(BufferedStorage bufferedStorage) + { + Assert.SdkRequiresNotNull(bufferedStorage); + Cache = default; + StartCache = new Ref(ref bufferedStorage.NextAcquireCache); + BufferedStorage = bufferedStorage; + } + + public void Dispose() + { + lock (BufferedStorage.Locker) + { + Release(); + } + } + + /// + /// Moves to the next that contains data from the specified range. + /// + /// The start offset of the range. + /// The size of the range. + /// if a from the + /// specified range was found. if no matching Caches exist, + /// or if all matching Caches have already been iterated. + public bool AcquireNextOverlappedCache(long offset, long size) + { + Assert.SdkRequiresNotNull(BufferedStorage); + + bool isFirst = Cache.IsNull; + ref Cache start = ref isFirst ? ref StartCache.Value : ref Unsafe.Add(ref Cache.Value, 1); + + // Make sure the Cache instance is in-range. + Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, + ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); + + Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), + BufferedStorage.CacheCount))); + + lock (BufferedStorage.Locker) + { + Release(); + Assert.SdkAssert(Cache.IsNull); + + for (ref Cache cache = ref start; ; cache = ref Unsafe.Add(ref cache, 1)) + { + // Wrap to the front of the list if we've reached the end. + ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), + BufferedStorage.CacheCount); + if (!Unsafe.IsAddressLessThan(ref cache, ref end)) + { + cache = ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches); + } + + // Break if we've iterated all the Caches + if (!isFirst && Unsafe.AreSame(ref cache, ref StartCache.Value)) + { + break; + } + + if (cache.IsValid() && cache.Hits(offset, size) && cache.TryAcquireCache()) + { + cache.Unlink(); + Cache = new Ref(ref cache); + return true; + } + + isFirst = false; + } + + Cache = default; + return false; + } + } + + /// + /// Moves to the next dirty . + /// + /// if a dirty was found. + /// if no dirty Caches exist, + /// or if all dirty Caches have already been iterated. + public bool AcquireNextDirtyCache() + { + Assert.SdkRequiresNotNull(BufferedStorage); + + ref Cache start = ref Cache.IsNull + ? ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches) + : ref Unsafe.Add(ref Cache.Value, 1); + + ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), + BufferedStorage.CacheCount); + + // Validate the range. + Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, + ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); + + Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, ref end)); + + Release(); + Assert.SdkAssert(Cache.IsNull); + + // Find the next dirty Cache + for (ref Cache cache = ref start; + Unsafe.IsAddressLessThan(ref cache, ref end); + cache = ref Unsafe.Add(ref cache, 1)) + { + if (cache.IsValid() && cache.IsDirty() && cache.TryAcquireCache()) + { + cache.Unlink(); + Cache = new Ref(ref cache); + return true; + } + } + + Cache = default; + return false; + } + + /// + /// Moves to the next valid . + /// + /// if a valid was found. + /// if no valid Caches exist, + /// or if all valid Caches have already been iterated. + public bool AcquireNextValidCache() + { + Assert.SdkRequiresNotNull(BufferedStorage); + + ref Cache start = ref Cache.IsNull + ? ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches) + : ref Unsafe.Add(ref Cache.Value, 1); + + ref Cache end = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), + BufferedStorage.CacheCount); + + // Validate the range. + Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref start, + ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); + + Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref start, ref end)); + + Release(); + Assert.SdkAssert(Cache.IsNull); + + // Find the next valid Cache + for (ref Cache cache = ref start; + Unsafe.IsAddressLessThan(ref cache, ref end); + cache = ref Unsafe.Add(ref cache, 1)) + { + if (cache.IsValid() && cache.TryAcquireCache()) + { + cache.Unlink(); + Cache = new Ref(ref cache); + return true; + } + } + + Cache = default; + return false; + } + + /// + /// Moves to a that can be used for + /// fetching a new block from the base . + /// + /// if a was acquired. + /// Otherwise, . + public bool AcquireFetchableCache() + { + Assert.SdkRequiresNotNull(BufferedStorage); + + lock (BufferedStorage.Locker) + { + Release(); + Assert.SdkAssert(Cache.IsNull); + + Cache = new Ref(ref BufferedStorage.NextFetchCache); + + if (!Cache.IsNull) + { + if (Cache.Value.IsValid()) + Cache.Value.TryAcquireCache(); + + Cache.Value.Unlink(); + } + + return !Cache.IsNull; + } + } + + /// + /// Reads from the current 's buffer. + /// The provided must be inside the block of + /// data held by the . + /// + /// The offset in the base to be read from. + /// The buffer in which to place the read data. + public void Read(long offset, Span buffer) + { + Assert.SdkRequires(!Cache.IsNull); + Cache.Value.Read(offset, buffer); + } + + /// + /// Buffers data to be written to the base when the current + /// is flushed. The provided + /// must be contained by the block of data held by the . + /// + /// The offset in the base to be written to. + /// The buffer containing the data to be written. + public void Write(long offset, ReadOnlySpan buffer) + { + Assert.SdkRequires(!Cache.IsNull); + Cache.Value.Write(offset, buffer); + } + + /// + /// If the current is dirty, + /// flushes its data to the base . + /// + /// The of the operation. + public Result Flush() + { + Assert.SdkRequires(!Cache.IsNull); + return Cache.Value.Flush(); + } + + /// + /// Invalidates the data in the current . + /// Any dirty data will be discarded. + /// + public void Invalidate() + { + Assert.SdkRequires(!Cache.IsNull); + Cache.Value.Invalidate(); + } + + /// + /// Checks if the current covers any of the specified range. + /// + /// The start offset of the range to check. + /// The size of the range to check. + /// if the current 's range + /// covers any of the input range. Otherwise, . + public bool Hits(long offset, long size) + { + Assert.SdkRequires(!Cache.IsNull); + return Cache.Value.Hits(offset, size); + } + + /// + /// Releases the current to return to the fetch list. + /// + private void Release() + { + if (!Cache.IsNull) + { + // Make sure the Cache instance is in-range. + Assert.SdkAssert(!Unsafe.IsAddressLessThan(ref Cache.Value, + ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches))); + + Assert.SdkAssert(!Unsafe.IsAddressGreaterThan(ref Cache.Value, + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(BufferedStorage.Caches), + BufferedStorage.CacheCount))); + + Cache.Value.Link(); + Cache = default; + } + } + } + + /// + /// Provides exclusive access to a + /// entry in a . + /// + private ref struct UniqueCache + { + // ReSharper disable once MemberHidesStaticFromOuterClass + private Ref Cache { get; set; } + private BufferedStorage BufferedStorage { get; } + + public UniqueCache(BufferedStorage bufferedStorage) + { + Assert.SdkRequiresNotNull(bufferedStorage); + Cache = default; + BufferedStorage = bufferedStorage; + } + + /// + /// Disposes the , releasing any held . + /// + public void Dispose() + { + if (!Cache.IsNull) + { + lock (BufferedStorage.Locker) + { + Cache.Value.UnprepareFetch(); + } + } + } + + /// + /// Attempts to gain exclusive access to the held by + /// and prepare it to read a new block from the base . + /// + /// The to gain exclusive access to. + /// The of the operation, and if exclusive + /// access to the was gained; if not. + public (Result Result, bool wasUpgradeSuccessful) Upgrade(in SharedCache sharedCache) + { + Assert.SdkRequires(BufferedStorage == sharedCache.BufferedStorage); + Assert.SdkRequires(!sharedCache.Cache.IsNull); + + lock (BufferedStorage.Locker) + { + (Result Result, bool wasUpgradeSuccessful) result = sharedCache.Cache.Value.PrepareFetch(); + + if (result.Result.IsSuccess() && result.wasUpgradeSuccessful) + Cache = sharedCache.Cache; + + return result; + } + } + + /// + /// Reads the storage block containing the specified offset into the + /// 's buffer, and sets the Cache to that offset. + /// + /// An offset in the block to fetch. + /// : The operation was successful.
+ /// : A buffer could not be allocated.
+ public Result Fetch(long offset) + { + Assert.SdkRequires(!Cache.IsNull); + + return Cache.Value.Fetch(offset); + } + + /// + /// Fills the 's buffer from an input buffer containing a block of data + /// read from the base , and sets the Cache to that offset. + /// + /// The start offset of the block in the base + /// that the data was read from. + /// A buffer containing the data read from the base . + /// : The operation was successful.
+ /// : A buffer could not be allocated.
+ public Result FetchFromBuffer(long offset, ReadOnlySpan buffer) + { + Assert.SdkRequires(!Cache.IsNull); + + return Cache.Value.FetchFromBuffer(offset, buffer); + } + } + + private SubStorage BaseStorage { get; set; } + private IBufferManager BufferManager { get; set; } + private long BlockSize { get; set; } + + private long _baseStorageSize; + private long BaseStorageSize + { + get => _baseStorageSize; + set => _baseStorageSize = value; + } + + private Cache[] Caches { get; set; } + private int CacheCount { get; set; } + private int NextAcquireCacheIndex { get; set; } + private int NextFetchCacheIndex { get; set; } + private object Locker { get; } = new(); + private bool BulkReadEnabled { get; set; } + + /// + /// The at which new s will begin iterating. + /// + private ref Cache NextAcquireCache => ref Caches[NextAcquireCacheIndex]; + + /// + /// A list of s that can be used for fetching + /// new blocks of data from the base . + /// + private ref Cache NextFetchCache => ref Caches[NextFetchCacheIndex]; + + /// + /// Creates an uninitialized . + /// + public BufferedStorage() + { + NextAcquireCacheIndex = InvalidIndex; + NextFetchCacheIndex = InvalidIndex; + } + + /// + /// Disposes the , flushing any cached data. + /// + public override void Dispose() + { + FinalizeObject(); + base.Dispose(); + } + + /// + /// Initializes the . + /// Calling this method again afterwards will flush the current cache and + /// reinitialize the with the new parameters. + /// + /// The base storage to use. + /// The buffer manager used to allocate and cache memory. + /// The size of each cached block. Must be a power of 2. + /// The maximum number of blocks that can be cached at one time. + /// + public Result Initialize(SubStorage baseStorage, IBufferManager bufferManager, int blockSize, int cacheCount) + { + Assert.SdkRequiresNotNull(baseStorage); + Assert.SdkRequiresNotNull(bufferManager); + Assert.SdkRequiresLess(0, blockSize); + Assert.SdkRequires(BitUtil.IsPowerOfTwo(blockSize)); + Assert.SdkRequiresLess(0, cacheCount); + + // Get the base storage size. + Result rc = baseStorage.GetSize(out _baseStorageSize); + if (rc.IsFailure()) return rc; + + // Set members. + BaseStorage = baseStorage; + BufferManager = bufferManager; + BlockSize = blockSize; + CacheCount = cacheCount; + + // Allocate the caches. + if (Caches != null) + { + for (int i = 0; i < Caches.Length; i++) + { + Caches[i].FinalizeObject(); + } + } + + Caches = new Cache[cacheCount]; + if (Caches == null) + { + return ResultFs.AllocationMemoryFailedInBufferedStorageA.Log(); + } + + // Initialize the caches. + for (int i = 0; i < Caches.Length; i++) + { + Caches[i].Initialize(this, i); + } + + NextAcquireCacheIndex = 0; + return Result.Success; + } + + /// + /// Finalizes this , flushing all buffers and leaving it in an uninitialized state. + /// + public void FinalizeObject() + { + BaseStorage = null; + BaseStorageSize = 0; + + foreach (Cache cache in Caches) + { + cache.Dispose(); + } + + Caches = null; + CacheCount = 0; + NextFetchCacheIndex = InvalidIndex; + } + + /// + /// Has this been initialized? + /// + /// if this is initialized. + /// Otherwise, . + public bool IsInitialized() => Caches != null; + + protected override Result DoRead(long offset, Span destination) + { + Assert.SdkRequires(IsInitialized()); + + // Succeed if zero size. + if (destination.Length == 0) + return Result.Success; + + // Do the read. + return ReadCore(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + Assert.SdkRequires(IsInitialized()); + + // Succeed if zero size. + if (source.Length == 0) + return Result.Success; + + // Do the read. + return WriteCore(offset, source); + } + + protected override Result DoGetSize(out long size) + { + Assert.SdkRequires(IsInitialized()); + + size = BaseStorageSize; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + Assert.SdkRequires(IsInitialized()); + + Result rc; + long prevSize = BaseStorageSize; + if (prevSize < size) + { + // Prepare to expand. + if (!Alignment.IsAlignedPow2(prevSize, (uint)BlockSize)) { using var cache = new SharedCache(this); - int dirtyCount = 0; + long invalidateOffset = prevSize; + long invalidateSize = size - prevSize; - while (cache.AcquireNextDirtyCache()) + if (cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)) { - if (++dirtyCount > 1) + rc = cache.Flush(); + if (rc.IsFailure()) return rc; + + cache.Invalidate(); + } + + Assert.SdkAssert(!cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)); + } + } + else if (size < prevSize) + { + // Prepare to shrink. + using var cache = new SharedCache(this); + long invalidateOffset = prevSize; + long invalidateSize = size - prevSize; + bool isFragment = Alignment.IsAlignedPow2(size, (uint)BlockSize); + + while (cache.AcquireNextOverlappedCache(invalidateOffset, invalidateSize)) + { + if (isFragment && cache.Hits(invalidateOffset, 1)) + { + rc = cache.Flush(); + if (rc.IsFailure()) return rc; + } + + cache.Invalidate(); + } + } + + // Set the size. + rc = BaseStorage.SetSize(size); + if (rc.IsFailure()) return rc; + + // Get our new size. + rc = BaseStorage.GetSize(out long newSize); + if (rc.IsFailure()) return rc; + + BaseStorageSize = newSize; + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + Assert.SdkRequires(IsInitialized()); + + // Invalidate caches if needed. + if (operationId == OperationId.InvalidateCache) + { + using var cache = new SharedCache(this); + + while (cache.AcquireNextOverlappedCache(offset, size)) + cache.Invalidate(); + } + + return BaseStorage.OperateRange(outBuffer, operationId, offset, size, inBuffer); + } + + protected override Result DoFlush() + { + Assert.SdkRequires(IsInitialized()); + + // Flush caches. + using var cache = new SharedCache(this); + while (cache.AcquireNextDirtyCache()) + { + Result flushResult = cache.Flush(); + if (flushResult.IsFailure()) return flushResult; + } + + // Flush the base storage. + return BaseStorage.Flush(); + } + + /// + /// Invalidates all cached data. Any unflushed data will be discarded. + /// + public void InvalidateCaches() + { + Assert.SdkRequires(IsInitialized()); + + using var cache = new SharedCache(this); + while (cache.AcquireNextValidCache()) + cache.Invalidate(); + } + + /// + /// Gets the used by this . + /// + /// The buffer manager. + public IBufferManager GetBufferManager() => BufferManager; + + public void EnableBulkRead() => BulkReadEnabled = true; + + /// + /// Flushes the cache to the base if less than 1/8 of the + /// 's space can be used for allocation. + /// + /// The of the operation. + private Result PrepareAllocation() + { + uint flushThreshold = (uint)BufferManager.GetTotalSize() / 8; + + if (BufferManager.GetTotalAllocatableSize() < flushThreshold) + { + Result rc = Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + /// + /// Flushes all dirty caches if less than 25% of the space + /// in the is allocatable. + /// + /// + private Result ControlDirtiness() + { + uint flushThreshold = (uint)BufferManager.GetTotalSize() / 4; + + if (BufferManager.GetTotalAllocatableSize() < flushThreshold) + { + using var cache = new SharedCache(this); + int dirtyCount = 0; + + while (cache.AcquireNextDirtyCache()) + { + if (++dirtyCount > 1) + { + Result rc = cache.Flush(); + if (rc.IsFailure()) return rc; + + cache.Invalidate(); + } + } + } + + return Result.Success; + } + + /// + /// Reads data from the base into the destination buffer. + /// + /// The offset in the at which to begin reading. + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be equal to the length of the buffer. + /// The of the operation. + private Result ReadCore(long offset, Span destination) + { + Assert.SdkRequiresNotNull(Caches); + Assert.SdkRequiresNotNull(destination); + + // Validate the offset. + long baseStorageSize = BaseStorageSize; + if (offset < 0 || offset > baseStorageSize) + return ResultFs.InvalidOffset.Log(); + + // Setup tracking variables. + long remainingSize = Math.Min(destination.Length, baseStorageSize - offset); + long currentOffset = offset; + long bufferOffset = 0; + + // Try doing a bulk read if enabled. + // + // The behavior of which blocks are cached should be the same between bulk reads and non-bulk reads. + // If the head and tail offsets of the range to be read are not aligned to block boundaries, those + // head and/or tail partial blocks will end up in the cache if doing a non-bulk read. + // + // This is imitated during bulk reads by tracking if there are any partial head or tail blocks that aren't + // already in the cache. After the bulk read is complete these partial blocks will be added to the cache. + if (BulkReadEnabled) + { + // Read any blocks at the head of the range that are cached. + bool headCacheNeeded = + ReadHeadCache(ref currentOffset, destination, ref remainingSize, ref bufferOffset); + if (remainingSize == 0) return Result.Success; + + // Read any blocks at the tail of the range that are cached. + bool tailCacheNeeded = ReadTailCache(currentOffset, destination, ref remainingSize, bufferOffset); + if (remainingSize == 0) return Result.Success; + + // Perform bulk reads. + const long bulkReadSizeMax = 1024 * 1024 * 2; // 2 MB + + if (remainingSize < bulkReadSizeMax) + { + // Try to do a bulk read. + Result rc = BulkRead(currentOffset, destination.Slice((int)bufferOffset, (int)remainingSize), + headCacheNeeded, tailCacheNeeded); + + // If the read fails due to insufficient pooled buffer size, + // then we want to fall back to the normal read path. + if (!ResultFs.AllocationPooledBufferNotEnoughSize.Includes(rc)) + return rc; + } + } + + // Repeatedly read until we're done. + while (remainingSize > 0) + { + // Determine how much to read this iteration. + int currentSize; + + // If the offset is in the middle of a block. Read the remaining part of that block. + if (!Alignment.IsAlignedPow2(currentOffset, (uint)BlockSize)) + { + long alignedSize = BlockSize - (currentOffset & (BlockSize - 1)); + currentSize = (int)Math.Min(alignedSize, remainingSize); + } + // If we only have a partial block left to read, read that partial block. + else if (remainingSize < BlockSize) + { + currentSize = (int)remainingSize; + } + // We have at least one full block to read. Read all the remaining full blocks at once. + else + { + currentSize = (int)Alignment.AlignDownPow2(remainingSize, (uint)BlockSize); + } + + Span currentDestination = destination.Slice((int)bufferOffset, currentSize); + + // If reading a single block or less, read it using the cache + if (currentSize <= BlockSize) + { + using var cache = new SharedCache(this); + + // Get the cache for our current block + if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) + { + // The block wasn't in the cache. Read the block from the base storage + Result rc = PrepareAllocation(); + if (rc.IsFailure()) return rc; + + // Loop until we can get exclusive access to the cache block + while (true) + { + if (!cache.AcquireFetchableCache()) + return ResultFs.OutOfResource.Log(); + + // Try to upgrade out SharedCache to a UniqueCache + using var fetchCache = new UniqueCache(this); + (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); + if (upgradeResult.Result.IsFailure()) + return upgradeResult.Result; + + // Fetch the data from the base storage into the cache buffer if successful + if (upgradeResult.wasUpgradeSuccessful) + { + rc = fetchCache.Fetch(currentOffset); + if (rc.IsFailure()) return rc; + + break; + } + } + + rc = ControlDirtiness(); + if (rc.IsFailure()) return rc; + } + + // Copy the data from the cache buffer to the destination buffer + cache.Read(currentOffset, currentDestination); + } + // If reading multiple blocks, flush the cache entries for all those blocks and + // read directly from the base storage into the destination buffer in a single read. + else + { + // Flush all the cache blocks in the storage range being read + using (var cache = new SharedCache(this)) + { + while (cache.AcquireNextOverlappedCache(currentOffset, currentSize)) { Result rc = cache.Flush(); if (rc.IsFailure()) return rc; @@ -1233,481 +1376,337 @@ namespace LibHac.FsSystem.Save cache.Invalidate(); } } + + // Read directly from the base storage to the destination buffer + Result rcRead = BaseStorage.Read(currentOffset, currentDestination); + if (rcRead.IsFailure()) return rcRead; } - return Result.Success; + remainingSize -= currentSize; + currentOffset += currentSize; + bufferOffset += currentSize; } - /// - /// Reads data from the base into the destination buffer. - /// - /// The offset in the at which to begin reading. - /// The buffer where the read bytes will be stored. - /// The number of bytes read will be equal to the length of the buffer. - /// The of the operation. - private Result ReadCore(long offset, Span destination) + return Result.Success; + } + + /// + /// Reads as much data into the beginning of the buffer that can be found in the cache. Returns + /// if the next uncached data to read from the base + /// is not aligned to the beginning of a block. + /// + /// The storage offset at which to begin reading. When this function returns, contains + /// the new offset at which to begin reading if any data was read by this function. + /// The buffer to read data into. + /// The size of the data to read. When this function returns, contains the new size + /// if any data was read by this function. + /// The offset of the buffer to begin writing data to. When this function returns, + /// contains the new offset to write data to if any data was read by this function. + /// if the remaining data to read contains a partial block at the start. + /// Otherwise, . + private bool ReadHeadCache(ref long offset, Span buffer, ref long size, ref long bufferOffset) + { + bool isCacheNeeded = !Alignment.IsAlignedPow2(offset, (uint)BlockSize); + + while (size > 0) { - Assert.SdkRequiresNotNull(Caches); - Assert.SdkRequiresNotNull(destination); + long currentSize; - // Validate the offset. - long baseStorageSize = BaseStorageSize; - if (offset < 0 || offset > baseStorageSize) - return ResultFs.InvalidOffset.Log(); - - // Setup tracking variables. - long remainingSize = Math.Min(destination.Length, baseStorageSize - offset); - long currentOffset = offset; - long bufferOffset = 0; - - // Try doing a bulk read if enabled. - // - // The behavior of which blocks are cached should be the same between bulk reads and non-bulk reads. - // If the head and tail offsets of the range to be read are not aligned to block boundaries, those - // head and/or tail partial blocks will end up in the cache if doing a non-bulk read. - // - // This is imitated during bulk reads by tracking if there are any partial head or tail blocks that aren't - // already in the cache. After the bulk read is complete these partial blocks will be added to the cache. - if (BulkReadEnabled) + if (!Alignment.IsAlignedPow2(offset, (uint)BlockSize)) { - // Read any blocks at the head of the range that are cached. - bool headCacheNeeded = - ReadHeadCache(ref currentOffset, destination, ref remainingSize, ref bufferOffset); - if (remainingSize == 0) return Result.Success; - - // Read any blocks at the tail of the range that are cached. - bool tailCacheNeeded = ReadTailCache(currentOffset, destination, ref remainingSize, bufferOffset); - if (remainingSize == 0) return Result.Success; - - // Perform bulk reads. - const long bulkReadSizeMax = 1024 * 1024 * 2; // 2 MB - - if (remainingSize < bulkReadSizeMax) - { - // Try to do a bulk read. - Result rc = BulkRead(currentOffset, destination.Slice((int)bufferOffset, (int)remainingSize), - headCacheNeeded, tailCacheNeeded); - - // If the read fails due to insufficient pooled buffer size, - // then we want to fall back to the normal read path. - if (!ResultFs.AllocationPooledBufferNotEnoughSize.Includes(rc)) - return rc; - } + long alignedSize = Alignment.AlignUpPow2(offset, (uint)BlockSize) - offset; + currentSize = Math.Min(alignedSize, size); } - - // Repeatedly read until we're done. - while (remainingSize > 0) + else if (size < BlockSize) { - // Determine how much to read this iteration. - int currentSize; - - // If the offset is in the middle of a block. Read the remaining part of that block. - if (!Alignment.IsAlignedPow2(currentOffset, (uint)BlockSize)) - { - long alignedSize = BlockSize - (currentOffset & (BlockSize - 1)); - currentSize = (int)Math.Min(alignedSize, remainingSize); - } - // If we only have a partial block left to read, read that partial block. - else if (remainingSize < BlockSize) - { - currentSize = (int)remainingSize; - } - // We have at least one full block to read. Read all the remaining full blocks at once. - else - { - currentSize = (int)Alignment.AlignDownPow2(remainingSize, (uint)BlockSize); - } - - Span currentDestination = destination.Slice((int)bufferOffset, currentSize); - - // If reading a single block or less, read it using the cache - if (currentSize <= BlockSize) - { - using var cache = new SharedCache(this); - - // Get the cache for our current block - if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) - { - // The block wasn't in the cache. Read the block from the base storage - Result rc = PrepareAllocation(); - if (rc.IsFailure()) return rc; - - // Loop until we can get exclusive access to the cache block - while (true) - { - if (!cache.AcquireFetchableCache()) - return ResultFs.OutOfResource.Log(); - - // Try to upgrade out SharedCache to a UniqueCache - using var fetchCache = new UniqueCache(this); - (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); - if (upgradeResult.Result.IsFailure()) - return upgradeResult.Result; - - // Fetch the data from the base storage into the cache buffer if successful - if (upgradeResult.wasUpgradeSuccessful) - { - rc = fetchCache.Fetch(currentOffset); - if (rc.IsFailure()) return rc; - - break; - } - } - - rc = ControlDirtiness(); - if (rc.IsFailure()) return rc; - } - - // Copy the data from the cache buffer to the destination buffer - cache.Read(currentOffset, currentDestination); - } - // If reading multiple blocks, flush the cache entries for all those blocks and - // read directly from the base storage into the destination buffer in a single read. - else - { - // Flush all the cache blocks in the storage range being read - using (var cache = new SharedCache(this)) - { - while (cache.AcquireNextOverlappedCache(currentOffset, currentSize)) - { - Result rc = cache.Flush(); - if (rc.IsFailure()) return rc; - - cache.Invalidate(); - } - } - - // Read directly from the base storage to the destination buffer - Result rcRead = BaseStorage.Read(currentOffset, currentDestination); - if (rcRead.IsFailure()) return rcRead; - } - - remainingSize -= currentSize; - currentOffset += currentSize; - bufferOffset += currentSize; - } - - return Result.Success; - } - - /// - /// Reads as much data into the beginning of the buffer that can be found in the cache. Returns - /// if the next uncached data to read from the base - /// is not aligned to the beginning of a block. - /// - /// The storage offset at which to begin reading. When this function returns, contains - /// the new offset at which to begin reading if any data was read by this function. - /// The buffer to read data into. - /// The size of the data to read. When this function returns, contains the new size - /// if any data was read by this function. - /// The offset of the buffer to begin writing data to. When this function returns, - /// contains the new offset to write data to if any data was read by this function. - /// if the remaining data to read contains a partial block at the start. - /// Otherwise, . - private bool ReadHeadCache(ref long offset, Span buffer, ref long size, ref long bufferOffset) - { - bool isCacheNeeded = !Alignment.IsAlignedPow2(offset, (uint)BlockSize); - - while (size > 0) - { - long currentSize; - - if (!Alignment.IsAlignedPow2(offset, (uint)BlockSize)) - { - long alignedSize = Alignment.AlignUpPow2(offset, (uint)BlockSize) - offset; - currentSize = Math.Min(alignedSize, size); - } - else if (size < BlockSize) - { - currentSize = size; - } - else - { - currentSize = BlockSize; - } - - using var cache = new SharedCache(this); - - if (!cache.AcquireNextOverlappedCache(offset, currentSize)) - break; - - cache.Read(offset, buffer.Slice((int)bufferOffset, (int)currentSize)); - offset += currentSize; - bufferOffset += currentSize; - size -= currentSize; - isCacheNeeded = false; - } - - return isCacheNeeded; - } - - private bool ReadTailCache(long offset, Span buffer, ref long size, long bufferOffset) - { - bool isCacheNeeded = !Alignment.IsAlignedPow2(offset + size, (uint)BlockSize); - - while (size > 0) - { - long currentOffsetEnd = offset + size; - long currentSize; - - if (!Alignment.IsAlignedPow2(currentOffsetEnd, (uint)BlockSize)) - { - long alignedSize = currentOffsetEnd - Alignment.AlignDownPow2(currentOffsetEnd, (uint)BlockSize); - currentSize = Math.Min(alignedSize, size); - } - else if (size < BlockSize) - { - currentSize = size; - } - else - { - currentSize = BlockSize; - } - - long currentOffset = currentOffsetEnd - currentSize; - Assert.SdkGreaterEqual(currentOffset, 0); - - using var cache = new SharedCache(this); - - if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) - break; - - int currentBufferOffset = (int)(bufferOffset + currentOffset - offset); - cache.Read(currentOffset, buffer.Slice(currentBufferOffset, (int)currentSize)); - size -= currentSize; - isCacheNeeded = false; - } - - return isCacheNeeded; - } - - /// - /// Reads directly from the base to the destination - /// using a single read. - /// - /// The offset at which to begin reading - /// The buffer where the read bytes will be stored. - /// The number of bytes read will be equal to the length of the buffer. - /// Should the head block of the read data be cached? - /// Should the tail block of the read data be cached? - /// The of the operation. - private Result BulkRead(long offset, Span buffer, bool isHeadCacheNeeded, bool isTailCacheNeeded) - { - Result rc; - - // Determine aligned extents. - long alignedOffset = Alignment.AlignDownPow2(offset, (uint)BlockSize); - long alignedOffsetEnd = Math.Min(Alignment.AlignUpPow2(offset + buffer.Length, (uint)BlockSize), - BaseStorageSize); - long alignedSize = alignedOffsetEnd - alignedOffset; - - // Allocate a work buffer if either the head or tail of the range isn't aligned. - // Otherwise directly use the output buffer. - Span workBuffer; - using var pooledBuffer = new PooledBuffer(); - - if (offset == alignedOffset && buffer.Length == alignedSize) - { - workBuffer = buffer; + currentSize = size; } else { - pooledBuffer.AllocateParticularlyLarge((int)alignedSize, 1); - if (pooledBuffer.GetSize() < alignedSize) - return ResultFs.AllocationPooledBufferNotEnoughSize.Log(); - - workBuffer = pooledBuffer.GetBuffer(); + currentSize = BlockSize; } - // Ensure cache is coherent. - using (var cache = new SharedCache(this)) - { - while (cache.AcquireNextOverlappedCache(alignedOffset, alignedSize)) - { - rc = cache.Flush(); - if (rc.IsFailure()) return rc; + using var cache = new SharedCache(this); - cache.Invalidate(); + if (!cache.AcquireNextOverlappedCache(offset, currentSize)) + break; + + cache.Read(offset, buffer.Slice((int)bufferOffset, (int)currentSize)); + offset += currentSize; + bufferOffset += currentSize; + size -= currentSize; + isCacheNeeded = false; + } + + return isCacheNeeded; + } + + private bool ReadTailCache(long offset, Span buffer, ref long size, long bufferOffset) + { + bool isCacheNeeded = !Alignment.IsAlignedPow2(offset + size, (uint)BlockSize); + + while (size > 0) + { + long currentOffsetEnd = offset + size; + long currentSize; + + if (!Alignment.IsAlignedPow2(currentOffsetEnd, (uint)BlockSize)) + { + long alignedSize = currentOffsetEnd - Alignment.AlignDownPow2(currentOffsetEnd, (uint)BlockSize); + currentSize = Math.Min(alignedSize, size); + } + else if (size < BlockSize) + { + currentSize = size; + } + else + { + currentSize = BlockSize; + } + + long currentOffset = currentOffsetEnd - currentSize; + Assert.SdkGreaterEqual(currentOffset, 0); + + using var cache = new SharedCache(this); + + if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) + break; + + int currentBufferOffset = (int)(bufferOffset + currentOffset - offset); + cache.Read(currentOffset, buffer.Slice(currentBufferOffset, (int)currentSize)); + size -= currentSize; + isCacheNeeded = false; + } + + return isCacheNeeded; + } + + /// + /// Reads directly from the base to the destination + /// using a single read. + /// + /// The offset at which to begin reading + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be equal to the length of the buffer. + /// Should the head block of the read data be cached? + /// Should the tail block of the read data be cached? + /// The of the operation. + private Result BulkRead(long offset, Span buffer, bool isHeadCacheNeeded, bool isTailCacheNeeded) + { + Result rc; + + // Determine aligned extents. + long alignedOffset = Alignment.AlignDownPow2(offset, (uint)BlockSize); + long alignedOffsetEnd = Math.Min(Alignment.AlignUpPow2(offset + buffer.Length, (uint)BlockSize), + BaseStorageSize); + long alignedSize = alignedOffsetEnd - alignedOffset; + + // Allocate a work buffer if either the head or tail of the range isn't aligned. + // Otherwise directly use the output buffer. + Span workBuffer; + using var pooledBuffer = new PooledBuffer(); + + if (offset == alignedOffset && buffer.Length == alignedSize) + { + workBuffer = buffer; + } + else + { + pooledBuffer.AllocateParticularlyLarge((int)alignedSize, 1); + if (pooledBuffer.GetSize() < alignedSize) + return ResultFs.AllocationPooledBufferNotEnoughSize.Log(); + + workBuffer = pooledBuffer.GetBuffer(); + } + + // Ensure cache is coherent. + using (var cache = new SharedCache(this)) + { + while (cache.AcquireNextOverlappedCache(alignedOffset, alignedSize)) + { + rc = cache.Flush(); + if (rc.IsFailure()) return rc; + + cache.Invalidate(); + } + } + + // Read from the base storage. + rc = BaseStorage.Read(alignedOffset, workBuffer.Slice(0, (int)alignedSize)); + if (rc.IsFailure()) return rc; + if (workBuffer != buffer) + { + workBuffer.Slice((int)(offset - alignedOffset), buffer.Length).CopyTo(buffer); + } + + bool cached = false; + + // Cache the head block if needed. + if (isHeadCacheNeeded) + { + rc = PrepareAllocation(); + if (rc.IsFailure()) return rc; + + using var cache = new SharedCache(this); + while (true) + { + if (!cache.AcquireFetchableCache()) + return ResultFs.OutOfResource.Log(); + + using var fetchCache = new UniqueCache(this); + (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); + if (upgradeResult.Result.IsFailure()) + return upgradeResult.Result; + + if (upgradeResult.wasUpgradeSuccessful) + { + rc = fetchCache.FetchFromBuffer(alignedOffset, workBuffer.Slice(0, (int)alignedSize)); + if (rc.IsFailure()) return rc; + break; } } - // Read from the base storage. - rc = BaseStorage.Read(alignedOffset, workBuffer.Slice(0, (int)alignedSize)); - if (rc.IsFailure()) return rc; - if (workBuffer != buffer) - { - workBuffer.Slice((int)(offset - alignedOffset), buffer.Length).CopyTo(buffer); - } + cached = true; + } - bool cached = false; - - // Cache the head block if needed. - if (isHeadCacheNeeded) + // Cache the tail block if needed. + if (isTailCacheNeeded && (!isHeadCacheNeeded || alignedSize > BlockSize)) + { + if (!cached) { rc = PrepareAllocation(); if (rc.IsFailure()) return rc; - - using var cache = new SharedCache(this); - while (true) - { - if (!cache.AcquireFetchableCache()) - return ResultFs.OutOfResource.Log(); - - using var fetchCache = new UniqueCache(this); - (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); - if (upgradeResult.Result.IsFailure()) - return upgradeResult.Result; - - if (upgradeResult.wasUpgradeSuccessful) - { - rc = fetchCache.FetchFromBuffer(alignedOffset, workBuffer.Slice(0, (int)alignedSize)); - if (rc.IsFailure()) return rc; - break; - } - } - - cached = true; } - // Cache the tail block if needed. - if (isTailCacheNeeded && (!isHeadCacheNeeded || alignedSize > BlockSize)) + using var cache = new SharedCache(this); + while (true) { - if (!cached) + if (!cache.AcquireFetchableCache()) + return ResultFs.OutOfResource.Log(); + + using var fetchCache = new UniqueCache(this); + (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); + if (upgradeResult.Result.IsFailure()) + return upgradeResult.Result; + + if (upgradeResult.wasUpgradeSuccessful) + { + long tailCacheOffset = Alignment.AlignDownPow2(offset + buffer.Length, (uint)BlockSize); + long tailCacheSize = alignedSize - tailCacheOffset + alignedOffset; + + rc = fetchCache.FetchFromBuffer(tailCacheOffset, + workBuffer.Slice((int)(tailCacheOffset - alignedOffset), (int)tailCacheSize)); + if (rc.IsFailure()) return rc; + break; + } + } + } + + if (cached) + { + rc = ControlDirtiness(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + private Result WriteCore(long offset, ReadOnlySpan source) + { + Assert.SdkRequiresNotNull(Caches); + Assert.SdkRequiresNotNull(source); + + // Validate the offset. + long baseStorageSize = BaseStorageSize; + + if (offset < 0 || baseStorageSize < offset) + return ResultFs.InvalidOffset.Log(); + + // Setup tracking variables. + int remainingSize = (int)Math.Min(source.Length, baseStorageSize - offset); + long currentOffset = offset; + int bufferOffset = 0; + + // Repeatedly read until we're done. + while (remainingSize > 0) + { + // Determine how much to read this iteration. + ReadOnlySpan currentSource = source.Slice(bufferOffset); + int currentSize; + + if (!Alignment.IsAlignedPow2(currentOffset, (uint)BlockSize)) + { + int alignedSize = (int)(BlockSize - (currentOffset & (BlockSize - 1))); + currentSize = Math.Min(alignedSize, remainingSize); + } + else if (remainingSize < BlockSize) + { + currentSize = remainingSize; + } + else + { + currentSize = Alignment.AlignDownPow2(remainingSize, (uint)BlockSize); + } + + Result rc; + if (currentSize < BlockSize) + { + using var cache = new SharedCache(this); + + if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) { rc = PrepareAllocation(); if (rc.IsFailure()) return rc; - } - using var cache = new SharedCache(this); - while (true) - { - if (!cache.AcquireFetchableCache()) - return ResultFs.OutOfResource.Log(); - - using var fetchCache = new UniqueCache(this); - (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); - if (upgradeResult.Result.IsFailure()) - return upgradeResult.Result; - - if (upgradeResult.wasUpgradeSuccessful) + while (true) { - long tailCacheOffset = Alignment.AlignDownPow2(offset + buffer.Length, (uint)BlockSize); - long tailCacheSize = alignedSize - tailCacheOffset + alignedOffset; + if (!cache.AcquireFetchableCache()) + return ResultFs.OutOfResource.Log(); - rc = fetchCache.FetchFromBuffer(tailCacheOffset, - workBuffer.Slice((int)(tailCacheOffset - alignedOffset), (int)tailCacheSize)); - if (rc.IsFailure()) return rc; - break; + using var fetchCache = new UniqueCache(this); + (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); + if (upgradeResult.Result.IsFailure()) + return upgradeResult.Result; + + if (upgradeResult.wasUpgradeSuccessful) + { + rc = fetchCache.Fetch(currentOffset); + if (rc.IsFailure()) return rc; + break; + } } } - } + cache.Write(currentOffset, currentSource.Slice(0, currentSize)); + + BufferManagerUtility.EnableBlockingBufferManagerAllocation(); - if (cached) - { rc = ControlDirtiness(); if (rc.IsFailure()) return rc; } - - return Result.Success; - } - - private Result WriteCore(long offset, ReadOnlySpan source) - { - Assert.SdkRequiresNotNull(Caches); - Assert.SdkRequiresNotNull(source); - - // Validate the offset. - long baseStorageSize = BaseStorageSize; - - if (offset < 0 || baseStorageSize < offset) - return ResultFs.InvalidOffset.Log(); - - // Setup tracking variables. - int remainingSize = (int)Math.Min(source.Length, baseStorageSize - offset); - long currentOffset = offset; - int bufferOffset = 0; - - // Repeatedly read until we're done. - while (remainingSize > 0) + else { - // Determine how much to read this iteration. - ReadOnlySpan currentSource = source.Slice(bufferOffset); - int currentSize; - - if (!Alignment.IsAlignedPow2(currentOffset, (uint)BlockSize)) + using (var cache = new SharedCache(this)) { - int alignedSize = (int)(BlockSize - (currentOffset & (BlockSize - 1))); - currentSize = Math.Min(alignedSize, remainingSize); - } - else if (remainingSize < BlockSize) - { - currentSize = remainingSize; - } - else - { - currentSize = Alignment.AlignDownPow2(remainingSize, (uint)BlockSize); - } - - Result rc; - if (currentSize < BlockSize) - { - using var cache = new SharedCache(this); - - if (!cache.AcquireNextOverlappedCache(currentOffset, currentSize)) + while (cache.AcquireNextOverlappedCache(currentOffset, currentSize)) { - rc = PrepareAllocation(); + rc = cache.Flush(); if (rc.IsFailure()) return rc; - while (true) - { - if (!cache.AcquireFetchableCache()) - return ResultFs.OutOfResource.Log(); - - using var fetchCache = new UniqueCache(this); - (Result Result, bool wasUpgradeSuccessful) upgradeResult = fetchCache.Upgrade(in cache); - if (upgradeResult.Result.IsFailure()) - return upgradeResult.Result; - - if (upgradeResult.wasUpgradeSuccessful) - { - rc = fetchCache.Fetch(currentOffset); - if (rc.IsFailure()) return rc; - break; - } - } + cache.Invalidate(); } - cache.Write(currentOffset, currentSource.Slice(0, currentSize)); - - BufferManagerUtility.EnableBlockingBufferManagerAllocation(); - - rc = ControlDirtiness(); - if (rc.IsFailure()) return rc; - } - else - { - using (var cache = new SharedCache(this)) - { - while (cache.AcquireNextOverlappedCache(currentOffset, currentSize)) - { - rc = cache.Flush(); - if (rc.IsFailure()) return rc; - - cache.Invalidate(); - } - } - - rc = BaseStorage.Write(currentOffset, currentSource.Slice(0, currentSize)); - if (rc.IsFailure()) return rc; - - BufferManagerUtility.EnableBlockingBufferManagerAllocation(); } - remainingSize -= currentSize; - currentOffset += currentSize; - bufferOffset += currentSize; + rc = BaseStorage.Write(currentOffset, currentSource.Slice(0, currentSize)); + if (rc.IsFailure()) return rc; + + BufferManagerUtility.EnableBlockingBufferManagerAllocation(); } - return Result.Success; + remainingSize -= currentSize; + currentOffset += currentSize; + bufferOffset += currentSize; } + + return Result.Success; } } diff --git a/src/LibHac/FsSystem/Save/DuplexBitmap.cs b/src/LibHac/FsSystem/Save/DuplexBitmap.cs index 993eb7d5..a0fde23a 100644 --- a/src/LibHac/FsSystem/Save/DuplexBitmap.cs +++ b/src/LibHac/FsSystem/Save/DuplexBitmap.cs @@ -3,39 +3,38 @@ using System.Collections; using System.IO; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class DuplexBitmap { - public class DuplexBitmap + private IStorage Data { get; } + public BitArray Bitmap { get; } + + public DuplexBitmap(IStorage bitmapStorage, int lengthBits) { - private IStorage Data { get; } - public BitArray Bitmap { get; } + Data = bitmapStorage; + Bitmap = new BitArray(lengthBits); + ReadBitmap(lengthBits); + } - public DuplexBitmap(IStorage bitmapStorage, int lengthBits) + private void ReadBitmap(int lengthBits) + { + uint mask = unchecked((uint)(1 << 31)); + var reader = new BinaryReader(Data.AsStream()); + int bitsRemaining = lengthBits; + int bitmapPos = 0; + + while (bitsRemaining > 0) { - Data = bitmapStorage; - Bitmap = new BitArray(lengthBits); - ReadBitmap(lengthBits); - } + int bitsToRead = Math.Min(bitsRemaining, 32); + uint val = reader.ReadUInt32(); - private void ReadBitmap(int lengthBits) - { - uint mask = unchecked((uint)(1 << 31)); - var reader = new BinaryReader(Data.AsStream()); - int bitsRemaining = lengthBits; - int bitmapPos = 0; - - while (bitsRemaining > 0) + for (int i = 0; i < bitsToRead; i++) { - int bitsToRead = Math.Min(bitsRemaining, 32); - uint val = reader.ReadUInt32(); - - for (int i = 0; i < bitsToRead; i++) - { - Bitmap[bitmapPos] = (val & mask) != 0; - bitmapPos++; - bitsRemaining--; - val <<= 1; - } + Bitmap[bitmapPos] = (val & mask) != 0; + bitmapPos++; + bitsRemaining--; + val <<= 1; } } } diff --git a/src/LibHac/FsSystem/Save/DuplexStorage.cs b/src/LibHac/FsSystem/Save/DuplexStorage.cs index 9a40bcad..298fd72d 100644 --- a/src/LibHac/FsSystem/Save/DuplexStorage.cs +++ b/src/LibHac/FsSystem/Save/DuplexStorage.cs @@ -1,127 +1,126 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class DuplexStorage : IStorage { - public class DuplexStorage : IStorage + private int BlockSize { get; } + private IStorage BitmapStorage { get; } + private IStorage DataA { get; } + private IStorage DataB { get; } + private DuplexBitmap Bitmap { get; } + + private long Length { get; } + + public DuplexStorage(IStorage dataA, IStorage dataB, IStorage bitmap, int blockSize) { - private int BlockSize { get; } - private IStorage BitmapStorage { get; } - private IStorage DataA { get; } - private IStorage DataB { get; } - private DuplexBitmap Bitmap { get; } + DataA = dataA; + DataB = dataB; + BitmapStorage = bitmap; + BlockSize = blockSize; - private long Length { get; } + bitmap.GetSize(out long bitmapSize).ThrowIfFailure(); - public DuplexStorage(IStorage dataA, IStorage dataB, IStorage bitmap, int blockSize) + Bitmap = new DuplexBitmap(BitmapStorage, (int)(bitmapSize * 8)); + DataA.GetSize(out long dataSize).ThrowIfFailure(); + Length = dataSize; + } + + protected override Result DoRead(long offset, Span destination) + { + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; + + if (!CheckAccessRange(offset, destination.Length, Length)) + return ResultFs.OutOfRange.Log(); + + while (remaining > 0) { - DataA = dataA; - DataB = dataB; - BitmapStorage = bitmap; - BlockSize = blockSize; + int blockNum = (int)(inPos / BlockSize); + int blockPos = (int)(inPos % BlockSize); - bitmap.GetSize(out long bitmapSize).ThrowIfFailure(); + int bytesToRead = Math.Min(remaining, BlockSize - blockPos); - Bitmap = new DuplexBitmap(BitmapStorage, (int)(bitmapSize * 8)); - DataA.GetSize(out long dataSize).ThrowIfFailure(); - Length = dataSize; - } + IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; - protected override Result DoRead(long offset, Span destination) - { - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; - - if (!CheckAccessRange(offset, destination.Length, Length)) - return ResultFs.OutOfRange.Log(); - - while (remaining > 0) - { - int blockNum = (int)(inPos / BlockSize); - int blockPos = (int)(inPos % BlockSize); - - int bytesToRead = Math.Min(remaining, BlockSize - blockPos); - - IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; - - Result rc = data.Read(inPos, destination.Slice(outPos, bytesToRead)); - if (rc.IsFailure()) return rc; - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; - } - - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - long inPos = offset; - int outPos = 0; - int remaining = source.Length; - - if (!CheckAccessRange(offset, source.Length, Length)) - return ResultFs.OutOfRange.Log(); - - while (remaining > 0) - { - int blockNum = (int)(inPos / BlockSize); - int blockPos = (int)(inPos % BlockSize); - - int bytesToWrite = Math.Min(remaining, BlockSize - blockPos); - - IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; - - Result rc = data.Write(inPos, source.Slice(outPos, bytesToWrite)); - if (rc.IsFailure()) return rc; - - outPos += bytesToWrite; - inPos += bytesToWrite; - remaining -= bytesToWrite; - } - - return Result.Success; - } - - protected override Result DoFlush() - { - Result rc = BitmapStorage.Flush(); + Result rc = data.Read(inPos, destination.Slice(outPos, bytesToRead)); if (rc.IsFailure()) return rc; - rc = DataA.Flush(); + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; + } + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + if (!CheckAccessRange(offset, source.Length, Length)) + return ResultFs.OutOfRange.Log(); + + while (remaining > 0) + { + int blockNum = (int)(inPos / BlockSize); + int blockPos = (int)(inPos % BlockSize); + + int bytesToWrite = Math.Min(remaining, BlockSize - blockPos); + + IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; + + Result rc = data.Write(inPos, source.Slice(outPos, bytesToWrite)); if (rc.IsFailure()) return rc; - rc = DataB.Flush(); - if (rc.IsFailure()) return rc; - - return Result.Success; + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; } - protected override Result DoSetSize(long size) + return Result.Success; + } + + protected override Result DoFlush() + { + Result rc = BitmapStorage.Flush(); + if (rc.IsFailure()) return rc; + + rc = DataA.Flush(); + if (rc.IsFailure()) return rc; + + rc = DataB.Flush(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public void FsTrim() + { + DataA.GetSize(out long dataSize).ThrowIfFailure(); + + int blockCount = (int)(dataSize / BlockSize); + + for (int i = 0; i < blockCount; i++) { - return ResultFs.NotImplemented.Log(); - } + IStorage dataToClear = Bitmap.Bitmap[i] ? DataA : DataB; - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - public void FsTrim() - { - DataA.GetSize(out long dataSize).ThrowIfFailure(); - - int blockCount = (int)(dataSize / BlockSize); - - for (int i = 0; i < blockCount; i++) - { - IStorage dataToClear = Bitmap.Bitmap[i] ? DataA : DataB; - - dataToClear.Slice(i * BlockSize, BlockSize).Fill(SaveDataFileSystem.TrimFillValue); - } + dataToClear.Slice(i * BlockSize, BlockSize).Fill(SaveDataFileSystem.TrimFillValue); } } } diff --git a/src/LibHac/FsSystem/Save/Header.cs b/src/LibHac/FsSystem/Save/Header.cs index 70728888..e92517f2 100644 --- a/src/LibHac/FsSystem/Save/Header.cs +++ b/src/LibHac/FsSystem/Save/Header.cs @@ -4,285 +4,284 @@ using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class Header { - public class Header + public IStorage MainStorage { get; } + public IStorage MainHeader { get; } + public IStorage DuplexHeader { get; } + public IStorage DataIvfcHeader { get; } + public IStorage JournalHeader { get; } + public IStorage SaveHeader { get; } + public IStorage MainRemapHeader { get; } + public IStorage MetaDataRemapHeader { get; } + public IStorage ExtraDataStorage { get; } + public IStorage FatIvfcHeader { get; } + public IStorage DuplexMasterBitmapA { get; } + public IStorage DuplexMasterBitmapB { get; } + public IStorage DataIvfcMaster { get; } + public IStorage FatIvfcMaster { get; } + + public byte[] Cmac { get; set; } + public FsLayout Layout { get; set; } + public DuplexHeader Duplex { get; set; } + public IvfcHeader Ivfc { get; set; } + public IvfcHeader FatIvfc { get; set; } + + public ExtraData ExtraData { get; set; } + + public IStorage MasterHash { get; } + + public Validity SignatureValidity { get; } + public Validity HeaderHashValidity { get; } + + public byte[] Data { get; } + + public Header(IStorage storage, KeySet keySet) { - public IStorage MainStorage { get; } - public IStorage MainHeader { get; } - public IStorage DuplexHeader { get; } - public IStorage DataIvfcHeader { get; } - public IStorage JournalHeader { get; } - public IStorage SaveHeader { get; } - public IStorage MainRemapHeader { get; } - public IStorage MetaDataRemapHeader { get; } - public IStorage ExtraDataStorage { get; } - public IStorage FatIvfcHeader { get; } - public IStorage DuplexMasterBitmapA { get; } - public IStorage DuplexMasterBitmapB { get; } - public IStorage DataIvfcMaster { get; } - public IStorage FatIvfcMaster { get; } + MainStorage = storage; + MainHeader = MainStorage.Slice(0x100, 0x200); + DuplexHeader = MainStorage.Slice(0x300, 0x44); + DataIvfcHeader = MainStorage.Slice(0x344, 0xC0); + JournalHeader = MainStorage.Slice(0x408, 0x200); + SaveHeader = MainStorage.Slice(0x608, 0x48); + MainRemapHeader = MainStorage.Slice(0x650, 0x40); + MetaDataRemapHeader = MainStorage.Slice(0x690, 0x40); + ExtraDataStorage = MainStorage.Slice(0x6D8, 0x400); + FatIvfcHeader = MainStorage.Slice(0xAD8, 0xC0); - public byte[] Cmac { get; set; } - public FsLayout Layout { get; set; } - public DuplexHeader Duplex { get; set; } - public IvfcHeader Ivfc { get; set; } - public IvfcHeader FatIvfc { get; set; } + Layout = new FsLayout(MainHeader); - public ExtraData ExtraData { get; set; } + DuplexMasterBitmapA = MainStorage.Slice(Layout.DuplexMasterOffsetA, Layout.DuplexMasterSize); + DuplexMasterBitmapB = MainStorage.Slice(Layout.DuplexMasterOffsetB, Layout.DuplexMasterSize); + DataIvfcMaster = MainStorage.Slice(Layout.IvfcMasterHashOffsetA, Layout.IvfcMasterHashSize); + FatIvfcMaster = MainStorage.Slice(Layout.FatIvfcMasterHashA, Layout.IvfcMasterHashSize); - public IStorage MasterHash { get; } + var reader = new BinaryReader(storage.AsStream()); - public Validity SignatureValidity { get; } - public Validity HeaderHashValidity { get; } + reader.BaseStream.Position = 0; + Data = reader.ReadBytes(0x4000); + reader.BaseStream.Position = 0; - public byte[] Data { get; } + Cmac = reader.ReadBytes(0x10); - public Header(IStorage storage, KeySet keySet) + reader.BaseStream.Position = 0x100; + + reader.BaseStream.Position = 0x300; + Duplex = new DuplexHeader(reader); + + reader.BaseStream.Position = 0x6D8; + ExtraData = new ExtraData(reader); + + Ivfc = new IvfcHeader(DataIvfcHeader) { NumLevels = 5 }; + + if (Layout.Version >= 0x50000) { - MainStorage = storage; - MainHeader = MainStorage.Slice(0x100, 0x200); - DuplexHeader = MainStorage.Slice(0x300, 0x44); - DataIvfcHeader = MainStorage.Slice(0x344, 0xC0); - JournalHeader = MainStorage.Slice(0x408, 0x200); - SaveHeader = MainStorage.Slice(0x608, 0x48); - MainRemapHeader = MainStorage.Slice(0x650, 0x40); - MetaDataRemapHeader = MainStorage.Slice(0x690, 0x40); - ExtraDataStorage = MainStorage.Slice(0x6D8, 0x400); - FatIvfcHeader = MainStorage.Slice(0xAD8, 0xC0); - - Layout = new FsLayout(MainHeader); - - DuplexMasterBitmapA = MainStorage.Slice(Layout.DuplexMasterOffsetA, Layout.DuplexMasterSize); - DuplexMasterBitmapB = MainStorage.Slice(Layout.DuplexMasterOffsetB, Layout.DuplexMasterSize); - DataIvfcMaster = MainStorage.Slice(Layout.IvfcMasterHashOffsetA, Layout.IvfcMasterHashSize); - FatIvfcMaster = MainStorage.Slice(Layout.FatIvfcMasterHashA, Layout.IvfcMasterHashSize); - - var reader = new BinaryReader(storage.AsStream()); - - reader.BaseStream.Position = 0; - Data = reader.ReadBytes(0x4000); - reader.BaseStream.Position = 0; - - Cmac = reader.ReadBytes(0x10); - - reader.BaseStream.Position = 0x100; - - reader.BaseStream.Position = 0x300; - Duplex = new DuplexHeader(reader); - - reader.BaseStream.Position = 0x6D8; - ExtraData = new ExtraData(reader); - - Ivfc = new IvfcHeader(DataIvfcHeader) { NumLevels = 5 }; - - if (Layout.Version >= 0x50000) - { - FatIvfc = new IvfcHeader(FatIvfcHeader) { NumLevels = 4 }; - } - - MasterHash = storage.Slice(Layout.IvfcMasterHashOffsetA, Layout.IvfcMasterHashSize); - - Span actualHeaderHash = stackalloc byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(Data.AsSpan(0x300, 0x3d00), actualHeaderHash); - - HeaderHashValidity = Utilities.SpansEqual(Layout.Hash, actualHeaderHash) ? Validity.Valid : Validity.Invalid; - SignatureValidity = ValidateSignature(keySet); + FatIvfc = new IvfcHeader(FatIvfcHeader) { NumLevels = 4 }; } - private Validity ValidateSignature(KeySet keySet) - { - Span calculatedCmac = stackalloc byte[0x10]; + MasterHash = storage.Slice(Layout.IvfcMasterHashOffsetA, Layout.IvfcMasterHashSize); - Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keySet.DeviceUniqueSaveMacKeys[0]); + Span actualHeaderHash = stackalloc byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(Data.AsSpan(0x300, 0x3d00), actualHeaderHash); - return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid; - } + HeaderHashValidity = Utilities.SpansEqual(Layout.Hash, actualHeaderHash) ? Validity.Valid : Validity.Invalid; + SignatureValidity = ValidateSignature(keySet); } - public class FsLayout + private Validity ValidateSignature(KeySet keySet) { - public string Magic { get; set; } - public uint Version { get; set; } - public byte[] Hash { get; set; } - public long FileMapEntryOffset { get; set; } - public long FileMapEntrySize { get; set; } - public long MetaMapEntryOffset { get; set; } - public long MetaMapEntrySize { get; set; } - public long FileMapDataOffset { get; set; } - public long FileMapDataSize { get; set; } - public long DuplexL1OffsetA { get; set; } - public long DuplexL1OffsetB { get; set; } - public long DuplexL1Size { get; set; } - public long DuplexDataOffsetA { get; set; } - public long DuplexDataOffsetB { get; set; } - public long DuplexDataSize { get; set; } - public long JournalDataOffset { get; set; } - public long JournalDataSizeA { get; set; } - public long JournalDataSizeB { get; set; } - public long JournalSize { get; set; } - public long DuplexMasterOffsetA { get; set; } - public long DuplexMasterOffsetB { get; set; } - public long DuplexMasterSize { get; set; } - public long IvfcMasterHashOffsetA { get; set; } - public long IvfcMasterHashOffsetB { get; set; } - public long IvfcMasterHashSize { get; set; } - public long JournalMapTableOffset { get; set; } - public long JournalMapTableSize { get; set; } - public long JournalPhysicalBitmapOffset { get; set; } - public long JournalPhysicalBitmapSize { get; set; } - public long JournalVirtualBitmapOffset { get; set; } - public long JournalVirtualBitmapSize { get; set; } - public long JournalFreeBitmapOffset { get; set; } - public long JournalFreeBitmapSize { get; set; } - public long IvfcL1Offset { get; set; } - public long IvfcL1Size { get; set; } - public long IvfcL2Offset { get; set; } - public long IvfcL2Size { get; set; } - public long IvfcL3Offset { get; set; } - public long IvfcL3Size { get; set; } - public long FatOffset { get; set; } - public long FatSize { get; set; } - public long DuplexIndex { get; set; } - public long FatIvfcMasterHashA { get; set; } - public long FatIvfcMasterHashB { get; set; } - public long FatIvfcL1Offset { get; set; } - public long FatIvfcL1Size { get; set; } - public long FatIvfcL2Offset { get; set; } - public long FatIvfcL2Size { get; set; } + Span calculatedCmac = stackalloc byte[0x10]; - public FsLayout(IStorage storage) - { - var reader = new BinaryReader(storage.AsStream()); + Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keySet.DeviceUniqueSaveMacKeys[0]); - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - Hash = reader.ReadBytes(0x20); - FileMapEntryOffset = reader.ReadInt64(); - FileMapEntrySize = reader.ReadInt64(); - MetaMapEntryOffset = reader.ReadInt64(); - MetaMapEntrySize = reader.ReadInt64(); - FileMapDataOffset = reader.ReadInt64(); - FileMapDataSize = reader.ReadInt64(); - DuplexL1OffsetA = reader.ReadInt64(); - DuplexL1OffsetB = reader.ReadInt64(); - DuplexL1Size = reader.ReadInt64(); - DuplexDataOffsetA = reader.ReadInt64(); - DuplexDataOffsetB = reader.ReadInt64(); - DuplexDataSize = reader.ReadInt64(); - JournalDataOffset = reader.ReadInt64(); - JournalDataSizeA = reader.ReadInt64(); - JournalDataSizeB = reader.ReadInt64(); - JournalSize = reader.ReadInt64(); - DuplexMasterOffsetA = reader.ReadInt64(); - DuplexMasterOffsetB = reader.ReadInt64(); - DuplexMasterSize = reader.ReadInt64(); - IvfcMasterHashOffsetA = reader.ReadInt64(); - IvfcMasterHashOffsetB = reader.ReadInt64(); - IvfcMasterHashSize = reader.ReadInt64(); - JournalMapTableOffset = reader.ReadInt64(); - JournalMapTableSize = reader.ReadInt64(); - JournalPhysicalBitmapOffset = reader.ReadInt64(); - JournalPhysicalBitmapSize = reader.ReadInt64(); - JournalVirtualBitmapOffset = reader.ReadInt64(); - JournalVirtualBitmapSize = reader.ReadInt64(); - JournalFreeBitmapOffset = reader.ReadInt64(); - JournalFreeBitmapSize = reader.ReadInt64(); - IvfcL1Offset = reader.ReadInt64(); - IvfcL1Size = reader.ReadInt64(); - IvfcL2Offset = reader.ReadInt64(); - IvfcL2Size = reader.ReadInt64(); - IvfcL3Offset = reader.ReadInt64(); - IvfcL3Size = reader.ReadInt64(); - FatOffset = reader.ReadInt64(); - FatSize = reader.ReadInt64(); - DuplexIndex = reader.ReadByte(); - - reader.BaseStream.Position += 7; - FatIvfcMasterHashA = reader.ReadInt64(); - FatIvfcMasterHashB = reader.ReadInt64(); - FatIvfcL1Offset = reader.ReadInt64(); - FatIvfcL1Size = reader.ReadInt64(); - FatIvfcL2Offset = reader.ReadInt64(); - FatIvfcL2Size = reader.ReadInt64(); - } + return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid; } +} - public class DuplexHeader +public class FsLayout +{ + public string Magic { get; set; } + public uint Version { get; set; } + public byte[] Hash { get; set; } + public long FileMapEntryOffset { get; set; } + public long FileMapEntrySize { get; set; } + public long MetaMapEntryOffset { get; set; } + public long MetaMapEntrySize { get; set; } + public long FileMapDataOffset { get; set; } + public long FileMapDataSize { get; set; } + public long DuplexL1OffsetA { get; set; } + public long DuplexL1OffsetB { get; set; } + public long DuplexL1Size { get; set; } + public long DuplexDataOffsetA { get; set; } + public long DuplexDataOffsetB { get; set; } + public long DuplexDataSize { get; set; } + public long JournalDataOffset { get; set; } + public long JournalDataSizeA { get; set; } + public long JournalDataSizeB { get; set; } + public long JournalSize { get; set; } + public long DuplexMasterOffsetA { get; set; } + public long DuplexMasterOffsetB { get; set; } + public long DuplexMasterSize { get; set; } + public long IvfcMasterHashOffsetA { get; set; } + public long IvfcMasterHashOffsetB { get; set; } + public long IvfcMasterHashSize { get; set; } + public long JournalMapTableOffset { get; set; } + public long JournalMapTableSize { get; set; } + public long JournalPhysicalBitmapOffset { get; set; } + public long JournalPhysicalBitmapSize { get; set; } + public long JournalVirtualBitmapOffset { get; set; } + public long JournalVirtualBitmapSize { get; set; } + public long JournalFreeBitmapOffset { get; set; } + public long JournalFreeBitmapSize { get; set; } + public long IvfcL1Offset { get; set; } + public long IvfcL1Size { get; set; } + public long IvfcL2Offset { get; set; } + public long IvfcL2Size { get; set; } + public long IvfcL3Offset { get; set; } + public long IvfcL3Size { get; set; } + public long FatOffset { get; set; } + public long FatSize { get; set; } + public long DuplexIndex { get; set; } + public long FatIvfcMasterHashA { get; set; } + public long FatIvfcMasterHashB { get; set; } + public long FatIvfcL1Offset { get; set; } + public long FatIvfcL1Size { get; set; } + public long FatIvfcL2Offset { get; set; } + public long FatIvfcL2Size { get; set; } + + public FsLayout(IStorage storage) { - public string Magic { get; } - public uint Version { get; } - public DuplexInfo[] Layers { get; } = new DuplexInfo[3]; + var reader = new BinaryReader(storage.AsStream()); - public DuplexHeader(BinaryReader reader) - { - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + Hash = reader.ReadBytes(0x20); + FileMapEntryOffset = reader.ReadInt64(); + FileMapEntrySize = reader.ReadInt64(); + MetaMapEntryOffset = reader.ReadInt64(); + MetaMapEntrySize = reader.ReadInt64(); + FileMapDataOffset = reader.ReadInt64(); + FileMapDataSize = reader.ReadInt64(); + DuplexL1OffsetA = reader.ReadInt64(); + DuplexL1OffsetB = reader.ReadInt64(); + DuplexL1Size = reader.ReadInt64(); + DuplexDataOffsetA = reader.ReadInt64(); + DuplexDataOffsetB = reader.ReadInt64(); + DuplexDataSize = reader.ReadInt64(); + JournalDataOffset = reader.ReadInt64(); + JournalDataSizeA = reader.ReadInt64(); + JournalDataSizeB = reader.ReadInt64(); + JournalSize = reader.ReadInt64(); + DuplexMasterOffsetA = reader.ReadInt64(); + DuplexMasterOffsetB = reader.ReadInt64(); + DuplexMasterSize = reader.ReadInt64(); + IvfcMasterHashOffsetA = reader.ReadInt64(); + IvfcMasterHashOffsetB = reader.ReadInt64(); + IvfcMasterHashSize = reader.ReadInt64(); + JournalMapTableOffset = reader.ReadInt64(); + JournalMapTableSize = reader.ReadInt64(); + JournalPhysicalBitmapOffset = reader.ReadInt64(); + JournalPhysicalBitmapSize = reader.ReadInt64(); + JournalVirtualBitmapOffset = reader.ReadInt64(); + JournalVirtualBitmapSize = reader.ReadInt64(); + JournalFreeBitmapOffset = reader.ReadInt64(); + JournalFreeBitmapSize = reader.ReadInt64(); + IvfcL1Offset = reader.ReadInt64(); + IvfcL1Size = reader.ReadInt64(); + IvfcL2Offset = reader.ReadInt64(); + IvfcL2Size = reader.ReadInt64(); + IvfcL3Offset = reader.ReadInt64(); + IvfcL3Size = reader.ReadInt64(); + FatOffset = reader.ReadInt64(); + FatSize = reader.ReadInt64(); + DuplexIndex = reader.ReadByte(); - for (int i = 0; i < Layers.Length; i++) - { - Layers[i] = new DuplexInfo(reader); - } - } + reader.BaseStream.Position += 7; + FatIvfcMasterHashA = reader.ReadInt64(); + FatIvfcMasterHashB = reader.ReadInt64(); + FatIvfcL1Offset = reader.ReadInt64(); + FatIvfcL1Size = reader.ReadInt64(); + FatIvfcL2Offset = reader.ReadInt64(); + FatIvfcL2Size = reader.ReadInt64(); } +} - public class DuplexInfo +public class DuplexHeader +{ + public string Magic { get; } + public uint Version { get; } + public DuplexInfo[] Layers { get; } = new DuplexInfo[3]; + + public DuplexHeader(BinaryReader reader) { - public long Offset { get; } - public long Length { get; set; } - public int BlockSizePower { get; set; } - public int BlockSize { get; set; } + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); - public DuplexInfo() { } - - public DuplexInfo(BinaryReader reader) + for (int i = 0; i < Layers.Length; i++) { - Offset = reader.ReadInt64(); - Length = reader.ReadInt64(); - BlockSizePower = reader.ReadInt32(); - BlockSize = 1 << BlockSizePower; - } - } - - public class ExtraData - { - public ulong TitleId { get; } - public Guid UserId { get; } - public ulong SaveId { get; } - public SaveDataType Type { get; } - - public ulong SaveOwnerId { get; } - public long Timestamp { get; } - public long Field50 { get; } - public uint Field54 { get; } - public long DataSize { get; } - public long JournalSize { get; } - - public ExtraData(BinaryReader reader) - { - TitleId = reader.ReadUInt64(); - UserId = ToGuid(reader.ReadBytes(0x10)); - SaveId = reader.ReadUInt64(); - Type = (SaveDataType)reader.ReadByte(); - reader.BaseStream.Position += 0x1f; - - SaveOwnerId = reader.ReadUInt64(); - Timestamp = reader.ReadInt64(); - Field50 = reader.ReadUInt32(); - Field54 = reader.ReadUInt32(); - DataSize = reader.ReadInt64(); - JournalSize = reader.ReadInt64(); - } - - private static Guid ToGuid(byte[] bytes) - { - byte[] b = new byte[0x10]; - Array.Copy(bytes, b, 0x10); - - // The Guid constructor uses a weird, mixed-endian format - Array.Reverse(b, 10, 6); - - return new Guid(b); + Layers[i] = new DuplexInfo(reader); } } } + +public class DuplexInfo +{ + public long Offset { get; } + public long Length { get; set; } + public int BlockSizePower { get; set; } + public int BlockSize { get; set; } + + public DuplexInfo() { } + + public DuplexInfo(BinaryReader reader) + { + Offset = reader.ReadInt64(); + Length = reader.ReadInt64(); + BlockSizePower = reader.ReadInt32(); + BlockSize = 1 << BlockSizePower; + } +} + +public class ExtraData +{ + public ulong TitleId { get; } + public Guid UserId { get; } + public ulong SaveId { get; } + public SaveDataType Type { get; } + + public ulong SaveOwnerId { get; } + public long Timestamp { get; } + public long Field50 { get; } + public uint Field54 { get; } + public long DataSize { get; } + public long JournalSize { get; } + + public ExtraData(BinaryReader reader) + { + TitleId = reader.ReadUInt64(); + UserId = ToGuid(reader.ReadBytes(0x10)); + SaveId = reader.ReadUInt64(); + Type = (SaveDataType)reader.ReadByte(); + reader.BaseStream.Position += 0x1f; + + SaveOwnerId = reader.ReadUInt64(); + Timestamp = reader.ReadInt64(); + Field50 = reader.ReadUInt32(); + Field54 = reader.ReadUInt32(); + DataSize = reader.ReadInt64(); + JournalSize = reader.ReadInt64(); + } + + private static Guid ToGuid(byte[] bytes) + { + byte[] b = new byte[0x10]; + Array.Copy(bytes, b, 0x10); + + // The Guid constructor uses a weird, mixed-endian format + Array.Reverse(b, 10, 6); + + return new Guid(b); + } +} diff --git a/src/LibHac/FsSystem/Save/HierarchicalDuplexStorage.cs b/src/LibHac/FsSystem/Save/HierarchicalDuplexStorage.cs index 42292cf0..b4bb8e0d 100644 --- a/src/LibHac/FsSystem/Save/HierarchicalDuplexStorage.cs +++ b/src/LibHac/FsSystem/Save/HierarchicalDuplexStorage.cs @@ -1,78 +1,77 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class HierarchicalDuplexStorage : IStorage { - public class HierarchicalDuplexStorage : IStorage + private DuplexStorage[] Layers { get; } + private DuplexStorage DataLayer { get; } + private long Length { get; } + + public HierarchicalDuplexStorage(DuplexFsLayerInfo[] layers, bool masterBit) { - private DuplexStorage[] Layers { get; } - private DuplexStorage DataLayer { get; } - private long Length { get; } + Layers = new DuplexStorage[layers.Length - 1]; - public HierarchicalDuplexStorage(DuplexFsLayerInfo[] layers, bool masterBit) + for (int i = 0; i < Layers.Length; i++) { - Layers = new DuplexStorage[layers.Length - 1]; + IStorage bitmap; - for (int i = 0; i < Layers.Length; i++) + if (i == 0) { - IStorage bitmap; - - if (i == 0) - { - bitmap = masterBit ? layers[0].DataB : layers[0].DataA; - } - else - { - bitmap = Layers[i - 1]; - } - - Layers[i] = new DuplexStorage(layers[i + 1].DataA, layers[i + 1].DataB, bitmap, layers[i + 1].Info.BlockSize); + bitmap = masterBit ? layers[0].DataB : layers[0].DataA; + } + else + { + bitmap = Layers[i - 1]; } - DataLayer = Layers[Layers.Length - 1]; - DataLayer.GetSize(out long dataSize).ThrowIfFailure(); - Length = dataSize; + Layers[i] = new DuplexStorage(layers[i + 1].DataA, layers[i + 1].DataB, bitmap, layers[i + 1].Info.BlockSize); } - protected override Result DoRead(long offset, Span destination) - { - return DataLayer.Read(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - return DataLayer.Write(offset, source); - } - - protected override Result DoFlush() - { - return DataLayer.Flush(); - } - - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - public void FsTrim() - { - foreach (DuplexStorage layer in Layers) - { - layer.FsTrim(); - } - } + DataLayer = Layers[Layers.Length - 1]; + DataLayer.GetSize(out long dataSize).ThrowIfFailure(); + Length = dataSize; } - public class DuplexFsLayerInfo + protected override Result DoRead(long offset, Span destination) { - public IStorage DataA { get; set; } - public IStorage DataB { get; set; } - public DuplexInfo Info { get; set; } + return DataLayer.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return DataLayer.Write(offset, source); + } + + protected override Result DoFlush() + { + return DataLayer.Flush(); + } + + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public void FsTrim() + { + foreach (DuplexStorage layer in Layers) + { + layer.FsTrim(); + } } } + +public class DuplexFsLayerInfo +{ + public IStorage DataA { get; set; } + public IStorage DataB { get; set; } + public DuplexInfo Info { get; set; } +} diff --git a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs index aab406cb..f02aff8b 100644 --- a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs +++ b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs @@ -5,408 +5,407 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class HierarchicalSaveFileTable { - public class HierarchicalSaveFileTable + private SaveFsList> FileTable { get; } + private SaveFsList> DirectoryTable { get; } + + public HierarchicalSaveFileTable(IStorage dirTable, IStorage fileTable) { - private SaveFsList> FileTable { get; } - private SaveFsList> DirectoryTable { get; } + FileTable = new SaveFsList>(fileTable); + DirectoryTable = new SaveFsList>(dirTable); + } - public HierarchicalSaveFileTable(IStorage dirTable, IStorage fileTable) + public bool TryOpenFile(U8Span path, out SaveFileInfo fileInfo) + { + if (!FindPathRecursive(path, out SaveEntryKey key)) { - FileTable = new SaveFsList>(fileTable); - DirectoryTable = new SaveFsList>(dirTable); - } - - public bool TryOpenFile(U8Span path, out SaveFileInfo fileInfo) - { - if (!FindPathRecursive(path, out SaveEntryKey key)) - { - UnsafeHelpers.SkipParamInit(out fileInfo); - return false; - } - - if (FileTable.TryGetValue(ref key, out TableEntry value)) - { - fileInfo = value.Value; - return true; - } - - fileInfo = default; + UnsafeHelpers.SkipParamInit(out fileInfo); return false; } - public bool FindNextFile(ref SaveFindPosition position, out SaveFileInfo info, out string name) + if (FileTable.TryGetValue(ref key, out TableEntry value)) { - if (position.NextFile == 0) - { - UnsafeHelpers.SkipParamInit(out info, out name); - return false; - } - - Span nameBytes = stackalloc byte[FileTable.MaxNameLength]; - - bool success = FileTable.TryGetValue(position.NextFile, out TableEntry entry, ref nameBytes); - - // todo error message - if (!success) - { - UnsafeHelpers.SkipParamInit(out info, out name); - return false; - } - - position.NextFile = entry.NextSibling; - info = entry.Value; - - name = StringUtils.NullTerminatedUtf8ToString(nameBytes); - + fileInfo = value.Value; return true; } - public bool FindNextDirectory(ref SaveFindPosition position, out string name) + fileInfo = default; + return false; + } + + public bool FindNextFile(ref SaveFindPosition position, out SaveFileInfo info, out string name) + { + if (position.NextFile == 0) { - if (position.NextDirectory == 0) - { - UnsafeHelpers.SkipParamInit(out name); - return false; - } - - Span nameBytes = stackalloc byte[DirectoryTable.MaxNameLength]; - - bool success = DirectoryTable.TryGetValue(position.NextDirectory, out TableEntry entry, ref nameBytes); - - // todo error message - if (!success) - { - UnsafeHelpers.SkipParamInit(out name); - return false; - } - - position.NextDirectory = entry.NextSibling; - - name = StringUtils.NullTerminatedUtf8ToString(nameBytes); - - return true; + UnsafeHelpers.SkipParamInit(out info, out name); + return false; } - public void AddFile(U8Span path, ref SaveFileInfo fileInfo) - { - if (path.Length == 1 && path[0] == '/') throw new ArgumentException("Path cannot be empty"); + Span nameBytes = stackalloc byte[FileTable.MaxNameLength]; - CreateFileRecursive(path, ref fileInfo); + bool success = FileTable.TryGetValue(position.NextFile, out TableEntry entry, ref nameBytes); + + // todo error message + if (!success) + { + UnsafeHelpers.SkipParamInit(out info, out name); + return false; } - public void AddDirectory(U8Span path) - { - if (path.Length == 1 && path[0] == '/') throw new ArgumentException("Path cannot be empty"); + position.NextFile = entry.NextSibling; + info = entry.Value; - CreateDirectoryRecursive(path); + name = StringUtils.NullTerminatedUtf8ToString(nameBytes); + + return true; + } + + public bool FindNextDirectory(ref SaveFindPosition position, out string name) + { + if (position.NextDirectory == 0) + { + UnsafeHelpers.SkipParamInit(out name); + return false; } - private void CreateFileRecursive(ReadOnlySpan path, ref SaveFileInfo fileInfo) + Span nameBytes = stackalloc byte[DirectoryTable.MaxNameLength]; + + bool success = DirectoryTable.TryGetValue(position.NextDirectory, out TableEntry entry, ref nameBytes); + + // todo error message + if (!success) { - var parser = new PathParser(path); - var key = new SaveEntryKey(parser.GetCurrent(), 0); + UnsafeHelpers.SkipParamInit(out name); + return false; + } - int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key); + position.NextDirectory = entry.NextSibling; - int index = FileTable.GetIndexFromKey(ref key).Index; - var fileEntry = new TableEntry(); + name = StringUtils.NullTerminatedUtf8ToString(nameBytes); - // File already exists. Update file info. - if (index >= 0) - { - FileTable.GetValue(index, out fileEntry); - fileEntry.Value = fileInfo; - FileTable.SetValue(index, ref fileEntry); - return; - } + return true; + } + public void AddFile(U8Span path, ref SaveFileInfo fileInfo) + { + if (path.Length == 1 && path[0] == '/') throw new ArgumentException("Path cannot be empty"); + + CreateFileRecursive(path, ref fileInfo); + } + + public void AddDirectory(U8Span path) + { + if (path.Length == 1 && path[0] == '/') throw new ArgumentException("Path cannot be empty"); + + CreateDirectoryRecursive(path); + } + + private void CreateFileRecursive(ReadOnlySpan path, ref SaveFileInfo fileInfo) + { + var parser = new PathParser(path); + var key = new SaveEntryKey(parser.GetCurrent(), 0); + + int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key); + + int index = FileTable.GetIndexFromKey(ref key).Index; + var fileEntry = new TableEntry(); + + // File already exists. Update file info. + if (index >= 0) + { + FileTable.GetValue(index, out fileEntry); fileEntry.Value = fileInfo; - index = FileTable.Add(ref key, ref fileEntry); - - LinkFileToParent(parentIndex, index); + FileTable.SetValue(index, ref fileEntry); + return; } - private void CreateDirectoryRecursive(ReadOnlySpan path) + fileEntry.Value = fileInfo; + index = FileTable.Add(ref key, ref fileEntry); + + LinkFileToParent(parentIndex, index); + } + + private void CreateDirectoryRecursive(ReadOnlySpan path) + { + var parser = new PathParser(path); + var key = new SaveEntryKey(parser.GetCurrent(), 0); + + int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key); + + int index = DirectoryTable.GetIndexFromKey(ref key).Index; + var dirEntry = new TableEntry(); + + // Directory already exists. Do nothing. + if (index >= 0) return; + + index = DirectoryTable.Add(ref key, ref dirEntry); + + LinkDirectoryToParent(parentIndex, index); + } + + private int CreateParentDirectoryRecursive(ref PathParser parser, ref SaveEntryKey key) + { + int prevIndex = 0; + + while (!parser.IsFinished()) { - var parser = new PathParser(path); - var key = new SaveEntryKey(parser.GetCurrent(), 0); - - int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key); - int index = DirectoryTable.GetIndexFromKey(ref key).Index; - var dirEntry = new TableEntry(); - // Directory already exists. Do nothing. - if (index >= 0) return; - - index = DirectoryTable.Add(ref key, ref dirEntry); - - LinkDirectoryToParent(parentIndex, index); - } - - private int CreateParentDirectoryRecursive(ref PathParser parser, ref SaveEntryKey key) - { - int prevIndex = 0; - - while (!parser.IsFinished()) + if (index < 0) { - int index = DirectoryTable.GetIndexFromKey(ref key).Index; + var newEntry = new TableEntry(); + index = DirectoryTable.Add(ref key, ref newEntry); - if (index < 0) + if (prevIndex > 0) { - var newEntry = new TableEntry(); - index = DirectoryTable.Add(ref key, ref newEntry); - - if (prevIndex > 0) - { - LinkDirectoryToParent(prevIndex, index); - } + LinkDirectoryToParent(prevIndex, index); } - - prevIndex = index; - key.Parent = index; - parser.TryGetNext(out key.Name); } - return prevIndex; + prevIndex = index; + key.Parent = index; + parser.TryGetNext(out key.Name); } - private void LinkFileToParent(int parentIndex, int fileIndex) + return prevIndex; + } + + private void LinkFileToParent(int parentIndex, int fileIndex) + { + DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); + FileTable.GetValue(fileIndex, out TableEntry fileEntry); + + fileEntry.NextSibling = parentEntry.Value.NextFile; + parentEntry.Value.NextFile = fileIndex; + + DirectoryTable.SetValue(parentIndex, ref parentEntry); + FileTable.SetValue(fileIndex, ref fileEntry); + } + + private void LinkDirectoryToParent(int parentIndex, int dirIndex) + { + DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); + DirectoryTable.GetValue(dirIndex, out TableEntry dirEntry); + + dirEntry.NextSibling = parentEntry.Value.NextDirectory; + parentEntry.Value.NextDirectory = dirIndex; + + DirectoryTable.SetValue(parentIndex, ref parentEntry); + DirectoryTable.SetValue(dirIndex, ref dirEntry); + } + + private void UnlinkFileFromParent(int parentIndex, int fileIndex) + { + DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); + FileTable.GetValue(fileIndex, out TableEntry fileEntry); + + if (parentEntry.Value.NextFile == fileIndex) { - DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); - FileTable.GetValue(fileIndex, out TableEntry fileEntry); - - fileEntry.NextSibling = parentEntry.Value.NextFile; - parentEntry.Value.NextFile = fileIndex; - + parentEntry.Value.NextFile = fileEntry.NextSibling; DirectoryTable.SetValue(parentIndex, ref parentEntry); - FileTable.SetValue(fileIndex, ref fileEntry); + return; } - private void LinkDirectoryToParent(int parentIndex, int dirIndex) + int prevIndex = parentEntry.Value.NextFile; + FileTable.GetValue(prevIndex, out TableEntry prevEntry); + int curIndex = prevEntry.NextSibling; + + while (curIndex != 0) { - DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); - DirectoryTable.GetValue(dirIndex, out TableEntry dirEntry); + FileTable.GetValue(curIndex, out TableEntry curEntry); - dirEntry.NextSibling = parentEntry.Value.NextDirectory; - parentEntry.Value.NextDirectory = dirIndex; - - DirectoryTable.SetValue(parentIndex, ref parentEntry); - DirectoryTable.SetValue(dirIndex, ref dirEntry); - } - - private void UnlinkFileFromParent(int parentIndex, int fileIndex) - { - DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); - FileTable.GetValue(fileIndex, out TableEntry fileEntry); - - if (parentEntry.Value.NextFile == fileIndex) + if (curIndex == fileIndex) { - parentEntry.Value.NextFile = fileEntry.NextSibling; - DirectoryTable.SetValue(parentIndex, ref parentEntry); + prevEntry.NextSibling = curEntry.NextSibling; + FileTable.SetValue(prevIndex, ref prevEntry); return; } - int prevIndex = parentEntry.Value.NextFile; - FileTable.GetValue(prevIndex, out TableEntry prevEntry); - int curIndex = prevEntry.NextSibling; - - while (curIndex != 0) - { - FileTable.GetValue(curIndex, out TableEntry curEntry); - - if (curIndex == fileIndex) - { - prevEntry.NextSibling = curEntry.NextSibling; - FileTable.SetValue(prevIndex, ref prevEntry); - return; - } - - prevIndex = curIndex; - prevEntry = curEntry; - curIndex = prevEntry.NextSibling; - } - } - - private void UnlinkDirectoryFromParent(int parentIndex, int dirIndex) - { - DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); - DirectoryTable.GetValue(dirIndex, out TableEntry dirEntry); - - if (parentEntry.Value.NextDirectory == dirIndex) - { - parentEntry.Value.NextDirectory = dirEntry.NextSibling; - DirectoryTable.SetValue(parentIndex, ref parentEntry); - return; - } - - int prevIndex = parentEntry.Value.NextDirectory; - DirectoryTable.GetValue(prevIndex, out TableEntry prevEntry); - int curIndex = prevEntry.NextSibling; - - while (curIndex != 0) - { - DirectoryTable.GetValue(curIndex, out TableEntry curEntry); - - if (curIndex == dirIndex) - { - prevEntry.NextSibling = curEntry.NextSibling; - DirectoryTable.SetValue(prevIndex, ref prevEntry); - return; - } - - prevIndex = curIndex; - prevEntry = curEntry; - curIndex = prevEntry.NextSibling; - } - } - - public void DeleteFile(U8Span path) - { - FindPathRecursive(path, out SaveEntryKey key); - int parentIndex = key.Parent; - - int toDeleteIndex = FileTable.GetIndexFromKey(ref key).Index; - if (toDeleteIndex < 0) throw new FileNotFoundException(); - - UnlinkFileFromParent(parentIndex, toDeleteIndex); - - FileTable.Remove(ref key); - } - - public void DeleteDirectory(U8Span path) - { - FindPathRecursive(path, out SaveEntryKey key); - int parentIndex = key.Parent; - - int toDeleteIndex = DirectoryTable.GetIndexFromKey(ref key).Index; - if (toDeleteIndex < 0) throw new DirectoryNotFoundException(); - - DirectoryTable.GetValue(toDeleteIndex, out TableEntry toDeleteEntry); - - if (toDeleteEntry.Value.NextDirectory != 0 || toDeleteEntry.Value.NextFile != 0) - { - throw new IOException("Directory is not empty."); - } - - UnlinkDirectoryFromParent(parentIndex, toDeleteIndex); - - DirectoryTable.Remove(ref key); - } - - public void RenameFile(U8Span srcPath, U8Span dstPath) - { - if (srcPath.Value == dstPath.Value || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _)) - { - throw new IOException("Destination path already exists."); - } - - if (!FindPathRecursive(srcPath, out SaveEntryKey oldKey)) - { - throw new FileNotFoundException(); - } - - int fileIndex = FileTable.GetIndexFromKey(ref oldKey).Index; - - if (!FindPathRecursive(dstPath, out SaveEntryKey newKey)) - { - throw new FileNotFoundException(); - } - - if (oldKey.Parent != newKey.Parent) - { - UnlinkFileFromParent(oldKey.Parent, fileIndex); - LinkFileToParent(newKey.Parent, fileIndex); - } - - FileTable.ChangeKey(ref oldKey, ref newKey); - } - - public Result RenameDirectory(U8Span srcPath, U8Span dstPath) - { - if (srcPath.Value == dstPath.Value || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _)) - { - return ResultFs.PathAlreadyExists.Log(); - } - - if (!FindPathRecursive(srcPath, out SaveEntryKey oldKey)) - { - return ResultFs.PathNotFound.Log(); - } - - int dirIndex = DirectoryTable.GetIndexFromKey(ref oldKey).Index; - - if (!FindPathRecursive(dstPath, out SaveEntryKey newKey)) - { - return ResultFs.PathNotFound.Log(); - } - - if (PathTools.IsSubPath(srcPath, dstPath)) - { - return ResultFs.DirectoryNotRenamable.Log(); - } - - if (oldKey.Parent != newKey.Parent) - { - UnlinkDirectoryFromParent(oldKey.Parent, dirIndex); - LinkDirectoryToParent(newKey.Parent, dirIndex); - } - - DirectoryTable.ChangeKey(ref oldKey, ref newKey); - - return Result.Success; - } - - public bool TryOpenDirectory(U8Span path, out SaveFindPosition position) - { - UnsafeHelpers.SkipParamInit(out position); - - if (!FindPathRecursive(path, out SaveEntryKey key)) - { - return false; - } - - if (DirectoryTable.TryGetValue(ref key, out TableEntry entry)) - { - position = entry.Value; - return true; - } - - return false; - } - - private bool FindPathRecursive(ReadOnlySpan path, out SaveEntryKey key) - { - var parser = new PathParser(path); - key = new SaveEntryKey(parser.GetCurrent(), 0); - - while (!parser.IsFinished()) - { - key.Parent = DirectoryTable.GetIndexFromKey(ref key).Index; - - if (key.Parent < 0) return false; - - parser.TryGetNext(out key.Name); - } - - return true; - } - - public void TrimFreeEntries() - { - DirectoryTable.TrimFreeEntries(); - FileTable.TrimFreeEntries(); - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - // todo: Change constraint to "unmanaged" after updating to - // a newer SDK https://github.com/dotnet/csharplang/issues/1937 - private struct TableEntry where T : struct - { - public int NextSibling; - public T Value; + prevIndex = curIndex; + prevEntry = curEntry; + curIndex = prevEntry.NextSibling; } } + + private void UnlinkDirectoryFromParent(int parentIndex, int dirIndex) + { + DirectoryTable.GetValue(parentIndex, out TableEntry parentEntry); + DirectoryTable.GetValue(dirIndex, out TableEntry dirEntry); + + if (parentEntry.Value.NextDirectory == dirIndex) + { + parentEntry.Value.NextDirectory = dirEntry.NextSibling; + DirectoryTable.SetValue(parentIndex, ref parentEntry); + return; + } + + int prevIndex = parentEntry.Value.NextDirectory; + DirectoryTable.GetValue(prevIndex, out TableEntry prevEntry); + int curIndex = prevEntry.NextSibling; + + while (curIndex != 0) + { + DirectoryTable.GetValue(curIndex, out TableEntry curEntry); + + if (curIndex == dirIndex) + { + prevEntry.NextSibling = curEntry.NextSibling; + DirectoryTable.SetValue(prevIndex, ref prevEntry); + return; + } + + prevIndex = curIndex; + prevEntry = curEntry; + curIndex = prevEntry.NextSibling; + } + } + + public void DeleteFile(U8Span path) + { + FindPathRecursive(path, out SaveEntryKey key); + int parentIndex = key.Parent; + + int toDeleteIndex = FileTable.GetIndexFromKey(ref key).Index; + if (toDeleteIndex < 0) throw new FileNotFoundException(); + + UnlinkFileFromParent(parentIndex, toDeleteIndex); + + FileTable.Remove(ref key); + } + + public void DeleteDirectory(U8Span path) + { + FindPathRecursive(path, out SaveEntryKey key); + int parentIndex = key.Parent; + + int toDeleteIndex = DirectoryTable.GetIndexFromKey(ref key).Index; + if (toDeleteIndex < 0) throw new DirectoryNotFoundException(); + + DirectoryTable.GetValue(toDeleteIndex, out TableEntry toDeleteEntry); + + if (toDeleteEntry.Value.NextDirectory != 0 || toDeleteEntry.Value.NextFile != 0) + { + throw new IOException("Directory is not empty."); + } + + UnlinkDirectoryFromParent(parentIndex, toDeleteIndex); + + DirectoryTable.Remove(ref key); + } + + public void RenameFile(U8Span srcPath, U8Span dstPath) + { + if (srcPath.Value == dstPath.Value || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _)) + { + throw new IOException("Destination path already exists."); + } + + if (!FindPathRecursive(srcPath, out SaveEntryKey oldKey)) + { + throw new FileNotFoundException(); + } + + int fileIndex = FileTable.GetIndexFromKey(ref oldKey).Index; + + if (!FindPathRecursive(dstPath, out SaveEntryKey newKey)) + { + throw new FileNotFoundException(); + } + + if (oldKey.Parent != newKey.Parent) + { + UnlinkFileFromParent(oldKey.Parent, fileIndex); + LinkFileToParent(newKey.Parent, fileIndex); + } + + FileTable.ChangeKey(ref oldKey, ref newKey); + } + + public Result RenameDirectory(U8Span srcPath, U8Span dstPath) + { + if (srcPath.Value == dstPath.Value || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _)) + { + return ResultFs.PathAlreadyExists.Log(); + } + + if (!FindPathRecursive(srcPath, out SaveEntryKey oldKey)) + { + return ResultFs.PathNotFound.Log(); + } + + int dirIndex = DirectoryTable.GetIndexFromKey(ref oldKey).Index; + + if (!FindPathRecursive(dstPath, out SaveEntryKey newKey)) + { + return ResultFs.PathNotFound.Log(); + } + + if (PathTools.IsSubPath(srcPath, dstPath)) + { + return ResultFs.DirectoryNotRenamable.Log(); + } + + if (oldKey.Parent != newKey.Parent) + { + UnlinkDirectoryFromParent(oldKey.Parent, dirIndex); + LinkDirectoryToParent(newKey.Parent, dirIndex); + } + + DirectoryTable.ChangeKey(ref oldKey, ref newKey); + + return Result.Success; + } + + public bool TryOpenDirectory(U8Span path, out SaveFindPosition position) + { + UnsafeHelpers.SkipParamInit(out position); + + if (!FindPathRecursive(path, out SaveEntryKey key)) + { + return false; + } + + if (DirectoryTable.TryGetValue(ref key, out TableEntry entry)) + { + position = entry.Value; + return true; + } + + return false; + } + + private bool FindPathRecursive(ReadOnlySpan path, out SaveEntryKey key) + { + var parser = new PathParser(path); + key = new SaveEntryKey(parser.GetCurrent(), 0); + + while (!parser.IsFinished()) + { + key.Parent = DirectoryTable.GetIndexFromKey(ref key).Index; + + if (key.Parent < 0) return false; + + parser.TryGetNext(out key.Name); + } + + return true; + } + + public void TrimFreeEntries() + { + DirectoryTable.TrimFreeEntries(); + FileTable.TrimFreeEntries(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + // todo: Change constraint to "unmanaged" after updating to + // a newer SDK https://github.com/dotnet/csharplang/issues/1937 + private struct TableEntry where T : struct + { + public int NextSibling; + public T Value; + } } diff --git a/src/LibHac/FsSystem/Save/JournalMap.cs b/src/LibHac/FsSystem/Save/JournalMap.cs index 91ddb13f..1bfa0baf 100644 --- a/src/LibHac/FsSystem/Save/JournalMap.cs +++ b/src/LibHac/FsSystem/Save/JournalMap.cs @@ -2,102 +2,101 @@ using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class JournalMap { - public class JournalMap + private int MapEntryLength = 8; + public JournalMapHeader Header { get; } + private JournalMapEntry[] Entries { get; } + + private IStorage HeaderStorage { get; } + private IStorage MapStorage { get; } + private IStorage ModifiedPhysicalBlocks { get; } + private IStorage ModifiedVirtualBlocks { get; } + private IStorage FreeBlocks { get; } + + public JournalMap(IStorage header, JournalMapParams mapInfo) { - private int MapEntryLength = 8; - public JournalMapHeader Header { get; } - private JournalMapEntry[] Entries { get; } + HeaderStorage = header; + MapStorage = mapInfo.MapStorage; + ModifiedPhysicalBlocks = mapInfo.PhysicalBlockBitmap; + ModifiedVirtualBlocks = mapInfo.VirtualBlockBitmap; + FreeBlocks = mapInfo.FreeBlockBitmap; - private IStorage HeaderStorage { get; } - private IStorage MapStorage { get; } - private IStorage ModifiedPhysicalBlocks { get; } - private IStorage ModifiedVirtualBlocks { get; } - private IStorage FreeBlocks { get; } + Header = new JournalMapHeader(HeaderStorage); + Entries = ReadMapEntries(MapStorage, Header.MainDataBlockCount); + } - public JournalMap(IStorage header, JournalMapParams mapInfo) + public int GetPhysicalBlock(int virtualBlock) + { + return Entries[virtualBlock].PhysicalIndex; + } + + private static JournalMapEntry[] ReadMapEntries(IStorage mapTable, int count) + { + var tableReader = new BinaryReader(mapTable.AsStream()); + var map = new JournalMapEntry[count]; + + for (int i = 0; i < count; i++) { - HeaderStorage = header; - MapStorage = mapInfo.MapStorage; - ModifiedPhysicalBlocks = mapInfo.PhysicalBlockBitmap; - ModifiedVirtualBlocks = mapInfo.VirtualBlockBitmap; - FreeBlocks = mapInfo.FreeBlockBitmap; - - Header = new JournalMapHeader(HeaderStorage); - Entries = ReadMapEntries(MapStorage, Header.MainDataBlockCount); - } - - public int GetPhysicalBlock(int virtualBlock) - { - return Entries[virtualBlock].PhysicalIndex; - } - - private static JournalMapEntry[] ReadMapEntries(IStorage mapTable, int count) - { - var tableReader = new BinaryReader(mapTable.AsStream()); - var map = new JournalMapEntry[count]; - - for (int i = 0; i < count; i++) + var entry = new JournalMapEntry { - var entry = new JournalMapEntry - { - VirtualIndex = i, - PhysicalIndex = tableReader.ReadInt32() & 0x7FFFFFFF - }; + VirtualIndex = i, + PhysicalIndex = tableReader.ReadInt32() & 0x7FFFFFFF + }; - map[i] = entry; - tableReader.BaseStream.Position += 4; - } - - return map; + map[i] = entry; + tableReader.BaseStream.Position += 4; } - public IStorage GetMapStorage() => MapStorage; - public IStorage GetHeaderStorage() => HeaderStorage; - public IStorage GetModifiedPhysicalBlocksStorage() => ModifiedPhysicalBlocks; - public IStorage GetModifiedVirtualBlocksStorage() => ModifiedVirtualBlocks; - public IStorage GetFreeBlocksStorage() => FreeBlocks; - - public void FsTrim() - { - int virtualBlockCount = Header.MainDataBlockCount; - int physicalBlockCount = virtualBlockCount + Header.JournalBlockCount; - - int blockMapLength = virtualBlockCount * MapEntryLength; - int physicalBitmapLength = Alignment.AlignUp(physicalBlockCount, 32) / 8; - int virtualBitmapLength = Alignment.AlignUp(virtualBlockCount, 32) / 8; - - MapStorage.Slice(blockMapLength).Fill(SaveDataFileSystem.TrimFillValue); - FreeBlocks.Slice(physicalBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); - ModifiedPhysicalBlocks.Slice(physicalBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); - ModifiedVirtualBlocks.Slice(virtualBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); - } + return map; } - public class JournalMapHeader + public IStorage GetMapStorage() => MapStorage; + public IStorage GetHeaderStorage() => HeaderStorage; + public IStorage GetModifiedPhysicalBlocksStorage() => ModifiedPhysicalBlocks; + public IStorage GetModifiedVirtualBlocksStorage() => ModifiedVirtualBlocks; + public IStorage GetFreeBlocksStorage() => FreeBlocks; + + public void FsTrim() { - public int Version { get; } - public int MainDataBlockCount { get; } - public int JournalBlockCount { get; } - public int FieldC { get; } + int virtualBlockCount = Header.MainDataBlockCount; + int physicalBlockCount = virtualBlockCount + Header.JournalBlockCount; - public JournalMapHeader(IStorage storage) - { - var reader = new BinaryReader(storage.AsStream()); + int blockMapLength = virtualBlockCount * MapEntryLength; + int physicalBitmapLength = Alignment.AlignUp(physicalBlockCount, 32) / 8; + int virtualBitmapLength = Alignment.AlignUp(virtualBlockCount, 32) / 8; - Version = reader.ReadInt32(); - MainDataBlockCount = reader.ReadInt32(); - JournalBlockCount = reader.ReadInt32(); - FieldC = reader.ReadInt32(); - } - } - - public class JournalMapParams - { - public IStorage MapStorage { get; set; } - public IStorage PhysicalBlockBitmap { get; set; } - public IStorage VirtualBlockBitmap { get; set; } - public IStorage FreeBlockBitmap { get; set; } + MapStorage.Slice(blockMapLength).Fill(SaveDataFileSystem.TrimFillValue); + FreeBlocks.Slice(physicalBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); + ModifiedPhysicalBlocks.Slice(physicalBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); + ModifiedVirtualBlocks.Slice(virtualBitmapLength).Fill(SaveDataFileSystem.TrimFillValue); } } + +public class JournalMapHeader +{ + public int Version { get; } + public int MainDataBlockCount { get; } + public int JournalBlockCount { get; } + public int FieldC { get; } + + public JournalMapHeader(IStorage storage) + { + var reader = new BinaryReader(storage.AsStream()); + + Version = reader.ReadInt32(); + MainDataBlockCount = reader.ReadInt32(); + JournalBlockCount = reader.ReadInt32(); + FieldC = reader.ReadInt32(); + } +} + +public class JournalMapParams +{ + public IStorage MapStorage { get; set; } + public IStorage PhysicalBlockBitmap { get; set; } + public IStorage VirtualBlockBitmap { get; set; } + public IStorage FreeBlockBitmap { get; set; } +} diff --git a/src/LibHac/FsSystem/Save/JournalStorage.cs b/src/LibHac/FsSystem/Save/JournalStorage.cs index 9945570c..a773908e 100644 --- a/src/LibHac/FsSystem/Save/JournalStorage.cs +++ b/src/LibHac/FsSystem/Save/JournalStorage.cs @@ -3,163 +3,162 @@ using System.Collections; using System.IO; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class JournalStorage : IStorage { - public class JournalStorage : IStorage + private IStorage BaseStorage { get; } + private IStorage HeaderStorage { get; } + public JournalMap Map { get; } + + public JournalHeader Header { get; } + + public int BlockSize { get; } + + private long Length { get; } + private bool LeaveOpen { get; } + + public JournalStorage(IStorage baseStorage, IStorage header, JournalMapParams mapInfo, bool leaveOpen) { - private IStorage BaseStorage { get; } - private IStorage HeaderStorage { get; } - public JournalMap Map { get; } + BaseStorage = baseStorage; + HeaderStorage = header; + Header = new JournalHeader(HeaderStorage); - public JournalHeader Header { get; } + IStorage mapHeader = header.Slice(0x20, 0x10); + Map = new JournalMap(mapHeader, mapInfo); - public int BlockSize { get; } + BlockSize = (int)Header.BlockSize; + Length = Header.TotalSize - Header.JournalSize; - private long Length { get; } - private bool LeaveOpen { get; } - - public JournalStorage(IStorage baseStorage, IStorage header, JournalMapParams mapInfo, bool leaveOpen) - { - BaseStorage = baseStorage; - HeaderStorage = header; - Header = new JournalHeader(HeaderStorage); - - IStorage mapHeader = header.Slice(0x20, 0x10); - Map = new JournalMap(mapHeader, mapInfo); - - BlockSize = (int)Header.BlockSize; - Length = Header.TotalSize - Header.JournalSize; - - LeaveOpen = leaveOpen; - } - - protected override Result DoRead(long offset, Span destination) - { - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; - - if (!CheckAccessRange(offset, destination.Length, Length)) - return ResultFs.OutOfRange.Log(); - - while (remaining > 0) - { - int blockNum = (int)(inPos / BlockSize); - int blockPos = (int)(inPos % BlockSize); - - long physicalOffset = Map.GetPhysicalBlock(blockNum) * BlockSize + blockPos; - - int bytesToRead = Math.Min(remaining, BlockSize - blockPos); - - Result rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead)); - if (rc.IsFailure()) return rc; - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; - } - - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - long inPos = offset; - int outPos = 0; - int remaining = source.Length; - - if (!CheckAccessRange(offset, source.Length, Length)) - return ResultFs.OutOfRange.Log(); - - while (remaining > 0) - { - int blockNum = (int)(inPos / BlockSize); - int blockPos = (int)(inPos % BlockSize); - - long physicalOffset = Map.GetPhysicalBlock(blockNum) * BlockSize + blockPos; - - int bytesToWrite = Math.Min(remaining, BlockSize - blockPos); - - Result rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite)); - if (rc.IsFailure()) return rc; - - outPos += bytesToWrite; - inPos += bytesToWrite; - remaining -= bytesToWrite; - } - - return Result.Success; - } - - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } - - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); - } - - base.Dispose(); - } - - public IStorage GetBaseStorage() => BaseStorage; - public IStorage GetHeaderStorage() => HeaderStorage; - - public void FsTrim() - { - // todo replace with a bitmap reader class when added - BitArray bitmap = new DuplexBitmap(Map.GetFreeBlocksStorage(), - Map.Header.JournalBlockCount + Map.Header.MainDataBlockCount).Bitmap; - - for (int i = 0; i < bitmap.Length; i++) - { - if (!bitmap[i]) continue; - - BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, i * BlockSize, BlockSize); - } - - Map.FsTrim(); - } + LeaveOpen = leaveOpen; } - public class JournalHeader + protected override Result DoRead(long offset, Span destination) { - public string Magic { get; } - public uint Version { get; } - public long TotalSize { get; } - public long JournalSize { get; } - public long BlockSize { get; } + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; - public JournalHeader(IStorage storage) + if (!CheckAccessRange(offset, destination.Length, Length)) + return ResultFs.OutOfRange.Log(); + + while (remaining > 0) { - var reader = new BinaryReader(storage.AsStream()); + int blockNum = (int)(inPos / BlockSize); + int blockPos = (int)(inPos % BlockSize); - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - TotalSize = reader.ReadInt64(); - JournalSize = reader.ReadInt64(); - BlockSize = reader.ReadInt64(); + long physicalOffset = Map.GetPhysicalBlock(blockNum) * BlockSize + blockPos; + + int bytesToRead = Math.Min(remaining, BlockSize - blockPos); + + Result rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead)); + if (rc.IsFailure()) return rc; + + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; } + + return Result.Success; } - public class JournalMapEntry + protected override Result DoWrite(long offset, ReadOnlySpan source) { - public int PhysicalIndex { get; set; } - public int VirtualIndex { get; set; } + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + if (!CheckAccessRange(offset, source.Length, Length)) + return ResultFs.OutOfRange.Log(); + + while (remaining > 0) + { + int blockNum = (int)(inPos / BlockSize); + int blockPos = (int)(inPos % BlockSize); + + long physicalOffset = Map.GetPhysicalBlock(blockNum) * BlockSize + blockPos; + + int bytesToWrite = Math.Min(remaining, BlockSize - blockPos); + + Result rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite)); + if (rc.IsFailure()) return rc; + + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; + } + + return Result.Success; + } + + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } + + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) + { + BaseStorage?.Dispose(); + } + + base.Dispose(); + } + + public IStorage GetBaseStorage() => BaseStorage; + public IStorage GetHeaderStorage() => HeaderStorage; + + public void FsTrim() + { + // todo replace with a bitmap reader class when added + BitArray bitmap = new DuplexBitmap(Map.GetFreeBlocksStorage(), + Map.Header.JournalBlockCount + Map.Header.MainDataBlockCount).Bitmap; + + for (int i = 0; i < bitmap.Length; i++) + { + if (!bitmap[i]) continue; + + BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, i * BlockSize, BlockSize); + } + + Map.FsTrim(); } } + +public class JournalHeader +{ + public string Magic { get; } + public uint Version { get; } + public long TotalSize { get; } + public long JournalSize { get; } + public long BlockSize { get; } + + public JournalHeader(IStorage storage) + { + var reader = new BinaryReader(storage.AsStream()); + + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + TotalSize = reader.ReadInt64(); + JournalSize = reader.ReadInt64(); + BlockSize = reader.ReadInt64(); + } +} + +public class JournalMapEntry +{ + public int PhysicalIndex { get; set; } + public int VirtualIndex { get; set; } +} diff --git a/src/LibHac/FsSystem/Save/RemapStorage.cs b/src/LibHac/FsSystem/Save/RemapStorage.cs index 39e66da0..d97477f0 100644 --- a/src/LibHac/FsSystem/Save/RemapStorage.cs +++ b/src/LibHac/FsSystem/Save/RemapStorage.cs @@ -4,271 +4,270 @@ using System.IO; using System.Linq; using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class RemapStorage : IStorage { - public class RemapStorage : IStorage + private const int MapEntryLength = 0x20; + + private IStorage BaseStorage { get; } + private IStorage HeaderStorage { get; } + private IStorage MapEntryStorage { get; } + private bool LeaveOpen { get; } + + private RemapHeader Header { get; } + public MapEntry[] MapEntries { get; set; } + public RemapSegment[] Segments { get; set; } + + /// + /// Creates a new + /// + /// A of the main data of the RemapStream. + /// The object assumes complete ownership of the Storage. + /// The header for this RemapStorage. + /// The remapping entries for this RemapStorage. + /// to leave the storage open after the object is disposed; otherwise, . + public RemapStorage(IStorage storage, IStorage header, IStorage mapEntries, bool leaveOpen) { - private const int MapEntryLength = 0x20; + BaseStorage = storage; + HeaderStorage = header; + MapEntryStorage = mapEntries; - private IStorage BaseStorage { get; } - private IStorage HeaderStorage { get; } - private IStorage MapEntryStorage { get; } - private bool LeaveOpen { get; } + Header = new RemapHeader(HeaderStorage); - private RemapHeader Header { get; } - public MapEntry[] MapEntries { get; set; } - public RemapSegment[] Segments { get; set; } + MapEntries = new MapEntry[Header.MapEntryCount]; + var reader = new BinaryReader(MapEntryStorage.AsStream()); - /// - /// Creates a new - /// - /// A of the main data of the RemapStream. - /// The object assumes complete ownership of the Storage. - /// The header for this RemapStorage. - /// The remapping entries for this RemapStorage. - /// to leave the storage open after the object is disposed; otherwise, . - public RemapStorage(IStorage storage, IStorage header, IStorage mapEntries, bool leaveOpen) + for (int i = 0; i < Header.MapEntryCount; i++) { - BaseStorage = storage; - HeaderStorage = header; - MapEntryStorage = mapEntries; + MapEntries[i] = new MapEntry(reader); + } - Header = new RemapHeader(HeaderStorage); + LeaveOpen = leaveOpen; - MapEntries = new MapEntry[Header.MapEntryCount]; - var reader = new BinaryReader(MapEntryStorage.AsStream()); + Segments = InitSegments(Header, MapEntries); + } - for (int i = 0; i < Header.MapEntryCount; i++) + protected override Result DoRead(long offset, Span destination) + { + if (destination.Length == 0) return Result.Success; + + MapEntry entry = GetMapEntry(offset); + + long inPos = offset; + int outPos = 0; + int remaining = destination.Length; + + while (remaining > 0) + { + long entryPos = inPos - entry.VirtualOffset; + + int bytesToRead = (int)Math.Min(entry.VirtualOffsetEnd - inPos, remaining); + BaseStorage.Read(entry.PhysicalOffset + entryPos, destination.Slice(outPos, bytesToRead)); + + outPos += bytesToRead; + inPos += bytesToRead; + remaining -= bytesToRead; + + if (inPos >= entry.VirtualOffsetEnd) { - MapEntries[i] = new MapEntry(reader); - } - - LeaveOpen = leaveOpen; - - Segments = InitSegments(Header, MapEntries); - } - - protected override Result DoRead(long offset, Span destination) - { - if (destination.Length == 0) return Result.Success; - - MapEntry entry = GetMapEntry(offset); - - long inPos = offset; - int outPos = 0; - int remaining = destination.Length; - - while (remaining > 0) - { - long entryPos = inPos - entry.VirtualOffset; - - int bytesToRead = (int)Math.Min(entry.VirtualOffsetEnd - inPos, remaining); - BaseStorage.Read(entry.PhysicalOffset + entryPos, destination.Slice(outPos, bytesToRead)); - - outPos += bytesToRead; - inPos += bytesToRead; - remaining -= bytesToRead; - - if (inPos >= entry.VirtualOffsetEnd) - { - entry = entry.Next; - } - } - - return Result.Success; - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - if (source.Length == 0) return Result.Success; - - MapEntry entry = GetMapEntry(offset); - - long inPos = offset; - int outPos = 0; - int remaining = source.Length; - - while (remaining > 0) - { - long entryPos = inPos - entry.VirtualOffset; - - int bytesToWrite = (int)Math.Min(entry.VirtualOffsetEnd - inPos, remaining); - - Result rc = BaseStorage.Write(entry.PhysicalOffset + entryPos, source.Slice(outPos, bytesToWrite)); - if (rc.IsFailure()) return rc; - - outPos += bytesToWrite; - inPos += bytesToWrite; - remaining -= bytesToWrite; - - if (inPos >= entry.VirtualOffsetEnd) - { - entry = entry.Next; - } - } - - return Result.Success; - } - - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } - - protected override Result DoSetSize(long size) - { - return ResultFs.UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage.Log(); - } - - protected override Result DoGetSize(out long size) - { - // todo: Different result code - size = -1; - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); + entry = entry.Next; } } - public IStorage GetBaseStorage() => BaseStorage; - public IStorage GetHeaderStorage() => HeaderStorage; - public IStorage GetMapEntryStorage() => MapEntryStorage; + return Result.Success; + } - private static RemapSegment[] InitSegments(RemapHeader header, MapEntry[] mapEntries) + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + if (source.Length == 0) return Result.Success; + + MapEntry entry = GetMapEntry(offset); + + long inPos = offset; + int outPos = 0; + int remaining = source.Length; + + while (remaining > 0) { - var segments = new RemapSegment[header.MapSegmentCount]; - int entryIdx = 0; + long entryPos = inPos - entry.VirtualOffset; - for (int i = 0; i < header.MapSegmentCount; i++) + int bytesToWrite = (int)Math.Min(entry.VirtualOffsetEnd - inPos, remaining); + + Result rc = BaseStorage.Write(entry.PhysicalOffset + entryPos, source.Slice(outPos, bytesToWrite)); + if (rc.IsFailure()) return rc; + + outPos += bytesToWrite; + inPos += bytesToWrite; + remaining -= bytesToWrite; + + if (inPos >= entry.VirtualOffsetEnd) + { + entry = entry.Next; + } + } + + return Result.Success; + } + + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage.Log(); + } + + protected override Result DoGetSize(out long size) + { + // todo: Different result code + size = -1; + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) + { + BaseStorage?.Dispose(); + } + } + + public IStorage GetBaseStorage() => BaseStorage; + public IStorage GetHeaderStorage() => HeaderStorage; + public IStorage GetMapEntryStorage() => MapEntryStorage; + + private static RemapSegment[] InitSegments(RemapHeader header, MapEntry[] mapEntries) + { + var segments = new RemapSegment[header.MapSegmentCount]; + int entryIdx = 0; + + for (int i = 0; i < header.MapSegmentCount; i++) + { + var seg = new RemapSegment(); + seg.Entries.Add(mapEntries[entryIdx]); + seg.Offset = mapEntries[entryIdx].VirtualOffset; + mapEntries[entryIdx].Segment = seg; + entryIdx++; + + while (entryIdx < mapEntries.Length && + mapEntries[entryIdx - 1].VirtualOffsetEnd == mapEntries[entryIdx].VirtualOffset) { - var seg = new RemapSegment(); - seg.Entries.Add(mapEntries[entryIdx]); - seg.Offset = mapEntries[entryIdx].VirtualOffset; mapEntries[entryIdx].Segment = seg; + mapEntries[entryIdx - 1].Next = mapEntries[entryIdx]; + seg.Entries.Add(mapEntries[entryIdx]); entryIdx++; - - while (entryIdx < mapEntries.Length && - mapEntries[entryIdx - 1].VirtualOffsetEnd == mapEntries[entryIdx].VirtualOffset) - { - mapEntries[entryIdx].Segment = seg; - mapEntries[entryIdx - 1].Next = mapEntries[entryIdx]; - seg.Entries.Add(mapEntries[entryIdx]); - entryIdx++; - } - - seg.Length = seg.Entries[seg.Entries.Count - 1].VirtualOffsetEnd - seg.Entries[0].VirtualOffset; - segments[i] = seg; } - return segments; + seg.Length = seg.Entries[seg.Entries.Count - 1].VirtualOffsetEnd - seg.Entries[0].VirtualOffset; + segments[i] = seg; } - private MapEntry GetMapEntry(long offset) - { - int segmentIdx = GetSegmentFromVirtualOffset(offset); + return segments; + } - if (segmentIdx < Segments.Length) + private MapEntry GetMapEntry(long offset) + { + int segmentIdx = GetSegmentFromVirtualOffset(offset); + + if (segmentIdx < Segments.Length) + { + RemapSegment segment = Segments[segmentIdx]; + + foreach (MapEntry entry in segment.Entries) { - RemapSegment segment = Segments[segmentIdx]; - - foreach (MapEntry entry in segment.Entries) - { - if (entry.VirtualOffsetEnd > offset) return entry; - } + if (entry.VirtualOffsetEnd > offset) return entry; } - - throw new ArgumentOutOfRangeException(nameof(offset)); } - public int GetSegmentFromVirtualOffset(long virtualOffset) - { - return (int)((ulong)virtualOffset >> (64 - Header.SegmentBits)); - } - - public long GetOffsetFromVirtualOffset(long virtualOffset) - { - return virtualOffset & GetOffsetMask(); - } - - public long ToVirtualOffset(int segment, long offset) - { - long seg = (segment << (64 - Header.SegmentBits)) & GetSegmentMask(); - long off = offset & GetOffsetMask(); - return seg | off; - } - - private long GetOffsetMask() - { - return (1 << (64 - Header.SegmentBits)) - 1; - } - - private long GetSegmentMask() - { - return ~GetOffsetMask(); - } - - public void FsTrim() - { - int mapEntriesLength = Header.MapEntryCount * MapEntryLength; - long dataEnd = MapEntries.Max(x => x.PhysicalOffsetEnd); - - MapEntryStorage.Slice(mapEntriesLength).Fill(SaveDataFileSystem.TrimFillValue); - BaseStorage.Slice(dataEnd).Fill(SaveDataFileSystem.TrimFillValue); - } + throw new ArgumentOutOfRangeException(nameof(offset)); } - public class RemapHeader + public int GetSegmentFromVirtualOffset(long virtualOffset) { - public string Magic { get; } - public uint Version { get; } - public int MapEntryCount { get; } - public int MapSegmentCount { get; } - public int SegmentBits { get; } - - public RemapHeader(IStorage storage) - { - var reader = new BinaryReader(storage.AsStream()); - - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - MapEntryCount = reader.ReadInt32(); - MapSegmentCount = reader.ReadInt32(); - SegmentBits = reader.ReadInt32(); - } + return (int)((ulong)virtualOffset >> (64 - Header.SegmentBits)); } - public class MapEntry + public long GetOffsetFromVirtualOffset(long virtualOffset) { - public long VirtualOffset { get; } - public long PhysicalOffset { get; } - public long Size { get; } - public int Alignment { get; } - public int Field1C { get; } - - public long VirtualOffsetEnd => VirtualOffset + Size; - public long PhysicalOffsetEnd => PhysicalOffset + Size; - internal RemapSegment Segment { get; set; } - internal MapEntry Next { get; set; } - - public MapEntry(BinaryReader reader) - { - VirtualOffset = reader.ReadInt64(); - PhysicalOffset = reader.ReadInt64(); - Size = reader.ReadInt64(); - Alignment = reader.ReadInt32(); - Field1C = reader.ReadInt32(); - } + return virtualOffset & GetOffsetMask(); } - public class RemapSegment + public long ToVirtualOffset(int segment, long offset) { - public List Entries { get; } = new List(); - public long Offset { get; internal set; } - public long Length { get; internal set; } + long seg = (segment << (64 - Header.SegmentBits)) & GetSegmentMask(); + long off = offset & GetOffsetMask(); + return seg | off; + } + + private long GetOffsetMask() + { + return (1 << (64 - Header.SegmentBits)) - 1; + } + + private long GetSegmentMask() + { + return ~GetOffsetMask(); + } + + public void FsTrim() + { + int mapEntriesLength = Header.MapEntryCount * MapEntryLength; + long dataEnd = MapEntries.Max(x => x.PhysicalOffsetEnd); + + MapEntryStorage.Slice(mapEntriesLength).Fill(SaveDataFileSystem.TrimFillValue); + BaseStorage.Slice(dataEnd).Fill(SaveDataFileSystem.TrimFillValue); } } + +public class RemapHeader +{ + public string Magic { get; } + public uint Version { get; } + public int MapEntryCount { get; } + public int MapSegmentCount { get; } + public int SegmentBits { get; } + + public RemapHeader(IStorage storage) + { + var reader = new BinaryReader(storage.AsStream()); + + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + MapEntryCount = reader.ReadInt32(); + MapSegmentCount = reader.ReadInt32(); + SegmentBits = reader.ReadInt32(); + } +} + +public class MapEntry +{ + public long VirtualOffset { get; } + public long PhysicalOffset { get; } + public long Size { get; } + public int Alignment { get; } + public int Field1C { get; } + + public long VirtualOffsetEnd => VirtualOffset + Size; + public long PhysicalOffsetEnd => PhysicalOffset + Size; + internal RemapSegment Segment { get; set; } + internal MapEntry Next { get; set; } + + public MapEntry(BinaryReader reader) + { + VirtualOffset = reader.ReadInt64(); + PhysicalOffset = reader.ReadInt64(); + Size = reader.ReadInt64(); + Alignment = reader.ReadInt32(); + Field1C = reader.ReadInt32(); + } +} + +public class RemapSegment +{ + public List Entries { get; } = new List(); + public long Offset { get; internal set; } + public long Length { get; internal set; } +} diff --git a/src/LibHac/FsSystem/Save/SaveDataDirectory.cs b/src/LibHac/FsSystem/Save/SaveDataDirectory.cs index 074b657e..dde1a017 100644 --- a/src/LibHac/FsSystem/Save/SaveDataDirectory.cs +++ b/src/LibHac/FsSystem/Save/SaveDataDirectory.cs @@ -4,86 +4,85 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class SaveDataDirectory : IDirectory { - public class SaveDataDirectory : IDirectory + private SaveDataFileSystemCore ParentFileSystem { get; } + + private OpenDirectoryMode Mode { get; } + + private SaveFindPosition InitialPosition { get; } + private SaveFindPosition _currentPosition; + + public SaveDataDirectory(SaveDataFileSystemCore fs, SaveFindPosition position, OpenDirectoryMode mode) { - private SaveDataFileSystemCore ParentFileSystem { get; } + ParentFileSystem = fs; + InitialPosition = position; + _currentPosition = position; + Mode = mode; + } - private OpenDirectoryMode Mode { get; } + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer); + } - private SaveFindPosition InitialPosition { get; } - private SaveFindPosition _currentPosition; + protected override Result DoGetEntryCount(out long entryCount) + { + SaveFindPosition position = InitialPosition; - public SaveDataDirectory(SaveDataFileSystemCore fs, SaveFindPosition position, OpenDirectoryMode mode) + return ReadImpl(out entryCount, ref position, Span.Empty); + } + + private Result ReadImpl(out long entriesRead, ref SaveFindPosition position, Span entryBuffer) + { + HierarchicalSaveFileTable tab = ParentFileSystem.FileTable; + + int i = 0; + + if (Mode.HasFlag(OpenDirectoryMode.Directory)) { - ParentFileSystem = fs; - InitialPosition = position; - _currentPosition = position; - Mode = mode; - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer); - } - - protected override Result DoGetEntryCount(out long entryCount) - { - SaveFindPosition position = InitialPosition; - - return ReadImpl(out entryCount, ref position, Span.Empty); - } - - private Result ReadImpl(out long entriesRead, ref SaveFindPosition position, Span entryBuffer) - { - HierarchicalSaveFileTable tab = ParentFileSystem.FileTable; - - int i = 0; - - if (Mode.HasFlag(OpenDirectoryMode.Directory)) + while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name)) { - while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name)) + if (!entryBuffer.IsEmpty) { - if (!entryBuffer.IsEmpty) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - Span nameUtf8 = Encoding.UTF8.GetBytes(name); + ref DirectoryEntry entry = ref entryBuffer[i]; + Span nameUtf8 = Encoding.UTF8.GetBytes(name); - StringUtils.Copy(entry.Name, nameUtf8); - entry.Name[64] = 0; + StringUtils.Copy(entry.Name, nameUtf8); + entry.Name[64] = 0; - entry.Type = DirectoryEntryType.Directory; - entry.Size = 0; - } - - i++; + entry.Type = DirectoryEntryType.Directory; + entry.Size = 0; } + + i++; } - - if (Mode.HasFlag(OpenDirectoryMode.File)) - { - while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out SaveFileInfo info, out string name)) - { - if (!entryBuffer.IsEmpty) - { - ref DirectoryEntry entry = ref entryBuffer[i]; - Span nameUtf8 = Encoding.UTF8.GetBytes(name); - - StringUtils.Copy(entry.Name, nameUtf8); - entry.Name[64] = 0; - - entry.Type = DirectoryEntryType.File; - entry.Size = info.Length; - } - - i++; - } - } - - entriesRead = i; - - return Result.Success; } + + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out SaveFileInfo info, out string name)) + { + if (!entryBuffer.IsEmpty) + { + ref DirectoryEntry entry = ref entryBuffer[i]; + Span nameUtf8 = Encoding.UTF8.GetBytes(name); + + StringUtils.Copy(entry.Name, nameUtf8); + entry.Name[64] = 0; + + entry.Type = DirectoryEntryType.File; + entry.Size = info.Length; + } + + i++; + } + } + + entriesRead = i; + + return Result.Success; } } diff --git a/src/LibHac/FsSystem/Save/SaveDataFile.cs b/src/LibHac/FsSystem/Save/SaveDataFile.cs index de1a002d..85cacd27 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFile.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFile.cs @@ -4,105 +4,104 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class SaveDataFile : IFile { - public class SaveDataFile : IFile + private AllocationTableStorage BaseStorage { get; } + private U8String Path { get; } + private HierarchicalSaveFileTable FileTable { get; } + private long Size { get; set; } + private OpenMode Mode { get; } + + public SaveDataFile(AllocationTableStorage baseStorage, U8Span path, HierarchicalSaveFileTable fileTable, long size, OpenMode mode) { - private AllocationTableStorage BaseStorage { get; } - private U8String Path { get; } - private HierarchicalSaveFileTable FileTable { get; } - private long Size { get; set; } - private OpenMode Mode { get; } + Mode = mode; + BaseStorage = baseStorage; + Path = path.ToU8String(); + FileTable = fileTable; + Size = size; + } - public SaveDataFile(AllocationTableStorage baseStorage, U8Span path, HierarchicalSaveFileTable fileTable, long size, OpenMode mode) + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (toRead == 0) { - Mode = mode; - BaseStorage = baseStorage; - Path = path.ToU8String(); - FileTable = fileTable; - Size = size; - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); - - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - if (toRead == 0) - { - bytesRead = 0; - return Result.Success; - } - - rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); - if (rc.IsFailure()) return rc; - - bytesRead = toRead; + bytesRead = 0; return Result.Success; } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); + if (rc.IsFailure()) return rc; + + bytesRead = toRead; + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (isResizeNeeded) { - Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + rc = DoSetSize(offset + source.Length); if (rc.IsFailure()) return rc; - - if (isResizeNeeded) - { - rc = DoSetSize(offset + source.Length); - if (rc.IsFailure()) return rc; - } - - rc = BaseStorage.Write(offset, source); - if (rc.IsFailure()) return rc; - - if (option.HasFlushFlag()) - { - return Flush(); - } - - return Result.Success; } - protected override Result DoFlush() + rc = BaseStorage.Write(offset, source); + if (rc.IsFailure()) return rc; + + if (option.HasFlushFlag()) { - return BaseStorage.Flush(); + return Flush(); } - protected override Result DoGetSize(out long size) + return Result.Success; + } + + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } + + protected override Result DoGetSize(out long size) + { + size = Size; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); + if (Size == size) return Result.Success; + + Result rc = BaseStorage.SetSize(size); + if (rc.IsFailure()) return rc; + + if (!FileTable.TryOpenFile(Path, out SaveFileInfo fileInfo)) { - size = Size; - return Result.Success; + throw new FileNotFoundException(); } - protected override Result DoSetSize(long size) - { - if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); - if (Size == size) return Result.Success; + fileInfo.StartBlock = BaseStorage.InitialBlock; + fileInfo.Length = size; - Result rc = BaseStorage.SetSize(size); - if (rc.IsFailure()) return rc; + FileTable.AddFile(Path, ref fileInfo); - if (!FileTable.TryOpenFile(Path, out SaveFileInfo fileInfo)) - { - throw new FileNotFoundException(); - } + Size = size; - fileInfo.StartBlock = BaseStorage.InitialBlock; - fileInfo.Length = size; + return Result.Success; + } - FileTable.AddFile(Path, ref fileInfo); - - Size = size; - - return Result.Success; - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); } } diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index bd37dc42..765a5da9 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -7,329 +7,328 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Path = LibHac.Fs.Path; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class SaveDataFileSystem : IFileSystem { - public class SaveDataFileSystem : IFileSystem + internal const byte TrimFillValue = 0; + + public Header Header { get; } + private bool IsFirstHeaderInUse { get; } + + private SharedRef _baseStorageShared; + public IStorage BaseStorage { get; private set; } + public bool LeaveOpen { get; } + + public SaveDataFileSystemCore SaveDataFileSystemCore { get; } + + public RemapStorage DataRemapStorage { get; } + public RemapStorage MetaRemapStorage { get; } + + public HierarchicalDuplexStorage DuplexStorage { get; } + public JournalStorage JournalStorage { get; } + + public HierarchicalIntegrityVerificationStorage CoreDataIvfcStorage { get; } + public HierarchicalIntegrityVerificationStorage FatIvfcStorage { get; } + + private KeySet KeySet { get; } + + public SaveDataFileSystem(KeySet keySet, ref SharedRef storage, + IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + : this(keySet, storage.Get, integrityCheckLevel, true) { - internal const byte TrimFillValue = 0; + _baseStorageShared = SharedRef.CreateMove(ref storage); + } - public Header Header { get; } - private bool IsFirstHeaderInUse { get; } + public SaveDataFileSystem(KeySet keySet, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + { + BaseStorage = storage; + LeaveOpen = leaveOpen; + KeySet = keySet; - private SharedRef _baseStorageShared; - public IStorage BaseStorage { get; private set; } - public bool LeaveOpen { get; } + var headerA = new Header(BaseStorage, keySet); + var headerB = new Header(BaseStorage.Slice(0x4000), keySet); - public SaveDataFileSystemCore SaveDataFileSystemCore { get; } - - public RemapStorage DataRemapStorage { get; } - public RemapStorage MetaRemapStorage { get; } - - public HierarchicalDuplexStorage DuplexStorage { get; } - public JournalStorage JournalStorage { get; } - - public HierarchicalIntegrityVerificationStorage CoreDataIvfcStorage { get; } - public HierarchicalIntegrityVerificationStorage FatIvfcStorage { get; } - - private KeySet KeySet { get; } - - public SaveDataFileSystem(KeySet keySet, ref SharedRef storage, - IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) - : this(keySet, storage.Get, integrityCheckLevel, true) + if (headerA.HeaderHashValidity == Validity.Valid) { - _baseStorageShared = SharedRef.CreateMove(ref storage); + IsFirstHeaderInUse = true; + } + else if (headerB.HeaderHashValidity == Validity.Valid) + { + IsFirstHeaderInUse = false; + } + else + { + ThrowHelper.ThrowResult(ResultFs.JournalIntegritySaveDataControlAreaVerificationFailed.Value, "Savedata header is not valid."); } - public SaveDataFileSystem(KeySet keySet, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + Header = IsFirstHeaderInUse ? headerA : headerB; + + FsLayout layout = Header.Layout; + + IStorage dataRemapBase = BaseStorage.Slice(layout.FileMapDataOffset, layout.FileMapDataSize); + IStorage dataRemapEntries = BaseStorage.Slice(layout.FileMapEntryOffset, layout.FileMapEntrySize); + IStorage metadataRemapEntries = BaseStorage.Slice(layout.MetaMapEntryOffset, layout.MetaMapEntrySize); + + DataRemapStorage = new RemapStorage(dataRemapBase, Header.MainRemapHeader, dataRemapEntries, leaveOpen); + + DuplexStorage = InitDuplexStorage(DataRemapStorage, Header); + + MetaRemapStorage = new RemapStorage(DuplexStorage, Header.MetaDataRemapHeader, metadataRemapEntries, leaveOpen); + + var journalMapInfo = new JournalMapParams { - BaseStorage = storage; - LeaveOpen = leaveOpen; - KeySet = keySet; + MapStorage = MetaRemapStorage.Slice(layout.JournalMapTableOffset, layout.JournalMapTableSize), + PhysicalBlockBitmap = MetaRemapStorage.Slice(layout.JournalPhysicalBitmapOffset, layout.JournalPhysicalBitmapSize), + VirtualBlockBitmap = MetaRemapStorage.Slice(layout.JournalVirtualBitmapOffset, layout.JournalVirtualBitmapSize), + FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize) + }; - var headerA = new Header(BaseStorage, keySet); - var headerB = new Header(BaseStorage.Slice(0x4000), keySet); + IStorage journalData = DataRemapStorage.Slice(layout.JournalDataOffset, + layout.JournalDataSizeB + layout.JournalSize); - if (headerA.HeaderHashValidity == Validity.Valid) - { - IsFirstHeaderInUse = true; - } - else if (headerB.HeaderHashValidity == Validity.Valid) - { - IsFirstHeaderInUse = false; - } - else - { - ThrowHelper.ThrowResult(ResultFs.JournalIntegritySaveDataControlAreaVerificationFailed.Value, "Savedata header is not valid."); - } + JournalStorage = new JournalStorage(journalData, Header.JournalHeader, journalMapInfo, leaveOpen); - Header = IsFirstHeaderInUse ? headerA : headerB; + CoreDataIvfcStorage = InitJournalIvfcStorage(integrityCheckLevel); - FsLayout layout = Header.Layout; + IStorage fatStorage = MetaRemapStorage.Slice(layout.FatOffset, layout.FatSize); - IStorage dataRemapBase = BaseStorage.Slice(layout.FileMapDataOffset, layout.FileMapDataSize); - IStorage dataRemapEntries = BaseStorage.Slice(layout.FileMapEntryOffset, layout.FileMapEntrySize); - IStorage metadataRemapEntries = BaseStorage.Slice(layout.MetaMapEntryOffset, layout.MetaMapEntrySize); - - DataRemapStorage = new RemapStorage(dataRemapBase, Header.MainRemapHeader, dataRemapEntries, leaveOpen); - - DuplexStorage = InitDuplexStorage(DataRemapStorage, Header); - - MetaRemapStorage = new RemapStorage(DuplexStorage, Header.MetaDataRemapHeader, metadataRemapEntries, leaveOpen); - - var journalMapInfo = new JournalMapParams - { - MapStorage = MetaRemapStorage.Slice(layout.JournalMapTableOffset, layout.JournalMapTableSize), - PhysicalBlockBitmap = MetaRemapStorage.Slice(layout.JournalPhysicalBitmapOffset, layout.JournalPhysicalBitmapSize), - VirtualBlockBitmap = MetaRemapStorage.Slice(layout.JournalVirtualBitmapOffset, layout.JournalVirtualBitmapSize), - FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize) - }; - - IStorage journalData = DataRemapStorage.Slice(layout.JournalDataOffset, - layout.JournalDataSizeB + layout.JournalSize); - - JournalStorage = new JournalStorage(journalData, Header.JournalHeader, journalMapInfo, leaveOpen); - - CoreDataIvfcStorage = InitJournalIvfcStorage(integrityCheckLevel); - - IStorage fatStorage = MetaRemapStorage.Slice(layout.FatOffset, layout.FatSize); - - if (Header.Layout.Version >= 0x50000) - { - FatIvfcStorage = InitFatIvfcStorage(integrityCheckLevel); - fatStorage = FatIvfcStorage; - } - - SaveDataFileSystemCore = new SaveDataFileSystemCore(CoreDataIvfcStorage, fatStorage, Header.SaveHeader); + if (Header.Layout.Version >= 0x50000) + { + FatIvfcStorage = InitFatIvfcStorage(integrityCheckLevel); + fatStorage = FatIvfcStorage; } - private static HierarchicalDuplexStorage InitDuplexStorage(IStorage baseStorage, Header header) + SaveDataFileSystemCore = new SaveDataFileSystemCore(CoreDataIvfcStorage, fatStorage, Header.SaveHeader); + } + + private static HierarchicalDuplexStorage InitDuplexStorage(IStorage baseStorage, Header header) + { + FsLayout layout = header.Layout; + var duplexLayers = new DuplexFsLayerInfo[3]; + + duplexLayers[0] = new DuplexFsLayerInfo { - FsLayout layout = header.Layout; - var duplexLayers = new DuplexFsLayerInfo[3]; + DataA = header.DuplexMasterBitmapA, + DataB = header.DuplexMasterBitmapB, + Info = header.Duplex.Layers[0] + }; - duplexLayers[0] = new DuplexFsLayerInfo - { - DataA = header.DuplexMasterBitmapA, - DataB = header.DuplexMasterBitmapB, - Info = header.Duplex.Layers[0] - }; + duplexLayers[1] = new DuplexFsLayerInfo + { + DataA = baseStorage.Slice(layout.DuplexL1OffsetA, layout.DuplexL1Size), + DataB = baseStorage.Slice(layout.DuplexL1OffsetB, layout.DuplexL1Size), + Info = header.Duplex.Layers[1] + }; - duplexLayers[1] = new DuplexFsLayerInfo - { - DataA = baseStorage.Slice(layout.DuplexL1OffsetA, layout.DuplexL1Size), - DataB = baseStorage.Slice(layout.DuplexL1OffsetB, layout.DuplexL1Size), - Info = header.Duplex.Layers[1] - }; + duplexLayers[2] = new DuplexFsLayerInfo + { + DataA = baseStorage.Slice(layout.DuplexDataOffsetA, layout.DuplexDataSize), + DataB = baseStorage.Slice(layout.DuplexDataOffsetB, layout.DuplexDataSize), + Info = header.Duplex.Layers[2] + }; - duplexLayers[2] = new DuplexFsLayerInfo - { - DataA = baseStorage.Slice(layout.DuplexDataOffsetA, layout.DuplexDataSize), - DataB = baseStorage.Slice(layout.DuplexDataOffsetB, layout.DuplexDataSize), - Info = header.Duplex.Layers[2] - }; + return new HierarchicalDuplexStorage(duplexLayers, layout.DuplexIndex == 1); + } - return new HierarchicalDuplexStorage(duplexLayers, layout.DuplexIndex == 1); + private HierarchicalIntegrityVerificationStorage InitJournalIvfcStorage(IntegrityCheckLevel integrityCheckLevel) + { + const int ivfcLevels = 5; + IvfcHeader ivfc = Header.Ivfc; + var levels = new List { Header.DataIvfcMaster }; + + for (int i = 0; i < ivfcLevels - 2; i++) + { + IvfcLevelHeader level = ivfc.LevelHeaders[i]; + levels.Add(MetaRemapStorage.Slice(level.Offset, level.Size)); } - private HierarchicalIntegrityVerificationStorage InitJournalIvfcStorage(IntegrityCheckLevel integrityCheckLevel) + IvfcLevelHeader dataLevel = ivfc.LevelHeaders[ivfcLevels - 2]; + levels.Add(JournalStorage.Slice(dataLevel.Offset, dataLevel.Size)); + + return new HierarchicalIntegrityVerificationStorage(ivfc, levels, IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen); + } + + private HierarchicalIntegrityVerificationStorage InitFatIvfcStorage(IntegrityCheckLevel integrityCheckLevel) + { + return new HierarchicalIntegrityVerificationStorage(Header.FatIvfc, Header.FatIvfcMaster, MetaRemapStorage, + IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen); + } + + protected override Result DoCreateDirectory(in Path path) + { + Result result = SaveDataFileSystemCore.CreateDirectory(in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + Result result = SaveDataFileSystemCore.CreateFile(in path, size, option); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoDeleteDirectory(in Path path) + { + Result result = SaveDataFileSystemCore.DeleteDirectory(in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + Result result = SaveDataFileSystemCore.DeleteDirectoryRecursively(in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + Result result = SaveDataFileSystemCore.CleanDirectoryRecursively(in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoDeleteFile(in Path path) + { + Result result = SaveDataFileSystemCore.DeleteFile(in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + Result result = SaveDataFileSystemCore.OpenDirectory(ref outDirectory, in path, mode); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + Result result = SaveDataFileSystemCore.OpenFile(ref outFile, in path, mode); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + Result result = SaveDataFileSystemCore.RenameDirectory(in currentPath, in newPath); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + Result result = SaveDataFileSystemCore.RenameFile(in currentPath, in newPath); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + Result result = SaveDataFileSystemCore.GetEntryType(out entryType, in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + Result result = SaveDataFileSystemCore.GetFreeSpaceSize(out freeSpace, in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + Result result = SaveDataFileSystemCore.GetTotalSpaceSize(out totalSpace, in path); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + protected override Result DoCommit() + { + Result result = Commit(KeySet); + + return SaveResults.ConvertToExternalResult(result).LogConverted(result); + } + + public Result Commit(KeySet keySet) + { + CoreDataIvfcStorage.Flush(); + FatIvfcStorage?.Flush(); + + Stream headerStream = BaseStorage.AsStream(); + + byte[] hashData = new byte[0x3d00]; + + headerStream.Position = 0x300; + headerStream.Read(hashData, 0, hashData.Length); + + byte[] hash = new byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(hashData, hash); + + headerStream.Position = 0x108; + headerStream.Write(hash, 0, hash.Length); + + if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsZeros()) return ResultFs.PreconditionViolation.Log(); + + byte[] cmacData = new byte[0x200]; + byte[] cmac = new byte[0x10]; + + headerStream.Position = 0x100; + headerStream.Read(cmacData, 0, 0x200); + + Aes.CalculateCmac(cmac, cmacData, keySet.DeviceUniqueSaveMacKeys[0]); + + headerStream.Position = 0; + headerStream.Write(cmac, 0, 0x10); + headerStream.Flush(); + + return Result.Success; + } + + public void FsTrim() + { + MetaRemapStorage.FsTrim(); + DataRemapStorage.FsTrim(); + DuplexStorage.FsTrim(); + JournalStorage.FsTrim(); + CoreDataIvfcStorage.FsTrim(); + FatIvfcStorage?.FsTrim(); + SaveDataFileSystemCore.FsTrim(); + + int unusedHeaderOffset = IsFirstHeaderInUse ? 0x4000 : 0; + BaseStorage.Slice(unusedHeaderOffset, 0x4000).Fill(TrimFillValue); + } + + public Validity Verify(IProgressReport logger = null) + { + Validity journalValidity = CoreDataIvfcStorage.Validate(true, logger); + CoreDataIvfcStorage.SetLevelValidities(Header.Ivfc); + + if (FatIvfcStorage == null) return journalValidity; + + Validity fatValidity = FatIvfcStorage.Validate(true, logger); + FatIvfcStorage.SetLevelValidities(Header.Ivfc); + + if (journalValidity != Validity.Valid) return journalValidity; + if (fatValidity != Validity.Valid) return fatValidity; + + return journalValidity; + } + + public override void Dispose() + { + if (!LeaveOpen) { - const int ivfcLevels = 5; - IvfcHeader ivfc = Header.Ivfc; - var levels = new List { Header.DataIvfcMaster }; - - for (int i = 0; i < ivfcLevels - 2; i++) - { - IvfcLevelHeader level = ivfc.LevelHeaders[i]; - levels.Add(MetaRemapStorage.Slice(level.Offset, level.Size)); - } - - IvfcLevelHeader dataLevel = ivfc.LevelHeaders[ivfcLevels - 2]; - levels.Add(JournalStorage.Slice(dataLevel.Offset, dataLevel.Size)); - - return new HierarchicalIntegrityVerificationStorage(ivfc, levels, IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen); + BaseStorage?.Dispose(); + BaseStorage = null; } - private HierarchicalIntegrityVerificationStorage InitFatIvfcStorage(IntegrityCheckLevel integrityCheckLevel) - { - return new HierarchicalIntegrityVerificationStorage(Header.FatIvfc, Header.FatIvfcMaster, MetaRemapStorage, - IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen); - } + _baseStorageShared.Destroy(); - protected override Result DoCreateDirectory(in Path path) - { - Result result = SaveDataFileSystemCore.CreateDirectory(in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - Result result = SaveDataFileSystemCore.CreateFile(in path, size, option); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoDeleteDirectory(in Path path) - { - Result result = SaveDataFileSystemCore.DeleteDirectory(in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - Result result = SaveDataFileSystemCore.DeleteDirectoryRecursively(in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - Result result = SaveDataFileSystemCore.CleanDirectoryRecursively(in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoDeleteFile(in Path path) - { - Result result = SaveDataFileSystemCore.DeleteFile(in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - Result result = SaveDataFileSystemCore.OpenDirectory(ref outDirectory, in path, mode); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - Result result = SaveDataFileSystemCore.OpenFile(ref outFile, in path, mode); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - Result result = SaveDataFileSystemCore.RenameDirectory(in currentPath, in newPath); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - Result result = SaveDataFileSystemCore.RenameFile(in currentPath, in newPath); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - Result result = SaveDataFileSystemCore.GetEntryType(out entryType, in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - Result result = SaveDataFileSystemCore.GetFreeSpaceSize(out freeSpace, in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - Result result = SaveDataFileSystemCore.GetTotalSpaceSize(out totalSpace, in path); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - protected override Result DoCommit() - { - Result result = Commit(KeySet); - - return SaveResults.ConvertToExternalResult(result).LogConverted(result); - } - - public Result Commit(KeySet keySet) - { - CoreDataIvfcStorage.Flush(); - FatIvfcStorage?.Flush(); - - Stream headerStream = BaseStorage.AsStream(); - - byte[] hashData = new byte[0x3d00]; - - headerStream.Position = 0x300; - headerStream.Read(hashData, 0, hashData.Length); - - byte[] hash = new byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(hashData, hash); - - headerStream.Position = 0x108; - headerStream.Write(hash, 0, hash.Length); - - if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsZeros()) return ResultFs.PreconditionViolation.Log(); - - byte[] cmacData = new byte[0x200]; - byte[] cmac = new byte[0x10]; - - headerStream.Position = 0x100; - headerStream.Read(cmacData, 0, 0x200); - - Aes.CalculateCmac(cmac, cmacData, keySet.DeviceUniqueSaveMacKeys[0]); - - headerStream.Position = 0; - headerStream.Write(cmac, 0, 0x10); - headerStream.Flush(); - - return Result.Success; - } - - public void FsTrim() - { - MetaRemapStorage.FsTrim(); - DataRemapStorage.FsTrim(); - DuplexStorage.FsTrim(); - JournalStorage.FsTrim(); - CoreDataIvfcStorage.FsTrim(); - FatIvfcStorage?.FsTrim(); - SaveDataFileSystemCore.FsTrim(); - - int unusedHeaderOffset = IsFirstHeaderInUse ? 0x4000 : 0; - BaseStorage.Slice(unusedHeaderOffset, 0x4000).Fill(TrimFillValue); - } - - public Validity Verify(IProgressReport logger = null) - { - Validity journalValidity = CoreDataIvfcStorage.Validate(true, logger); - CoreDataIvfcStorage.SetLevelValidities(Header.Ivfc); - - if (FatIvfcStorage == null) return journalValidity; - - Validity fatValidity = FatIvfcStorage.Validate(true, logger); - FatIvfcStorage.SetLevelValidities(Header.Ivfc); - - if (journalValidity != Validity.Valid) return journalValidity; - if (fatValidity != Validity.Valid) return fatValidity; - - return journalValidity; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); - BaseStorage = null; - } - - _baseStorageShared.Destroy(); - - base.Dispose(); - } + base.Dispose(); } } diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs index 4aa27a75..25c24550 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystemCore.cs @@ -5,283 +5,282 @@ using LibHac.Fs.Fsa; using LibHac.Util; using Path = LibHac.Fs.Path; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +public class SaveDataFileSystemCore : IFileSystem { - public class SaveDataFileSystemCore : IFileSystem + private IStorage BaseStorage { get; } + private IStorage HeaderStorage { get; } + + public AllocationTable AllocationTable { get; } + private SaveHeader Header { get; } + + public HierarchicalSaveFileTable FileTable { get; } + + public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header) { - private IStorage BaseStorage { get; } - private IStorage HeaderStorage { get; } + HeaderStorage = header; + BaseStorage = storage; + AllocationTable = new AllocationTable(allocationTable, header.Slice(0x18, 0x30)); - public AllocationTable AllocationTable { get; } - private SaveHeader Header { get; } + Header = new SaveHeader(HeaderStorage); - public HierarchicalSaveFileTable FileTable { get; } + AllocationTableStorage dirTableStorage = OpenFatStorage(AllocationTable.Header.DirectoryTableBlock); + AllocationTableStorage fileTableStorage = OpenFatStorage(AllocationTable.Header.FileTableBlock); - public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header) + FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage); + } + + private Result CheckIfNormalized(in Path path) + { + Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, path.GetString()); + if (rc.IsFailure()) return rc; + + if (!isNormalized) + return ResultFs.NotNormalized.Log(); + + return Result.Success; + } + + protected override Result DoCreateDirectory(in Path path) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + FileTable.AddDirectory(new U8Span(path.GetString())); + + return Result.Success; + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + if (size == 0) { - HeaderStorage = header; - BaseStorage = storage; - AllocationTable = new AllocationTable(allocationTable, header.Slice(0x18, 0x30)); - - Header = new SaveHeader(HeaderStorage); - - AllocationTableStorage dirTableStorage = OpenFatStorage(AllocationTable.Header.DirectoryTableBlock); - AllocationTableStorage fileTableStorage = OpenFatStorage(AllocationTable.Header.FileTableBlock); - - FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage); - } - - private Result CheckIfNormalized(in Path path) - { - Result rc = PathNormalizer.IsNormalized(out bool isNormalized, out _, path.GetString()); - if (rc.IsFailure()) return rc; - - if (!isNormalized) - return ResultFs.NotNormalized.Log(); + var emptyFileEntry = new SaveFileInfo { StartBlock = int.MinValue, Length = size }; + FileTable.AddFile(new U8Span(path.GetString()), ref emptyFileEntry); return Result.Success; } - protected override Result DoCreateDirectory(in Path path) + int blockCount = (int)BitUtil.DivideUp(size, AllocationTable.Header.BlockSize); + int startBlock = AllocationTable.Allocate(blockCount); + + if (startBlock == -1) { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - FileTable.AddDirectory(new U8Span(path.GetString())); - - return Result.Success; + return ResultFs.AllocationTableFull.Log(); } - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size }; + + FileTable.AddFile(new U8Span(path.GetString()), ref fileEntry); + + return Result.Success; + } + + protected override Result DoDeleteDirectory(in Path path) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + FileTable.DeleteDirectory(new U8Span(path.GetString())); + + return Result.Success; + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + rc = CleanDirectoryRecursively(in path); + if (rc.IsFailure()) return rc; + + rc = DeleteDirectory(in path); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, new U8Span(path.GetString()).ToString()); + + return Result.Success; + } + + protected override Result DoDeleteFile(in Path path) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo)) { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - if (size == 0) - { - var emptyFileEntry = new SaveFileInfo { StartBlock = int.MinValue, Length = size }; - FileTable.AddFile(new U8Span(path.GetString()), ref emptyFileEntry); - - return Result.Success; - } - - int blockCount = (int)BitUtil.DivideUp(size, AllocationTable.Header.BlockSize); - int startBlock = AllocationTable.Allocate(blockCount); - - if (startBlock == -1) - { - return ResultFs.AllocationTableFull.Log(); - } - - var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size }; - - FileTable.AddFile(new U8Span(path.GetString()), ref fileEntry); - - return Result.Success; - } - - protected override Result DoDeleteDirectory(in Path path) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - FileTable.DeleteDirectory(new U8Span(path.GetString())); - - return Result.Success; - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - rc = CleanDirectoryRecursively(in path); - if (rc.IsFailure()) return rc; - - rc = DeleteDirectory(in path); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, new U8Span(path.GetString()).ToString()); - - return Result.Success; - } - - protected override Result DoDeleteFile(in Path path) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo)) - { - return ResultFs.PathNotFound.Log(); - } - - if (fileInfo.StartBlock != int.MinValue) - { - AllocationTable.Free(fileInfo.StartBlock); - } - - FileTable.DeleteFile(new U8Span(path.GetString())); - - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - if (!FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition position)) - { - return ResultFs.PathNotFound.Log(); - } - - outDirectory.Reset(new SaveDataDirectory(this, position, mode)); - - return Result.Success; - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo)) - { - return ResultFs.PathNotFound.Log(); - } - - AllocationTableStorage storage = OpenFatStorage(fileInfo.StartBlock); - - outFile.Reset(new SaveDataFile(storage, new U8Span(path.GetString()), FileTable, fileInfo.Length, mode)); - - return Result.Success; - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - Result rc = CheckIfNormalized(in currentPath); - if (rc.IsFailure()) return rc; - - rc = CheckIfNormalized(in newPath); - if (rc.IsFailure()) return rc; - - return FileTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - Result rc = CheckIfNormalized(in currentPath); - if (rc.IsFailure()) return rc; - - rc = CheckIfNormalized(in newPath); - if (rc.IsFailure()) return rc; - - FileTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); - - return Result.Success; - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); - - Result rc = CheckIfNormalized(in path); - if (rc.IsFailure()) return rc; - - if (FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo _)) - { - entryType = DirectoryEntryType.File; - return Result.Success; - } - - if (FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition _)) - { - entryType = DirectoryEntryType.Directory; - return Result.Success; - } - return ResultFs.PathNotFound.Log(); } - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + if (fileInfo.StartBlock != int.MinValue) { - int freeBlockCount = AllocationTable.GetFreeListLength(); - freeSpace = Header.BlockSize * freeBlockCount; - - return Result.Success; + AllocationTable.Free(fileInfo.StartBlock); } - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - totalSpace = Header.BlockSize * Header.BlockCount; + FileTable.DeleteFile(new U8Span(path.GetString())); - return Result.Success; - } - - protected override Result DoCommit() - { - return Result.Success; - } - - public IStorage GetBaseStorage() => BaseStorage; - public IStorage GetHeaderStorage() => HeaderStorage; - - public void FsTrim() - { - AllocationTable.FsTrim(); - - foreach (DirectoryEntryEx file in this.EnumerateEntries("*", SearchOptions.RecurseSubdirectories)) - { - if (FileTable.TryOpenFile(file.FullPath.ToU8Span(), out SaveFileInfo fileInfo) && fileInfo.StartBlock >= 0) - { - AllocationTable.FsTrimList(fileInfo.StartBlock); - - OpenFatStorage(fileInfo.StartBlock).Slice(fileInfo.Length).Fill(SaveDataFileSystem.TrimFillValue); - } - } - - int freeIndex = AllocationTable.GetFreeListBlockIndex(); - if (freeIndex == 0) return; - - AllocationTable.FsTrimList(freeIndex); - - OpenFatStorage(freeIndex).Fill(SaveDataFileSystem.TrimFillValue); - - FileTable.TrimFreeEntries(); - } - - private AllocationTableStorage OpenFatStorage(int blockIndex) - { - return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex); - } + return Result.Success; } - public class SaveHeader + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) { - public string Magic { get; } - public uint Version { get; } - public long BlockCount { get; } - public long BlockSize { get; } + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; - - public SaveHeader(IStorage storage) + if (!FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition position)) { - var reader = new BinaryReader(storage.AsStream()); - - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - BlockCount = reader.ReadInt64(); - BlockSize = reader.ReadInt64(); + return ResultFs.PathNotFound.Log(); } + + outDirectory.Reset(new SaveDataDirectory(this, position, mode)); + + return Result.Success; + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + if (!FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo fileInfo)) + { + return ResultFs.PathNotFound.Log(); + } + + AllocationTableStorage storage = OpenFatStorage(fileInfo.StartBlock); + + outFile.Reset(new SaveDataFile(storage, new U8Span(path.GetString()), FileTable, fileInfo.Length, mode)); + + return Result.Success; + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + Result rc = CheckIfNormalized(in currentPath); + if (rc.IsFailure()) return rc; + + rc = CheckIfNormalized(in newPath); + if (rc.IsFailure()) return rc; + + return FileTable.RenameDirectory(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + Result rc = CheckIfNormalized(in currentPath); + if (rc.IsFailure()) return rc; + + rc = CheckIfNormalized(in newPath); + if (rc.IsFailure()) return rc; + + FileTable.RenameFile(new U8Span(currentPath.GetString()), new U8Span(newPath.GetString())); + + return Result.Success; + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); + + Result rc = CheckIfNormalized(in path); + if (rc.IsFailure()) return rc; + + if (FileTable.TryOpenFile(new U8Span(path.GetString()), out SaveFileInfo _)) + { + entryType = DirectoryEntryType.File; + return Result.Success; + } + + if (FileTable.TryOpenDirectory(new U8Span(path.GetString()), out SaveFindPosition _)) + { + entryType = DirectoryEntryType.Directory; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + int freeBlockCount = AllocationTable.GetFreeListLength(); + freeSpace = Header.BlockSize * freeBlockCount; + + return Result.Success; + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + totalSpace = Header.BlockSize * Header.BlockCount; + + return Result.Success; + } + + protected override Result DoCommit() + { + return Result.Success; + } + + public IStorage GetBaseStorage() => BaseStorage; + public IStorage GetHeaderStorage() => HeaderStorage; + + public void FsTrim() + { + AllocationTable.FsTrim(); + + foreach (DirectoryEntryEx file in this.EnumerateEntries("*", SearchOptions.RecurseSubdirectories)) + { + if (FileTable.TryOpenFile(file.FullPath.ToU8Span(), out SaveFileInfo fileInfo) && fileInfo.StartBlock >= 0) + { + AllocationTable.FsTrimList(fileInfo.StartBlock); + + OpenFatStorage(fileInfo.StartBlock).Slice(fileInfo.Length).Fill(SaveDataFileSystem.TrimFillValue); + } + } + + int freeIndex = AllocationTable.GetFreeListBlockIndex(); + if (freeIndex == 0) return; + + AllocationTable.FsTrimList(freeIndex); + + OpenFatStorage(freeIndex).Fill(SaveDataFileSystem.TrimFillValue); + + FileTable.TrimFreeEntries(); + } + + private AllocationTableStorage OpenFatStorage(int blockIndex) + { + return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex); + } +} + +public class SaveHeader +{ + public string Magic { get; } + public uint Version { get; } + public long BlockCount { get; } + public long BlockSize { get; } + + + public SaveHeader(IStorage storage) + { + var reader = new BinaryReader(storage.AsStream()); + + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + BlockCount = reader.ReadInt64(); + BlockSize = reader.ReadInt64(); } } diff --git a/src/LibHac/FsSystem/Save/SaveExtensions.cs b/src/LibHac/FsSystem/Save/SaveExtensions.cs index 0507f229..e490b260 100644 --- a/src/LibHac/FsSystem/Save/SaveExtensions.cs +++ b/src/LibHac/FsSystem/Save/SaveExtensions.cs @@ -1,17 +1,16 @@ using System.Collections.Generic; -namespace LibHac.FsSystem.Save -{ - public static class SaveExtensions - { - public static IEnumerable<(int block, int length)> DumpChain(this AllocationTable table, int startBlock) - { - var iterator = new AllocationTableIterator(table, startBlock); +namespace LibHac.FsSystem.Save; - do - { - yield return (iterator.PhysicalBlock, iterator.CurrentSegmentSize); - } while (iterator.MoveNext()); - } +public static class SaveExtensions +{ + public static IEnumerable<(int block, int length)> DumpChain(this AllocationTable table, int startBlock) + { + var iterator = new AllocationTableIterator(table, startBlock); + + do + { + yield return (iterator.PhysicalBlock, iterator.CurrentSegmentSize); + } while (iterator.MoveNext()); } } diff --git a/src/LibHac/FsSystem/Save/SaveFsEntry.cs b/src/LibHac/FsSystem/Save/SaveFsEntry.cs index 49e01214..35a614fe 100644 --- a/src/LibHac/FsSystem/Save/SaveFsEntry.cs +++ b/src/LibHac/FsSystem/Save/SaveFsEntry.cs @@ -1,37 +1,36 @@ using System; using System.Runtime.InteropServices; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +internal ref struct SaveEntryKey { - internal ref struct SaveEntryKey - { - public ReadOnlySpan Name; - public int Parent; + public ReadOnlySpan Name; + public int Parent; - public SaveEntryKey(ReadOnlySpan name, int parent) - { - Name = name; - Parent = parent; - } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)] - public struct SaveFileInfo + public SaveEntryKey(ReadOnlySpan name, int parent) { - public int StartBlock; - public long Length; - public long Reserved; - } - - /// - /// Represents the current position when enumerating a directory's contents. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)] - public struct SaveFindPosition - { - /// The ID of the next directory to be enumerated. - public int NextDirectory; - /// The ID of the next file to be enumerated. - public int NextFile; + Name = name; + Parent = parent; } } + +[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)] +public struct SaveFileInfo +{ + public int StartBlock; + public long Length; + public long Reserved; +} + +/// +/// Represents the current position when enumerating a directory's contents. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)] +public struct SaveFindPosition +{ + /// The ID of the next directory to be enumerated. + public int NextDirectory; + /// The ID of the next file to be enumerated. + public int NextFile; +} diff --git a/src/LibHac/FsSystem/Save/SaveFsList.cs b/src/LibHac/FsSystem/Save/SaveFsList.cs index 6d61de10..454ac336 100644 --- a/src/LibHac/FsSystem/Save/SaveFsList.cs +++ b/src/LibHac/FsSystem/Save/SaveFsList.cs @@ -7,374 +7,373 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +// todo: Change constraint to "unmanaged" after updating to +// a newer SDK https://github.com/dotnet/csharplang/issues/1937 +internal class SaveFsList where T : struct { - // todo: Change constraint to "unmanaged" after updating to - // a newer SDK https://github.com/dotnet/csharplang/issues/1937 - internal class SaveFsList where T : struct + private const int FreeListHeadIndex = 0; + private const int UsedListHeadIndex = 1; + private const int CapacityIncrement = 0x4000; + + public int MaxNameLength { get; } = 0x40; + + private IStorage Storage { get; } + + private readonly int _sizeOfEntry = Unsafe.SizeOf(); + + public SaveFsList(IStorage tableStorage) { - private const int FreeListHeadIndex = 0; - private const int UsedListHeadIndex = 1; - private const int CapacityIncrement = 0x4000; + Storage = tableStorage; + } - public int MaxNameLength { get; } = 0x40; + public (int Index, int PreviousIndex) GetIndexFromKey(ref SaveEntryKey key) + { + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span name = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); - private IStorage Storage { get; } + int capacity = GetListCapacity(); - private readonly int _sizeOfEntry = Unsafe.SizeOf(); + ReadEntry(UsedListHeadIndex, entryBytes); + int prevIndex = UsedListHeadIndex; + int index = entry.Next; - public SaveFsList(IStorage tableStorage) + while (index > 0) { - Storage = tableStorage; - } + if (index > capacity) throw new IndexOutOfRangeException("Save entry index out of range"); - public (int Index, int PreviousIndex) GetIndexFromKey(ref SaveEntryKey key) - { - Span entryBytes = stackalloc byte[_sizeOfEntry]; - Span name = entryBytes.Slice(4, MaxNameLength); - ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + ReadEntry(index, out entry); - int capacity = GetListCapacity(); - - ReadEntry(UsedListHeadIndex, entryBytes); - int prevIndex = UsedListHeadIndex; - int index = entry.Next; - - while (index > 0) + if (entry.Parent == key.Parent && StringUtils.Compare(name, key.Name) == 0) { - if (index > capacity) throw new IndexOutOfRangeException("Save entry index out of range"); - - ReadEntry(index, out entry); - - if (entry.Parent == key.Parent && StringUtils.Compare(name, key.Name) == 0) - { - return (index, prevIndex); - } - - prevIndex = index; - index = entry.Next; + return (index, prevIndex); } - return (-1, -1); + prevIndex = index; + index = entry.Next; } - public int Add(ref SaveEntryKey key, ref T value) + return (-1, -1); + } + + public int Add(ref SaveEntryKey key, ref T value) + { + int index = GetIndexFromKey(ref key).Index; + + if (index != -1) { - int index = GetIndexFromKey(ref key).Index; - - if (index != -1) - { - SetValue(index, ref value); - return index; - } - - index = AllocateEntry(); - - ReadEntry(index, out SaveFsEntry entry); - entry.Value = value; - WriteEntry(index, ref entry, ref key); - + SetValue(index, ref value); return index; } - private int AllocateEntry() + index = AllocateEntry(); + + ReadEntry(index, out SaveFsEntry entry); + entry.Value = value; + WriteEntry(index, ref entry, ref key); + + return index; + } + + private int AllocateEntry() + { + ReadEntry(FreeListHeadIndex, out SaveFsEntry freeListHead); + ReadEntry(UsedListHeadIndex, out SaveFsEntry usedListHead); + + if (freeListHead.Next != 0) { - ReadEntry(FreeListHeadIndex, out SaveFsEntry freeListHead); - ReadEntry(UsedListHeadIndex, out SaveFsEntry usedListHead); + ReadEntry(freeListHead.Next, out SaveFsEntry firstFreeEntry); - if (freeListHead.Next != 0) - { - ReadEntry(freeListHead.Next, out SaveFsEntry firstFreeEntry); + int allocatedIndex = freeListHead.Next; - int allocatedIndex = freeListHead.Next; - - freeListHead.Next = firstFreeEntry.Next; - firstFreeEntry.Next = usedListHead.Next; - usedListHead.Next = allocatedIndex; - - WriteEntry(FreeListHeadIndex, ref freeListHead); - WriteEntry(UsedListHeadIndex, ref usedListHead); - WriteEntry(allocatedIndex, ref firstFreeEntry); - - return allocatedIndex; - } - - int length = GetListLength(); - int capacity = GetListCapacity(); - - if (capacity == 0 || length >= capacity) - { - Storage.GetSize(out long currentSize).ThrowIfFailure(); - Storage.SetSize(currentSize + CapacityIncrement); - - Storage.GetSize(out long newSize).ThrowIfFailure(); - SetListCapacity((int)(newSize / _sizeOfEntry)); - } - - SetListLength(length + 1); - - ReadEntry(length, out SaveFsEntry newEntry); - - newEntry.Next = usedListHead.Next; - usedListHead.Next = length; + freeListHead.Next = firstFreeEntry.Next; + firstFreeEntry.Next = usedListHead.Next; + usedListHead.Next = allocatedIndex; + WriteEntry(FreeListHeadIndex, ref freeListHead); WriteEntry(UsedListHeadIndex, ref usedListHead); - WriteEntry(length, ref newEntry); + WriteEntry(allocatedIndex, ref firstFreeEntry); - return length; + return allocatedIndex; } - private void Free(int entryIndex) + int length = GetListLength(); + int capacity = GetListCapacity(); + + if (capacity == 0 || length >= capacity) { - ReadEntry(FreeListHeadIndex, out SaveFsEntry freeEntry); - ReadEntry(entryIndex, out SaveFsEntry entry); + Storage.GetSize(out long currentSize).ThrowIfFailure(); + Storage.SetSize(currentSize + CapacityIncrement); - entry.Next = freeEntry.Next; - freeEntry.Next = entryIndex; - - WriteEntry(FreeListHeadIndex, ref freeEntry); - WriteEntry(entryIndex, ref entry); + Storage.GetSize(out long newSize).ThrowIfFailure(); + SetListCapacity((int)(newSize / _sizeOfEntry)); } - public bool TryGetValue(ref SaveEntryKey key, out T value) + SetListLength(length + 1); + + ReadEntry(length, out SaveFsEntry newEntry); + + newEntry.Next = usedListHead.Next; + usedListHead.Next = length; + + WriteEntry(UsedListHeadIndex, ref usedListHead); + WriteEntry(length, ref newEntry); + + return length; + } + + private void Free(int entryIndex) + { + ReadEntry(FreeListHeadIndex, out SaveFsEntry freeEntry); + ReadEntry(entryIndex, out SaveFsEntry entry); + + entry.Next = freeEntry.Next; + freeEntry.Next = entryIndex; + + WriteEntry(FreeListHeadIndex, ref freeEntry); + WriteEntry(entryIndex, ref entry); + } + + public bool TryGetValue(ref SaveEntryKey key, out T value) + { + UnsafeHelpers.SkipParamInit(out value); + + int index = GetIndexFromKey(ref key).Index; + + if (index < 0) + return false; + + return TryGetValue(index, out value); + } + + public bool TryGetValue(int index, out T value) + { + UnsafeHelpers.SkipParamInit(out value); + + if (index < 0 || index >= GetListCapacity()) + return false; + + GetValue(index, out value); + return true; + } + + public void GetValue(int index, out T value) + { + ReadEntry(index, out SaveFsEntry entry); + value = entry.Value; + } + + /// + /// Gets the value and name associated with the specific index. + /// + /// The index of the value to get. + /// Contains the corresponding value if the method returns . + /// The name of the given index will be written to this span if the method returns . + /// This span must be at least bytes long. + /// if the contains an element with + /// the specified key; otherwise, . + public bool TryGetValue(int index, out T value, ref Span name) + { + UnsafeHelpers.SkipParamInit(out value); + Debug.Assert(name.Length >= MaxNameLength); + + if (index < 0 || index >= GetListCapacity()) + return false; + + GetValue(index, out value, ref name); + return true; + } + + /// + /// Gets the value and name associated with the specific index. + /// + /// The index of the value to get. + /// Contains the corresponding value when the method returns. + /// The name of the given index will be written to this span when the method returns. + /// This span must be at least bytes long. + public void GetValue(int index, out T value, ref Span name) + { + Debug.Assert(name.Length >= MaxNameLength); + + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span nameSpan = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(index, out entry); + + nameSpan.CopyTo(name); + value = entry.Value; + } + + public void SetValue(int index, ref T value) + { + Span entryBytes = stackalloc byte[_sizeOfEntry]; + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(index, out entry); + + entry.Value = value; + + WriteEntry(index, ref entry); + } + + public void Remove(ref SaveEntryKey key) + { + (int index, int previousIndex) = GetIndexFromKey(ref key); + + ReadEntry(previousIndex, out SaveFsEntry prevEntry); + ReadEntry(index, out SaveFsEntry entryToDel); + + prevEntry.Next = entryToDel.Next; + WriteEntry(previousIndex, ref prevEntry); + + Free(index); + } + + public void ChangeKey(ref SaveEntryKey oldKey, ref SaveEntryKey newKey) + { + int index = GetIndexFromKey(ref oldKey).Index; + int newIndex = GetIndexFromKey(ref newKey).Index; + + if (index == -1) throw new KeyNotFoundException("Old key was not found."); + if (newIndex != -1) throw new KeyNotFoundException("New key already exists."); + + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span name = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(index, entryBytes); + + entry.Parent = newKey.Parent; + newKey.Name.CopyTo(name); + + WriteEntry(index, entryBytes); + } + + public void TrimFreeEntries() + { + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span name = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(FreeListHeadIndex, out entry); + + int index = entry.Next; + + while (entry.Next > 0) { - UnsafeHelpers.SkipParamInit(out value); - - int index = GetIndexFromKey(ref key).Index; - - if (index < 0) - return false; - - return TryGetValue(index, out value); - } - - public bool TryGetValue(int index, out T value) - { - UnsafeHelpers.SkipParamInit(out value); - - if (index < 0 || index >= GetListCapacity()) - return false; - - GetValue(index, out value); - return true; - } - - public void GetValue(int index, out T value) - { - ReadEntry(index, out SaveFsEntry entry); - value = entry.Value; - } - - /// - /// Gets the value and name associated with the specific index. - /// - /// The index of the value to get. - /// Contains the corresponding value if the method returns . - /// The name of the given index will be written to this span if the method returns . - /// This span must be at least bytes long. - /// if the contains an element with - /// the specified key; otherwise, . - public bool TryGetValue(int index, out T value, ref Span name) - { - UnsafeHelpers.SkipParamInit(out value); - Debug.Assert(name.Length >= MaxNameLength); - - if (index < 0 || index >= GetListCapacity()) - return false; - - GetValue(index, out value, ref name); - return true; - } - - /// - /// Gets the value and name associated with the specific index. - /// - /// The index of the value to get. - /// Contains the corresponding value when the method returns. - /// The name of the given index will be written to this span when the method returns. - /// This span must be at least bytes long. - public void GetValue(int index, out T value, ref Span name) - { - Debug.Assert(name.Length >= MaxNameLength); - - Span entryBytes = stackalloc byte[_sizeOfEntry]; - Span nameSpan = entryBytes.Slice(4, MaxNameLength); - ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); - ReadEntry(index, out entry); - nameSpan.CopyTo(name); - value = entry.Value; - } - - public void SetValue(int index, ref T value) - { - Span entryBytes = stackalloc byte[_sizeOfEntry]; - ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); - - ReadEntry(index, out entry); - - entry.Value = value; + entry.Parent = 0; + entry.Value = new T(); + name.Fill(SaveDataFileSystem.TrimFillValue); WriteEntry(index, ref entry); + + index = entry.Next; } - - public void Remove(ref SaveEntryKey key) - { - (int index, int previousIndex) = GetIndexFromKey(ref key); - - ReadEntry(previousIndex, out SaveFsEntry prevEntry); - ReadEntry(index, out SaveFsEntry entryToDel); - - prevEntry.Next = entryToDel.Next; - WriteEntry(previousIndex, ref prevEntry); - - Free(index); - } - - public void ChangeKey(ref SaveEntryKey oldKey, ref SaveEntryKey newKey) - { - int index = GetIndexFromKey(ref oldKey).Index; - int newIndex = GetIndexFromKey(ref newKey).Index; - - if (index == -1) throw new KeyNotFoundException("Old key was not found."); - if (newIndex != -1) throw new KeyNotFoundException("New key already exists."); - - Span entryBytes = stackalloc byte[_sizeOfEntry]; - Span name = entryBytes.Slice(4, MaxNameLength); - ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); - - ReadEntry(index, entryBytes); - - entry.Parent = newKey.Parent; - newKey.Name.CopyTo(name); - - WriteEntry(index, entryBytes); - } - - public void TrimFreeEntries() - { - Span entryBytes = stackalloc byte[_sizeOfEntry]; - Span name = entryBytes.Slice(4, MaxNameLength); - ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); - - ReadEntry(FreeListHeadIndex, out entry); - - int index = entry.Next; - - while (entry.Next > 0) - { - ReadEntry(index, out entry); - - entry.Parent = 0; - entry.Value = new T(); - name.Fill(SaveDataFileSystem.TrimFillValue); - - WriteEntry(index, ref entry); - - index = entry.Next; - } - } - - private int GetListCapacity() - { - Span buf = stackalloc byte[sizeof(int)]; - Storage.Read(4, buf).ThrowIfFailure(); - - return MemoryMarshal.Read(buf); - } - - private int GetListLength() - { - Span buf = stackalloc byte[sizeof(int)]; - Storage.Read(0, buf).ThrowIfFailure(); - - return MemoryMarshal.Read(buf); - } - - private void SetListCapacity(int capacity) - { - Span buf = stackalloc byte[sizeof(int)]; - MemoryMarshal.Write(buf, ref capacity); - - Storage.Write(4, buf).ThrowIfFailure(); - } - - private void SetListLength(int length) - { - Span buf = stackalloc byte[sizeof(int)]; - MemoryMarshal.Write(buf, ref length); - - Storage.Write(0, buf).ThrowIfFailure(); - } - - private void ReadEntry(int index, out SaveFsEntry entry) - { - Span bytes = stackalloc byte[_sizeOfEntry]; - ReadEntry(index, bytes); - - entry = GetEntryFromBytes(bytes); - } - - private void WriteEntry(int index, ref SaveFsEntry entry, ref SaveEntryKey key) - { - Span bytes = stackalloc byte[_sizeOfEntry]; - Span nameSpan = bytes.Slice(4, MaxNameLength); - - // Copy needed for .NET Framework compat - ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); - newEntry = entry; - - newEntry.Parent = key.Parent; - key.Name.CopyTo(nameSpan); - - nameSpan.Slice(key.Name.Length).Fill(0); - - WriteEntry(index, bytes); - } - - private void WriteEntry(int index, ref SaveFsEntry entry) - { - Span bytes = stackalloc byte[_sizeOfEntry]; - - // Copy needed for .NET Framework compat - ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); - newEntry = entry; - - WriteEntry(index, bytes); - } - - private void ReadEntry(int index, Span entry) - { - Debug.Assert(entry.Length == _sizeOfEntry); - - int offset = index * _sizeOfEntry; - Storage.Read(offset, entry); - } - - private void WriteEntry(int index, Span entry) - { - Debug.Assert(entry.Length == _sizeOfEntry); - - int offset = index * _sizeOfEntry; - Storage.Write(offset, entry); - } - - private ref SaveFsEntry GetEntryFromBytes(Span entry) - { - return ref MemoryMarshal.Cast(entry)[0]; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - private struct SaveFsEntry - { - public int Parent; - private NameDummy Name; - public T Value; - public int Next; - } - - [StructLayout(LayoutKind.Sequential, Size = 0x40)] - private struct NameDummy { } } + + private int GetListCapacity() + { + Span buf = stackalloc byte[sizeof(int)]; + Storage.Read(4, buf).ThrowIfFailure(); + + return MemoryMarshal.Read(buf); + } + + private int GetListLength() + { + Span buf = stackalloc byte[sizeof(int)]; + Storage.Read(0, buf).ThrowIfFailure(); + + return MemoryMarshal.Read(buf); + } + + private void SetListCapacity(int capacity) + { + Span buf = stackalloc byte[sizeof(int)]; + MemoryMarshal.Write(buf, ref capacity); + + Storage.Write(4, buf).ThrowIfFailure(); + } + + private void SetListLength(int length) + { + Span buf = stackalloc byte[sizeof(int)]; + MemoryMarshal.Write(buf, ref length); + + Storage.Write(0, buf).ThrowIfFailure(); + } + + private void ReadEntry(int index, out SaveFsEntry entry) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + ReadEntry(index, bytes); + + entry = GetEntryFromBytes(bytes); + } + + private void WriteEntry(int index, ref SaveFsEntry entry, ref SaveEntryKey key) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + Span nameSpan = bytes.Slice(4, MaxNameLength); + + // Copy needed for .NET Framework compat + ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); + newEntry = entry; + + newEntry.Parent = key.Parent; + key.Name.CopyTo(nameSpan); + + nameSpan.Slice(key.Name.Length).Fill(0); + + WriteEntry(index, bytes); + } + + private void WriteEntry(int index, ref SaveFsEntry entry) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + + // Copy needed for .NET Framework compat + ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); + newEntry = entry; + + WriteEntry(index, bytes); + } + + private void ReadEntry(int index, Span entry) + { + Debug.Assert(entry.Length == _sizeOfEntry); + + int offset = index * _sizeOfEntry; + Storage.Read(offset, entry); + } + + private void WriteEntry(int index, Span entry) + { + Debug.Assert(entry.Length == _sizeOfEntry); + + int offset = index * _sizeOfEntry; + Storage.Write(offset, entry); + } + + private ref SaveFsEntry GetEntryFromBytes(Span entry) + { + return ref MemoryMarshal.Cast(entry)[0]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct SaveFsEntry + { + public int Parent; + private NameDummy Name; + public T Value; + public int Next; + } + + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + private struct NameDummy { } } diff --git a/src/LibHac/FsSystem/Save/SaveResults.cs b/src/LibHac/FsSystem/Save/SaveResults.cs index 303badb4..cca529e0 100644 --- a/src/LibHac/FsSystem/Save/SaveResults.cs +++ b/src/LibHac/FsSystem/Save/SaveResults.cs @@ -1,129 +1,128 @@ using LibHac.Fs; -namespace LibHac.FsSystem.Save +namespace LibHac.FsSystem.Save; + +internal static class SaveResults { - internal static class SaveResults + public static Result ConvertToExternalResult(Result result) { - public static Result ConvertToExternalResult(Result result) + int description = (int)result.Description; + + if (result == Result.Success) { - int description = (int)result.Description; + return Result.Success; + } - if (result == Result.Success) + if (result.Module != ResultFs.ModuleFs) + { + return result; + } + + if (ResultFs.UnsupportedVersion.Includes(result)) + { + return ResultFs.UnsupportedSaveDataVersion.Value; + } + + if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) + { + if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) { - return Result.Success; + return ResultFs.IncorrectSaveDataIntegrityVerificationMagic.Value; } - if (result.Module != ResultFs.ModuleFs) + if (ResultFs.InvalidZeroHash.Includes(result)) { - return result; + return ResultFs.InvalidSaveDataZeroHash.Value; } - if (ResultFs.UnsupportedVersion.Includes(result)) + if (ResultFs.NonRealDataVerificationFailed.Includes(result)) { - return ResultFs.UnsupportedSaveDataVersion.Value; + return ResultFs.SaveDataNonRealDataVerificationFailed.Value; } - if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result)) + if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) { - if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result)) - { - return ResultFs.IncorrectSaveDataIntegrityVerificationMagic.Value; - } - - if (ResultFs.InvalidZeroHash.Includes(result)) - { - return ResultFs.InvalidSaveDataZeroHash.Value; - } - - if (ResultFs.NonRealDataVerificationFailed.Includes(result)) - { - return ResultFs.SaveDataNonRealDataVerificationFailed.Value; - } - - if (ResultFs.ClearedRealDataVerificationFailed.Includes(result)) - { - return ResultFs.ClearedSaveDataRealDataVerificationFailed.Value; - } - - if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) - { - return ResultFs.UnclearedSaveDataRealDataVerificationFailed.Value; - } - - return result; + return ResultFs.ClearedSaveDataRealDataVerificationFailed.Value; } - if (ResultFs.BuiltInStorageCorrupted.Includes(result)) + if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result)) { - if (ResultFs.InvalidGptPartitionSignature.Includes(result)) - { - return ResultFs.SaveDataGptHeaderSignatureVerificationFailed.Value; - } - - return result; - } - - if (ResultFs.HostFileSystemCorrupted.Includes(result)) - { - if (description > 4701 && description < 4706) - { - return new Result(ResultFs.ModuleFs, description - 260); - } - - return result; - } - - if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) - { - if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result)) - { - return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.Value; - } - - return result; - } - - if (ResultFs.DatabaseCorrupted.Includes(result)) - { - if (description > 4721 && description < 4729) - { - return new Result(ResultFs.ModuleFs, description - 260); - } - - return result; - } - - if (ResultFs.FatFileSystemCorrupted.Includes(result)) - { - return result; - } - - if (ResultFs.NotFound.Includes(result)) - { - return ResultFs.PathNotFound.Value; - } - - if (ResultFs.AlreadyExists.Includes(result)) - { - return ResultFs.PathAlreadyExists.Value; - } - - if (ResultFs.IncompatiblePath.Includes(result)) - { - return ResultFs.PathNotFound.Value; - } - - if (ResultFs.InvalidOffset.Includes(result)) - { - return ResultFs.OutOfRange.Value; - } - - if (ResultFs.AllocationTableFull.Includes(result)) - { - return ResultFs.UsableSpaceNotEnough.Value; + return ResultFs.UnclearedSaveDataRealDataVerificationFailed.Value; } return result; } + + if (ResultFs.BuiltInStorageCorrupted.Includes(result)) + { + if (ResultFs.InvalidGptPartitionSignature.Includes(result)) + { + return ResultFs.SaveDataGptHeaderSignatureVerificationFailed.Value; + } + + return result; + } + + if (ResultFs.HostFileSystemCorrupted.Includes(result)) + { + if (description > 4701 && description < 4706) + { + return new Result(ResultFs.ModuleFs, description - 260); + } + + return result; + } + + if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) + { + if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result)) + { + return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.Value; + } + + return result; + } + + if (ResultFs.DatabaseCorrupted.Includes(result)) + { + if (description > 4721 && description < 4729) + { + return new Result(ResultFs.ModuleFs, description - 260); + } + + return result; + } + + if (ResultFs.FatFileSystemCorrupted.Includes(result)) + { + return result; + } + + if (ResultFs.NotFound.Includes(result)) + { + return ResultFs.PathNotFound.Value; + } + + if (ResultFs.AlreadyExists.Includes(result)) + { + return ResultFs.PathAlreadyExists.Value; + } + + if (ResultFs.IncompatiblePath.Includes(result)) + { + return ResultFs.PathNotFound.Value; + } + + if (ResultFs.InvalidOffset.Includes(result)) + { + return ResultFs.OutOfRange.Value; + } + + if (ResultFs.AllocationTableFull.Includes(result)) + { + return ResultFs.UsableSpaceNotEnough.Value; + } + + return result; } } diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs index 40fcf9c5..81f866a3 100644 --- a/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs +++ b/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs @@ -3,158 +3,157 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Os; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager { - public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager + [NonCopyable] + private struct Cache { - [NonCopyable] - private struct Cache - { - // Note: Nintendo only supports caching SaveDataFileSystem. We support DirectorySaveDataFileSystem too, - // so we use a wrapper class to simplify the logic here. - private SharedRef _fileSystem; - private ulong _saveDataId; - private SaveDataSpaceId _spaceId; - - public void Dispose() - { - _fileSystem.Destroy(); - } - - public bool IsCached(SaveDataSpaceId spaceId, ulong saveDataId) - { - return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId; - } - - public void Move(ref SharedRef outFileSystem) - { - outFileSystem.SetByMove(ref _fileSystem); - } - - public void Register(ref SharedRef fileSystem) - { - _spaceId = fileSystem.Get.GetSaveDataSpaceId(); - _saveDataId = fileSystem.Get.GetSaveDataId(); - - _fileSystem.SetByMove(ref fileSystem); - } - - public void Unregister() - { - _fileSystem.Reset(); - } - } - - private SdkRecursiveMutexType _mutex; - private Cache[] _cachedFileSystems; - private int _maxCachedFileSystemCount; - private int _nextCacheIndex; - - public SaveDataFileSystemCacheManager() - { - _mutex.Initialize(); - } + // Note: Nintendo only supports caching SaveDataFileSystem. We support DirectorySaveDataFileSystem too, + // so we use a wrapper class to simplify the logic here. + private SharedRef _fileSystem; + private ulong _saveDataId; + private SaveDataSpaceId _spaceId; public void Dispose() { - Cache[] caches = Shared.Move(ref _cachedFileSystems); - - if (caches is not null) - { - for (int i = 0; i < caches.Length; i++) - { - caches[i].Dispose(); - } - } + _fileSystem.Destroy(); } - public Result Initialize(int maxCacheCount) + public bool IsCached(SaveDataSpaceId spaceId, ulong saveDataId) { - Assert.SdkRequiresGreaterEqual(maxCacheCount, 0); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - Assert.SdkAssert(_cachedFileSystems is null); - - _maxCachedFileSystemCount = maxCacheCount; - if (maxCacheCount <= 0) - return Result.Success; - - // Note: The original checks for overflow here - _cachedFileSystems = new Cache[maxCacheCount]; - return Result.Success; + return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId; } - public bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, - ulong saveDataId) + public void Move(ref SharedRef outFileSystem) { - Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - for (int i = 0; i < _maxCachedFileSystemCount; i++) - { - if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) - { - _cachedFileSystems[i].Move(ref outFileSystem); - return true; - } - } - - return false; - } - - public void Register(ref SharedRef fileSystem) - { - // Don't cache temporary save data - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - fileSystem.Reset(); + outFileSystem.SetByMove(ref _fileSystem); } public void Register(ref SharedRef fileSystem) { - if (_maxCachedFileSystemCount <= 0) - return; + _spaceId = fileSystem.Get.GetSaveDataSpaceId(); + _saveDataId = fileSystem.Get.GetSaveDataId(); - Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0); - Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex); + _fileSystem.SetByMove(ref fileSystem); + } - if (fileSystem.Get.GetSaveDataSpaceId() == SaveDataSpaceId.SdSystem) + public void Unregister() + { + _fileSystem.Reset(); + } + } + + private SdkRecursiveMutexType _mutex; + private Cache[] _cachedFileSystems; + private int _maxCachedFileSystemCount; + private int _nextCacheIndex; + + public SaveDataFileSystemCacheManager() + { + _mutex.Initialize(); + } + + public void Dispose() + { + Cache[] caches = Shared.Move(ref _cachedFileSystems); + + if (caches is not null) + { + for (int i = 0; i < caches.Length; i++) { - // Don't cache system save data - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - fileSystem.Reset(); + caches[i].Dispose(); } - else + } + } + + public Result Initialize(int maxCacheCount) + { + Assert.SdkRequiresGreaterEqual(maxCacheCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Assert.SdkAssert(_cachedFileSystems is null); + + _maxCachedFileSystemCount = maxCacheCount; + if (maxCacheCount <= 0) + return Result.Success; + + // Note: The original checks for overflow here + _cachedFileSystems = new Cache[maxCacheCount]; + return Result.Success; + } + + public bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, + ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) { - Result rc = fileSystem.Get.RollbackOnlyModified(); - if (rc.IsFailure()) return; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - _cachedFileSystems[_nextCacheIndex].Register(ref fileSystem); - _nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount; + _cachedFileSystems[i].Move(ref outFileSystem); + return true; } } - public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + return false; + } + + public void Register(ref SharedRef fileSystem) + { + // Don't cache temporary save data + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + fileSystem.Reset(); + } + + public void Register(ref SharedRef fileSystem) + { + if (_maxCachedFileSystemCount <= 0) + return; + + Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0); + Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex); + + if (fileSystem.Get.GetSaveDataSpaceId() == SaveDataSpaceId.SdSystem) { - Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + // Don't cache system save data + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + fileSystem.Reset(); + } + else + { + Result rc = fileSystem.Get.RollbackOnlyModified(); + if (rc.IsFailure()) return; using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - for (int i = 0; i < _maxCachedFileSystemCount; i++) - { - if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) - { - _cachedFileSystems[i].Unregister(); - } - } - } - - public UniqueLockRef GetScopedLock() - { - return new UniqueLockRef(ref _mutex); + _cachedFileSystems[_nextCacheIndex].Register(ref fileSystem); + _nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount; } } -} \ No newline at end of file + + public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) + { + _cachedFileSystems[i].Unregister(); + } + } + } + + public UniqueLockRef GetScopedLock() + { + return new UniqueLockRef(ref _mutex); + } +} diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs index 45a51141..a54cbf26 100644 --- a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs +++ b/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs @@ -6,144 +6,143 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem.Save; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// Wraps a save data . +/// Upon disposal the base file system is returned to the provided . +/// +/// The type of the base file system. Must be one of , +/// or . +public class SaveDataFileSystemCacheRegisterBase : IFileSystem where T : IFileSystem { - /// - /// Wraps a save data . - /// Upon disposal the base file system is returned to the provided . - /// - /// The type of the base file system. Must be one of , - /// or . - public class SaveDataFileSystemCacheRegisterBase : IFileSystem where T : IFileSystem + private SharedRef _baseFileSystem; + private ISaveDataFileSystemCacheManager _cacheManager; + + public SaveDataFileSystemCacheRegisterBase(ref SharedRef baseFileSystem, + ISaveDataFileSystemCacheManager cacheManager) { - private SharedRef _baseFileSystem; - private ISaveDataFileSystemCacheManager _cacheManager; - - public SaveDataFileSystemCacheRegisterBase(ref SharedRef baseFileSystem, - ISaveDataFileSystemCacheManager cacheManager) + if (typeof(T) != typeof(SaveDataFileSystemHolder) && typeof(T) != typeof(ApplicationTemporaryFileSystem)) { - if (typeof(T) != typeof(SaveDataFileSystemHolder) && typeof(T) != typeof(ApplicationTemporaryFileSystem)) - { - throw new NotSupportedException( - $"The file system type of a {nameof(SaveDataFileSystemCacheRegisterBase)} must be {nameof(SaveDataFileSystemHolder)} or {nameof(ApplicationTemporaryFileSystem)}."); - } - - _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - _cacheManager = cacheManager; + throw new NotSupportedException( + $"The file system type of a {nameof(SaveDataFileSystemCacheRegisterBase)} must be {nameof(SaveDataFileSystemHolder)} or {nameof(ApplicationTemporaryFileSystem)}."); } - public override void Dispose() - { - if (typeof(T) == typeof(SaveDataFileSystemHolder)) - { - _cacheManager.Register(ref GetBaseFileSystemNormal()); - } - else if (typeof(T) == typeof(ApplicationTemporaryFileSystem)) - { - _cacheManager.Register(ref GetBaseFileSystemTemp()); - } - else - { - Assert.SdkAssert(false, "Invalid save data file system type."); - } - } + _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); + _cacheManager = cacheManager; + } - // Hack around not being able to use Unsafe.As on ref structs - private ref SharedRef GetBaseFileSystemNormal() + public override void Dispose() + { + if (typeof(T) == typeof(SaveDataFileSystemHolder)) { - IL.Emit.Ldarg_0(); - IL.Emit.Ldflda(new FieldRef(typeof(SaveDataFileSystemCacheRegisterBase), nameof(_baseFileSystem))); - IL.Emit.Ret(); - throw IL.Unreachable(); + _cacheManager.Register(ref GetBaseFileSystemNormal()); } - - private ref SharedRef GetBaseFileSystemTemp() + else if (typeof(T) == typeof(ApplicationTemporaryFileSystem)) { - IL.Emit.Ldarg_0(); - IL.Emit.Ldflda(new FieldRef(typeof(SaveDataFileSystemCacheRegisterBase), nameof(_baseFileSystem))); - IL.Emit.Ret(); - throw IL.Unreachable(); + _cacheManager.Register(ref GetBaseFileSystemTemp()); } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + else { - return _baseFileSystem.Get.OpenFile(ref outFile, path, mode); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - return _baseFileSystem.Get.OpenDirectory(ref outDirectory, path, mode); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - return _baseFileSystem.Get.GetEntryType(out entryType, path); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - return _baseFileSystem.Get.CreateFile(path, size, option); - } - - protected override Result DoDeleteFile(in Path path) - { - return _baseFileSystem.Get.DeleteFile(path); - } - - protected override Result DoCreateDirectory(in Path path) - { - return _baseFileSystem.Get.CreateDirectory(path); - } - - protected override Result DoDeleteDirectory(in Path path) - { - return _baseFileSystem.Get.DeleteDirectory(path); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - return _baseFileSystem.Get.DeleteDirectoryRecursively(path); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - return _baseFileSystem.Get.CleanDirectoryRecursively(path); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - return _baseFileSystem.Get.RenameFile(currentPath, newPath); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - return _baseFileSystem.Get.RenameDirectory(currentPath, newPath); - } - - protected override Result DoCommit() - { - return _baseFileSystem.Get.Commit(); - } - - protected override Result DoCommitProvisionally(long counter) - { - return _baseFileSystem.Get.CommitProvisionally(counter); - } - - protected override Result DoRollback() - { - return _baseFileSystem.Get.Rollback(); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path); + Assert.SdkAssert(false, "Invalid save data file system type."); } } -} \ No newline at end of file + + // Hack around not being able to use Unsafe.As on ref structs + private ref SharedRef GetBaseFileSystemNormal() + { + IL.Emit.Ldarg_0(); + IL.Emit.Ldflda(new FieldRef(typeof(SaveDataFileSystemCacheRegisterBase), nameof(_baseFileSystem))); + IL.Emit.Ret(); + throw IL.Unreachable(); + } + + private ref SharedRef GetBaseFileSystemTemp() + { + IL.Emit.Ldarg_0(); + IL.Emit.Ldflda(new FieldRef(typeof(SaveDataFileSystemCacheRegisterBase), nameof(_baseFileSystem))); + IL.Emit.Ret(); + throw IL.Unreachable(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + return _baseFileSystem.Get.OpenFile(ref outFile, path, mode); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + return _baseFileSystem.Get.OpenDirectory(ref outDirectory, path, mode); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + return _baseFileSystem.Get.GetEntryType(out entryType, path); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + return _baseFileSystem.Get.CreateFile(path, size, option); + } + + protected override Result DoDeleteFile(in Path path) + { + return _baseFileSystem.Get.DeleteFile(path); + } + + protected override Result DoCreateDirectory(in Path path) + { + return _baseFileSystem.Get.CreateDirectory(path); + } + + protected override Result DoDeleteDirectory(in Path path) + { + return _baseFileSystem.Get.DeleteDirectory(path); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + return _baseFileSystem.Get.DeleteDirectoryRecursively(path); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + return _baseFileSystem.Get.CleanDirectoryRecursively(path); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + return _baseFileSystem.Get.RenameFile(currentPath, newPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + return _baseFileSystem.Get.RenameDirectory(currentPath, newPath); + } + + protected override Result DoCommit() + { + return _baseFileSystem.Get.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + return _baseFileSystem.Get.CommitProvisionally(counter); + } + + protected override Result DoRollback() + { + return _baseFileSystem.Get.Rollback(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path); + } +} diff --git a/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs b/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs index 9b2aa150..3080f154 100644 --- a/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs +++ b/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs @@ -4,50 +4,49 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class SaveDataFileSystemHolder : ForwardingFileSystem { - public class SaveDataFileSystemHolder : ForwardingFileSystem + public SaveDataFileSystemHolder(ref SharedRef baseFileSystem) : base(ref baseFileSystem) { - public SaveDataFileSystemHolder(ref SharedRef baseFileSystem) : base(ref baseFileSystem) + Assert.SdkRequires(BaseFileSystem.Get.GetType() == typeof(SaveDataFileSystemHolder) || + BaseFileSystem.Get.GetType() == typeof(ApplicationTemporaryFileSystem)); + } + + public SaveDataSpaceId GetSaveDataSpaceId() + { + IFileSystem baseFs = BaseFileSystem.Get; + + if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) { - Assert.SdkRequires(BaseFileSystem.Get.GetType() == typeof(SaveDataFileSystemHolder) || - BaseFileSystem.Get.GetType() == typeof(ApplicationTemporaryFileSystem)); + return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataSpaceId(); } - public SaveDataSpaceId GetSaveDataSpaceId() + throw new NotImplementedException(); + } + + public ulong GetSaveDataId() + { + IFileSystem baseFs = BaseFileSystem.Get; + + if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataSpaceId(); - } - - throw new NotImplementedException(); + return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataId(); } - public ulong GetSaveDataId() + throw new NotImplementedException(); + } + + public Result RollbackOnlyModified() + { + IFileSystem baseFs = BaseFileSystem.Get; + + if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataId(); - } - - throw new NotImplementedException(); + return ((DirectorySaveDataFileSystem)baseFs).Rollback(); } - public Result RollbackOnlyModified() - { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).Rollback(); - } - - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/src/LibHac/FsSystem/SectorStorage.cs b/src/LibHac/FsSystem/SectorStorage.cs index 10694031..39d6073f 100644 --- a/src/LibHac/FsSystem/SectorStorage.cs +++ b/src/LibHac/FsSystem/SectorStorage.cs @@ -2,87 +2,86 @@ using LibHac.Fs; using LibHac.Util; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class SectorStorage : IStorage { - public class SectorStorage : IStorage + protected IStorage BaseStorage { get; } + + public int SectorSize { get; } + public int SectorCount { get; private set; } + + private long Length { get; set; } + private bool LeaveOpen { get; } + + public SectorStorage(IStorage baseStorage, int sectorSize, bool leaveOpen) { - protected IStorage BaseStorage { get; } + BaseStorage = baseStorage; + SectorSize = sectorSize; - public int SectorSize { get; } - public int SectorCount { get; private set; } + baseStorage.GetSize(out long baseSize).ThrowIfFailure(); - private long Length { get; set; } - private bool LeaveOpen { get; } + SectorCount = (int)BitUtil.DivideUp(baseSize, SectorSize); + Length = baseSize; - public SectorStorage(IStorage baseStorage, int sectorSize, bool leaveOpen) + LeaveOpen = leaveOpen; + } + + protected override Result DoRead(long offset, Span destination) + { + ValidateSize(destination.Length, offset); + return BaseStorage.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + ValidateSize(source.Length, offset); + return BaseStorage.Write(offset, source); + } + + protected override Result DoFlush() + { + return BaseStorage.Flush(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + Result rc = BaseStorage.SetSize(size); + if (rc.IsFailure()) return rc; + + rc = BaseStorage.GetSize(out long newSize); + if (rc.IsFailure()) return rc; + + SectorCount = (int)BitUtil.DivideUp(newSize, SectorSize); + Length = newSize; + + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) { - BaseStorage = baseStorage; - SectorSize = sectorSize; - - baseStorage.GetSize(out long baseSize).ThrowIfFailure(); - - SectorCount = (int)BitUtil.DivideUp(baseSize, SectorSize); - Length = baseSize; - - LeaveOpen = leaveOpen; - } - - protected override Result DoRead(long offset, Span destination) - { - ValidateSize(destination.Length, offset); - return BaseStorage.Read(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - ValidateSize(source.Length, offset); - return BaseStorage.Write(offset, source); - } - - protected override Result DoFlush() - { - return BaseStorage.Flush(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - protected override Result DoSetSize(long size) - { - Result rc = BaseStorage.SetSize(size); - if (rc.IsFailure()) return rc; - - rc = BaseStorage.GetSize(out long newSize); - if (rc.IsFailure()) return rc; - - SectorCount = (int)BitUtil.DivideUp(newSize, SectorSize); - Length = newSize; - - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); - } - } - - /// - /// Validates that the size is a multiple of the sector size - /// - protected void ValidateSize(long size, long offset) - { - if (size < 0) - throw new ArgumentException("Size must be non-negative"); - if (offset < 0) - throw new ArgumentException("Offset must be non-negative"); - if (offset % SectorSize != 0) - throw new ArgumentException($"Offset must be a multiple of {SectorSize}"); + BaseStorage?.Dispose(); } } + + /// + /// Validates that the size is a multiple of the sector size + /// + protected void ValidateSize(long size, long offset) + { + if (size < 0) + throw new ArgumentException("Size must be non-negative"); + if (offset < 0) + throw new ArgumentException("Offset must be non-negative"); + if (offset % SectorSize != 0) + throw new ArgumentException($"Offset must be a multiple of {SectorSize}"); + } } diff --git a/src/LibHac/FsSystem/SemaphoreAdapter.cs b/src/LibHac/FsSystem/SemaphoreAdapter.cs index 46f94dcf..4995b860 100644 --- a/src/LibHac/FsSystem/SemaphoreAdapter.cs +++ b/src/LibHac/FsSystem/SemaphoreAdapter.cs @@ -2,35 +2,34 @@ using System.Threading; using LibHac.Os; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class SemaphoreAdapter : IDisposable, ILockable { - public class SemaphoreAdapter : IDisposable, ILockable + private SemaphoreSlim _semaphore; + + public SemaphoreAdapter(int initialCount, int maxCount) { - private SemaphoreSlim _semaphore; + _semaphore = new SemaphoreSlim(initialCount, maxCount); + } - public SemaphoreAdapter(int initialCount, int maxCount) - { - _semaphore = new SemaphoreSlim(initialCount, maxCount); - } + public bool TryLock() + { + return _semaphore.Wait(System.TimeSpan.Zero); + } - public bool TryLock() - { - return _semaphore.Wait(System.TimeSpan.Zero); - } + public void Lock() + { + _semaphore.Wait(); + } - public void Lock() - { - _semaphore.Wait(); - } + public void Unlock() + { + _semaphore.Release(); + } - public void Unlock() - { - _semaphore.Release(); - } - - public void Dispose() - { - _semaphore?.Dispose(); - } + public void Dispose() + { + _semaphore?.Dispose(); } } diff --git a/src/LibHac/FsSystem/StorageExtensions.cs b/src/LibHac/FsSystem/StorageExtensions.cs index 3b929a70..71f763d1 100644 --- a/src/LibHac/FsSystem/StorageExtensions.cs +++ b/src/LibHac/FsSystem/StorageExtensions.cs @@ -6,219 +6,218 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public static class StorageExtensions { - public static class StorageExtensions + public static IStorage Slice(this IStorage storage, long start) { - public static IStorage Slice(this IStorage storage, long start) + storage.GetSize(out long length).ThrowIfFailure(); + + if (length == -1) { - storage.GetSize(out long length).ThrowIfFailure(); - - if (length == -1) - { - return storage.Slice(start, length); - } - - return storage.Slice(start, length - start); + return storage.Slice(start, length); } - public static IStorage Slice(this IStorage storage, long start, long length) + return storage.Slice(start, length - start); + } + + public static IStorage Slice(this IStorage storage, long start, long length) + { + return storage.Slice(start, length, true); + } + + public static IStorage Slice(this IStorage storage, long start, long length, bool leaveOpen) + { + if (!leaveOpen) { - return storage.Slice(start, length, true); + return new SubStorage(storage, start, length); } - public static IStorage Slice(this IStorage storage, long start, long length, bool leaveOpen) + using (var sharedStorage = new SharedRef(storage)) { - if (!leaveOpen) - { - return new SubStorage(storage, start, length); - } - - using (var sharedStorage = new SharedRef(storage)) - { - return new SubStorage(in sharedStorage, start, length); - } - } - - public static Stream AsStream(this IStorage storage) => new StorageStream(storage, FileAccess.ReadWrite, true); - public static Stream AsStream(this IStorage storage, FileAccess access) => new StorageStream(storage, access, true); - public static Stream AsStream(this IStorage storage, FileAccess access, bool keepOpen) => new StorageStream(storage, access, keepOpen); - - public static IFile AsFile(this IStorage storage, OpenMode mode) => new StorageFile(storage, mode); - - public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null, int bufferSize = 81920) - { - input.GetSize(out long inputSize).ThrowIfFailure(); - output.GetSize(out long outputSize).ThrowIfFailure(); - - long remaining = Math.Min(inputSize, outputSize); - if (remaining < 0) throw new ArgumentException("Storage must have an explicit length"); - progress?.SetTotal(remaining); - - long pos = 0; - - using var buffer = new RentedArray(bufferSize); - int rentedBufferSize = buffer.Array.Length; - - while (remaining > 0) - { - int toCopy = (int)Math.Min(rentedBufferSize, remaining); - Span buf = buffer.Array.AsSpan(0, toCopy); - input.Read(pos, buf); - output.Write(pos, buf); - - remaining -= toCopy; - pos += toCopy; - - progress?.ReportAdd(toCopy); - } - - progress?.SetTotal(0); - } - - public static void Fill(this IStorage input, byte value, IProgressReport progress = null) - { - input.GetSize(out long inputSize).ThrowIfFailure(); - input.Fill(value, 0, inputSize, progress); - } - - public static void Fill(this IStorage input, byte value, long offset, long count, IProgressReport progress = null) - { - const int threshold = 0x400; - - if (count > threshold) - { - input.FillLarge(value, offset, count, progress); - return; - } - - Span buf = stackalloc byte[(int)count]; - buf.Fill(value); - - input.Write(offset, buf); - } - - private static void FillLarge(this IStorage input, byte value, long offset, long count, IProgressReport progress = null) - { - const int bufferSize = 0x4000; - - long remaining = count; - if (remaining < 0) throw new ArgumentException("Storage must have an explicit length"); - progress?.SetTotal(remaining); - - long pos = offset; - - using var buffer = new RentedArray(bufferSize); - int rentedBufferSize = buffer.Array.Length; - - buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value); - - while (remaining > 0) - { - int toFill = (int)Math.Min(rentedBufferSize, remaining); - Span buf = buffer.Array.AsSpan(0, toFill); - - input.Write(pos, buf); - - remaining -= toFill; - pos += toFill; - - progress?.ReportAdd(toFill); - } - - progress?.SetTotal(0); - } - - public static void WriteAllBytes(this IStorage input, string filename, IProgressReport progress = null) - { - input.GetSize(out long inputSize).ThrowIfFailure(); - - using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write)) - { - input.CopyToStream(outFile, inputSize, progress); - } - } - - public static byte[] ToArray(this IStorage storage) - { - if (storage == null) return new byte[0]; - - storage.GetSize(out long storageSize).ThrowIfFailure(); - - byte[] arr = new byte[storageSize]; - storage.CopyTo(new MemoryStorage(arr)); - return arr; - } - - public static T[] ToArray(this IStorage storage) where T : unmanaged - { - if (storage == null) return new T[0]; - - storage.GetSize(out long storageSize).ThrowIfFailure(); - - var arr = new T[storageSize / Unsafe.SizeOf()]; - Span dest = MemoryMarshal.Cast(arr.AsSpan()); - - storage.Read(0, dest); - return arr; - } - - public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null, int bufferSize = 0x8000) - { - long remaining = length; - long inOffset = 0; - - using var buffer = new RentedArray(bufferSize); - int rentedBufferSize = buffer.Array.Length; - - progress?.SetTotal(length); - - while (remaining > 0) - { - int toWrite = (int)Math.Min(rentedBufferSize, remaining); - input.Read(inOffset, buffer.Array.AsSpan(0, toWrite)); - - output.Write(buffer.Array, 0, toWrite); - remaining -= toWrite; - inOffset += toWrite; - progress?.ReportAdd(toWrite); - } - } - - public static void CopyToStream(this IStorage input, Stream output, int bufferSize = 0x8000) - { - input.GetSize(out long inputSize).ThrowIfFailure(); - CopyToStream(input, output, inputSize, bufferSize: bufferSize); - } - - public static IStorage AsStorage(this Stream stream) - { - if (stream == null) return null; - return new StreamStorage(stream, true); - } - - public static IStorage AsStorage(this Stream stream, bool keepOpen) - { - if (stream == null) return null; - return new StreamStorage(stream, keepOpen); - } - - public static IStorage AsStorage(this Stream stream, long start) - { - if (stream == null) return null; - return new StreamStorage(stream, true).Slice(start); - } - - public static IStorage AsStorage(this Stream stream, long start, int length) - { - if (stream == null) return null; - return new StreamStorage(stream, true).Slice(start, length); - } - - public static IStorage AsStorage(this Stream stream, long start, int length, bool keepOpen) - { - if (stream == null) return null; - return new StreamStorage(stream, keepOpen).Slice(start, length); + return new SubStorage(in sharedStorage, start, length); } } + + public static Stream AsStream(this IStorage storage) => new StorageStream(storage, FileAccess.ReadWrite, true); + public static Stream AsStream(this IStorage storage, FileAccess access) => new StorageStream(storage, access, true); + public static Stream AsStream(this IStorage storage, FileAccess access, bool keepOpen) => new StorageStream(storage, access, keepOpen); + + public static IFile AsFile(this IStorage storage, OpenMode mode) => new StorageFile(storage, mode); + + public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null, int bufferSize = 81920) + { + input.GetSize(out long inputSize).ThrowIfFailure(); + output.GetSize(out long outputSize).ThrowIfFailure(); + + long remaining = Math.Min(inputSize, outputSize); + if (remaining < 0) throw new ArgumentException("Storage must have an explicit length"); + progress?.SetTotal(remaining); + + long pos = 0; + + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + + while (remaining > 0) + { + int toCopy = (int)Math.Min(rentedBufferSize, remaining); + Span buf = buffer.Array.AsSpan(0, toCopy); + input.Read(pos, buf); + output.Write(pos, buf); + + remaining -= toCopy; + pos += toCopy; + + progress?.ReportAdd(toCopy); + } + + progress?.SetTotal(0); + } + + public static void Fill(this IStorage input, byte value, IProgressReport progress = null) + { + input.GetSize(out long inputSize).ThrowIfFailure(); + input.Fill(value, 0, inputSize, progress); + } + + public static void Fill(this IStorage input, byte value, long offset, long count, IProgressReport progress = null) + { + const int threshold = 0x400; + + if (count > threshold) + { + input.FillLarge(value, offset, count, progress); + return; + } + + Span buf = stackalloc byte[(int)count]; + buf.Fill(value); + + input.Write(offset, buf); + } + + private static void FillLarge(this IStorage input, byte value, long offset, long count, IProgressReport progress = null) + { + const int bufferSize = 0x4000; + + long remaining = count; + if (remaining < 0) throw new ArgumentException("Storage must have an explicit length"); + progress?.SetTotal(remaining); + + long pos = offset; + + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + + buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value); + + while (remaining > 0) + { + int toFill = (int)Math.Min(rentedBufferSize, remaining); + Span buf = buffer.Array.AsSpan(0, toFill); + + input.Write(pos, buf); + + remaining -= toFill; + pos += toFill; + + progress?.ReportAdd(toFill); + } + + progress?.SetTotal(0); + } + + public static void WriteAllBytes(this IStorage input, string filename, IProgressReport progress = null) + { + input.GetSize(out long inputSize).ThrowIfFailure(); + + using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write)) + { + input.CopyToStream(outFile, inputSize, progress); + } + } + + public static byte[] ToArray(this IStorage storage) + { + if (storage == null) return new byte[0]; + + storage.GetSize(out long storageSize).ThrowIfFailure(); + + byte[] arr = new byte[storageSize]; + storage.CopyTo(new MemoryStorage(arr)); + return arr; + } + + public static T[] ToArray(this IStorage storage) where T : unmanaged + { + if (storage == null) return new T[0]; + + storage.GetSize(out long storageSize).ThrowIfFailure(); + + var arr = new T[storageSize / Unsafe.SizeOf()]; + Span dest = MemoryMarshal.Cast(arr.AsSpan()); + + storage.Read(0, dest); + return arr; + } + + public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null, int bufferSize = 0x8000) + { + long remaining = length; + long inOffset = 0; + + using var buffer = new RentedArray(bufferSize); + int rentedBufferSize = buffer.Array.Length; + + progress?.SetTotal(length); + + while (remaining > 0) + { + int toWrite = (int)Math.Min(rentedBufferSize, remaining); + input.Read(inOffset, buffer.Array.AsSpan(0, toWrite)); + + output.Write(buffer.Array, 0, toWrite); + remaining -= toWrite; + inOffset += toWrite; + progress?.ReportAdd(toWrite); + } + } + + public static void CopyToStream(this IStorage input, Stream output, int bufferSize = 0x8000) + { + input.GetSize(out long inputSize).ThrowIfFailure(); + CopyToStream(input, output, inputSize, bufferSize: bufferSize); + } + + public static IStorage AsStorage(this Stream stream) + { + if (stream == null) return null; + return new StreamStorage(stream, true); + } + + public static IStorage AsStorage(this Stream stream, bool keepOpen) + { + if (stream == null) return null; + return new StreamStorage(stream, keepOpen); + } + + public static IStorage AsStorage(this Stream stream, long start) + { + if (stream == null) return null; + return new StreamStorage(stream, true).Slice(start); + } + + public static IStorage AsStorage(this Stream stream, long start, int length) + { + if (stream == null) return null; + return new StreamStorage(stream, true).Slice(start, length); + } + + public static IStorage AsStorage(this Stream stream, long start, int length, bool keepOpen) + { + if (stream == null) return null; + return new StreamStorage(stream, keepOpen).Slice(start, length); + } } diff --git a/src/LibHac/FsSystem/StorageFile.cs b/src/LibHac/FsSystem/StorageFile.cs index c9b28a61..f622791d 100644 --- a/src/LibHac/FsSystem/StorageFile.cs +++ b/src/LibHac/FsSystem/StorageFile.cs @@ -3,87 +3,86 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class StorageFile : IFile { - public class StorageFile : IFile + private IStorage BaseStorage { get; } + private OpenMode Mode { get; } + + public StorageFile(IStorage baseStorage, OpenMode mode) { - private IStorage BaseStorage { get; } - private OpenMode Mode { get; } + BaseStorage = baseStorage; + Mode = mode; + } - public StorageFile(IStorage baseStorage, OpenMode mode) + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); + + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (toRead == 0) { - BaseStorage = baseStorage; - Mode = mode; - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); - - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - if (toRead == 0) - { - bytesRead = 0; - return Result.Success; - } - - rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); - if (rc.IsFailure()) return rc; - - bytesRead = toRead; + bytesRead = 0; return Result.Success; } - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + rc = BaseStorage.Read(offset, destination.Slice(0, (int)toRead)); + if (rc.IsFailure()) return rc; + + bytesRead = toRead; + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + if (isResizeNeeded) { - Result rc = DryWrite(out bool isResizeNeeded, offset, source.Length, in option, Mode); + rc = DoSetSize(offset + source.Length); if (rc.IsFailure()) return rc; + } - if (isResizeNeeded) - { - rc = DoSetSize(offset + source.Length); - if (rc.IsFailure()) return rc; - } + rc = BaseStorage.Write(offset, source); + if (rc.IsFailure()) return rc; - rc = BaseStorage.Write(offset, source); - if (rc.IsFailure()) return rc; + if (option.HasFlushFlag()) + { + return Flush(); + } - if (option.HasFlushFlag()) - { - return Flush(); - } + return Result.Success; + } + protected override Result DoFlush() + { + if (!Mode.HasFlag(OpenMode.Write)) return Result.Success; - } - protected override Result DoFlush() - { - if (!Mode.HasFlag(OpenMode.Write)) - return Result.Success; + return BaseStorage.Flush(); + } - return BaseStorage.Flush(); - } + protected override Result DoGetSize(out long size) + { + return BaseStorage.GetSize(out size); + } - protected override Result DoGetSize(out long size) - { - return BaseStorage.GetSize(out size); - } + protected override Result DoSetSize(long size) + { + if (!Mode.HasFlag(OpenMode.Write)) + return ResultFs.WriteUnpermitted.Log(); - protected override Result DoSetSize(long size) - { - if (!Mode.HasFlag(OpenMode.Write)) - return ResultFs.WriteUnpermitted.Log(); + return BaseStorage.SetSize(size); + } - return BaseStorage.SetSize(size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - return ResultFs.NotImplemented.Log(); - } + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); } } diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs index 4381fbed..324a8e50 100644 --- a/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs @@ -4,353 +4,352 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +[Flags] +internal enum StorageType { - [Flags] - internal enum StorageType + Bis = 1 << 0, + SdCard = 1 << 1, + GameCard = 1 << 2, + Usb = 1 << 3, + + NonGameCard = Bis | SdCard | Usb, + All = Bis | SdCard | GameCard | Usb +} + +internal static class StorageLayoutType +{ + public static bool IsStorageFlagValid(StorageType storageFlag) { - Bis = 1 << 0, - SdCard = 1 << 1, - GameCard = 1 << 2, - Usb = 1 << 3, - - NonGameCard = Bis | SdCard | Usb, - All = Bis | SdCard | GameCard | Usb - } - - internal static class StorageLayoutType - { - public static bool IsStorageFlagValid(StorageType storageFlag) - { - return storageFlag != 0; - } - } - - internal struct ScopedStorageLayoutTypeSetter : IDisposable - { - // ReSharper disable once UnusedParameter.Local - public ScopedStorageLayoutTypeSetter(StorageType storageFlag) - { - // Todo: Implement - } - - public void Dispose() - { - - } - } - - internal class StorageLayoutTypeSetStorage : IStorage - { - private SharedRef _baseStorage; - private StorageType _storageFlag; - - public StorageLayoutTypeSetStorage(ref SharedRef baseStorage, StorageType storageFlag) - { - _baseStorage = SharedRef.CreateMove(ref baseStorage); - _storageFlag = storageFlag; - - Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); - } - - public override void Dispose() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - _baseStorage.Destroy(); - - base.Dispose(); - } - - protected override Result DoRead(long offset, Span destination) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.Read(offset, destination); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.Write(offset, source); - } - - protected override Result DoFlush() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.Flush(); - } - - protected override Result DoSetSize(long size) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } - } - - internal class StorageLayoutTypeSetFile : IFile - { - private IFile _baseFile; - private UniqueRef _baseFileUnique; - private SharedRef _baseFileShared; - private StorageType _storageFlag; - - public StorageLayoutTypeSetFile(ref UniqueRef baseFile, StorageType storageFlag) - { - _baseFile = baseFile.Get; - _baseFileUnique = new UniqueRef(ref baseFile); - _storageFlag = storageFlag; - - Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); - } - - public StorageLayoutTypeSetFile(ref SharedRef baseFile, StorageType storageFlag) - { - _baseFile = baseFile.Get; - _baseFileShared = SharedRef.CreateMove(ref baseFile); - _storageFlag = storageFlag; - - Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); - } - - public override void Dispose() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - - _baseFile = null; - _baseFileUnique.Destroy(); - _baseFileShared.Destroy(); - - base.Dispose(); - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.Read(out bytesRead, offset, destination, in option); - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.Write(offset, source, in option); - } - - protected override Result DoFlush() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.Flush(); - } - - protected override Result DoSetSize(long size) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.SetSize(size); - } - - protected override Result DoGetSize(out long size) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.GetSize(out size); - } - - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); - } - } - - internal class StorageLayoutTypeSetDirectory : IDirectory - { - private UniqueRef _baseDirectory; - private StorageType _storageFlag; - - public StorageLayoutTypeSetDirectory(ref UniqueRef baseDirectory, StorageType storageFlag) - { - _baseDirectory = new UniqueRef(ref baseDirectory); - _storageFlag = storageFlag; - } - - public override void Dispose() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - _baseDirectory.Destroy(); - - base.Dispose(); - } - - protected override Result DoRead(out long entriesRead, Span entryBuffer) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseDirectory.Get.Read(out entriesRead, entryBuffer); - } - - protected override Result DoGetEntryCount(out long entryCount) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseDirectory.Get.GetEntryCount(out entryCount); - } - } - - internal class StorageLayoutTypeSetFileSystem : IFileSystem - { - private SharedRef _baseFileSystem; - private StorageType _storageFlag; - - public StorageLayoutTypeSetFileSystem(ref SharedRef baseFileSystem, StorageType storageFlag) - { - _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - _storageFlag = storageFlag; - - Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); - } - - public override void Dispose() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - _baseFileSystem.Destroy(); - base.Dispose(); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.CreateFile(path, size, option); - } - - protected override Result DoDeleteFile(in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.DeleteFile(path); - } - - protected override Result DoCreateDirectory(in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.CreateDirectory(path); - } - - protected override Result DoDeleteDirectory(in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.DeleteDirectory(path); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.DeleteDirectoryRecursively(path); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.CleanDirectoryRecursively(path); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.RenameFile(currentPath, newPath); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.RenameDirectory(currentPath, newPath); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.GetEntryType(out entryType, path); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path); - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - using var baseFile = new UniqueRef(); - - Result rc = _baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); - if (rc.IsFailure()) return rc; - - outFile.Reset(new StorageLayoutTypeSetFile(ref baseFile.Ref(), _storageFlag)); - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - using var baseDirectory = new UniqueRef(); - - Result rc = _baseFileSystem.Get.OpenDirectory(ref baseDirectory.Ref(), in path, mode); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new StorageLayoutTypeSetDirectory(ref baseDirectory.Ref(), _storageFlag)); - return Result.Success; - } - - protected override Result DoCommit() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.Commit(); - } - - protected override Result DoCommitProvisionally(long counter) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.CommitProvisionally(counter); - } - - protected override Result DoRollback() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.Rollback(); - } - - protected override Result DoFlush() - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.Flush(); - } - - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path); - } - - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); - return _baseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, path); - } + return storageFlag != 0; + } +} + +internal struct ScopedStorageLayoutTypeSetter : IDisposable +{ + // ReSharper disable once UnusedParameter.Local + public ScopedStorageLayoutTypeSetter(StorageType storageFlag) + { + // Todo: Implement + } + + public void Dispose() + { + + } +} + +internal class StorageLayoutTypeSetStorage : IStorage +{ + private SharedRef _baseStorage; + private StorageType _storageFlag; + + public StorageLayoutTypeSetStorage(ref SharedRef baseStorage, StorageType storageFlag) + { + _baseStorage = SharedRef.CreateMove(ref baseStorage); + _storageFlag = storageFlag; + + Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); + } + + public override void Dispose() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + _baseStorage.Destroy(); + + base.Dispose(); + } + + protected override Result DoRead(long offset, Span destination) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.Write(offset, source); + } + + protected override Result DoFlush() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.Flush(); + } + + protected override Result DoSetSize(long size) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + } +} + +internal class StorageLayoutTypeSetFile : IFile +{ + private IFile _baseFile; + private UniqueRef _baseFileUnique; + private SharedRef _baseFileShared; + private StorageType _storageFlag; + + public StorageLayoutTypeSetFile(ref UniqueRef baseFile, StorageType storageFlag) + { + _baseFile = baseFile.Get; + _baseFileUnique = new UniqueRef(ref baseFile); + _storageFlag = storageFlag; + + Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); + } + + public StorageLayoutTypeSetFile(ref SharedRef baseFile, StorageType storageFlag) + { + _baseFile = baseFile.Get; + _baseFileShared = SharedRef.CreateMove(ref baseFile); + _storageFlag = storageFlag; + + Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); + } + + public override void Dispose() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + + _baseFile = null; + _baseFileUnique.Destroy(); + _baseFileShared.Destroy(); + + base.Dispose(); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.Read(out bytesRead, offset, destination, in option); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.Write(offset, source, in option); + } + + protected override Result DoFlush() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.Flush(); + } + + protected override Result DoSetSize(long size) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); + } +} + +internal class StorageLayoutTypeSetDirectory : IDirectory +{ + private UniqueRef _baseDirectory; + private StorageType _storageFlag; + + public StorageLayoutTypeSetDirectory(ref UniqueRef baseDirectory, StorageType storageFlag) + { + _baseDirectory = new UniqueRef(ref baseDirectory); + _storageFlag = storageFlag; + } + + public override void Dispose() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + _baseDirectory.Destroy(); + + base.Dispose(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseDirectory.Get.Read(out entriesRead, entryBuffer); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseDirectory.Get.GetEntryCount(out entryCount); + } +} + +internal class StorageLayoutTypeSetFileSystem : IFileSystem +{ + private SharedRef _baseFileSystem; + private StorageType _storageFlag; + + public StorageLayoutTypeSetFileSystem(ref SharedRef baseFileSystem, StorageType storageFlag) + { + _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); + _storageFlag = storageFlag; + + Assert.SdkAssert(StorageLayoutType.IsStorageFlagValid(storageFlag)); + } + + public override void Dispose() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + _baseFileSystem.Destroy(); + base.Dispose(); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.CreateFile(path, size, option); + } + + protected override Result DoDeleteFile(in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.DeleteFile(path); + } + + protected override Result DoCreateDirectory(in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.CreateDirectory(path); + } + + protected override Result DoDeleteDirectory(in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.DeleteDirectory(path); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.DeleteDirectoryRecursively(path); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.CleanDirectoryRecursively(path); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.RenameFile(currentPath, newPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.RenameDirectory(currentPath, newPath); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.GetEntryType(out entryType, path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + using var baseFile = new UniqueRef(); + + Result rc = _baseFileSystem.Get.OpenFile(ref baseFile.Ref(), in path, mode); + if (rc.IsFailure()) return rc; + + outFile.Reset(new StorageLayoutTypeSetFile(ref baseFile.Ref(), _storageFlag)); + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + using var baseDirectory = new UniqueRef(); + + Result rc = _baseFileSystem.Get.OpenDirectory(ref baseDirectory.Ref(), in path, mode); + if (rc.IsFailure()) return rc; + + outDirectory.Reset(new StorageLayoutTypeSetDirectory(ref baseDirectory.Ref(), _storageFlag)); + return Result.Success; + } + + protected override Result DoCommit() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.CommitProvisionally(counter); + } + + protected override Result DoRollback() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.Rollback(); + } + + protected override Result DoFlush() + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.Flush(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + using var scopedContext = new ScopedStorageLayoutTypeSetter(_storageFlag); + return _baseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, path); } } diff --git a/src/LibHac/FsSystem/StorageStream.cs b/src/LibHac/FsSystem/StorageStream.cs index 0f900f70..19bd6174 100644 --- a/src/LibHac/FsSystem/StorageStream.cs +++ b/src/LibHac/FsSystem/StorageStream.cs @@ -2,80 +2,79 @@ using System.IO; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class StorageStream : Stream { - public class StorageStream : Stream + private IStorage BaseStorage { get; } + private bool LeaveOpen { get; } + private long _length; + + public StorageStream(IStorage baseStorage, FileAccess access, bool leaveOpen) { - private IStorage BaseStorage { get; } - private bool LeaveOpen { get; } - private long _length; + BaseStorage = baseStorage; + LeaveOpen = leaveOpen; - public StorageStream(IStorage baseStorage, FileAccess access, bool leaveOpen) + baseStorage.GetSize(out _length).ThrowIfFailure(); + + CanRead = access.HasFlag(FileAccess.Read); + CanWrite = access.HasFlag(FileAccess.Write); + } + + public override int Read(byte[] buffer, int offset, int count) + { + int toRead = (int)Math.Min(count, Length - Position); + BaseStorage.Read(Position, buffer.AsSpan(offset, toRead)).ThrowIfFailure(); + + Position += toRead; + return toRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + BaseStorage.Write(Position, buffer.AsSpan(offset, count)).ThrowIfFailure(); + Position += count; + } + + public override void Flush() + { + BaseStorage.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) { - BaseStorage = baseStorage; - LeaveOpen = leaveOpen; - - baseStorage.GetSize(out _length).ThrowIfFailure(); - - CanRead = access.HasFlag(FileAccess.Read); - CanWrite = access.HasFlag(FileAccess.Write); + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + case SeekOrigin.End: + Position = Length - offset; + break; } - public override int Read(byte[] buffer, int offset, int count) - { - int toRead = (int)Math.Min(count, Length - Position); - BaseStorage.Read(Position, buffer.AsSpan(offset, toRead)).ThrowIfFailure(); + return Position; + } - Position += toRead; - return toRead; - } + public override void SetLength(long value) + { + BaseStorage.SetSize(value).ThrowIfFailure(); - public override void Write(byte[] buffer, int offset, int count) - { - BaseStorage.Write(Position, buffer.AsSpan(offset, count)).ThrowIfFailure(); - Position += count; - } + BaseStorage.GetSize(out _length).ThrowIfFailure(); + } - public override void Flush() - { - BaseStorage.Flush(); - } + public override bool CanRead { get; } + public override bool CanSeek => true; + public override bool CanWrite { get; } + public override long Length => _length; + public override long Position { get; set; } - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - case SeekOrigin.End: - Position = Length - offset; - break; - } - - return Position; - } - - public override void SetLength(long value) - { - BaseStorage.SetSize(value).ThrowIfFailure(); - - BaseStorage.GetSize(out _length).ThrowIfFailure(); - } - - public override bool CanRead { get; } - public override bool CanSeek => true; - public override bool CanWrite { get; } - public override long Length => _length; - public override long Position { get; set; } - - protected override void Dispose(bool disposing) - { - if (!LeaveOpen) BaseStorage?.Dispose(); - base.Dispose(disposing); - } + protected override void Dispose(bool disposing) + { + if (!LeaveOpen) BaseStorage?.Dispose(); + base.Dispose(disposing); } } diff --git a/src/LibHac/FsSystem/StreamFile.cs b/src/LibHac/FsSystem/StreamFile.cs index 1e2e80db..9c670e04 100644 --- a/src/LibHac/FsSystem/StreamFile.cs +++ b/src/LibHac/FsSystem/StreamFile.cs @@ -4,95 +4,94 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// Provides an interface for interacting with a +/// +public class StreamFile : IFile { - /// - /// Provides an interface for interacting with a - /// - public class StreamFile : IFile + // todo: handle Stream exceptions + + private OpenMode Mode { get; } + private Stream BaseStream { get; } + private object Locker { get; } = new object(); + + public StreamFile(Stream baseStream, OpenMode mode) { - // todo: handle Stream exceptions + BaseStream = baseStream; + Mode = mode; + } - private OpenMode Mode { get; } - private Stream BaseStream { get; } - private object Locker { get; } = new object(); + protected override Result DoRead(out long bytesRead, long offset, Span destination, + in ReadOption option) + { + UnsafeHelpers.SkipParamInit(out bytesRead); - public StreamFile(Stream baseStream, OpenMode mode) + Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + lock (Locker) { - BaseStream = baseStream; - Mode = mode; - } - - protected override Result DoRead(out long bytesRead, long offset, Span destination, - in ReadOption option) - { - UnsafeHelpers.SkipParamInit(out bytesRead); - - Result rc = DryRead(out long toRead, offset, destination.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - lock (Locker) - { - if (BaseStream.Position != offset) - { - BaseStream.Position = offset; - } - - bytesRead = BaseStream.Read(destination.Slice(0, (int)toRead)); - return Result.Success; - } - } - - protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) - { - Result rc = DryWrite(out _, offset, source.Length, in option, Mode); - if (rc.IsFailure()) return rc; - - lock (Locker) + if (BaseStream.Position != offset) { BaseStream.Position = offset; - BaseStream.Write(source); - } - - if (option.HasFlushFlag()) - { - return Flush(); } + bytesRead = BaseStream.Read(destination.Slice(0, (int)toRead)); return Result.Success; } + } - protected override Result DoFlush() + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + Result rc = DryWrite(out _, offset, source.Length, in option, Mode); + if (rc.IsFailure()) return rc; + + lock (Locker) { - lock (Locker) - { - BaseStream.Flush(); - return Result.Success; - } + BaseStream.Position = offset; + BaseStream.Write(source); } - protected override Result DoGetSize(out long size) + if (option.HasFlushFlag()) { - lock (Locker) - { - size = BaseStream.Length; - return Result.Success; - } + return Flush(); } - protected override Result DoSetSize(long size) - { - lock (Locker) - { - BaseStream.SetLength(size); - return Result.Success; - } - } + return Result.Success; + } - protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) + protected override Result DoFlush() + { + lock (Locker) { - return ResultFs.NotImplemented.Log(); + BaseStream.Flush(); + return Result.Success; } } + + protected override Result DoGetSize(out long size) + { + lock (Locker) + { + size = BaseStream.Length; + return Result.Success; + } + } + + protected override Result DoSetSize(long size) + { + lock (Locker) + { + BaseStream.SetLength(size); + return Result.Success; + } + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } } diff --git a/src/LibHac/FsSystem/StreamStorage.cs b/src/LibHac/FsSystem/StreamStorage.cs index 569e9d21..de859971 100644 --- a/src/LibHac/FsSystem/StreamStorage.cs +++ b/src/LibHac/FsSystem/StreamStorage.cs @@ -2,81 +2,80 @@ using System.IO; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +public class StreamStorage : IStorage { - public class StreamStorage : IStorage + // todo: handle Stream exceptions + + private Stream BaseStream { get; } + private object Locker { get; } = new object(); + private long Length { get; } + private bool LeaveOpen { get; } + + public StreamStorage(Stream baseStream, bool leaveOpen) { - // todo: handle Stream exceptions + BaseStream = baseStream; + Length = BaseStream.Length; + LeaveOpen = leaveOpen; + } - private Stream BaseStream { get; } - private object Locker { get; } = new object(); - private long Length { get; } - private bool LeaveOpen { get; } - - public StreamStorage(Stream baseStream, bool leaveOpen) + protected override Result DoRead(long offset, Span destination) + { + lock (Locker) { - BaseStream = baseStream; - Length = BaseStream.Length; - LeaveOpen = leaveOpen; + if (BaseStream.Position != offset) + { + BaseStream.Position = offset; + } + + BaseStream.Read(destination); } - protected override Result DoRead(long offset, Span destination) - { - lock (Locker) - { - if (BaseStream.Position != offset) - { - BaseStream.Position = offset; - } + return Result.Success; + } - BaseStream.Read(destination); + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + lock (Locker) + { + if (BaseStream.Position != offset) + { + BaseStream.Position = offset; } + BaseStream.Write(source); + } + + return Result.Success; + } + + protected override Result DoFlush() + { + lock (Locker) + { + BaseStream.Flush(); + return Result.Success; } + } - protected override Result DoWrite(long offset, ReadOnlySpan source) + protected override Result DoSetSize(long size) + { + return ResultFs.NotImplemented.Log(); + } + + protected override Result DoGetSize(out long size) + { + size = Length; + return Result.Success; + } + + public override void Dispose() + { + if (!LeaveOpen) { - lock (Locker) - { - if (BaseStream.Position != offset) - { - BaseStream.Position = offset; - } - - BaseStream.Write(source); - } - - return Result.Success; - } - - protected override Result DoFlush() - { - lock (Locker) - { - BaseStream.Flush(); - - return Result.Success; - } - } - - protected override Result DoSetSize(long size) - { - return ResultFs.NotImplemented.Log(); - } - - protected override Result DoGetSize(out long size) - { - size = Length; - return Result.Success; - } - - public override void Dispose() - { - if (!LeaveOpen) - { - BaseStream?.Dispose(); - } + BaseStream?.Dispose(); } } } diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index df20492c..7f39d404 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -3,257 +3,256 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// An that uses a directory of another as its root directory. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +public class SubdirectoryFileSystem : IFileSystem { - /// - /// An that uses a directory of another as its root directory. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - public class SubdirectoryFileSystem : IFileSystem + private IFileSystem _baseFileSystem; + private SharedRef _baseFileSystemShared; + private Path.Stored _rootPath; + + public SubdirectoryFileSystem(IFileSystem baseFileSystem) { - private IFileSystem _baseFileSystem; - private SharedRef _baseFileSystemShared; - private Path.Stored _rootPath; + _baseFileSystem = baseFileSystem; + } - public SubdirectoryFileSystem(IFileSystem baseFileSystem) - { - _baseFileSystem = baseFileSystem; - } + public SubdirectoryFileSystem(ref SharedRef baseFileSystem) + { + _baseFileSystemShared = SharedRef.CreateMove(ref baseFileSystem); + _baseFileSystem = _baseFileSystemShared.Get; + } - public SubdirectoryFileSystem(ref SharedRef baseFileSystem) - { - _baseFileSystemShared = SharedRef.CreateMove(ref baseFileSystem); - _baseFileSystem = _baseFileSystemShared.Get; - } + public override void Dispose() + { + _baseFileSystemShared.Destroy(); + base.Dispose(); + } - public override void Dispose() - { - _baseFileSystemShared.Destroy(); - base.Dispose(); - } + public Result Initialize(in Path rootPath) + { + return _rootPath.Initialize(in rootPath); + } - public Result Initialize(in Path rootPath) - { - return _rootPath.Initialize(in rootPath); - } + private Result ResolveFullPath(ref Path outPath, in Path relativePath) + { + using Path rootPath = _rootPath.DangerousGetPath(); + return outPath.Combine(in rootPath, in relativePath); + } - private Result ResolveFullPath(ref Path outPath, in Path relativePath) - { - using Path rootPath = _rootPath.DangerousGetPath(); - return outPath.Combine(in rootPath, in relativePath); - } + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + UnsafeHelpers.SkipParamInit(out entryType); - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - UnsafeHelpers.SkipParamInit(out entryType); + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.GetEntryType(out entryType, in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.GetEntryType(out entryType, in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.GetFreeSpaceSize(out freeSpace, in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.GetFreeSpaceSize(out freeSpace, in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.GetTotalSpaceSize(out totalSpace, in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.GetTotalSpaceSize(out totalSpace, in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); - protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.GetFileTimeStampRaw(out timeStamp, in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.GetFileTimeStampRaw(out timeStamp, in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.OpenFile(ref outFile, in fullPath, mode); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.OpenFile(ref outFile, in fullPath, mode); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.OpenDirectory(ref outDirectory, in fullPath, mode); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.OpenDirectory(ref outDirectory, in fullPath, mode); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.CreateFile(in fullPath, size, option); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.CreateFile(in fullPath, size, option); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoDeleteFile(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoDeleteFile(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.DeleteFile(in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.DeleteFile(in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoCreateDirectory(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoCreateDirectory(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.CreateDirectory(in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.CreateDirectory(in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoDeleteDirectory(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoDeleteDirectory(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.DeleteDirectory(in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.DeleteDirectory(in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.DeleteDirectoryRecursively(in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.DeleteDirectoryRecursively(in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoCleanDirectoryRecursively(in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoCleanDirectoryRecursively(in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.CleanDirectoryRecursively(in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.CleanDirectoryRecursively(in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + using var currentFullPath = new Path(); + Result rc = ResolveFullPath(ref currentFullPath.Ref(), in currentPath); + if (rc.IsFailure()) return rc; - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - using var currentFullPath = new Path(); - Result rc = ResolveFullPath(ref currentFullPath.Ref(), in currentPath); - if (rc.IsFailure()) return rc; + using var newFullPath = new Path(); + rc = ResolveFullPath(ref newFullPath.Ref(), in newPath); + if (rc.IsFailure()) return rc; - using var newFullPath = new Path(); - rc = ResolveFullPath(ref newFullPath.Ref(), in newPath); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.RenameFile(in currentFullPath, in newFullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.RenameFile(in currentFullPath, in newFullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + using var currentFullPath = new Path(); + Result rc = ResolveFullPath(ref currentFullPath.Ref(), in currentPath); + if (rc.IsFailure()) return rc; - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - using var currentFullPath = new Path(); - Result rc = ResolveFullPath(ref currentFullPath.Ref(), in currentPath); - if (rc.IsFailure()) return rc; + using var newFullPath = new Path(); + rc = ResolveFullPath(ref newFullPath.Ref(), in newPath); + if (rc.IsFailure()) return rc; - using var newFullPath = new Path(); - rc = ResolveFullPath(ref newFullPath.Ref(), in newPath); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.RenameDirectory(in currentFullPath, in newFullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.RenameDirectory(in currentFullPath, in newFullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + in Path path) + { + using var fullPath = new Path(); + Result rc = ResolveFullPath(ref fullPath.Ref(), in path); + if (rc.IsFailure()) return rc; - protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, - in Path path) - { - using var fullPath = new Path(); - Result rc = ResolveFullPath(ref fullPath.Ref(), in path); - if (rc.IsFailure()) return rc; + rc = _baseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, in fullPath); + if (rc.IsFailure()) return rc; - rc = _baseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, in fullPath); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + protected override Result DoCommit() + { + return _baseFileSystem.Commit(); + } - protected override Result DoCommit() - { - return _baseFileSystem.Commit(); - } + protected override Result DoCommitProvisionally(long counter) + { + return _baseFileSystem.CommitProvisionally(counter); + } - protected override Result DoCommitProvisionally(long counter) - { - return _baseFileSystem.CommitProvisionally(counter); - } - - protected override Result DoRollback() - { - return _baseFileSystem.Rollback(); - } + protected override Result DoRollback() + { + return _baseFileSystem.Rollback(); } } diff --git a/src/LibHac/FsSystem/ThreadPriorityChanger.cs b/src/LibHac/FsSystem/ThreadPriorityChanger.cs index 14c96d9c..7c148970 100644 --- a/src/LibHac/FsSystem/ThreadPriorityChanger.cs +++ b/src/LibHac/FsSystem/ThreadPriorityChanger.cs @@ -1,51 +1,50 @@ using System; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +// Todo: Actually implement both of these structs +public struct ScopedThreadPriorityChanger : IDisposable { - // Todo: Actually implement both of these structs - public struct ScopedThreadPriorityChanger : IDisposable + public enum Mode { - public enum Mode - { - Absolute, - Relative - } - - public ScopedThreadPriorityChanger(int priority, Mode mode) - { - // Change the current thread priority - } - - public void Dispose() - { - // Change thread priority back - } + Absolute, + Relative } - public struct ScopedThreadPriorityChangerByAccessPriority : IDisposable + public ScopedThreadPriorityChanger(int priority, Mode mode) { - public enum AccessMode - { - Read, - Write - } + // Change the current thread priority + } - private ScopedThreadPriorityChanger _scopedChanger; - - public ScopedThreadPriorityChangerByAccessPriority(AccessMode mode) - { - _scopedChanger = new ScopedThreadPriorityChanger(GetThreadPriorityByAccessPriority(mode), - ScopedThreadPriorityChanger.Mode.Absolute); - } - - public void Dispose() - { - _scopedChanger.Dispose(); - } - - private static int GetThreadPriorityByAccessPriority(AccessMode mode) - { - return 0; - } + public void Dispose() + { + // Change thread priority back + } +} + +public struct ScopedThreadPriorityChangerByAccessPriority : IDisposable +{ + public enum AccessMode + { + Read, + Write + } + + private ScopedThreadPriorityChanger _scopedChanger; + + public ScopedThreadPriorityChangerByAccessPriority(AccessMode mode) + { + _scopedChanger = new ScopedThreadPriorityChanger(GetThreadPriorityByAccessPriority(mode), + ScopedThreadPriorityChanger.Mode.Absolute); + } + + public void Dispose() + { + _scopedChanger.Dispose(); + } + + private static int GetThreadPriorityByAccessPriority(AccessMode mode) + { + return 0; } } diff --git a/src/LibHac/FsSystem/TruncatedSubStorage.cs b/src/LibHac/FsSystem/TruncatedSubStorage.cs index 836c5db7..5f25bb88 100644 --- a/src/LibHac/FsSystem/TruncatedSubStorage.cs +++ b/src/LibHac/FsSystem/TruncatedSubStorage.cs @@ -1,47 +1,46 @@ using System; using LibHac.Fs; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +/// +/// A that truncates reads and writes that extend past the end of the base storage. +/// +/// +/// When reading and writing from a , the size of the base +/// storage will be checked. If needed, the size of the requested read/write will be truncated +/// to stay within the bounds of the base storage. +/// +public class TruncatedSubStorage : SubStorage { - /// - /// A that truncates reads and writes that extend past the end of the base storage. - /// - /// - /// When reading and writing from a , the size of the base - /// storage will be checked. If needed, the size of the requested read/write will be truncated - /// to stay within the bounds of the base storage. - /// - public class TruncatedSubStorage : SubStorage + public TruncatedSubStorage() { } + public TruncatedSubStorage(SubStorage other) : base(other) { } + + protected override Result DoRead(long offset, Span destination) { - public TruncatedSubStorage() { } - public TruncatedSubStorage(SubStorage other) : base(other) { } + if (destination.Length == 0) + return Result.Success; - protected override Result DoRead(long offset, Span destination) - { - if (destination.Length == 0) - return Result.Success; + Result rc = BaseStorage.GetSize(out long baseStorageSize); + if (rc.IsFailure()) return rc; - Result rc = BaseStorage.GetSize(out long baseStorageSize); - if (rc.IsFailure()) return rc; + long availableSize = baseStorageSize - offset; + long sizeToRead = Math.Min(destination.Length, availableSize); - long availableSize = baseStorageSize - offset; - long sizeToRead = Math.Min(destination.Length, availableSize); + return base.DoRead(offset, destination.Slice(0, (int)sizeToRead)); + } - return base.DoRead(offset, destination.Slice(0, (int)sizeToRead)); - } + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + if (source.Length == 0) + return Result.Success; - protected override Result DoWrite(long offset, ReadOnlySpan source) - { - if (source.Length == 0) - return Result.Success; + Result rc = BaseStorage.GetSize(out long baseStorageSize); + if (rc.IsFailure()) return rc; - Result rc = BaseStorage.GetSize(out long baseStorageSize); - if (rc.IsFailure()) return rc; + long availableSize = baseStorageSize - offset; + long sizeToWrite = Math.Min(source.Length, availableSize); - long availableSize = baseStorageSize - offset; - long sizeToWrite = Math.Min(source.Length, availableSize); - - return base.DoWrite(offset, source.Slice(0, (int)sizeToWrite)); - } + return base.DoWrite(offset, source.Slice(0, (int)sizeToWrite)); } } diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs index 37cc468d..0cc151bb 100644 --- a/src/LibHac/FsSystem/Utility.cs +++ b/src/LibHac/FsSystem/Utility.cs @@ -5,423 +5,422 @@ using LibHac.Fs.Common; using LibHac.Fs.Fsa; using LibHac.Os; -namespace LibHac.FsSystem -{ - /// - /// Various utility functions used by the namespace. - /// - /// Based on FS 12.1.0 (nnSdk 12.3.1) - internal static class Utility - { - public delegate Result FsIterationTask(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure); +namespace LibHac.FsSystem; - /// - /// Used to pass various ref structs to an . - /// - /// - /// C# does not allow closures over byref-like types. This struct is used as a sort of manual imitation of a closure struct. - /// It contains various fields that can used if needed to pass references to methods. - /// The main shortcomings are that every type that might possibly be passed must have a field in the struct. - /// The struct must also be manually passed through the method. - /// And because ref fields aren't as thing as of C# 10, some ref structs may have to be copied into the closure struct. - /// - [NonCopyable] - public ref struct FsIterationTaskClosure +/// +/// Various utility functions used by the namespace. +/// +/// Based on FS 12.1.0 (nnSdk 12.3.1) +internal static class Utility +{ + public delegate Result FsIterationTask(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure); + + /// + /// Used to pass various ref structs to an . + /// + /// + /// C# does not allow closures over byref-like types. This struct is used as a sort of manual imitation of a closure struct. + /// It contains various fields that can used if needed to pass references to methods. + /// The main shortcomings are that every type that might possibly be passed must have a field in the struct. + /// The struct must also be manually passed through the method. + /// And because ref fields aren't as thing as of C# 10, some ref structs may have to be copied into the closure struct. + /// + [NonCopyable] + public ref struct FsIterationTaskClosure + { + public Span Buffer; + public Path DestinationPathBuffer; + public IFileSystem SourceFileSystem; + public IFileSystem DestFileSystem; + } + + private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + + private static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + using var directory = new UniqueRef(); + + Result rc = fs.OpenDirectory(ref directory.Ref(), in workPath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + while (true) { - public Span Buffer; - public Path DestinationPathBuffer; - public IFileSystem SourceFileSystem; - public IFileSystem DestFileSystem; + rc = directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + if (entriesRead == 0) + break; + + workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) + { + rc = onEnterDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; } - private static ReadOnlySpan RootPath => new[] { (byte)'/' }; + return Result.Success; + } - private static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, - ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, - ref FsIterationTaskClosure closure) + private static Result CleanupDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, + ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + using var directory = new UniqueRef(); + + while (true) { - using var directory = new UniqueRef(); - Result rc = fs.OpenDirectory(ref directory.Ref(), in workPath, OpenDirectoryMode.All); if (rc.IsFailure()) return rc; - while (true) + rc = directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + if (rc.IsFailure()) return rc; + + directory.Reset(); + + if (entriesRead == 0) + break; + + rc = workPath.AppendChild(dirEntry.Name); + if (rc.IsFailure()) return rc; + + if (dirEntry.Type == DirectoryEntryType.Directory) { - rc = directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); + rc = onEnterDir(in workPath, in dirEntry, ref closure); if (rc.IsFailure()) return rc; - if (entriesRead == 0) - break; - - workPath.AppendChild(dirEntry.Name); + rc = CleanupDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); if (rc.IsFailure()) return rc; - if (dirEntry.Type == DirectoryEntryType.Directory) - { - rc = onEnterDir(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - - rc = IterateDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, - onFile, ref closure); - if (rc.IsFailure()) return rc; - - rc = onExitDir(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - } - else - { - rc = onFile(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - } - - rc = workPath.RemoveChild(); + rc = onExitDir(in workPath, in dirEntry, ref closure); + if (rc.IsFailure()) return rc; + } + else + { + rc = onFile(in workPath, in dirEntry, ref closure); if (rc.IsFailure()) return rc; } - return Result.Success; + rc = workPath.RemoveChild(); + if (rc.IsFailure()) return rc; } - private static Result CleanupDirectoryRecursivelyInternal(IFileSystem fs, ref Path workPath, - ref DirectoryEntry dirEntry, FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, - ref FsIterationTaskClosure closure) + return Result.Success; + } + + public static Result IterateDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + using var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursivelyInternal(fs, ref pathBuffer.Ref(), ref dirEntry, onEnterDir, onExitDir, + onFile, ref closure); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public static Result CleanupDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, + FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, + ref FsIterationTaskClosure closure) + { + using var pathBuffer = new Path(); + Result rc = pathBuffer.Initialize(in rootPath); + if (rc.IsFailure()) return rc; + + return CleanupDirectoryRecursivelyInternal(fs, ref pathBuffer.Ref(), ref dirEntry, onEnterDir, onExitDir, onFile, + ref closure); + } + + public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, + in Path sourcePath, Span workBuffer) + { + // Open source file. + using var sourceFile = new UniqueRef(); + Result rc = sourceFileSystem.OpenFile(ref sourceFile.Ref(), sourcePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = sourceFile.Get.GetSize(out long fileSize); + if (rc.IsFailure()) return rc; + + using var destFile = new UniqueRef(); + rc = destFileSystem.CreateFile(in destPath, fileSize); + if (rc.IsFailure()) return rc; + + rc = destFileSystem.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); + if (rc.IsFailure()) return rc; + + // Read/Write file in work buffer sized chunks. + long remaining = fileSize; + long offset = 0; + + while (remaining > 0) { - using var directory = new UniqueRef(); + rc = sourceFile.Get.Read(out long bytesRead, offset, workBuffer, ReadOption.None); + if (rc.IsFailure()) return rc; - while (true) - { - Result rc = fs.OpenDirectory(ref directory.Ref(), in workPath, OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; + rc = destFile.Get.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); + if (rc.IsFailure()) return rc; - rc = directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)); - if (rc.IsFailure()) return rc; - - directory.Reset(); - - if (entriesRead == 0) - break; - - rc = workPath.AppendChild(dirEntry.Name); - if (rc.IsFailure()) return rc; - - if (dirEntry.Type == DirectoryEntryType.Directory) - { - rc = onEnterDir(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - - rc = CleanupDirectoryRecursivelyInternal(fs, ref workPath, ref dirEntry, onEnterDir, onExitDir, - onFile, ref closure); - if (rc.IsFailure()) return rc; - - rc = onExitDir(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - } - else - { - rc = onFile(in workPath, in dirEntry, ref closure); - if (rc.IsFailure()) return rc; - } - - rc = workPath.RemoveChild(); - if (rc.IsFailure()) return rc; - } - - return Result.Success; + remaining -= bytesRead; + offset += bytesRead; } - public static Result IterateDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, - FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, - ref FsIterationTaskClosure closure) + return Result.Success; + } + + public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, + in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) { - using var pathBuffer = new Path(); - Result rc = pathBuffer.Initialize(in rootPath); + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); if (rc.IsFailure()) return rc; - rc = IterateDirectoryRecursivelyInternal(fs, ref pathBuffer.Ref(), ref dirEntry, onEnterDir, onExitDir, - onFile, ref closure); - if (rc.IsFailure()) return rc; - - return Result.Success; + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); } - public static Result CleanupDirectoryRecursively(IFileSystem fs, in Path rootPath, ref DirectoryEntry dirEntry, - FsIterationTask onEnterDir, FsIterationTask onExitDir, FsIterationTask onFile, - ref FsIterationTaskClosure closure) + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) { - using var pathBuffer = new Path(); - Result rc = pathBuffer.Initialize(in rootPath); - if (rc.IsFailure()) return rc; - - return CleanupDirectoryRecursivelyInternal(fs, ref pathBuffer.Ref(), ref dirEntry, onEnterDir, onExitDir, onFile, - ref closure); + return closure.DestinationPathBuffer.RemoveChild(); } - public static Result CopyFile(IFileSystem destFileSystem, IFileSystem sourceFileSystem, in Path destPath, - in Path sourcePath, Span workBuffer) + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) { - // Open source file. - using var sourceFile = new UniqueRef(); - Result rc = sourceFileSystem.OpenFile(ref sourceFile.Ref(), sourcePath, OpenMode.Read); + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); if (rc.IsFailure()) return rc; - rc = sourceFile.Get.GetSize(out long fileSize); + rc = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); if (rc.IsFailure()) return rc; - using var destFile = new UniqueRef(); - rc = destFileSystem.CreateFile(in destPath, fileSize); + return closure.DestinationPathBuffer.RemoveChild(); + } + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = sourceFileSystem; + closure.DestFileSystem = destinationFileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + rc = IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, + OnFile, ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath, + in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + { + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); + if (rc.IsFailure()) return rc; + + static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); if (rc.IsFailure()) return rc; - rc = destFileSystem.OpenFile(ref destFile.Ref(), in destPath, OpenMode.Write); + return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); + } + + static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + return closure.DestinationPathBuffer.RemoveChild(); + } + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); + if (rc.IsFailure()) return rc; + + rc = CopyFile(closure.SourceFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, + in path, closure.Buffer); + if (rc.IsFailure()) return rc; + + return closure.DestinationPathBuffer.RemoveChild(); + } + + rc = IterateDirectoryRecursively(fileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, OnFile, + ref closure); + + closure.DestinationPathBuffer.Dispose(); + return rc; + } + + public static Result VerifyDirectoryRecursively(IFileSystem fileSystem, Span workBuffer) + { + static Result OnEnterAndExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => + Result.Success; + + static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) + { + using var file = new UniqueRef(); + + Result rc = closure.SourceFileSystem.OpenFile(ref file.Ref(), in path, OpenMode.Read); if (rc.IsFailure()) return rc; - // Read/Write file in work buffer sized chunks. - long remaining = fileSize; long offset = 0; - while (remaining > 0) + while (true) { - rc = sourceFile.Get.Read(out long bytesRead, offset, workBuffer, ReadOption.None); + rc = file.Get.Read(out long bytesRead, offset, closure.Buffer, ReadOption.None); if (rc.IsFailure()) return rc; - rc = destFile.Get.Write(offset, workBuffer.Slice(0, (int)bytesRead), WriteOption.None); - if (rc.IsFailure()) return rc; + if (bytesRead < closure.Buffer.Length) + break; - remaining -= bytesRead; offset += bytesRead; } return Result.Success; } - public static Result CopyDirectoryRecursively(IFileSystem destinationFileSystem, IFileSystem sourceFileSystem, - in Path destinationPath, in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) + using var rootPath = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref rootPath.Ref(), RootPath); + if (rc.IsFailure()) return rc; + + var closure = new FsIterationTaskClosure(); + closure.Buffer = workBuffer; + closure.SourceFileSystem = fileSystem; + + var dirEntryBuffer = new DirectoryEntry(); + + return IterateDirectoryRecursively(fileSystem, in rootPath, ref dirEntryBuffer, OnEnterAndExitDir, + OnEnterAndExitDir, OnFile, ref closure); + } + + private static Result EnsureDirectoryImpl(IFileSystem fileSystem, in Path path) + { + using var pathCopy = new Path(); + bool isFinished; + + Result rc = pathCopy.Initialize(in path); + if (rc.IsFailure()) return rc; + + using var parser = new DirectoryPathParser(); + rc = parser.Initialize(ref pathCopy.Ref()); + if (rc.IsFailure()) return rc; + + do { - static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); - } - - static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - return closure.DestinationPathBuffer.RemoveChild(); - } - - static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - rc = CopyFile(closure.DestFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, - in path, closure.Buffer); - if (rc.IsFailure()) return rc; - - return closure.DestinationPathBuffer.RemoveChild(); - } - - var closure = new FsIterationTaskClosure(); - closure.Buffer = workBuffer; - closure.SourceFileSystem = sourceFileSystem; - closure.DestFileSystem = destinationFileSystem; - - Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); - if (rc.IsFailure()) return rc; - - rc = IterateDirectoryRecursively(sourceFileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, - OnFile, ref closure); - - closure.DestinationPathBuffer.Dispose(); - return rc; - } - - public static Result CopyDirectoryRecursively(IFileSystem fileSystem, in Path destinationPath, - in Path sourcePath, ref DirectoryEntry dirEntry, Span workBuffer) - { - var closure = new FsIterationTaskClosure(); - closure.Buffer = workBuffer; - closure.SourceFileSystem = fileSystem; - - Result rc = closure.DestinationPathBuffer.Initialize(destinationPath); - if (rc.IsFailure()) return rc; - - static Result OnEnterDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - return closure.SourceFileSystem.CreateDirectory(in closure.DestinationPathBuffer); - } - - static Result OnExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - return closure.DestinationPathBuffer.RemoveChild(); - } - - static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - Result rc = closure.DestinationPathBuffer.AppendChild(entry.Name); - if (rc.IsFailure()) return rc; - - rc = CopyFile(closure.SourceFileSystem, closure.SourceFileSystem, in closure.DestinationPathBuffer, - in path, closure.Buffer); - if (rc.IsFailure()) return rc; - - return closure.DestinationPathBuffer.RemoveChild(); - } - - rc = IterateDirectoryRecursively(fileSystem, in sourcePath, ref dirEntry, OnEnterDir, OnExitDir, OnFile, - ref closure); - - closure.DestinationPathBuffer.Dispose(); - return rc; - } - - public static Result VerifyDirectoryRecursively(IFileSystem fileSystem, Span workBuffer) - { - static Result OnEnterAndExitDir(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) => - Result.Success; - - static Result OnFile(in Path path, in DirectoryEntry entry, ref FsIterationTaskClosure closure) - { - using var file = new UniqueRef(); - - Result rc = closure.SourceFileSystem.OpenFile(ref file.Ref(), in path, OpenMode.Read); - if (rc.IsFailure()) return rc; - - long offset = 0; - - while (true) - { - rc = file.Get.Read(out long bytesRead, offset, closure.Buffer, ReadOption.None); - if (rc.IsFailure()) return rc; - - if (bytesRead < closure.Buffer.Length) - break; - - offset += bytesRead; - } - - return Result.Success; - } - - using var rootPath = new Path(); - Result rc = PathFunctions.SetUpFixedPath(ref rootPath.Ref(), RootPath); - if (rc.IsFailure()) return rc; - - var closure = new FsIterationTaskClosure(); - closure.Buffer = workBuffer; - closure.SourceFileSystem = fileSystem; - - var dirEntryBuffer = new DirectoryEntry(); - - return IterateDirectoryRecursively(fileSystem, in rootPath, ref dirEntryBuffer, OnEnterAndExitDir, - OnEnterAndExitDir, OnFile, ref closure); - } - - private static Result EnsureDirectoryImpl(IFileSystem fileSystem, in Path path) - { - using var pathCopy = new Path(); - bool isFinished; - - Result rc = pathCopy.Initialize(in path); - if (rc.IsFailure()) return rc; - - using var parser = new DirectoryPathParser(); - rc = parser.Initialize(ref pathCopy.Ref()); - if (rc.IsFailure()) return rc; - - do - { - // Check if the path exists - rc = fileSystem.GetEntryType(out DirectoryEntryType type, in parser.CurrentPath); - if (!rc.IsSuccess()) - { - // Something went wrong if we get a result other than PathNotFound - if (!ResultFs.PathNotFound.Includes(rc)) - return rc; - - // Create the directory - rc = fileSystem.CreateDirectory(in parser.CurrentPath); - if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) - return rc; - - // Check once more if the path exists - rc = fileSystem.GetEntryType(out type, in parser.CurrentPath); - if (rc.IsFailure()) return rc; - } - - if (type == DirectoryEntryType.File) - return ResultFs.PathAlreadyExists.Log(); - - rc = parser.ReadNext(out isFinished); - if (rc.IsFailure()) return rc; - } while (!isFinished); - - return Result.Success; - } - - public static Result EnsureDirectory(IFileSystem fileSystem, in Path path) - { - Result rc = fileSystem.GetEntryType(out _, in path); - + // Check if the path exists + rc = fileSystem.GetEntryType(out DirectoryEntryType type, in parser.CurrentPath); if (!rc.IsSuccess()) { + // Something went wrong if we get a result other than PathNotFound if (!ResultFs.PathNotFound.Includes(rc)) return rc; - rc = EnsureDirectoryImpl(fileSystem, in path); + // Create the directory + rc = fileSystem.CreateDirectory(in parser.CurrentPath); + if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) + return rc; + + // Check once more if the path exists + rc = fileSystem.GetEntryType(out type, in parser.CurrentPath); if (rc.IsFailure()) return rc; } - return Result.Success; - } + if (type == DirectoryEntryType.File) + return ResultFs.PathAlreadyExists.Log(); - public static void AddCounter(Span counter, ulong value) - { - const int bitsPerByte = 8; - - ulong remaining = value; - byte carry = 0; - - for (int i = 0; i < counter.Length; i++) - { - int sum = counter[counter.Length - 1 - i] + (byte)remaining + carry; - carry = (byte)(sum >> bitsPerByte); - - counter[counter.Length - 1 - i] = (byte)sum; - - remaining >>= bitsPerByte; - - if (carry == 0 && remaining == 0) - break; - } - } - - public static Result TryAcquireCountSemaphore(ref UniqueLock outUniqueLock, - SemaphoreAdapter semaphore) - { - using var uniqueLock = new UniqueLock(semaphore, new DeferLock()); - - if (!uniqueLock.TryLock()) - return ResultFs.OpenCountLimit.Log(); - - outUniqueLock.Set(ref uniqueLock.Ref()); - return Result.Success; - } - - public static Result MakeUniqueLockWithPin(ref UniqueRef outUniqueLock, - SemaphoreAdapter semaphore, ref SharedRef objectToPin) where T : class, IDisposable - { - using var semaphoreAdapter = new UniqueLock(); - Result rc = TryAcquireCountSemaphore(ref semaphoreAdapter.Ref(), semaphore); + rc = parser.ReadNext(out isFinished); if (rc.IsFailure()) return rc; + } while (!isFinished); - var lockWithPin = new UniqueLockWithPin(ref semaphoreAdapter.Ref(), ref objectToPin); - using var uniqueLock = new UniqueRef(lockWithPin); + return Result.Success; + } - outUniqueLock.Set(ref uniqueLock.Ref()); - return Result.Success; + public static Result EnsureDirectory(IFileSystem fileSystem, in Path path) + { + Result rc = fileSystem.GetEntryType(out _, in path); + + if (!rc.IsSuccess()) + { + if (!ResultFs.PathNotFound.Includes(rc)) + return rc; + + rc = EnsureDirectoryImpl(fileSystem, in path); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public static void AddCounter(Span counter, ulong value) + { + const int bitsPerByte = 8; + + ulong remaining = value; + byte carry = 0; + + for (int i = 0; i < counter.Length; i++) + { + int sum = counter[counter.Length - 1 - i] + (byte)remaining + carry; + carry = (byte)(sum >> bitsPerByte); + + counter[counter.Length - 1 - i] = (byte)sum; + + remaining >>= bitsPerByte; + + if (carry == 0 && remaining == 0) + break; } } + + public static Result TryAcquireCountSemaphore(ref UniqueLock outUniqueLock, + SemaphoreAdapter semaphore) + { + using var uniqueLock = new UniqueLock(semaphore, new DeferLock()); + + if (!uniqueLock.TryLock()) + return ResultFs.OpenCountLimit.Log(); + + outUniqueLock.Set(ref uniqueLock.Ref()); + return Result.Success; + } + + public static Result MakeUniqueLockWithPin(ref UniqueRef outUniqueLock, + SemaphoreAdapter semaphore, ref SharedRef objectToPin) where T : class, IDisposable + { + using var semaphoreAdapter = new UniqueLock(); + Result rc = TryAcquireCountSemaphore(ref semaphoreAdapter.Ref(), semaphore); + if (rc.IsFailure()) return rc; + + var lockWithPin = new UniqueLockWithPin(ref semaphoreAdapter.Ref(), ref objectToPin); + using var uniqueLock = new UniqueRef(lockWithPin); + + outUniqueLock.Set(ref uniqueLock.Ref()); + return Result.Success; + } } diff --git a/src/LibHac/FsSystem/ValueStringBuilder.cs b/src/LibHac/FsSystem/ValueStringBuilder.cs index 4765aa4b..f17fe9a4 100644 --- a/src/LibHac/FsSystem/ValueStringBuilder.cs +++ b/src/LibHac/FsSystem/ValueStringBuilder.cs @@ -4,268 +4,267 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace LibHac.FsSystem +namespace LibHac.FsSystem; + +internal ref struct ValueStringBuilder { - internal ref struct ValueStringBuilder + private char[] _arrayToReturnToPool; + private Span _chars; + private int _pos; + + public ValueStringBuilder(Span initialBuffer) { - private char[] _arrayToReturnToPool; - private Span _chars; - private int _pos; + _arrayToReturnToPool = null; + _chars = initialBuffer; + _pos = 0; + } - public ValueStringBuilder(Span initialBuffer) + public ValueStringBuilder(int initialCapacity) + { + _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _pos = 0; + } + + public int Length + { + get => _pos; + set { - _arrayToReturnToPool = null; - _chars = initialBuffer; - _pos = 0; + Debug.Assert(value >= 0); + Debug.Assert(value <= _chars.Length); + _pos = value; } + } - public ValueStringBuilder(int initialCapacity) + public int Capacity => _chars.Length; + + public void EnsureCapacity(int capacity) + { + if (capacity > _chars.Length) + Grow(capacity - _chars.Length); + } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null char after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (char* c = builder)" + /// + public ref char GetPinnableReference() + { + return ref MemoryMarshal.GetReference(_chars); + } + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null char after + public ref char GetPinnableReference(bool terminate) + { + if (terminate) { - _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); - _chars = _arrayToReturnToPool; - _pos = 0; + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; } + return ref MemoryMarshal.GetReference(_chars); + } - public int Length + public ref char this[int index] + { + get { - get => _pos; - set - { - Debug.Assert(value >= 0); - Debug.Assert(value <= _chars.Length); - _pos = value; - } + Debug.Assert(index < _pos); + return ref _chars[index]; } + } - public int Capacity => _chars.Length; + public override string ToString() + { + string s = _chars.Slice(0, _pos).ToString(); + Dispose(); + return s; + } - public void EnsureCapacity(int capacity) + /// Returns the underlying storage of the builder. + public Span RawChars => _chars; + + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null char after + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) { - if (capacity > _chars.Length) - Grow(capacity - _chars.Length); + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; } + return _chars.Slice(0, _pos); + } - /// - /// Get a pinnable reference to the builder. - /// Does not ensure there is a null char after - /// This overload is pattern matched in the C# 7.3+ compiler so you can omit - /// the explicit method call, and write eg "fixed (char* c = builder)" - /// - public ref char GetPinnableReference() - { - return ref MemoryMarshal.GetReference(_chars); - } + public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); + public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); + public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); - /// - /// Get a pinnable reference to the builder. - /// - /// Ensures that the builder has a null char after - public ref char GetPinnableReference(bool terminate) + public bool TryCopyTo(Span destination, out int charsWritten) + { + if (_chars.Slice(0, _pos).TryCopyTo(destination)) { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return ref MemoryMarshal.GetReference(_chars); - } - - public ref char this[int index] - { - get - { - Debug.Assert(index < _pos); - return ref _chars[index]; - } - } - - public override string ToString() - { - string s = _chars.Slice(0, _pos).ToString(); + charsWritten = _pos; Dispose(); - return s; + return true; + } + else + { + charsWritten = 0; + Dispose(); + return false; + } + } + + public void Insert(int index, char value, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); } - /// Returns the underlying storage of the builder. - public Span RawChars => _chars; + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + _chars.Slice(index, count).Fill(value); + _pos += count; + } - /// - /// Returns a span around the contents of the builder. - /// - /// Ensures that the builder has a null char after - public ReadOnlySpan AsSpan(bool terminate) + public void Insert(int index, string s) + { + int count = s.Length; + + if (_pos > (_chars.Length - count)) { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return _chars.Slice(0, _pos); + Grow(count); } - public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); - public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); - public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + int remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + s.AsSpan().CopyTo(_chars.Slice(index)); + _pos += count; + } - public bool TryCopyTo(Span destination, out int charsWritten) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + int pos = _pos; + if ((uint)pos < (uint)_chars.Length) { - if (_chars.Slice(0, _pos).TryCopyTo(destination)) - { - charsWritten = _pos; - Dispose(); - return true; - } - else - { - charsWritten = 0; - Dispose(); - return false; - } + _chars[pos] = c; + _pos = pos + 1; + } + else + { + GrowAndAppend(c); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(string s) + { + int pos = _pos; + if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + { + _chars[pos] = s[0]; + _pos = pos + 1; + } + else + { + AppendSlow(s); + } + } + + private void AppendSlow(string s) + { + int pos = _pos; + if (pos > _chars.Length - s.Length) + { + Grow(s.Length); } - public void Insert(int index, char value, int count) - { - if (_pos > _chars.Length - count) - { - Grow(count); - } + s.AsSpan().CopyTo(_chars.Slice(pos)); + _pos += s.Length; + } - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - _chars.Slice(index, count).Fill(value); - _pos += count; + public void Append(char c, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); } - public void Insert(int index, string s) + Span dst = _chars.Slice(_pos, count); + for (int i = 0; i < dst.Length; i++) { - int count = s.Length; + dst[i] = c; + } + _pos += count; + } - if (_pos > (_chars.Length - count)) - { - Grow(count); - } - - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - s.AsSpan().CopyTo(_chars.Slice(index)); - _pos += count; + public void Append(ReadOnlySpan value) + { + int pos = _pos; + if (pos > _chars.Length - value.Length) + { + Grow(value.Length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c) + value.CopyTo(_chars.Slice(_pos)); + _pos += value.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int origPos = _pos; + if (origPos > _chars.Length - length) { - int pos = _pos; - if ((uint)pos < (uint)_chars.Length) - { - _chars[pos] = c; - _pos = pos + 1; - } - else - { - GrowAndAppend(c); - } + Grow(length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(string s) + _pos = origPos + length; + return _chars.Slice(origPos, length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(char c) + { + Grow(1); + Append(c); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int requiredAdditionalCapacity) + { + Debug.Assert(requiredAdditionalCapacity > 0); + + char[] poolArray = ArrayPool.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2)); + + _chars.CopyTo(poolArray); + + char[] toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) { - int pos = _pos; - if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. - { - _chars[pos] = s[0]; - _pos = pos + 1; - } - else - { - AppendSlow(s); - } + ArrayPool.Shared.Return(toReturn); } + } - private void AppendSlow(string s) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + char[] toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) { - int pos = _pos; - if (pos > _chars.Length - s.Length) - { - Grow(s.Length); - } - - s.AsSpan().CopyTo(_chars.Slice(pos)); - _pos += s.Length; - } - - public void Append(char c, int count) - { - if (_pos > _chars.Length - count) - { - Grow(count); - } - - Span dst = _chars.Slice(_pos, count); - for (int i = 0; i < dst.Length; i++) - { - dst[i] = c; - } - _pos += count; - } - - public void Append(ReadOnlySpan value) - { - int pos = _pos; - if (pos > _chars.Length - value.Length) - { - Grow(value.Length); - } - - value.CopyTo(_chars.Slice(_pos)); - _pos += value.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AppendSpan(int length) - { - int origPos = _pos; - if (origPos > _chars.Length - length) - { - Grow(length); - } - - _pos = origPos + length; - return _chars.Slice(origPos, length); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowAndAppend(char c) - { - Grow(1); - Append(c); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void Grow(int requiredAdditionalCapacity) - { - Debug.Assert(requiredAdditionalCapacity > 0); - - char[] poolArray = ArrayPool.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2)); - - _chars.CopyTo(poolArray); - - char[] toReturn = _arrayToReturnToPool; - _chars = _arrayToReturnToPool = poolArray; - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - char[] toReturn = _arrayToReturnToPool; - this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } + ArrayPool.Shared.Return(toReturn); } } } diff --git a/src/LibHac/GcSrv/IGameCardDeviceManager.cs b/src/LibHac/GcSrv/IGameCardDeviceManager.cs index 4cc7ae92..ceb129b4 100644 --- a/src/LibHac/GcSrv/IGameCardDeviceManager.cs +++ b/src/LibHac/GcSrv/IGameCardDeviceManager.cs @@ -1,15 +1,14 @@ using System; using LibHac.Fs.Impl; -namespace LibHac.GcSrv +namespace LibHac.GcSrv; + +internal interface IGameCardDeviceManager { - internal interface IGameCardDeviceManager - { - Result AcquireReadLock(out UniqueLock locker, uint handle); - Result AcquireReadLockSecureMode(out UniqueLock locker, ref uint handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash); - Result AcquireWriteLock(out SharedLock locker); - Result HandleGameCardAccessResult(Result result); - Result GetHandle(out uint handle); - bool IsSecureMode(); - } + Result AcquireReadLock(out UniqueLock locker, uint handle); + Result AcquireReadLockSecureMode(out UniqueLock locker, ref uint handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash); + Result AcquireWriteLock(out SharedLock locker); + Result HandleGameCardAccessResult(Result result); + Result GetHandle(out uint handle); + bool IsSecureMode(); } diff --git a/src/LibHac/GcSrv/IGameCardKeyManager.cs b/src/LibHac/GcSrv/IGameCardKeyManager.cs index de8d39d3..4cba4440 100644 --- a/src/LibHac/GcSrv/IGameCardKeyManager.cs +++ b/src/LibHac/GcSrv/IGameCardKeyManager.cs @@ -1,9 +1,8 @@ using System; -namespace LibHac.GcSrv +namespace LibHac.GcSrv; + +public interface IGameCardKeyManager { - public interface IGameCardKeyManager - { - void PresetInternalKeys(ReadOnlySpan gameCardKey, ReadOnlySpan gameCardCertificate); - } + void PresetInternalKeys(ReadOnlySpan gameCardKey, ReadOnlySpan gameCardCertificate); } diff --git a/src/LibHac/HashHelpers.cs b/src/LibHac/HashHelpers.cs index 06d29deb..c668c357 100644 --- a/src/LibHac/HashHelpers.cs +++ b/src/LibHac/HashHelpers.cs @@ -6,102 +6,101 @@ using System; using System.Diagnostics; -namespace LibHac +namespace LibHac; + +internal static class HashHelpers { - internal static class HashHelpers + public const int HashCollisionThreshold = 100; + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + public static readonly int[] Primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; + + public static bool IsPrime(int candidate) { - public const int HashCollisionThreshold = 100; - - // This is the maximum prime smaller than Array.MaxArrayLength - public const int MaxPrimeArrayLength = 0x7FEFFFFD; - - public const int HashPrime = 101; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - // We prefer the low computation costs of higher prime numbers over the increased - // memory allocation of a fixed prime number i.e. when right sizing a HashSet. - public static readonly int[] Primes = { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; - - public static bool IsPrime(int candidate) + if ((candidate & 1) != 0) { - if ((candidate & 1) != 0) + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - return true; + if ((candidate % divisor) == 0) + return false; } - return (candidate == 2); - } - - public static int GetPrime(int min) - { - if (min < 0) - throw new ArgumentException(nameof(min)); - - for (int i = 0; i < Primes.Length; i++) - { - int prime = Primes[i]; - if (prime >= min) - return prime; - } - - //outside of our predefined table. - //compute the hard way. - for (int i = (min | 1); i < int.MaxValue; i += 2) - { - if (IsPrime(i) && ((i - 1) % HashPrime != 0)) - return i; - } - return min; - } - - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) - { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); - } - - public static int GetRomFsPrime(int min) - { - if (min < 3) return 3; - if (min < 19) return min | 1; - - for (int i = (min | 1); i < int.MaxValue; i += 2) - { - if (i % 2 != 0 && i % 3 != 0 && i % 5 != 0 && i % 7 != 0 && i % 11 != 0 && i % 13 != 0 && i % 17 != 0) - return i; - } - - return min; + return true; } + return (candidate == 2); } -} \ No newline at end of file + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException(nameof(min)); + + for (int i = 0; i < Primes.Length; i++) + { + int prime = Primes[i]; + if (prime >= min) + return prime; + } + + //outside of our predefined table. + //compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + public static int GetRomFsPrime(int min) + { + if (min < 3) return 3; + if (min < 19) return min | 1; + + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (i % 2 != 0 && i % 3 != 0 && i % 5 != 0 && i % 7 != 0 && i % 11 != 0 && i % 13 != 0 && i % 17 != 0) + return i; + } + + return min; + } +} diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs index a20960d5..e1756c0f 100644 --- a/src/LibHac/Horizon.cs +++ b/src/LibHac/Horizon.cs @@ -8,79 +8,78 @@ using LibHac.Ncm; using LibHac.Os; using LibHac.Sm; -namespace LibHac +namespace LibHac; + +public class Horizon { - public class Horizon + private const int InitialProcessCountMax = 0x50; + + internal ITickGenerator TickGenerator { get; } + internal ServiceManager ServiceManager { get; } + private HorizonClient LoaderClient { get; } + + private ulong _currentInitialProcessId; + private ulong _currentProcessId; + + public Horizon(HorizonConfiguration config) { - private const int InitialProcessCountMax = 0x50; + _currentProcessId = InitialProcessCountMax; - internal ITickGenerator TickGenerator { get; } - internal ServiceManager ServiceManager { get; } - private HorizonClient LoaderClient { get; } + TickGenerator = config.TickGenerator ?? new DefaultTickGenerator(); + ServiceManager = new ServiceManager(); - private ulong _currentInitialProcessId; - private ulong _currentProcessId; - - public Horizon(HorizonConfiguration config) - { - _currentProcessId = InitialProcessCountMax; - - TickGenerator = config.TickGenerator ?? new DefaultTickGenerator(); - ServiceManager = new ServiceManager(); - - LoaderClient = CreatePrivilegedHorizonClient(); - } - - public HorizonClient CreatePrivilegedHorizonClient() - { - ulong processId = Interlocked.Increment(ref _currentInitialProcessId); - - Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients."); - - // Todo: Register process with FS - - return new HorizonClient(this, new ProcessId(processId)); - } - - public HorizonClient CreateHorizonClient() - { - ulong processId = Interlocked.Increment(ref _currentProcessId); - - // Todo: Register process with FS - - return new HorizonClient(this, new ProcessId(processId)); - } - - public HorizonClient CreateHorizonClient(ProgramLocation location, AccessControlBits.Bits fsPermissions) - { - var descriptor = new AccessControlDescriptor(); - var dataHeader = new AccessControlDataHeader(); - - descriptor.Version = 1; - dataHeader.Version = 1; - - descriptor.AccessFlags = (ulong)fsPermissions; - dataHeader.AccessFlags = (ulong)fsPermissions; - - return CreateHorizonClientImpl(location, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), - SpanHelpers.AsReadOnlyByteSpan(in descriptor)); - } - - public HorizonClient CreateHorizonClient(ProgramLocation location, ReadOnlySpan fsAccessControlData, - ReadOnlySpan fsAccessControlDescriptor) - { - return CreateHorizonClientImpl(location, fsAccessControlData, fsAccessControlDescriptor); - } - - private HorizonClient CreateHorizonClientImpl(ProgramLocation location, ReadOnlySpan accessControlData, - ReadOnlySpan accessControlDescriptor) - { - HorizonClient client = CreateHorizonClient(); - - LoaderClient.Fs.RegisterProgram(client.ProcessId.Value, location.ProgramId, location.StorageId, - accessControlData, accessControlDescriptor).ThrowIfFailure(); - - return client; - } + LoaderClient = CreatePrivilegedHorizonClient(); } -} \ No newline at end of file + + public HorizonClient CreatePrivilegedHorizonClient() + { + ulong processId = Interlocked.Increment(ref _currentInitialProcessId); + + Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients."); + + // Todo: Register process with FS + + return new HorizonClient(this, new ProcessId(processId)); + } + + public HorizonClient CreateHorizonClient() + { + ulong processId = Interlocked.Increment(ref _currentProcessId); + + // Todo: Register process with FS + + return new HorizonClient(this, new ProcessId(processId)); + } + + public HorizonClient CreateHorizonClient(ProgramLocation location, AccessControlBits.Bits fsPermissions) + { + var descriptor = new AccessControlDescriptor(); + var dataHeader = new AccessControlDataHeader(); + + descriptor.Version = 1; + dataHeader.Version = 1; + + descriptor.AccessFlags = (ulong)fsPermissions; + dataHeader.AccessFlags = (ulong)fsPermissions; + + return CreateHorizonClientImpl(location, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), + SpanHelpers.AsReadOnlyByteSpan(in descriptor)); + } + + public HorizonClient CreateHorizonClient(ProgramLocation location, ReadOnlySpan fsAccessControlData, + ReadOnlySpan fsAccessControlDescriptor) + { + return CreateHorizonClientImpl(location, fsAccessControlData, fsAccessControlDescriptor); + } + + private HorizonClient CreateHorizonClientImpl(ProgramLocation location, ReadOnlySpan accessControlData, + ReadOnlySpan accessControlDescriptor) + { + HorizonClient client = CreateHorizonClient(); + + LoaderClient.Fs.RegisterProgram(client.ProcessId.Value, location.ProgramId, location.StorageId, + accessControlData, accessControlDescriptor).ThrowIfFailure(); + + return client; + } +} diff --git a/src/LibHac/HorizonClient.cs b/src/LibHac/HorizonClient.cs index a56e84a0..92a1a289 100644 --- a/src/LibHac/HorizonClient.cs +++ b/src/LibHac/HorizonClient.cs @@ -6,46 +6,45 @@ using LibHac.Lr; using LibHac.Os; using LibHac.Sm; -namespace LibHac +namespace LibHac; + +public class HorizonClient : IDisposable { - public class HorizonClient : IDisposable + // ReSharper disable once UnusedAutoPropertyAccessor.Local + private Horizon Horizon { get; } + internal ProcessId ProcessId { get; } + + private Lazy ArpLazy { get; } + + public FileSystemClient Fs { get; } + public ServiceManagerClient Sm { get; } + public OsState Os { get; } + public DiagClient Diag { get; } + public LrClient Lr { get; } + public ArpClient Arp => ArpLazy.Value; + + internal HorizonClient(Horizon horizon, ProcessId processId) { - // ReSharper disable once UnusedAutoPropertyAccessor.Local - private Horizon Horizon { get; } - internal ProcessId ProcessId { get; } + Horizon = horizon; + ProcessId = processId; - private Lazy ArpLazy { get; } + Fs = new FileSystemClient(this); + Sm = new ServiceManagerClient(Horizon.ServiceManager); + Os = new OsState(this, horizon.TickGenerator); + Diag = new DiagClient(this); + Lr = new LrClient(this); - public FileSystemClient Fs { get; } - public ServiceManagerClient Sm { get; } - public OsState Os { get; } - public DiagClient Diag { get; } - public LrClient Lr { get; } - public ArpClient Arp => ArpLazy.Value; + ArpLazy = new Lazy(InitArpClient, true); + } - internal HorizonClient(Horizon horizon, ProcessId processId) - { - Horizon = horizon; - ProcessId = processId; + public void Dispose() + { + Fs.Dispose(); + Lr.Dispose(); + } - Fs = new FileSystemClient(this); - Sm = new ServiceManagerClient(Horizon.ServiceManager); - Os = new OsState(this, horizon.TickGenerator); - Diag = new DiagClient(this); - Lr = new LrClient(this); - - ArpLazy = new Lazy(InitArpClient, true); - } - - public void Dispose() - { - Fs.Dispose(); - Lr.Dispose(); - } - - private ArpClient InitArpClient() - { - return new ArpClient(this); - } + private ArpClient InitArpClient() + { + return new ArpClient(this); } } diff --git a/src/LibHac/HorizonConfiguration.cs b/src/LibHac/HorizonConfiguration.cs index 71a7535d..463c1174 100644 --- a/src/LibHac/HorizonConfiguration.cs +++ b/src/LibHac/HorizonConfiguration.cs @@ -1,17 +1,16 @@ #nullable enable using LibHac.Os; -namespace LibHac +namespace LibHac; + +/// +/// Contains configuration options for instantiating a object. +/// +public class HorizonConfiguration { /// - /// Contains configuration options for instantiating a object. + /// Used when getting the current system . + /// If , a default is used. /// - public class HorizonConfiguration - { - /// - /// Used when getting the current system . - /// If , a default is used. - /// - public ITickGenerator? TickGenerator { get; set; } - } + public ITickGenerator? TickGenerator { get; set; } } diff --git a/src/LibHac/HorizonFactory.cs b/src/LibHac/HorizonFactory.cs index 8850a88f..b4beef80 100644 --- a/src/LibHac/HorizonFactory.cs +++ b/src/LibHac/HorizonFactory.cs @@ -3,33 +3,32 @@ using LibHac.Common.Keys; using LibHac.Fs.Fsa; using LibHac.FsSrv; -namespace LibHac +namespace LibHac; + +public static class HorizonFactory { - public static class HorizonFactory + public static Horizon CreateWithDefaultFsConfig(HorizonConfiguration config, IFileSystem rootFileSystem, + KeySet keySet) { - public static Horizon CreateWithDefaultFsConfig(HorizonConfiguration config, IFileSystem rootFileSystem, - KeySet keySet) + var horizon = new Horizon(config); + + HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); + var fsServer = new FileSystemServer(fsServerClient); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer); + + var fsServerConfig = new FileSystemServerConfig { - var horizon = new Horizon(config); + DeviceOperator = defaultObjects.DeviceOperator, + ExternalKeySet = keySet.ExternalKeySet, + FsCreators = defaultObjects.FsCreators, + }; - HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); - var fsServer = new FileSystemServer(fsServerClient); + FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer); + HorizonClient bcatServerClient = horizon.CreateHorizonClient(); + _ = new BcatServer(bcatServerClient); - var fsServerConfig = new FileSystemServerConfig - { - DeviceOperator = defaultObjects.DeviceOperator, - ExternalKeySet = keySet.ExternalKeySet, - FsCreators = defaultObjects.FsCreators, - }; - - FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); - - HorizonClient bcatServerClient = horizon.CreateHorizonClient(); - _ = new BcatServer(bcatServerClient); - - return horizon; - } + return horizon; } } diff --git a/src/LibHac/HorizonResultException.cs b/src/LibHac/HorizonResultException.cs index aed2b411..aa2eded6 100644 --- a/src/LibHac/HorizonResultException.cs +++ b/src/LibHac/HorizonResultException.cs @@ -1,69 +1,68 @@ using System; -namespace LibHac +namespace LibHac; + +public class HorizonResultException : LibHacException { - public class HorizonResultException : LibHacException + /// + /// The result code of the error. + /// + public Result ResultValue { get; } + + /// + /// The original, internal result code if it was converted to a more general external result code. + /// + public Result InternalResultValue { get; } + + public string InnerMessage { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The result code for the reason for the exception. + public HorizonResultException(Result result) { - /// - /// The result code of the error. - /// - public Result ResultValue { get; } + InternalResultValue = result; + ResultValue = result; + } - /// - /// The original, internal result code if it was converted to a more general external result code. - /// - public Result InternalResultValue { get; } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The result code for the reason for the exception. + /// The error message that explains the reason for the exception. + public HorizonResultException(Result result, string message) + { + InternalResultValue = result; + ResultValue = result; + InnerMessage = message; + } - public string InnerMessage { get; } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The result code for the reason for the exception. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public HorizonResultException(Result result, string message, Exception innerException) + : base(string.Empty, innerException) + { + InternalResultValue = result; + ResultValue = result; + InnerMessage = message; + } - /// - /// Initializes a new instance of the class. - /// - /// The result code for the reason for the exception. - public HorizonResultException(Result result) + public override string Message + { + get { - InternalResultValue = result; - ResultValue = result; - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The result code for the reason for the exception. - /// The error message that explains the reason for the exception. - public HorizonResultException(Result result, string message) - { - InternalResultValue = result; - ResultValue = result; - InnerMessage = message; - } - - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The result code for the reason for the exception. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public HorizonResultException(Result result, string message, Exception innerException) - : base(string.Empty, innerException) - { - InternalResultValue = result; - ResultValue = result; - InnerMessage = message; - } - - public override string Message - { - get + if (!string.IsNullOrWhiteSpace(InnerMessage)) { - if (!string.IsNullOrWhiteSpace(InnerMessage)) - { - return $"{ResultValue.ToStringWithName()}: {InnerMessage}"; - } - - return ResultValue.ToStringWithName(); + return $"{ResultValue.ToStringWithName()}: {InnerMessage}"; } + + return ResultValue.ToStringWithName(); } } } diff --git a/src/LibHac/IProgressReport.cs b/src/LibHac/IProgressReport.cs index 6ae7a4ca..7800ea14 100644 --- a/src/LibHac/IProgressReport.cs +++ b/src/LibHac/IProgressReport.cs @@ -1,29 +1,28 @@ -namespace LibHac +namespace LibHac; + +public interface IProgressReport { - public interface IProgressReport - { - /// - /// Sets the current value of the to . - /// - /// The value to set. - void Report(long value); + /// + /// Sets the current value of the to . + /// + /// The value to set. + void Report(long value); - /// - /// Adds to the current value of the . - /// - /// The amount to add. - void ReportAdd(long value); + /// + /// Adds to the current value of the . + /// + /// The amount to add. + void ReportAdd(long value); - /// - /// Sets the maximum value of the to . - /// - /// The maximum value to set. - void SetTotal(long value); + /// + /// Sets the maximum value of the to . + /// + /// The maximum value to set. + void SetTotal(long value); - /// - /// Logs a message to the object. - /// - /// The message to output. - void LogMessage(string message); - } -} \ No newline at end of file + /// + /// Logs a message to the object. + /// + /// The message to output. + void LogMessage(string message); +} diff --git a/src/LibHac/Kernel/IniExtract.cs b/src/LibHac/Kernel/IniExtract.cs index ad22c006..6c2f3861 100644 --- a/src/LibHac/Kernel/IniExtract.cs +++ b/src/LibHac/Kernel/IniExtract.cs @@ -3,98 +3,97 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; -namespace LibHac.Kernel +namespace LibHac.Kernel; + +public static class IniExtract { - public static class IniExtract + /// + /// Locates and returns the offset and size of the initial process binary embedded in the kernel. + /// The INI is only embedded in the kernel in system versions >= 8.0.0. + /// + /// When this method returns, contains the offset of + /// the INI inside the kernel if it was found. + /// When this method returns, contains the size of the INI if it was found. + /// An containing the kernel to search. + /// if the embedded INI was found. + public static bool TryGetIni1Offset(out int offset, out int size, IStorage kernelStorage) { - /// - /// Locates and returns the offset and size of the initial process binary embedded in the kernel. - /// The INI is only embedded in the kernel in system versions >= 8.0.0. - /// - /// When this method returns, contains the offset of - /// the INI inside the kernel if it was found. - /// When this method returns, contains the size of the INI if it was found. - /// An containing the kernel to search. - /// if the embedded INI was found. - public static bool TryGetIni1Offset(out int offset, out int size, IStorage kernelStorage) - { - offset = 0; - size = 0; + offset = 0; + size = 0; - if (kernelStorage.GetSize(out long kernelSizeLong).IsFailure()) + if (kernelStorage.GetSize(out long kernelSizeLong).IsFailure()) + return false; + + uint kernelSize = (uint)kernelSizeLong; + + using (var array = new RentedArray(0x1000 + Unsafe.SizeOf())) + { + // The kernel map should be in the first 0x1000 bytes + if (kernelStorage.Read(0, array.Span).IsFailure()) return false; - uint kernelSize = (uint)kernelSizeLong; + ref byte start = ref array.Span[0]; - using (var array = new RentedArray(0x1000 + Unsafe.SizeOf())) + // Search every 4 bytes for a valid kernel map + for (int i = 0; i < 0x1000; i += sizeof(int)) { - // The kernel map should be in the first 0x1000 bytes - if (kernelStorage.Read(0, array.Span).IsFailure()) - return false; + ref KernelMap map = ref Unsafe.As(ref Unsafe.Add(ref start, i)); - ref byte start = ref array.Span[0]; - - // Search every 4 bytes for a valid kernel map - for (int i = 0; i < 0x1000; i += sizeof(int)) + if (IsValidKernelMap(in map, kernelSize)) { - ref KernelMap map = ref Unsafe.As(ref Unsafe.Add(ref start, i)); + // Verify the ini header at the offset in the found map + var header = new InitialProcessBinaryReader.IniHeader(); - if (IsValidKernelMap(in map, kernelSize)) - { - // Verify the ini header at the offset in the found map - var header = new InitialProcessBinaryReader.IniHeader(); + if (kernelStorage.Read(map.Ini1StartOffset, SpanHelpers.AsByteSpan(ref header)).IsFailure()) + return false; - if (kernelStorage.Read(map.Ini1StartOffset, SpanHelpers.AsByteSpan(ref header)).IsFailure()) - return false; + if (header.Magic != InitialProcessBinaryReader.ExpectedMagic) + return false; - if (header.Magic != InitialProcessBinaryReader.ExpectedMagic) - return false; - - offset = (int)map.Ini1StartOffset; - size = header.Size; - return true; - } + offset = (int)map.Ini1StartOffset; + size = header.Size; + return true; } - - return false; } - } - private static bool IsValidKernelMap(in KernelMap map, uint maxSize) - { - if (map.TextStartOffset != 0) return false; - if (map.TextStartOffset >= map.TextEndOffset) return false; - if ((map.TextEndOffset & 0xFFF) != 0) return false; - if (map.TextEndOffset > map.RodataStartOffset) return false; - if ((map.RodataStartOffset & 0xFFF) != 0) return false; - if (map.RodataStartOffset >= map.RodataEndOffset) return false; - if ((map.RodataEndOffset & 0xFFF) != 0) return false; - if (map.RodataEndOffset > map.DataStartOffset) return false; - if ((map.DataStartOffset & 0xFFF) != 0) return false; - if (map.DataStartOffset >= map.DataEndOffset) return false; - if (map.DataEndOffset > map.BssStartOffset) return false; - if (map.BssStartOffset > map.BssEndOffset) return false; - if (map.BssEndOffset > map.Ini1StartOffset) return false; - if (map.Ini1StartOffset > maxSize - Unsafe.SizeOf()) return false; - - return true; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KernelMap - { - public uint TextStartOffset; - public uint TextEndOffset; - public uint RodataStartOffset; - public uint RodataEndOffset; - public uint DataStartOffset; - public uint DataEndOffset; - public uint BssStartOffset; - public uint BssEndOffset; - public uint Ini1StartOffset; - public uint DynamicOffset; - public uint InitArrayStartOffset; - public uint InitArrayEndOffset; + return false; } } + + private static bool IsValidKernelMap(in KernelMap map, uint maxSize) + { + if (map.TextStartOffset != 0) return false; + if (map.TextStartOffset >= map.TextEndOffset) return false; + if ((map.TextEndOffset & 0xFFF) != 0) return false; + if (map.TextEndOffset > map.RodataStartOffset) return false; + if ((map.RodataStartOffset & 0xFFF) != 0) return false; + if (map.RodataStartOffset >= map.RodataEndOffset) return false; + if ((map.RodataEndOffset & 0xFFF) != 0) return false; + if (map.RodataEndOffset > map.DataStartOffset) return false; + if ((map.DataStartOffset & 0xFFF) != 0) return false; + if (map.DataStartOffset >= map.DataEndOffset) return false; + if (map.DataEndOffset > map.BssStartOffset) return false; + if (map.BssStartOffset > map.BssEndOffset) return false; + if (map.BssEndOffset > map.Ini1StartOffset) return false; + if (map.Ini1StartOffset > maxSize - Unsafe.SizeOf()) return false; + + return true; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KernelMap + { + public uint TextStartOffset; + public uint TextEndOffset; + public uint RodataStartOffset; + public uint RodataEndOffset; + public uint DataStartOffset; + public uint DataEndOffset; + public uint BssStartOffset; + public uint BssEndOffset; + public uint Ini1StartOffset; + public uint DynamicOffset; + public uint InitArrayStartOffset; + public uint InitArrayEndOffset; + } } diff --git a/src/LibHac/Kernel/InitialProcessBinaryReader.cs b/src/LibHac/Kernel/InitialProcessBinaryReader.cs index 4f8f81eb..c015c89b 100644 --- a/src/LibHac/Kernel/InitialProcessBinaryReader.cs +++ b/src/LibHac/Kernel/InitialProcessBinaryReader.cs @@ -5,105 +5,104 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs; -namespace LibHac.Kernel +namespace LibHac.Kernel; + +public class InitialProcessBinaryReader : IDisposable { - public class InitialProcessBinaryReader : IDisposable + internal const uint ExpectedMagic = 0x31494E49; // INI1 + private const int MaxProcessCount = 80; + + private SharedRef _storage; + private IniHeader _header; + private (int offset, int size)[] _offsets; + + public ref readonly IniHeader Header => ref _header; + public int ProcessCount => _header.ProcessCount; + + public void Dispose() { - internal const uint ExpectedMagic = 0x31494E49; // INI1 - private const int MaxProcessCount = 80; + _storage.Destroy(); + } - private SharedRef _storage; - private IniHeader _header; - private (int offset, int size)[] _offsets; + public Result Initialize(in SharedRef binaryStorage) + { + if (!binaryStorage.HasValue) + return ResultLibHac.NullArgument.Log(); - public ref readonly IniHeader Header => ref _header; - public int ProcessCount => _header.ProcessCount; + // Verify there's enough data to read the header + Result rc = binaryStorage.Get.GetSize(out long iniSize); + if (rc.IsFailure()) return rc; - public void Dispose() + if (iniSize < Unsafe.SizeOf()) + return ResultLibHac.InvalidIniFileSize.Log(); + + // Read the INI file header and validate some of its values. + rc = binaryStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; + + if (_header.Magic != ExpectedMagic) + return ResultLibHac.InvalidIniMagic.Log(); + + if ((uint)_header.ProcessCount > MaxProcessCount) + return ResultLibHac.InvalidIniProcessCount.Log(); + + // There's no metadata with the offsets of each KIP; they're all stored sequentially in the file. + // Read the size of each KIP to get their offsets. + rc = GetKipOffsets(out _offsets, binaryStorage, _header.ProcessCount); + if (rc.IsFailure()) return rc; + + _storage.SetByCopy(in binaryStorage); + return Result.Success; + } + + public Result OpenKipStorage(ref UniqueRef outStorage, int index) + { + if ((uint)index >= _header.ProcessCount) + return ResultLibHac.ArgumentOutOfRange.Log(); + + (int offset, int size) range = _offsets[index]; + outStorage.Reset(new SubStorage(in _storage, range.offset, range.size)); + return Result.Success; + } + + private static Result GetKipOffsets(out (int offset, int size)[] kipOffsets, in SharedRef iniStorage, + int processCount) + { + Assert.SdkRequiresLessEqual(processCount, MaxProcessCount); + + UnsafeHelpers.SkipParamInit(out kipOffsets); + + Result rc = iniStorage.Get.GetSize(out long iniStorageSize); + if (rc.IsFailure()) return rc.Miss(); + + var offsets = new (int offset, int size)[processCount]; + int offset = Unsafe.SizeOf(); + using var kipReader = new KipReader(); + + for (int i = 0; i < processCount; i++) { - _storage.Destroy(); - } + using var kipStorage = + new SharedRef(new SubStorage(in iniStorage, offset, iniStorageSize - offset)); - public Result Initialize(in SharedRef binaryStorage) - { - if (!binaryStorage.HasValue) - return ResultLibHac.NullArgument.Log(); - - // Verify there's enough data to read the header - Result rc = binaryStorage.Get.GetSize(out long iniSize); + rc = kipReader.Initialize(in kipStorage); if (rc.IsFailure()) return rc; - if (iniSize < Unsafe.SizeOf()) - return ResultLibHac.InvalidIniFileSize.Log(); - - // Read the INI file header and validate some of its values. - rc = binaryStorage.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); - if (rc.IsFailure()) return rc; - - if (_header.Magic != ExpectedMagic) - return ResultLibHac.InvalidIniMagic.Log(); - - if ((uint)_header.ProcessCount > MaxProcessCount) - return ResultLibHac.InvalidIniProcessCount.Log(); - - // There's no metadata with the offsets of each KIP; they're all stored sequentially in the file. - // Read the size of each KIP to get their offsets. - rc = GetKipOffsets(out _offsets, binaryStorage, _header.ProcessCount); - if (rc.IsFailure()) return rc; - - _storage.SetByCopy(in binaryStorage); - return Result.Success; + int kipSize = kipReader.GetFileSize(); + offsets[i] = (offset, kipSize); + offset += kipSize; } - public Result OpenKipStorage(ref UniqueRef outStorage, int index) - { - if ((uint)index >= _header.ProcessCount) - return ResultLibHac.ArgumentOutOfRange.Log(); - - (int offset, int size) range = _offsets[index]; - outStorage.Reset(new SubStorage(in _storage, range.offset, range.size)); - return Result.Success; - } - - private static Result GetKipOffsets(out (int offset, int size)[] kipOffsets, in SharedRef iniStorage, - int processCount) - { - Assert.SdkRequiresLessEqual(processCount, MaxProcessCount); - - UnsafeHelpers.SkipParamInit(out kipOffsets); - - Result rc = iniStorage.Get.GetSize(out long iniStorageSize); - if (rc.IsFailure()) return rc.Miss(); - - var offsets = new (int offset, int size)[processCount]; - int offset = Unsafe.SizeOf(); - using var kipReader = new KipReader(); - - for (int i = 0; i < processCount; i++) - { - using var kipStorage = - new SharedRef(new SubStorage(in iniStorage, offset, iniStorageSize - offset)); - - rc = kipReader.Initialize(in kipStorage); - if (rc.IsFailure()) return rc; - - int kipSize = kipReader.GetFileSize(); - offsets[i] = (offset, kipSize); - offset += kipSize; - } - - kipOffsets = offsets; - return Result.Success; - } + kipOffsets = offsets; + return Result.Success; + } - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct IniHeader - { - public uint Magic; - public int Size; - public int ProcessCount; - public uint Reserved; - } + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct IniHeader + { + public uint Magic; + public int Size; + public int ProcessCount; + public uint Reserved; } } diff --git a/src/LibHac/Kernel/KipHeader.cs b/src/LibHac/Kernel/KipHeader.cs index f1f376af..649e7cb3 100644 --- a/src/LibHac/Kernel/KipHeader.cs +++ b/src/LibHac/Kernel/KipHeader.cs @@ -3,74 +3,73 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Kernel +namespace LibHac.Kernel; + +[StructLayout(LayoutKind.Explicit, Size = 0x100)] +public struct KipHeader { - [StructLayout(LayoutKind.Explicit, Size = 0x100)] - public struct KipHeader + public const uint Kip1Magic = 0x3150494B; // KIP1 + public const int NameSize = 12; + public const int SegmentCount = 6; + + [FieldOffset(0x00)] public uint Magic; + + [FieldOffset(0x04)] private byte _name; + + [FieldOffset(0x10)] public ulong ProgramId; + [FieldOffset(0x18)] public int Version; + + [FieldOffset(0x1C)] public byte Priority; + [FieldOffset(0x1D)] public byte IdealCoreId; + [FieldOffset(0x1F)] public Flag Flags; + + [FieldOffset(0x20)] public int TextMemoryOffset; + [FieldOffset(0x24)] public int TextSize; + [FieldOffset(0x28)] public int TextFileSize; + + [FieldOffset(0x2C)] public int AffinityMask; + + [FieldOffset(0x30)] public int RoMemoryOffset; + [FieldOffset(0x34)] public int RoSize; + [FieldOffset(0x38)] public int RoFileSize; + + [FieldOffset(0x3C)] public int StackSize; + + [FieldOffset(0x40)] public int DataMemoryOffset; + [FieldOffset(0x44)] public int DataSize; + [FieldOffset(0x48)] public int DataFileSize; + + [FieldOffset(0x50)] public int BssMemoryOffset; + [FieldOffset(0x54)] public int BssSize; + [FieldOffset(0x58)] public int BssFileSize; + + [FieldOffset(0x80)] private uint _capabilities; + + public Span Name => SpanHelpers.CreateSpan(ref _name, NameSize); + + public Span Segments => + SpanHelpers.CreateSpan(ref Unsafe.As(ref TextMemoryOffset), SegmentCount); + + public Span Capabilities => SpanHelpers.CreateSpan(ref _capabilities, 0x80 / sizeof(uint)); + + public bool IsValid => Magic == Kip1Magic; + + [Flags] + public enum Flag : byte { - public const uint Kip1Magic = 0x3150494B; // KIP1 - public const int NameSize = 12; - public const int SegmentCount = 6; + TextCompress = 1 << 0, + RoCompress = 1 << 1, + DataCompress = 1 << 2, + Is64BitInstruction = 1 << 3, + ProcessAddressSpace64Bit = 1 << 4, + UseSecureMemory = 1 << 5 + } - [FieldOffset(0x00)] public uint Magic; - - [FieldOffset(0x04)] private byte _name; - - [FieldOffset(0x10)] public ulong ProgramId; - [FieldOffset(0x18)] public int Version; - - [FieldOffset(0x1C)] public byte Priority; - [FieldOffset(0x1D)] public byte IdealCoreId; - [FieldOffset(0x1F)] public Flag Flags; - - [FieldOffset(0x20)] public int TextMemoryOffset; - [FieldOffset(0x24)] public int TextSize; - [FieldOffset(0x28)] public int TextFileSize; - - [FieldOffset(0x2C)] public int AffinityMask; - - [FieldOffset(0x30)] public int RoMemoryOffset; - [FieldOffset(0x34)] public int RoSize; - [FieldOffset(0x38)] public int RoFileSize; - - [FieldOffset(0x3C)] public int StackSize; - - [FieldOffset(0x40)] public int DataMemoryOffset; - [FieldOffset(0x44)] public int DataSize; - [FieldOffset(0x48)] public int DataFileSize; - - [FieldOffset(0x50)] public int BssMemoryOffset; - [FieldOffset(0x54)] public int BssSize; - [FieldOffset(0x58)] public int BssFileSize; - - [FieldOffset(0x80)] private uint _capabilities; - - public Span Name => SpanHelpers.CreateSpan(ref _name, NameSize); - - public Span Segments => - SpanHelpers.CreateSpan(ref Unsafe.As(ref TextMemoryOffset), SegmentCount); - - public Span Capabilities => SpanHelpers.CreateSpan(ref _capabilities, 0x80 / sizeof(uint)); - - public bool IsValid => Magic == Kip1Magic; - - [Flags] - public enum Flag : byte - { - TextCompress = 1 << 0, - RoCompress = 1 << 1, - DataCompress = 1 << 2, - Is64BitInstruction = 1 << 3, - ProcessAddressSpace64Bit = 1 << 4, - UseSecureMemory = 1 << 5 - } - - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct SegmentHeader - { - public int MemoryOffset; - public int Size; - public int FileSize; - } + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct SegmentHeader + { + public int MemoryOffset; + public int Size; + public int FileSize; } } diff --git a/src/LibHac/Kernel/KipReader.cs b/src/LibHac/Kernel/KipReader.cs index 78df5080..901c87b6 100644 --- a/src/LibHac/Kernel/KipReader.cs +++ b/src/LibHac/Kernel/KipReader.cs @@ -6,307 +6,306 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; -namespace LibHac.Kernel +namespace LibHac.Kernel; + +public class KipReader : IDisposable { - public class KipReader : IDisposable + private SharedRef _kipStorage; + + private KipHeader _header; + + public ReadOnlySpan Capabilities => _header.Capabilities; + public U8Span Name => new U8Span(_header.Name); + + public ulong ProgramId => _header.ProgramId; + public int Version => _header.Version; + public byte Priority => _header.Priority; + public byte IdealCoreId => _header.IdealCoreId; + + public bool IsTextCompressed => _header.Flags.HasFlag(KipHeader.Flag.TextCompress); + public bool IsRoCompressed => _header.Flags.HasFlag(KipHeader.Flag.RoCompress); + public bool IsDataCompressed => _header.Flags.HasFlag(KipHeader.Flag.DataCompress); + public bool Is64Bit => _header.Flags.HasFlag(KipHeader.Flag.Is64BitInstruction); + public bool Is64BitAddressSpace => _header.Flags.HasFlag(KipHeader.Flag.ProcessAddressSpace64Bit); + public bool UsesSecureMemory => _header.Flags.HasFlag(KipHeader.Flag.UseSecureMemory); + + public ReadOnlySpan Segments => _header.Segments; + + public int AffinityMask => _header.AffinityMask; + public int StackSize => _header.StackSize; + + public void Dispose() { - private SharedRef _kipStorage; + _kipStorage.Destroy(); + } - private KipHeader _header; + public Result Initialize(in SharedRef kipData) + { + if (!kipData.HasValue) + return ResultLibHac.NullArgument.Log(); - public ReadOnlySpan Capabilities => _header.Capabilities; - public U8Span Name => new U8Span(_header.Name); + // Verify there's enough data to read the header + Result rc = kipData.Get.GetSize(out long kipSize); + if (rc.IsFailure()) return rc; - public ulong ProgramId => _header.ProgramId; - public int Version => _header.Version; - public byte Priority => _header.Priority; - public byte IdealCoreId => _header.IdealCoreId; + if (kipSize < Unsafe.SizeOf()) + return ResultLibHac.InvalidKipFileSize.Log(); - public bool IsTextCompressed => _header.Flags.HasFlag(KipHeader.Flag.TextCompress); - public bool IsRoCompressed => _header.Flags.HasFlag(KipHeader.Flag.RoCompress); - public bool IsDataCompressed => _header.Flags.HasFlag(KipHeader.Flag.DataCompress); - public bool Is64Bit => _header.Flags.HasFlag(KipHeader.Flag.Is64BitInstruction); - public bool Is64BitAddressSpace => _header.Flags.HasFlag(KipHeader.Flag.ProcessAddressSpace64Bit); - public bool UsesSecureMemory => _header.Flags.HasFlag(KipHeader.Flag.UseSecureMemory); + rc = kipData.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; - public ReadOnlySpan Segments => _header.Segments; + if (!_header.IsValid) + return ResultLibHac.InvalidKipMagic.Log(); - public int AffinityMask => _header.AffinityMask; - public int StackSize => _header.StackSize; + _kipStorage.SetByCopy(in kipData); + return Result.Success; + } - public void Dispose() + /// + /// Gets the raw input KIP file. + /// + /// If the operation returns successfully, an + /// containing the KIP data. + /// The of the operation. + public Result GetRawData(ref UniqueRef outKipData) + { + int kipFileSize = GetFileSize(); + + Result rc = _kipStorage.Get.GetSize(out long inputFileSize); + if (rc.IsFailure()) return rc; + + // Verify the input KIP file isn't truncated + if (inputFileSize < kipFileSize) + return ResultLibHac.InvalidKipFileSize.Log(); + + outKipData.Reset(new SubStorage(in _kipStorage, 0, kipFileSize)); + return Result.Success; + } + + public Result GetSegmentSize(SegmentType segment, out int size) + { + UnsafeHelpers.SkipParamInit(out size); + + switch (segment) { - _kipStorage.Destroy(); - } - - public Result Initialize(in SharedRef kipData) - { - if (!kipData.HasValue) - return ResultLibHac.NullArgument.Log(); - - // Verify there's enough data to read the header - Result rc = kipData.Get.GetSize(out long kipSize); - if (rc.IsFailure()) return rc; - - if (kipSize < Unsafe.SizeOf()) - return ResultLibHac.InvalidKipFileSize.Log(); - - rc = kipData.Get.Read(0, SpanHelpers.AsByteSpan(ref _header)); - if (rc.IsFailure()) return rc; - - if (!_header.IsValid) - return ResultLibHac.InvalidKipMagic.Log(); - - _kipStorage.SetByCopy(in kipData); - return Result.Success; - } - - /// - /// Gets the raw input KIP file. - /// - /// If the operation returns successfully, an - /// containing the KIP data. - /// The of the operation. - public Result GetRawData(ref UniqueRef outKipData) - { - int kipFileSize = GetFileSize(); - - Result rc = _kipStorage.Get.GetSize(out long inputFileSize); - if (rc.IsFailure()) return rc; - - // Verify the input KIP file isn't truncated - if (inputFileSize < kipFileSize) - return ResultLibHac.InvalidKipFileSize.Log(); - - outKipData.Reset(new SubStorage(in _kipStorage, 0, kipFileSize)); - return Result.Success; - } - - public Result GetSegmentSize(SegmentType segment, out int size) - { - UnsafeHelpers.SkipParamInit(out size); - - switch (segment) - { - case SegmentType.Text: - case SegmentType.Ro: - case SegmentType.Data: - case SegmentType.Bss: - case SegmentType.Reserved1: - case SegmentType.Reserved2: - size = _header.Segments[(int)segment].Size; - return Result.Success; - default: - return ResultLibHac.ArgumentOutOfRange.Log(); - } - } - - public int GetFileSize() - { - int size = Unsafe.SizeOf(); - - for (int i = 0; i < Segments.Length; i++) - { - size += Segments[i].FileSize; - } - - return size; - } - - public int GetUncompressedSize() - { - int size = Unsafe.SizeOf(); - - for (int i = 0; i < Segments.Length; i++) - { - if (Segments[i].FileSize != 0) - { - size += Segments[i].Size; - } - } - - return size; - } - - public Result ReadSegment(SegmentType segment, Span buffer) - { - Result rc = GetSegmentSize(segment, out int segmentSize); - if (rc.IsFailure()) return rc; - - if (buffer.Length < segmentSize) - return ResultLibHac.BufferTooSmall.Log(); - - KipHeader.SegmentHeader segmentHeader = Segments[(int)segment]; - - // Return early for empty segments. - if (segmentHeader.Size == 0) + case SegmentType.Text: + case SegmentType.Ro: + case SegmentType.Data: + case SegmentType.Bss: + case SegmentType.Reserved1: + case SegmentType.Reserved2: + size = _header.Segments[(int)segment].Size; return Result.Success; - - // The segment is all zeros if it has no data. - if (segmentHeader.FileSize == 0) - { - buffer.Slice(0, segmentHeader.Size).Clear(); - return Result.Success; - } - - int offset = CalculateSegmentOffset((int)segment); - - // Verify the segment offset is in-range - rc = _kipStorage.Get.GetSize(out long kipSize); - if (rc.IsFailure()) return rc; - - if (kipSize < offset + segmentHeader.FileSize) - return ResultLibHac.InvalidKipFileSize.Log(); - - // Read the segment data. - rc = _kipStorage.Get.Read(offset, buffer.Slice(0, segmentHeader.FileSize)); - if (rc.IsFailure()) return rc; - - // Decompress if necessary. - bool isCompressed = segment switch - { - SegmentType.Text => IsTextCompressed, - SegmentType.Ro => IsRoCompressed, - SegmentType.Data => IsDataCompressed, - _ => false - }; - - if (isCompressed) - { - rc = DecompressBlz(buffer, segmentHeader.FileSize); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - - public Result ReadUncompressedKip(Span buffer) - { - if (buffer.Length < GetUncompressedSize()) - return ResultLibHac.BufferTooSmall.Log(); - - Span segmentBuffer = buffer.Slice(Unsafe.SizeOf()); - - // Read each of the segments into the buffer. - for (int i = 0; i < Segments.Length; i++) - { - if (Segments[i].FileSize != 0) - { - Result rc = ReadSegment((SegmentType)i, segmentBuffer); - if (rc.IsFailure()) return rc; - - segmentBuffer = segmentBuffer.Slice(Segments[i].Size); - } - } - - // Copy the header to the buffer and update the sizes and flags. - ref KipHeader header = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - header = _header; - - // Remove any compression flags. - const KipHeader.Flag compressFlagsMask = - ~(KipHeader.Flag.TextCompress | KipHeader.Flag.RoCompress | KipHeader.Flag.DataCompress); - - header.Flags &= compressFlagsMask; - - // Update each segment's uncompressed size. - foreach (ref KipHeader.SegmentHeader segment in header.Segments) - { - if (segment.FileSize != 0) - { - segment.FileSize = segment.Size; - } - } - - return Result.Success; - } - - private int CalculateSegmentOffset(int index) - { - Debug.Assert((uint)index <= (uint)SegmentType.Reserved2); - - int offset = Unsafe.SizeOf(); - ReadOnlySpan segments = Segments; - - for (int i = 0; i < index; i++) - { - offset += segments[i].FileSize; - } - - return offset; - } - - private static Result DecompressBlz(Span buffer, int compressedDataSize) - { - const int segmentFooterSize = 12; - - if (buffer.Length < segmentFooterSize) - return ResultLibHac.InvalidKipSegmentSize.Log(); - - // Parse the footer, endian agnostic. - Span footer = buffer.Slice(compressedDataSize - segmentFooterSize); - int totalCompSize = BinaryPrimitives.ReadInt32LittleEndian(footer); - int footerSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(4)); - int additionalSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(8)); - - if (buffer.Length < totalCompSize + additionalSize) - return ResultLibHac.BufferTooSmall.Log(); - - Span data = buffer.Slice(compressedDataSize - totalCompSize); - - int inOffset = totalCompSize - footerSize; - int outOffset = totalCompSize + additionalSize; - - while (outOffset != 0) - { - byte control = data[--inOffset]; - - // Each bit in the control byte is a flag indicating compressed or not compressed. - for (int i = 0; i < 8; i++) - { - if ((control & 0x80) != 0) - { - if (inOffset < 2) - return ResultLibHac.KipSegmentDecompressionFailed.Log(); - - inOffset -= 2; - ushort segmentValue = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(inOffset)); - int segmentOffset = (segmentValue & 0x0FFF) + 3; - int segmentSize = Math.Min(((segmentValue >> 12) & 0xF) + 3, outOffset); - - outOffset -= segmentSize; - - for (int j = 0; j < segmentSize; j++) - { - data[outOffset + j] = data[outOffset + segmentOffset + j]; - } - } - else - { - if (inOffset < 1) - return ResultLibHac.KipSegmentDecompressionFailed.Log(); - - // Copy directly. - data[--outOffset] = data[--inOffset]; - } - control <<= 1; - - if (outOffset == 0) - return Result.Success; - } - } - - return Result.Success; - } - - public enum SegmentType - { - Text = 0, - Ro = 1, - Data = 2, - Bss = 3, - Reserved1 = 4, - Reserved2 = 5 + default: + return ResultLibHac.ArgumentOutOfRange.Log(); } } + + public int GetFileSize() + { + int size = Unsafe.SizeOf(); + + for (int i = 0; i < Segments.Length; i++) + { + size += Segments[i].FileSize; + } + + return size; + } + + public int GetUncompressedSize() + { + int size = Unsafe.SizeOf(); + + for (int i = 0; i < Segments.Length; i++) + { + if (Segments[i].FileSize != 0) + { + size += Segments[i].Size; + } + } + + return size; + } + + public Result ReadSegment(SegmentType segment, Span buffer) + { + Result rc = GetSegmentSize(segment, out int segmentSize); + if (rc.IsFailure()) return rc; + + if (buffer.Length < segmentSize) + return ResultLibHac.BufferTooSmall.Log(); + + KipHeader.SegmentHeader segmentHeader = Segments[(int)segment]; + + // Return early for empty segments. + if (segmentHeader.Size == 0) + return Result.Success; + + // The segment is all zeros if it has no data. + if (segmentHeader.FileSize == 0) + { + buffer.Slice(0, segmentHeader.Size).Clear(); + return Result.Success; + } + + int offset = CalculateSegmentOffset((int)segment); + + // Verify the segment offset is in-range + rc = _kipStorage.Get.GetSize(out long kipSize); + if (rc.IsFailure()) return rc; + + if (kipSize < offset + segmentHeader.FileSize) + return ResultLibHac.InvalidKipFileSize.Log(); + + // Read the segment data. + rc = _kipStorage.Get.Read(offset, buffer.Slice(0, segmentHeader.FileSize)); + if (rc.IsFailure()) return rc; + + // Decompress if necessary. + bool isCompressed = segment switch + { + SegmentType.Text => IsTextCompressed, + SegmentType.Ro => IsRoCompressed, + SegmentType.Data => IsDataCompressed, + _ => false + }; + + if (isCompressed) + { + rc = DecompressBlz(buffer, segmentHeader.FileSize); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + public Result ReadUncompressedKip(Span buffer) + { + if (buffer.Length < GetUncompressedSize()) + return ResultLibHac.BufferTooSmall.Log(); + + Span segmentBuffer = buffer.Slice(Unsafe.SizeOf()); + + // Read each of the segments into the buffer. + for (int i = 0; i < Segments.Length; i++) + { + if (Segments[i].FileSize != 0) + { + Result rc = ReadSegment((SegmentType)i, segmentBuffer); + if (rc.IsFailure()) return rc; + + segmentBuffer = segmentBuffer.Slice(Segments[i].Size); + } + } + + // Copy the header to the buffer and update the sizes and flags. + ref KipHeader header = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + header = _header; + + // Remove any compression flags. + const KipHeader.Flag compressFlagsMask = + ~(KipHeader.Flag.TextCompress | KipHeader.Flag.RoCompress | KipHeader.Flag.DataCompress); + + header.Flags &= compressFlagsMask; + + // Update each segment's uncompressed size. + foreach (ref KipHeader.SegmentHeader segment in header.Segments) + { + if (segment.FileSize != 0) + { + segment.FileSize = segment.Size; + } + } + + return Result.Success; + } + + private int CalculateSegmentOffset(int index) + { + Debug.Assert((uint)index <= (uint)SegmentType.Reserved2); + + int offset = Unsafe.SizeOf(); + ReadOnlySpan segments = Segments; + + for (int i = 0; i < index; i++) + { + offset += segments[i].FileSize; + } + + return offset; + } + + private static Result DecompressBlz(Span buffer, int compressedDataSize) + { + const int segmentFooterSize = 12; + + if (buffer.Length < segmentFooterSize) + return ResultLibHac.InvalidKipSegmentSize.Log(); + + // Parse the footer, endian agnostic. + Span footer = buffer.Slice(compressedDataSize - segmentFooterSize); + int totalCompSize = BinaryPrimitives.ReadInt32LittleEndian(footer); + int footerSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(4)); + int additionalSize = BinaryPrimitives.ReadInt32LittleEndian(footer.Slice(8)); + + if (buffer.Length < totalCompSize + additionalSize) + return ResultLibHac.BufferTooSmall.Log(); + + Span data = buffer.Slice(compressedDataSize - totalCompSize); + + int inOffset = totalCompSize - footerSize; + int outOffset = totalCompSize + additionalSize; + + while (outOffset != 0) + { + byte control = data[--inOffset]; + + // Each bit in the control byte is a flag indicating compressed or not compressed. + for (int i = 0; i < 8; i++) + { + if ((control & 0x80) != 0) + { + if (inOffset < 2) + return ResultLibHac.KipSegmentDecompressionFailed.Log(); + + inOffset -= 2; + ushort segmentValue = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(inOffset)); + int segmentOffset = (segmentValue & 0x0FFF) + 3; + int segmentSize = Math.Min(((segmentValue >> 12) & 0xF) + 3, outOffset); + + outOffset -= segmentSize; + + for (int j = 0; j < segmentSize; j++) + { + data[outOffset + j] = data[outOffset + segmentOffset + j]; + } + } + else + { + if (inOffset < 1) + return ResultLibHac.KipSegmentDecompressionFailed.Log(); + + // Copy directly. + data[--outOffset] = data[--inOffset]; + } + control <<= 1; + + if (outOffset == 0) + return Result.Success; + } + } + + return Result.Success; + } + + public enum SegmentType + { + Text = 0, + Ro = 1, + Data = 2, + Bss = 3, + Reserved1 = 4, + Reserved2 = 5 + } } diff --git a/src/LibHac/KeyType.cs b/src/LibHac/KeyType.cs index be68f96e..44c249d3 100644 --- a/src/LibHac/KeyType.cs +++ b/src/LibHac/KeyType.cs @@ -1,10 +1,9 @@ -namespace LibHac +namespace LibHac; + +public enum KeyType { - public enum KeyType - { - None, - Common, - Unique, - Title - } + None, + Common, + Unique, + Title } diff --git a/src/LibHac/Kvdb/AutoBuffer.cs b/src/LibHac/Kvdb/AutoBuffer.cs index 98dbd930..f7c00164 100644 --- a/src/LibHac/Kvdb/AutoBuffer.cs +++ b/src/LibHac/Kvdb/AutoBuffer.cs @@ -1,35 +1,34 @@ using System; -namespace LibHac.Kvdb +namespace LibHac.Kvdb; + +internal struct AutoBuffer : IDisposable { - internal struct AutoBuffer : IDisposable + private const int Alignment = 0x10; + + private MemoryResource _memoryResource; + private MemoryResource.Buffer _buffer; + + public Span Get() => _buffer.Get(); + public int GetSize() => _buffer.Length; + + public Result Initialize(long size, MemoryResource memoryResource) { - private const int Alignment = 0x10; + MemoryResource.Buffer buffer = memoryResource.Allocate(size, Alignment); + if (!buffer.IsValid) + return ResultKvdb.AllocationFailed.Log(); - private MemoryResource _memoryResource; - private MemoryResource.Buffer _buffer; + _memoryResource = memoryResource; + _buffer = buffer; - public Span Get() => _buffer.Get(); - public int GetSize() => _buffer.Length; + return Result.Success; + } - public Result Initialize(long size, MemoryResource memoryResource) + public void Dispose() + { + if (_buffer.IsValid) { - MemoryResource.Buffer buffer = memoryResource.Allocate(size, Alignment); - if (!buffer.IsValid) - return ResultKvdb.AllocationFailed.Log(); - - _memoryResource = memoryResource; - _buffer = buffer; - - return Result.Success; - } - - public void Dispose() - { - if (_buffer.IsValid) - { - _memoryResource.Deallocate(ref _buffer, Alignment); - } + _memoryResource.Deallocate(ref _buffer, Alignment); } } } diff --git a/src/LibHac/Kvdb/BoundedString.cs b/src/LibHac/Kvdb/BoundedString.cs index 0a687966..3a3f24f5 100644 --- a/src/LibHac/Kvdb/BoundedString.cs +++ b/src/LibHac/Kvdb/BoundedString.cs @@ -4,18 +4,17 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac.Kvdb +namespace LibHac.Kvdb; + +public struct BoundedString where TSize : unmanaged { - public struct BoundedString where TSize : unmanaged - { - private TSize _string; + private TSize _string; - public Span Get() => SpanHelpers.AsByteSpan(ref _string); + public Span Get() => SpanHelpers.AsByteSpan(ref _string); - public int GetLength() => - StringUtils.GetLength(SpanHelpers.AsReadOnlyByteSpan(in _string), Unsafe.SizeOf()); - } - - [StructLayout(LayoutKind.Sequential, Size = 768)] - internal struct Size768 { } + public int GetLength() => + StringUtils.GetLength(SpanHelpers.AsReadOnlyByteSpan(in _string), Unsafe.SizeOf()); } + +[StructLayout(LayoutKind.Sequential, Size = 768)] +internal struct Size768 { } diff --git a/src/LibHac/Kvdb/FlatMapKeyValueStore.cs b/src/LibHac/Kvdb/FlatMapKeyValueStore.cs index a4af8343..2924a54d 100644 --- a/src/LibHac/Kvdb/FlatMapKeyValueStore.cs +++ b/src/LibHac/Kvdb/FlatMapKeyValueStore.cs @@ -6,178 +6,433 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.Kvdb +namespace LibHac.Kvdb; + +/// +/// Represents a collection of keys and values that are sorted by the key, +/// and may be saved and loaded from an archive file on disk. +/// +/// The type of the keys in the keys in the key-value store. +public class FlatMapKeyValueStore : IDisposable where TKey : unmanaged, IEquatable, IComparable { - /// - /// Represents a collection of keys and values that are sorted by the key, - /// and may be saved and loaded from an archive file on disk. - /// - /// The type of the keys in the keys in the key-value store. - public class FlatMapKeyValueStore : IDisposable where TKey : unmanaged, IEquatable, IComparable - { - private const int Alignment = 0x10; + private const int Alignment = 0x10; - private FileSystemClient _fsClient; - private Index _index; - private BoundedString _archivePath; - private MemoryResource _memoryResource; - private MemoryResource _memoryResourceForAutoBuffers; + private FileSystemClient _fsClient; + private Index _index; + private BoundedString _archivePath; + private MemoryResource _memoryResource; + private MemoryResource _memoryResourceForAutoBuffers; - private static ReadOnlySpan ArchiveFileName => // /imkvdb.arc - new[] - { - (byte) '/', (byte) 'i', (byte) 'm', (byte) 'k', (byte) 'v', (byte) 'd', (byte) 'b', (byte) '.', - (byte) 'a', (byte) 'r', (byte) 'c' - }; - - public int Count => _index.Count; - - public FlatMapKeyValueStore() + private static ReadOnlySpan ArchiveFileName => // /imkvdb.arc + new[] { - _index = new Index(); + (byte) '/', (byte) 'i', (byte) 'm', (byte) 'k', (byte) 'v', (byte) 'd', (byte) 'b', (byte) '.', + (byte) 'a', (byte) 'r', (byte) 'c' + }; - Unsafe.SkipInit(out _archivePath); - _archivePath.Get()[0] = 0; + public int Count => _index.Count; + + public FlatMapKeyValueStore() + { + _index = new Index(); + + Unsafe.SkipInit(out _archivePath); + _archivePath.Get()[0] = 0; + } + + /// + /// Initializes a . Reads and writes the store to and from the file imkvdb.arc + /// in the specified directory. This directory must exist when calling , + /// but it is not required for the imkvdb.arc file to exist. + /// + /// The to use for reading and writing the archive. + /// The directory path used to load and save the archive file. Directory must already exist. + /// The maximum number of entries that can be stored. + /// for allocating buffers to hold entries and values. + /// for allocating temporary buffers + /// when reading and writing the store to a file. + /// The of the operation. + public Result Initialize(FileSystemClient fsClient, U8Span rootPath, int capacity, + MemoryResource memoryResource, MemoryResource autoBufferMemoryResource) + { + // The root path must be an existing directory + Result rc = fsClient.GetEntryType(out DirectoryEntryType rootEntryType, rootPath); + if (rc.IsFailure()) return rc; + + if (rootEntryType == DirectoryEntryType.File) + return ResultFs.PathNotFound.Log(); + + var sb = new U8StringBuilder(_archivePath.Get()); + sb.Append(rootPath).Append(ArchiveFileName); + + rc = _index.Initialize(capacity, memoryResource); + if (rc.IsFailure()) return rc; + + _fsClient = fsClient; + _memoryResource = memoryResource; + _memoryResourceForAutoBuffers = autoBufferMemoryResource; + + return Result.Success; + } + + public void Dispose() + { + _index.Dispose(); + } + + /// + /// Clears all entries in the and loads all entries + /// from the database archive file, if it exists. + /// + /// The of the operation. + public Result Load() + { + // Clear any existing entries. + _index.Clear(); + + var buffer = new AutoBuffer(); + + try + { + Result rc = ReadArchive(ref buffer); + if (rc.IsFailure()) + { + // If the file is not found, we don't have any entries to load. + if (ResultFs.PathNotFound.Includes(rc)) + return Result.Success.LogConverted(rc); + + return rc; + } + + rc = LoadFrom(buffer.Get()); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Writes all entries in the to a database archive file. + /// + /// The of the operation. + public Result Save() + { + // Create a buffer to hold the archive. + var buffer = new AutoBuffer(); + Result rc = buffer.Initialize(CalculateArchiveSize(), _memoryResourceForAutoBuffers); + if (rc.IsFailure()) return rc; + + try + { + // Write the archive to the buffer. + Span span = buffer.Get(); + var writer = new KeyValueArchiveBufferWriter(span); + SaveTo(ref writer); + + // Save the buffer to disk. + return CommitArchive(span); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Gets the value associated with the specified key. + /// + /// If the method returns successfully, contains the size of + /// the value written to . This may be smaller than the + /// actual length of the value if was not large enough. + /// The key of the value to get. + /// If the method returns successfully, contains the value + /// associated with the specified key. Otherwise, the buffer will not be modified. + /// The of the operation. + /// Possible s:
+ /// + /// The specified key was not found in the .
+ public Result Get(out int valueSize, in TKey key, Span valueBuffer) + { + UnsafeHelpers.SkipParamInit(out valueSize); + + // Find entry. + ConstIterator iterator = _index.GetLowerBoundConstIterator(in key); + if (iterator.IsEnd()) + return ResultKvdb.KeyNotFound.Log(); + + if (!key.Equals(iterator.Get().Key)) + return ResultKvdb.KeyNotFound.Log(); + + // Truncate the output if the buffer is too small. + ReadOnlySpan value = iterator.GetValue(); + int size = Math.Min(valueBuffer.Length, value.Length); + + value.Slice(0, size).CopyTo(valueBuffer); + valueSize = size; + return Result.Success; + } + + /// + /// Adds the specified key and value to the . + /// The existing value is replaced if the key already exists. + /// + /// The key to add. + /// The value to add. + /// The of the operation. + public Result Set(in TKey key, ReadOnlySpan value) + { + return _index.Set(in key, value); + } + + /// + /// Deletes an element from the . + /// + /// The key of the element to delete. + /// The of the operation. + /// Possible s:
+ /// + /// The specified key was not found in the .
+ public Result Delete(in TKey key) + { + if (!_index.Delete(in key)) + return ResultKvdb.KeyNotFound.Log(); + + return Result.Success; + } + + /// + /// Creates an that starts at the first element in the . + /// + /// The created iterator. + public Iterator GetBeginIterator() + { + return _index.GetBeginIterator(); + } + + /// + /// Creates an that starts at the first element equal to or greater than + /// in the . + /// + /// The key at which to begin iteration. + /// The created iterator. + public Iterator GetLowerBoundIterator(in TKey key) + { + return _index.GetLowerBoundIterator(in key); + } + + /// + /// Fixes an iterator's current position and total length so that after an entry + /// is added or removed, the iterator will still be on the same entry. + /// + /// The iterator to fix. + /// The key that was added or removed. + public void FixIterator(ref Iterator iterator, in TKey key) + { + _index.FixIterator(ref iterator, in key); + } + + /// + /// Reads the database archive file into the provided buffer. + /// + /// The buffer the file will be read into. + /// The of the operation. + private Result ReadArchive(ref AutoBuffer buffer) + { + Result rc = _fsClient.OpenFile(out FileHandle file, new U8Span(_archivePath.Get()), OpenMode.Read); + if (rc.IsFailure()) return rc; + + try + { + rc = _fsClient.GetFileSize(out long archiveSize, file); + if (rc.IsFailure()) return rc; + + rc = buffer.Initialize(archiveSize, _memoryResourceForAutoBuffers); + if (rc.IsFailure()) return rc; + + rc = _fsClient.ReadFile(file, 0, buffer.Get()); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + finally + { + _fsClient.CloseFile(file); + } + } + + /// + /// Loads all key-value pairs from a key-value archive. + /// All keys in the archive are assumed to be in ascending order. + /// + /// The buffer containing the key-value archive. + /// The of the operation. + private Result LoadFrom(ReadOnlySpan buffer) + { + var reader = new KeyValueArchiveBufferReader(buffer); + + Result rc = reader.ReadEntryCount(out int entryCount); + if (rc.IsFailure()) return rc; + + for (int i = 0; i < entryCount; i++) + { + // Get size of key/value. + rc = reader.GetKeyValueSize(out _, out int valueSize); + if (rc.IsFailure()) return rc; + + // Allocate memory for value. + MemoryResource.Buffer newValue = _memoryResource.Allocate(valueSize, Alignment); + if (!newValue.IsValid) + return ResultKvdb.AllocationFailed.Log(); + + bool success = false; + try + { + // Read key and value. + Unsafe.SkipInit(out TKey key); + + rc = reader.ReadKeyValue(SpanHelpers.AsByteSpan(ref key), newValue.Get()); + if (rc.IsFailure()) return rc; + + rc = _index.AppendUnsafe(in key, newValue); + if (rc.IsFailure()) return rc; + + success = true; + } + finally + { + // Deallocate the buffer if we didn't succeed. + if (!success) + _memoryResource.Deallocate(ref newValue, Alignment); + } } - /// - /// Initializes a . Reads and writes the store to and from the file imkvdb.arc - /// in the specified directory. This directory must exist when calling , - /// but it is not required for the imkvdb.arc file to exist. - /// - /// The to use for reading and writing the archive. - /// The directory path used to load and save the archive file. Directory must already exist. - /// The maximum number of entries that can be stored. - /// for allocating buffers to hold entries and values. - /// for allocating temporary buffers - /// when reading and writing the store to a file. - /// The of the operation. - public Result Initialize(FileSystemClient fsClient, U8Span rootPath, int capacity, - MemoryResource memoryResource, MemoryResource autoBufferMemoryResource) + return Result.Success; + } + + private void SaveTo(ref KeyValueArchiveBufferWriter writer) + { + writer.WriteHeader(_index.Count); + + ConstIterator iterator = _index.GetBeginConstIterator(); + while (!iterator.IsEnd()) { - // The root path must be an existing directory - Result rc = fsClient.GetEntryType(out DirectoryEntryType rootEntryType, rootPath); + ReadOnlySpan key = SpanHelpers.AsReadOnlyByteSpan(in iterator.Get().Key); + writer.WriteEntry(key, iterator.GetValue()); + + iterator.Next(); + } + } + + private Result CommitArchive(ReadOnlySpan buffer) + { + var path = new U8Span(_archivePath.Get()); + + // Try to delete the archive, but allow deletion failure. + _fsClient.DeleteFile(path).IgnoreResult(); + + // Create new archive. + Result rc = _fsClient.CreateFile(path, buffer.Length); + if (rc.IsFailure()) return rc; + + // Write data to the archive. + rc = _fsClient.OpenFile(out FileHandle file, path, OpenMode.Write); + if (rc.IsFailure()) return rc; + + try + { + rc = _fsClient.WriteFile(file, 0, buffer, WriteOption.Flush); if (rc.IsFailure()) return rc; + } + finally + { + _fsClient.CloseFile(file); + } - if (rootEntryType == DirectoryEntryType.File) - return ResultFs.PathNotFound.Log(); + return Result.Success; + } - var sb = new U8StringBuilder(_archivePath.Get()); - sb.Append(rootPath).Append(ArchiveFileName); + private long CalculateArchiveSize() + { + var calculator = new KeyValueArchiveSizeCalculator(); + calculator.Initialize(); + ConstIterator iterator = _index.GetBeginConstIterator(); - rc = _index.Initialize(capacity, memoryResource); - if (rc.IsFailure()) return rc; + while (!iterator.IsEnd()) + { + calculator.AddEntry(Unsafe.SizeOf(), iterator.GetValue().Length); + iterator.Next(); + } - _fsClient = fsClient; + return calculator.Size; + } + + /// + /// Represents a key-value pair contained in a . + /// + public struct KeyValue + { + public TKey Key; + public MemoryResource.Buffer Value; + + public KeyValue(in TKey key, MemoryResource.Buffer value) + { + Key = key; + Value = value; + } + } + + /// + /// Manages the sorted list of entries in a . + /// + private struct Index : IDisposable + { + private int _count; + private int _capacity; + private KeyValue[] _entries; + private MemoryResource _memoryResource; + + /// + /// The number of elements currently in the . + /// + public int Count => _count; + + /// + /// Initializes the + /// + /// The maximum number of elements the will be able to hold. + /// The that will be used to allocate + /// memory for values added to the . + /// The of the operation. + public Result Initialize(int capacity, MemoryResource memoryResource) + { + // Initialize must only be called once. + Assert.SdkRequiresNull(_entries); + Assert.SdkRequiresNotNull(memoryResource); + + // FS uses the provided MemoryResource to allocate the KeyValue array. + // We can't do that here because the array will contain managed references. + _entries = new KeyValue[capacity]; + _capacity = capacity; _memoryResource = memoryResource; - _memoryResourceForAutoBuffers = autoBufferMemoryResource; return Result.Success; } public void Dispose() { - _index.Dispose(); - } - - /// - /// Clears all entries in the and loads all entries - /// from the database archive file, if it exists. - /// - /// The of the operation. - public Result Load() - { - // Clear any existing entries. - _index.Clear(); - - var buffer = new AutoBuffer(); - - try + if (_entries != null) { - Result rc = ReadArchive(ref buffer); - if (rc.IsFailure()) - { - // If the file is not found, we don't have any entries to load. - if (ResultFs.PathNotFound.Includes(rc)) - return Result.Success.LogConverted(rc); - - return rc; - } - - rc = LoadFrom(buffer.Get()); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - finally - { - buffer.Dispose(); + Clear(); + _entries = null; } } /// - /// Writes all entries in the to a database archive file. - /// - /// The of the operation. - public Result Save() - { - // Create a buffer to hold the archive. - var buffer = new AutoBuffer(); - Result rc = buffer.Initialize(CalculateArchiveSize(), _memoryResourceForAutoBuffers); - if (rc.IsFailure()) return rc; - - try - { - // Write the archive to the buffer. - Span span = buffer.Get(); - var writer = new KeyValueArchiveBufferWriter(span); - SaveTo(ref writer); - - // Save the buffer to disk. - return CommitArchive(span); - } - finally - { - buffer.Dispose(); - } - } - - /// - /// Gets the value associated with the specified key. - /// - /// If the method returns successfully, contains the size of - /// the value written to . This may be smaller than the - /// actual length of the value if was not large enough. - /// The key of the value to get. - /// If the method returns successfully, contains the value - /// associated with the specified key. Otherwise, the buffer will not be modified. - /// The of the operation. - /// Possible s:
- /// - /// The specified key was not found in the .
- public Result Get(out int valueSize, in TKey key, Span valueBuffer) - { - UnsafeHelpers.SkipParamInit(out valueSize); - - // Find entry. - ConstIterator iterator = _index.GetLowerBoundConstIterator(in key); - if (iterator.IsEnd()) - return ResultKvdb.KeyNotFound.Log(); - - if (!key.Equals(iterator.Get().Key)) - return ResultKvdb.KeyNotFound.Log(); - - // Truncate the output if the buffer is too small. - ReadOnlySpan value = iterator.GetValue(); - int size = Math.Min(valueBuffer.Length, value.Length); - - value.Slice(0, size).CopyTo(valueBuffer); - valueSize = size; - return Result.Success; - } - - /// - /// Adds the specified key and value to the . + /// Adds the specified key and value to the . /// The existing value is replaced if the key already exists. /// /// The key to add. @@ -185,43 +440,142 @@ namespace LibHac.Kvdb /// The of the operation. public Result Set(in TKey key, ReadOnlySpan value) { - return _index.Set(in key, value); - } + // The list is sorted by key. Find the index to insert at. + int index = GetLowerBoundIndex(in key); - /// - /// Deletes an element from the . - /// - /// The key of the element to delete. - /// The of the operation. - /// Possible s:
- /// - /// The specified key was not found in the .
- public Result Delete(in TKey key) - { - if (!_index.Delete(in key)) - return ResultKvdb.KeyNotFound.Log(); + if (index != _count && _entries[index].Key.Equals(key)) + { + // Key already exists. Free the old value. + _memoryResource.Deallocate(ref _entries[index].Value, Alignment); + } + else + { + // Need to insert a new entry. Check if there's room and shift the existing entries. + if (_count >= _capacity) + return ResultKvdb.OutOfKeyResource.Log(); + + Array.Copy(_entries, index, _entries, index + 1, _count - index); + _count++; + } + + // Allocate new value. + MemoryResource.Buffer newValue = _memoryResource.Allocate(value.Length, Alignment); + if (!newValue.IsValid) + return ResultKvdb.AllocationFailed.Log(); + + value.CopyTo(newValue.Get()); + + // Add the new entry to the list. + _entries[index] = new KeyValue(in key, newValue); return Result.Success; } /// - /// Creates an that starts at the first element in the . + /// Adds the specified key and value to the end of the list. + /// Does not verify that the list will be sorted properly. The caller must make sure the sorting will be correct. + /// Used when populating a new with already sorted entries. + /// + /// The key to add. + /// The value to add. + /// The of the operation. + public Result AppendUnsafe(in TKey key, MemoryResource.Buffer value) + { + if (_count >= _capacity) + return ResultKvdb.OutOfKeyResource.Log(); + + if (_count > 0) + { + // The key being added must be greater than the last key in the list. + Assert.SdkGreater(key, _entries[_count - 1].Key); + } + + _entries[_count] = new KeyValue(in key, value); + _count++; + + return Result.Success; + } + + /// + /// Removes all keys and values from the . + /// + public void Clear() + { + Span entries = _entries.AsSpan(0, _count); + + for (int i = 0; i < entries.Length; i++) + { + _memoryResource.Deallocate(ref entries[i].Value, Alignment); + } + + _count = 0; + } + + /// + /// Deletes an element from the . + /// + /// The key of the element to delete. + /// if the item was found and deleted. + /// if the key was not in the store. + public bool Delete(in TKey key) + { + int index = GetLowerBoundIndex(in key); + + // Make sure the key was found. + if (index == _count || !_entries[index].Key.Equals(key)) + { + return false; + } + + // Free the value buffer and shift the remaining elements down + _memoryResource.Deallocate(ref _entries[index].Value, Alignment); + + Array.Copy(_entries, index + 1, _entries, index, _count - (index + 1)); + _count--; + + return true; + } + + /// + /// Returns an iterator starting at the first element in the . /// /// The created iterator. public Iterator GetBeginIterator() { - return _index.GetBeginIterator(); + return new Iterator(_entries, 0, _count); } /// - /// Creates an that starts at the first element equal to or greater than - /// in the . + /// Returns a read-only iterator starting at the first element in the . + /// + /// The created iterator. + public ConstIterator GetBeginConstIterator() + { + return new ConstIterator(_entries, 0, _count); + } + + /// + /// Returns an iterator starting at the first element greater than or equal to . /// /// The key at which to begin iteration. /// The created iterator. public Iterator GetLowerBoundIterator(in TKey key) { - return _index.GetLowerBoundIterator(in key); + int index = GetLowerBoundIndex(in key); + + return new Iterator(_entries, index, _count); + } + + /// + /// Returns a read-only iterator starting at the first element greater than or equal to . + /// + /// The key at which to begin iteration. + /// The created iterator. + public ConstIterator GetLowerBoundConstIterator(in TKey key) + { + int index = GetLowerBoundIndex(in key); + + return new ConstIterator(_entries, index, _count); } /// @@ -232,498 +586,143 @@ namespace LibHac.Kvdb /// The key that was added or removed. public void FixIterator(ref Iterator iterator, in TKey key) { - _index.FixIterator(ref iterator, in key); + int keyIndex = GetLowerBoundIndex(in key); + iterator.Fix(keyIndex, _count); } - /// - /// Reads the database archive file into the provided buffer. - /// - /// The buffer the file will be read into. - /// The of the operation. - private Result ReadArchive(ref AutoBuffer buffer) + private int GetLowerBoundIndex(in TKey key) { - Result rc = _fsClient.OpenFile(out FileHandle file, new U8Span(_archivePath.Get()), OpenMode.Read); - if (rc.IsFailure()) return rc; + // The AsSpan takes care of any bounds checking + ReadOnlySpan entries = _entries.AsSpan(0, _count); - try - { - rc = _fsClient.GetFileSize(out long archiveSize, file); - if (rc.IsFailure()) return rc; - - rc = buffer.Initialize(archiveSize, _memoryResourceForAutoBuffers); - if (rc.IsFailure()) return rc; - - rc = _fsClient.ReadFile(file, 0, buffer.Get()); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - finally - { - _fsClient.CloseFile(file); - } + return BinarySearch(ref MemoryMarshal.GetReference(entries), entries.Length, in key); } - /// - /// Loads all key-value pairs from a key-value archive. - /// All keys in the archive are assumed to be in ascending order. - /// - /// The buffer containing the key-value archive. - /// The of the operation. - private Result LoadFrom(ReadOnlySpan buffer) + private static int BinarySearch(ref KeyValue spanStart, int length, in TKey item) { - var reader = new KeyValueArchiveBufferReader(buffer); + // A tweaked version of .NET's SpanHelpers.BinarySearch + int lo = 0; + int hi = length - 1; - Result rc = reader.ReadEntryCount(out int entryCount); - if (rc.IsFailure()) return rc; + TKey tempItem = item; - for (int i = 0; i < entryCount; i++) + while (lo <= hi) { - // Get size of key/value. - rc = reader.GetKeyValueSize(out _, out int valueSize); - if (rc.IsFailure()) return rc; + int i = (int)(((uint)hi + (uint)lo) >> 1); - // Allocate memory for value. - MemoryResource.Buffer newValue = _memoryResource.Allocate(valueSize, Alignment); - if (!newValue.IsValid) - return ResultKvdb.AllocationFailed.Log(); - - bool success = false; - try + int c = tempItem.CompareTo(Unsafe.Add(ref spanStart, i).Key); + if (c == 0) { - // Read key and value. - Unsafe.SkipInit(out TKey key); - - rc = reader.ReadKeyValue(SpanHelpers.AsByteSpan(ref key), newValue.Get()); - if (rc.IsFailure()) return rc; - - rc = _index.AppendUnsafe(in key, newValue); - if (rc.IsFailure()) return rc; - - success = true; + return i; } - finally + else if (c > 0) { - // Deallocate the buffer if we didn't succeed. - if (!success) - _memoryResource.Deallocate(ref newValue, Alignment); - } - } - - return Result.Success; - } - - private void SaveTo(ref KeyValueArchiveBufferWriter writer) - { - writer.WriteHeader(_index.Count); - - ConstIterator iterator = _index.GetBeginConstIterator(); - while (!iterator.IsEnd()) - { - ReadOnlySpan key = SpanHelpers.AsReadOnlyByteSpan(in iterator.Get().Key); - writer.WriteEntry(key, iterator.GetValue()); - - iterator.Next(); - } - } - - private Result CommitArchive(ReadOnlySpan buffer) - { - var path = new U8Span(_archivePath.Get()); - - // Try to delete the archive, but allow deletion failure. - _fsClient.DeleteFile(path).IgnoreResult(); - - // Create new archive. - Result rc = _fsClient.CreateFile(path, buffer.Length); - if (rc.IsFailure()) return rc; - - // Write data to the archive. - rc = _fsClient.OpenFile(out FileHandle file, path, OpenMode.Write); - if (rc.IsFailure()) return rc; - - try - { - rc = _fsClient.WriteFile(file, 0, buffer, WriteOption.Flush); - if (rc.IsFailure()) return rc; - } - finally - { - _fsClient.CloseFile(file); - } - - return Result.Success; - } - - private long CalculateArchiveSize() - { - var calculator = new KeyValueArchiveSizeCalculator(); - calculator.Initialize(); - ConstIterator iterator = _index.GetBeginConstIterator(); - - while (!iterator.IsEnd()) - { - calculator.AddEntry(Unsafe.SizeOf(), iterator.GetValue().Length); - iterator.Next(); - } - - return calculator.Size; - } - - /// - /// Represents a key-value pair contained in a . - /// - public struct KeyValue - { - public TKey Key; - public MemoryResource.Buffer Value; - - public KeyValue(in TKey key, MemoryResource.Buffer value) - { - Key = key; - Value = value; - } - } - - /// - /// Manages the sorted list of entries in a . - /// - private struct Index : IDisposable - { - private int _count; - private int _capacity; - private KeyValue[] _entries; - private MemoryResource _memoryResource; - - /// - /// The number of elements currently in the . - /// - public int Count => _count; - - /// - /// Initializes the - /// - /// The maximum number of elements the will be able to hold. - /// The that will be used to allocate - /// memory for values added to the . - /// The of the operation. - public Result Initialize(int capacity, MemoryResource memoryResource) - { - // Initialize must only be called once. - Assert.SdkRequiresNull(_entries); - Assert.SdkRequiresNotNull(memoryResource); - - // FS uses the provided MemoryResource to allocate the KeyValue array. - // We can't do that here because the array will contain managed references. - _entries = new KeyValue[capacity]; - _capacity = capacity; - _memoryResource = memoryResource; - - return Result.Success; - } - - public void Dispose() - { - if (_entries != null) - { - Clear(); - _entries = null; - } - } - - /// - /// Adds the specified key and value to the . - /// The existing value is replaced if the key already exists. - /// - /// The key to add. - /// The value to add. - /// The of the operation. - public Result Set(in TKey key, ReadOnlySpan value) - { - // The list is sorted by key. Find the index to insert at. - int index = GetLowerBoundIndex(in key); - - if (index != _count && _entries[index].Key.Equals(key)) - { - // Key already exists. Free the old value. - _memoryResource.Deallocate(ref _entries[index].Value, Alignment); + lo = i + 1; } else { - // Need to insert a new entry. Check if there's room and shift the existing entries. - if (_count >= _capacity) - return ResultKvdb.OutOfKeyResource.Log(); - - Array.Copy(_entries, index, _entries, index + 1, _count - index); - _count++; - } - - // Allocate new value. - MemoryResource.Buffer newValue = _memoryResource.Allocate(value.Length, Alignment); - if (!newValue.IsValid) - return ResultKvdb.AllocationFailed.Log(); - - value.CopyTo(newValue.Get()); - - // Add the new entry to the list. - _entries[index] = new KeyValue(in key, newValue); - - return Result.Success; - } - - /// - /// Adds the specified key and value to the end of the list. - /// Does not verify that the list will be sorted properly. The caller must make sure the sorting will be correct. - /// Used when populating a new with already sorted entries. - /// - /// The key to add. - /// The value to add. - /// The of the operation. - public Result AppendUnsafe(in TKey key, MemoryResource.Buffer value) - { - if (_count >= _capacity) - return ResultKvdb.OutOfKeyResource.Log(); - - if (_count > 0) - { - // The key being added must be greater than the last key in the list. - Assert.SdkGreater(key, _entries[_count - 1].Key); - } - - _entries[_count] = new KeyValue(in key, value); - _count++; - - return Result.Success; - } - - /// - /// Removes all keys and values from the . - /// - public void Clear() - { - Span entries = _entries.AsSpan(0, _count); - - for (int i = 0; i < entries.Length; i++) - { - _memoryResource.Deallocate(ref entries[i].Value, Alignment); - } - - _count = 0; - } - - /// - /// Deletes an element from the . - /// - /// The key of the element to delete. - /// if the item was found and deleted. - /// if the key was not in the store. - public bool Delete(in TKey key) - { - int index = GetLowerBoundIndex(in key); - - // Make sure the key was found. - if (index == _count || !_entries[index].Key.Equals(key)) - { - return false; - } - - // Free the value buffer and shift the remaining elements down - _memoryResource.Deallocate(ref _entries[index].Value, Alignment); - - Array.Copy(_entries, index + 1, _entries, index, _count - (index + 1)); - _count--; - - return true; - } - - /// - /// Returns an iterator starting at the first element in the . - /// - /// The created iterator. - public Iterator GetBeginIterator() - { - return new Iterator(_entries, 0, _count); - } - - /// - /// Returns a read-only iterator starting at the first element in the . - /// - /// The created iterator. - public ConstIterator GetBeginConstIterator() - { - return new ConstIterator(_entries, 0, _count); - } - - /// - /// Returns an iterator starting at the first element greater than or equal to . - /// - /// The key at which to begin iteration. - /// The created iterator. - public Iterator GetLowerBoundIterator(in TKey key) - { - int index = GetLowerBoundIndex(in key); - - return new Iterator(_entries, index, _count); - } - - /// - /// Returns a read-only iterator starting at the first element greater than or equal to . - /// - /// The key at which to begin iteration. - /// The created iterator. - public ConstIterator GetLowerBoundConstIterator(in TKey key) - { - int index = GetLowerBoundIndex(in key); - - return new ConstIterator(_entries, index, _count); - } - - /// - /// Fixes an iterator's current position and total length so that after an entry - /// is added or removed, the iterator will still be on the same entry. - /// - /// The iterator to fix. - /// The key that was added or removed. - public void FixIterator(ref Iterator iterator, in TKey key) - { - int keyIndex = GetLowerBoundIndex(in key); - iterator.Fix(keyIndex, _count); - } - - private int GetLowerBoundIndex(in TKey key) - { - // The AsSpan takes care of any bounds checking - ReadOnlySpan entries = _entries.AsSpan(0, _count); - - return BinarySearch(ref MemoryMarshal.GetReference(entries), entries.Length, in key); - } - - private static int BinarySearch(ref KeyValue spanStart, int length, in TKey item) - { - // A tweaked version of .NET's SpanHelpers.BinarySearch - int lo = 0; - int hi = length - 1; - - TKey tempItem = item; - - while (lo <= hi) - { - int i = (int)(((uint)hi + (uint)lo) >> 1); - - int c = tempItem.CompareTo(Unsafe.Add(ref spanStart, i).Key); - if (c == 0) - { - return i; - } - else if (c > 0) - { - lo = i + 1; - } - else - { - hi = i - 1; - } - } - - // If not found, return the index of the first element that is greater than item - return lo; - } - } - - /// - /// Iterates through the elements in a . - /// - public struct Iterator - { - private KeyValue[] _entries; - private int _index; - private int _length; - - internal Iterator(KeyValue[] entries, int startIndex, int length) - { - _entries = entries; - _index = startIndex; - _length = length; - } - - public ref KeyValue Get() => ref _entries[_index]; - public Span GetValue() => _entries[_index].Value.Get(); - - public ref T GetValue() where T : unmanaged - { - return ref SpanHelpers.AsStruct(_entries[_index].Value.Get()); - } - - public void Next() => _index++; - public bool IsEnd() => _index == _length; - - /// - /// Fixes the iterator current position and total length so that after an entry - /// is added or removed, the iterator will still be on the same entry. - /// - /// The index of the added or removed entry. - /// The new length of the list. - /// - public void Fix(int entryIndex, int newLength) - { - if (newLength > _length) - { - // An entry was added. entryIndex is the index of the new entry. - - // Only one entry can be added at a time. - Assert.SdkEqual(newLength, _length + 1); - - if (entryIndex <= _index) - { - // The new entry was added at or before the iterator's current index. - // Increment the index so we continue to be on the same entry. - _index++; - } - - _length = newLength; - } - else if (newLength < _length) - { - // An entry was removed. entryIndex is the index where the removed entry used to be. - - // Only one entry can be removed at a time. - Assert.SdkEqual(newLength, _length - 1); - - if (entryIndex < _index) - { - // The removed entry was before the iterator's current index. - // Decrement the index so we continue to be on the same entry. - // If the entry at the iterator's current index was removed, - // the iterator will now be at the next entry. - _index--; - } - - _length = newLength; + hi = i - 1; } } - } - /// - /// Iterates through the elements in a . - /// - public struct ConstIterator - { - private KeyValue[] _entries; - private int _index; - private int _length; - - public ConstIterator(KeyValue[] entries, int startIndex, int length) - { - _entries = entries; - _index = startIndex; - _length = length; - } - - public ref readonly KeyValue Get() => ref _entries[_index]; - public ReadOnlySpan GetValue() => _entries[_index].Value.Get(); - - public void Next() => _index++; - public bool IsEnd() => _index == _length; + // If not found, return the index of the first element that is greater than item + return lo; } } + + /// + /// Iterates through the elements in a . + /// + public struct Iterator + { + private KeyValue[] _entries; + private int _index; + private int _length; + + internal Iterator(KeyValue[] entries, int startIndex, int length) + { + _entries = entries; + _index = startIndex; + _length = length; + } + + public ref KeyValue Get() => ref _entries[_index]; + public Span GetValue() => _entries[_index].Value.Get(); + + public ref T GetValue() where T : unmanaged + { + return ref SpanHelpers.AsStruct(_entries[_index].Value.Get()); + } + + public void Next() => _index++; + public bool IsEnd() => _index == _length; + + /// + /// Fixes the iterator current position and total length so that after an entry + /// is added or removed, the iterator will still be on the same entry. + /// + /// The index of the added or removed entry. + /// The new length of the list. + /// + public void Fix(int entryIndex, int newLength) + { + if (newLength > _length) + { + // An entry was added. entryIndex is the index of the new entry. + + // Only one entry can be added at a time. + Assert.SdkEqual(newLength, _length + 1); + + if (entryIndex <= _index) + { + // The new entry was added at or before the iterator's current index. + // Increment the index so we continue to be on the same entry. + _index++; + } + + _length = newLength; + } + else if (newLength < _length) + { + // An entry was removed. entryIndex is the index where the removed entry used to be. + + // Only one entry can be removed at a time. + Assert.SdkEqual(newLength, _length - 1); + + if (entryIndex < _index) + { + // The removed entry was before the iterator's current index. + // Decrement the index so we continue to be on the same entry. + // If the entry at the iterator's current index was removed, + // the iterator will now be at the next entry. + _index--; + } + + _length = newLength; + } + } + } + + /// + /// Iterates through the elements in a . + /// + public struct ConstIterator + { + private KeyValue[] _entries; + private int _index; + private int _length; + + public ConstIterator(KeyValue[] entries, int startIndex, int length) + { + _entries = entries; + _index = startIndex; + _length = length; + } + + public ref readonly KeyValue Get() => ref _entries[_index]; + public ReadOnlySpan GetValue() => _entries[_index].Value.Get(); + + public void Next() => _index++; + public bool IsEnd() => _index == _length; + } } diff --git a/src/LibHac/Kvdb/KeyValueArchive.cs b/src/LibHac/Kvdb/KeyValueArchive.cs index 90db410b..314a5757 100644 --- a/src/LibHac/Kvdb/KeyValueArchive.cs +++ b/src/LibHac/Kvdb/KeyValueArchive.cs @@ -4,203 +4,202 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; -namespace LibHac.Kvdb +namespace LibHac.Kvdb; + +[StructLayout(LayoutKind.Sequential, Size = 0xC)] +public struct KeyValueArchiveHeader { - [StructLayout(LayoutKind.Sequential, Size = 0xC)] - public struct KeyValueArchiveHeader + public const uint ExpectedMagic = 0x564B4D49; // IMKV + + public uint Magic; + public int Reserved; + public int EntryCount; + + public bool IsValid() => Magic == ExpectedMagic; + + public KeyValueArchiveHeader(int entryCount) { - public const uint ExpectedMagic = 0x564B4D49; // IMKV - - public uint Magic; - public int Reserved; - public int EntryCount; - - public bool IsValid() => Magic == ExpectedMagic; - - public KeyValueArchiveHeader(int entryCount) - { - Magic = ExpectedMagic; - Reserved = 0; - EntryCount = entryCount; - } - } - - [StructLayout(LayoutKind.Sequential, Size = 0xC)] - internal struct KeyValueArchiveEntryHeader - { - public const uint ExpectedMagic = 0x4E454D49; // IMEN - - public uint Magic; - public int KeySize; - public int ValueSize; - - public bool IsValid() => Magic == ExpectedMagic; - - public KeyValueArchiveEntryHeader(int keySize, int valueSize) - { - Magic = ExpectedMagic; - KeySize = keySize; - ValueSize = valueSize; - } - } - - internal struct KeyValueArchiveSizeCalculator - { - public long Size { get; private set; } - - public void Initialize() - { - Size = Unsafe.SizeOf(); - } - - public void AddEntry(int keySize, int valueSize) - { - Size += Unsafe.SizeOf() + keySize + valueSize; - } - } - - internal ref struct KeyValueArchiveBufferReader - { - private ReadOnlySpan _buffer; - private int _offset; - - public KeyValueArchiveBufferReader(ReadOnlySpan buffer) - { - _buffer = buffer; - _offset = 0; - } - - public Result ReadEntryCount(out int count) - { - UnsafeHelpers.SkipParamInit(out count); - - // This should only be called at the start of reading stream. - Assert.SdkRequiresEqual(_offset, 0); - - // Read and validate header. - var header = new KeyValueArchiveHeader(); - - Result rc = Read(SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; - - if (!header.IsValid()) - return ResultKvdb.InvalidKeyValue.Log(); - - count = header.EntryCount; - return Result.Success; - } - - public Result GetKeyValueSize(out int keySize, out int valueSize) - { - UnsafeHelpers.SkipParamInit(out keySize, out valueSize); - - // This should only be called after ReadEntryCount. - Assert.SdkNotEqual(_offset, 0); - - // Peek the next entry header. - Unsafe.SkipInit(out KeyValueArchiveEntryHeader header); - - Result rc = Peek(SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; - - if (!header.IsValid()) - return ResultKvdb.InvalidKeyValue.Log(); - - keySize = header.KeySize; - valueSize = header.ValueSize; - - return Result.Success; - } - - public Result ReadKeyValue(Span keyBuffer, Span valueBuffer) - { - // This should only be called after ReadEntryCount. - Assert.SdkNotEqual(_offset, 0); - - // Read the next entry header. - Unsafe.SkipInit(out KeyValueArchiveEntryHeader header); - - Result rc = Read(SpanHelpers.AsByteSpan(ref header)); - if (rc.IsFailure()) return rc; - - if (!header.IsValid()) - return ResultKvdb.InvalidKeyValue.Log(); - - // Key size and Value size must be correct. - Assert.SdkEqual(keyBuffer.Length, header.KeySize); - Assert.SdkEqual(valueBuffer.Length, header.ValueSize); - - rc = Read(keyBuffer); - if (rc.IsFailure()) return rc; - - rc = Read(valueBuffer); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - private Result Peek(Span destBuffer) - { - // Bounds check. - if (_offset + destBuffer.Length > _buffer.Length || - _offset + destBuffer.Length <= _offset) - { - return ResultKvdb.InvalidKeyValue.Log(); - } - - _buffer.Slice(_offset, destBuffer.Length).CopyTo(destBuffer); - return Result.Success; - } - - private Result Read(Span destBuffer) - { - Result rc = Peek(destBuffer); - if (rc.IsFailure()) return rc; - - _offset += destBuffer.Length; - return Result.Success; - } - } - - internal ref struct KeyValueArchiveBufferWriter - { - private Span _buffer; - private int _offset; - - public KeyValueArchiveBufferWriter(Span buffer) - { - _buffer = buffer; - _offset = 0; - } - - private void Write(ReadOnlySpan source) - { - // Bounds check. - Abort.DoAbortUnless(_offset + source.Length <= _buffer.Length && - _offset + source.Length > _offset); - - source.CopyTo(_buffer.Slice(_offset)); - _offset += source.Length; - } - - public void WriteHeader(int entryCount) - { - // This should only be called at start of write. - Assert.SdkEqual(_offset, 0); - - var header = new KeyValueArchiveHeader(entryCount); - Write(SpanHelpers.AsByteSpan(ref header)); - } - - public void WriteEntry(ReadOnlySpan key, ReadOnlySpan value) - { - // This should only be called after writing header. - Assert.SdkNotEqual(_offset, 0); - - var header = new KeyValueArchiveEntryHeader(key.Length, value.Length); - Write(SpanHelpers.AsByteSpan(ref header)); - Write(key); - Write(value); - } + Magic = ExpectedMagic; + Reserved = 0; + EntryCount = entryCount; + } +} + +[StructLayout(LayoutKind.Sequential, Size = 0xC)] +internal struct KeyValueArchiveEntryHeader +{ + public const uint ExpectedMagic = 0x4E454D49; // IMEN + + public uint Magic; + public int KeySize; + public int ValueSize; + + public bool IsValid() => Magic == ExpectedMagic; + + public KeyValueArchiveEntryHeader(int keySize, int valueSize) + { + Magic = ExpectedMagic; + KeySize = keySize; + ValueSize = valueSize; + } +} + +internal struct KeyValueArchiveSizeCalculator +{ + public long Size { get; private set; } + + public void Initialize() + { + Size = Unsafe.SizeOf(); + } + + public void AddEntry(int keySize, int valueSize) + { + Size += Unsafe.SizeOf() + keySize + valueSize; + } +} + +internal ref struct KeyValueArchiveBufferReader +{ + private ReadOnlySpan _buffer; + private int _offset; + + public KeyValueArchiveBufferReader(ReadOnlySpan buffer) + { + _buffer = buffer; + _offset = 0; + } + + public Result ReadEntryCount(out int count) + { + UnsafeHelpers.SkipParamInit(out count); + + // This should only be called at the start of reading stream. + Assert.SdkRequiresEqual(_offset, 0); + + // Read and validate header. + var header = new KeyValueArchiveHeader(); + + Result rc = Read(SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + if (!header.IsValid()) + return ResultKvdb.InvalidKeyValue.Log(); + + count = header.EntryCount; + return Result.Success; + } + + public Result GetKeyValueSize(out int keySize, out int valueSize) + { + UnsafeHelpers.SkipParamInit(out keySize, out valueSize); + + // This should only be called after ReadEntryCount. + Assert.SdkNotEqual(_offset, 0); + + // Peek the next entry header. + Unsafe.SkipInit(out KeyValueArchiveEntryHeader header); + + Result rc = Peek(SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + if (!header.IsValid()) + return ResultKvdb.InvalidKeyValue.Log(); + + keySize = header.KeySize; + valueSize = header.ValueSize; + + return Result.Success; + } + + public Result ReadKeyValue(Span keyBuffer, Span valueBuffer) + { + // This should only be called after ReadEntryCount. + Assert.SdkNotEqual(_offset, 0); + + // Read the next entry header. + Unsafe.SkipInit(out KeyValueArchiveEntryHeader header); + + Result rc = Read(SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + if (!header.IsValid()) + return ResultKvdb.InvalidKeyValue.Log(); + + // Key size and Value size must be correct. + Assert.SdkEqual(keyBuffer.Length, header.KeySize); + Assert.SdkEqual(valueBuffer.Length, header.ValueSize); + + rc = Read(keyBuffer); + if (rc.IsFailure()) return rc; + + rc = Read(valueBuffer); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private Result Peek(Span destBuffer) + { + // Bounds check. + if (_offset + destBuffer.Length > _buffer.Length || + _offset + destBuffer.Length <= _offset) + { + return ResultKvdb.InvalidKeyValue.Log(); + } + + _buffer.Slice(_offset, destBuffer.Length).CopyTo(destBuffer); + return Result.Success; + } + + private Result Read(Span destBuffer) + { + Result rc = Peek(destBuffer); + if (rc.IsFailure()) return rc; + + _offset += destBuffer.Length; + return Result.Success; + } +} + +internal ref struct KeyValueArchiveBufferWriter +{ + private Span _buffer; + private int _offset; + + public KeyValueArchiveBufferWriter(Span buffer) + { + _buffer = buffer; + _offset = 0; + } + + private void Write(ReadOnlySpan source) + { + // Bounds check. + Abort.DoAbortUnless(_offset + source.Length <= _buffer.Length && + _offset + source.Length > _offset); + + source.CopyTo(_buffer.Slice(_offset)); + _offset += source.Length; + } + + public void WriteHeader(int entryCount) + { + // This should only be called at start of write. + Assert.SdkEqual(_offset, 0); + + var header = new KeyValueArchiveHeader(entryCount); + Write(SpanHelpers.AsByteSpan(ref header)); + } + + public void WriteEntry(ReadOnlySpan key, ReadOnlySpan value) + { + // This should only be called after writing header. + Assert.SdkNotEqual(_offset, 0); + + var header = new KeyValueArchiveEntryHeader(key.Length, value.Length); + Write(SpanHelpers.AsByteSpan(ref header)); + Write(key); + Write(value); } } diff --git a/src/LibHac/Kvdb/ResultKvdb.cs b/src/LibHac/Kvdb/ResultKvdb.cs index c2a43c4e..f0c4aa6b 100644 --- a/src/LibHac/Kvdb/ResultKvdb.cs +++ b/src/LibHac/Kvdb/ResultKvdb.cs @@ -9,25 +9,24 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Kvdb -{ - public static class ResultKvdb - { - public const int ModuleKvdb = 20; +namespace LibHac.Kvdb; - /// There is no more space in the database or the key is too long.
Error code: 2020-0001; Inner value: 0x214
- public static Result.Base OutOfKeyResource => new Result.Base(ModuleKvdb, 1); - /// Error code: 2020-0002; Inner value: 0x414 - public static Result.Base KeyNotFound => new Result.Base(ModuleKvdb, 2); - /// Error code: 2020-0004; Inner value: 0x814 - public static Result.Base AllocationFailed => new Result.Base(ModuleKvdb, 4); - /// Error code: 2020-0005; Inner value: 0xa14 - public static Result.Base InvalidKeyValue => new Result.Base(ModuleKvdb, 5); - /// Error code: 2020-0006; Inner value: 0xc14 - public static Result.Base BufferInsufficient => new Result.Base(ModuleKvdb, 6); - /// Error code: 2020-0008; Inner value: 0x1014 - public static Result.Base InvalidFileSystemState => new Result.Base(ModuleKvdb, 8); - /// Error code: 2020-0009; Inner value: 0x1214 - public static Result.Base NotCreated => new Result.Base(ModuleKvdb, 9); - } +public static class ResultKvdb +{ + public const int ModuleKvdb = 20; + + /// There is no more space in the database or the key is too long.
Error code: 2020-0001; Inner value: 0x214
+ public static Result.Base OutOfKeyResource => new Result.Base(ModuleKvdb, 1); + /// Error code: 2020-0002; Inner value: 0x414 + public static Result.Base KeyNotFound => new Result.Base(ModuleKvdb, 2); + /// Error code: 2020-0004; Inner value: 0x814 + public static Result.Base AllocationFailed => new Result.Base(ModuleKvdb, 4); + /// Error code: 2020-0005; Inner value: 0xa14 + public static Result.Base InvalidKeyValue => new Result.Base(ModuleKvdb, 5); + /// Error code: 2020-0006; Inner value: 0xc14 + public static Result.Base BufferInsufficient => new Result.Base(ModuleKvdb, 6); + /// Error code: 2020-0008; Inner value: 0x1014 + public static Result.Base InvalidFileSystemState => new Result.Base(ModuleKvdb, 8); + /// Error code: 2020-0009; Inner value: 0x1214 + public static Result.Base NotCreated => new Result.Base(ModuleKvdb, 9); } diff --git a/src/LibHac/LibHac.csproj b/src/LibHac/LibHac.csproj index c9a9a806..5a66e379 100644 --- a/src/LibHac/LibHac.csproj +++ b/src/LibHac/LibHac.csproj @@ -4,6 +4,7 @@ Library 0.14.0 net5.0 + 10.0 true diff --git a/src/LibHac/LibHacException.cs b/src/LibHac/LibHacException.cs index 45bb0213..3196078f 100644 --- a/src/LibHac/LibHacException.cs +++ b/src/LibHac/LibHacException.cs @@ -1,37 +1,36 @@ using System; -namespace LibHac +namespace LibHac; + +/// +/// This is the exception that is thrown when an error occurs +/// +public class LibHacException : Exception { /// - /// This is the exception that is thrown when an error occurs + /// Initializes a new instance of the class. /// - public class LibHacException : Exception + public LibHacException() { - /// - /// Initializes a new instance of the class. - /// - public LibHacException() - { - } + } - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The error message that explains the reason for the exception. - public LibHacException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public LibHacException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public LibHacException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public LibHacException(string message, Exception innerException) + : base(message, innerException) + { } } diff --git a/src/LibHac/Loader/MetaLoader.cs b/src/LibHac/Loader/MetaLoader.cs index 2668b3cd..a268dc60 100644 --- a/src/LibHac/Loader/MetaLoader.cs +++ b/src/LibHac/Loader/MetaLoader.cs @@ -6,230 +6,229 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.Loader +namespace LibHac.Loader; + +public class MetaLoader { - public class MetaLoader + private const int MetaCacheBufferSize = 0x8000; + + private readonly byte[] _npdmBuffer; + private bool _isValid; + + public MetaLoader() { - private const int MetaCacheBufferSize = 0x8000; + _npdmBuffer = new byte[MetaCacheBufferSize]; + } - private readonly byte[] _npdmBuffer; - private bool _isValid; + public Result Load(ReadOnlySpan npdmBuffer) + { + _isValid = false; - public MetaLoader() - { - _npdmBuffer = new byte[MetaCacheBufferSize]; - } + // Validate the meta + Result rc = GetNpdmFromBuffer(out _, npdmBuffer); + if (rc.IsFailure()) return rc; - public Result Load(ReadOnlySpan npdmBuffer) - { - _isValid = false; + npdmBuffer.CopyTo(_npdmBuffer); + _isValid = true; + return Result.Success; + } - // Validate the meta - Result rc = GetNpdmFromBuffer(out _, npdmBuffer); - if (rc.IsFailure()) return rc; + public Result LoadFromFile(HorizonClient hos, FileHandle file) + { + _isValid = false; - npdmBuffer.CopyTo(_npdmBuffer); - _isValid = true; - return Result.Success; - } + // Get file size + Result rc = hos.Fs.GetFileSize(out long npdmSize, file); + if (rc.IsFailure()) return rc; - public Result LoadFromFile(HorizonClient hos, FileHandle file) - { - _isValid = false; + if (npdmSize > MetaCacheBufferSize) + return ResultLoader.TooLargeMeta.Log(); - // Get file size - Result rc = hos.Fs.GetFileSize(out long npdmSize, file); - if (rc.IsFailure()) return rc; + // Read data into cache buffer + rc = hos.Fs.ReadFile(file, 0, _npdmBuffer.AsSpan(0, (int)npdmSize)); + if (rc.IsFailure()) return rc; - if (npdmSize > MetaCacheBufferSize) - return ResultLoader.TooLargeMeta.Log(); + // Validate the meta + rc = GetNpdmFromBuffer(out _, _npdmBuffer); + if (rc.IsFailure()) return rc; - // Read data into cache buffer - rc = hos.Fs.ReadFile(file, 0, _npdmBuffer.AsSpan(0, (int)npdmSize)); - if (rc.IsFailure()) return rc; + _isValid = true; + return Result.Success; + } - // Validate the meta - rc = GetNpdmFromBuffer(out _, _npdmBuffer); - if (rc.IsFailure()) return rc; + public Result GetNpdm(out Npdm npdm) + { + Assert.SdkRequires(_isValid); + Assert.SdkRequiresEqual(MetaCacheBufferSize, _npdmBuffer.Length); - _isValid = true; - return Result.Success; - } - - public Result GetNpdm(out Npdm npdm) - { - Assert.SdkRequires(_isValid); - Assert.SdkRequiresEqual(MetaCacheBufferSize, _npdmBuffer.Length); - - if (!_isValid) - { - npdm = default; - return ResultLoader.InvalidMeta.Log(); - } - - npdm = GetNpdmFromBufferUnsafe(ref MemoryMarshal.GetArrayDataReference(_npdmBuffer)); - return Result.Success; - } - - public static Result GetNpdmFromBuffer(out Npdm npdm, ReadOnlySpan npdmBuffer) + if (!_isValid) { npdm = default; - - int npdmSize = npdmBuffer.Length; - - if (npdmSize > MetaCacheBufferSize) - return ResultLoader.TooLargeMeta.Log(); - - Result rc = ValidateMeta(npdmBuffer); - if (rc.IsFailure()) return rc; - - ref readonly Meta meta = ref Unsafe.As(ref MemoryMarshal.GetReference(npdmBuffer)); - - ReadOnlySpan acidBuffer = npdmBuffer.Slice(meta.AcidOffset, meta.AcidSize); - ReadOnlySpan aciBuffer = npdmBuffer.Slice(meta.AciOffset, meta.AciSize); - - ref readonly AcidHeaderData acid = ref Unsafe.As(ref MemoryMarshal.GetReference(acidBuffer)); - ref readonly AciHeader aci = ref Unsafe.As(ref MemoryMarshal.GetReference(aciBuffer)); - - rc = ValidateAcid(acidBuffer); - if (rc.IsFailure()) return rc; - - rc = ValidateAci(aciBuffer); - if (rc.IsFailure()) return rc; - - // Set Npdm members. - npdm.Meta = new ReadOnlyRef(in meta); - npdm.Acid = new ReadOnlyRef(in acid); - npdm.Aci = new ReadOnlyRef(in aci); - - npdm.FsAccessControlDescriptor = acidBuffer.Slice(acid.FsAccessControlOffset, acid.FsAccessControlSize); - npdm.ServiceAccessControlDescriptor = acidBuffer.Slice(acid.ServiceAccessControlOffset, acid.ServiceAccessControlSize); - npdm.KernelCapabilityDescriptor = acidBuffer.Slice(acid.KernelCapabilityOffset, acid.KernelCapabilitySize); - - npdm.FsAccessControlData = aciBuffer.Slice(aci.FsAccessControlOffset, aci.FsAccessControlSize); - npdm.ServiceAccessControlData = aciBuffer.Slice(aci.ServiceAccessControlOffset, aci.ServiceAccessControlSize); - npdm.KernelCapabilityData = aciBuffer.Slice(aci.KernelCapabilityOffset, aci.KernelCapabilitySize); - - return Result.Success; + return ResultLoader.InvalidMeta.Log(); } - private static Npdm GetNpdmFromBufferUnsafe(ref byte npdmBuffer) - { - var npdm = new Npdm(); + npdm = GetNpdmFromBufferUnsafe(ref MemoryMarshal.GetArrayDataReference(_npdmBuffer)); + return Result.Success; + } - ref Meta meta = ref Unsafe.As(ref npdmBuffer); - ref AcidHeaderData acid = ref Unsafe.As(ref Unsafe.Add(ref npdmBuffer, meta.AcidOffset)); - ref AciHeader aci = ref Unsafe.As(ref Unsafe.Add(ref npdmBuffer, meta.AciOffset)); + public static Result GetNpdmFromBuffer(out Npdm npdm, ReadOnlySpan npdmBuffer) + { + npdm = default; - // Set Npdm members. - npdm.Meta = new ReadOnlyRef(in meta); - npdm.Acid = new ReadOnlyRef(in acid); - npdm.Aci = new ReadOnlyRef(in aci); + int npdmSize = npdmBuffer.Length; - npdm.FsAccessControlDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.FsAccessControlOffset), acid.FsAccessControlSize); - npdm.ServiceAccessControlDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.ServiceAccessControlOffset), acid.ServiceAccessControlSize); - npdm.KernelCapabilityDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.KernelCapabilityOffset), acid.KernelCapabilitySize); + if (npdmSize > MetaCacheBufferSize) + return ResultLoader.TooLargeMeta.Log(); - npdm.FsAccessControlData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.FsAccessControlOffset), aci.FsAccessControlSize); - npdm.ServiceAccessControlData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.ServiceAccessControlOffset), aci.ServiceAccessControlSize); - npdm.KernelCapabilityData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.KernelCapabilityOffset), aci.KernelCapabilitySize); + Result rc = ValidateMeta(npdmBuffer); + if (rc.IsFailure()) return rc; - return npdm; - } + ref readonly Meta meta = ref Unsafe.As(ref MemoryMarshal.GetReference(npdmBuffer)); - private static Result ValidateSubregion(int allowedStart, int allowedEnd, int start, int size, int minSize = 0) - { - if (size < minSize) return ResultLoader.InvalidMeta.Log(); - if (allowedStart > start) return ResultLoader.InvalidMeta.Log(); - if (start > allowedEnd) return ResultLoader.InvalidMeta.Log(); - if (start + size > allowedEnd) return ResultLoader.InvalidMeta.Log(); - return Result.Success; - } + ReadOnlySpan acidBuffer = npdmBuffer.Slice(meta.AcidOffset, meta.AcidSize); + ReadOnlySpan aciBuffer = npdmBuffer.Slice(meta.AciOffset, meta.AciSize); - private static Result ValidateMeta(ReadOnlySpan metaBuffer) - { - // Validate the buffer is large enough - if (metaBuffer.Length < Unsafe.SizeOf()) - return ResultLoader.InvalidMeta.Log(); + ref readonly AcidHeaderData acid = ref Unsafe.As(ref MemoryMarshal.GetReference(acidBuffer)); + ref readonly AciHeader aci = ref Unsafe.As(ref MemoryMarshal.GetReference(aciBuffer)); - ref Meta meta = ref Unsafe.As(ref MemoryMarshal.GetReference(metaBuffer)); + rc = ValidateAcid(acidBuffer); + if (rc.IsFailure()) return rc; - // Validate magic. - if (meta.Magic != Meta.MagicValue) - return ResultLoader.InvalidMeta.Log(); + rc = ValidateAci(aciBuffer); + if (rc.IsFailure()) return rc; - // Validate flags. - uint invalidFlagsMask = ~0x3Fu; + // Set Npdm members. + npdm.Meta = new ReadOnlyRef(in meta); + npdm.Acid = new ReadOnlyRef(in acid); + npdm.Aci = new ReadOnlyRef(in aci); - if ((meta.Flags & invalidFlagsMask) != 0) - return ResultLoader.InvalidMeta.Log(); + npdm.FsAccessControlDescriptor = acidBuffer.Slice(acid.FsAccessControlOffset, acid.FsAccessControlSize); + npdm.ServiceAccessControlDescriptor = acidBuffer.Slice(acid.ServiceAccessControlOffset, acid.ServiceAccessControlSize); + npdm.KernelCapabilityDescriptor = acidBuffer.Slice(acid.KernelCapabilityOffset, acid.KernelCapabilitySize); - // Validate Acid extents. - Result rc = ValidateSubregion(Unsafe.SizeOf(), metaBuffer.Length, meta.AcidOffset, - Unsafe.SizeOf()); - if (rc.IsFailure()) return rc; + npdm.FsAccessControlData = aciBuffer.Slice(aci.FsAccessControlOffset, aci.FsAccessControlSize); + npdm.ServiceAccessControlData = aciBuffer.Slice(aci.ServiceAccessControlOffset, aci.ServiceAccessControlSize); + npdm.KernelCapabilityData = aciBuffer.Slice(aci.KernelCapabilityOffset, aci.KernelCapabilitySize); - // Validate Aci extends. - rc = ValidateSubregion(Unsafe.SizeOf(), metaBuffer.Length, meta.AciOffset, Unsafe.SizeOf()); - if (rc.IsFailure()) return rc; + return Result.Success; + } - return Result.Success; - } + private static Npdm GetNpdmFromBufferUnsafe(ref byte npdmBuffer) + { + var npdm = new Npdm(); - private static Result ValidateAcid(ReadOnlySpan acidBuffer) - { - // Validate the buffer is large enough - if (acidBuffer.Length < Unsafe.SizeOf()) - return ResultLoader.InvalidMeta.Log(); + ref Meta meta = ref Unsafe.As(ref npdmBuffer); + ref AcidHeaderData acid = ref Unsafe.As(ref Unsafe.Add(ref npdmBuffer, meta.AcidOffset)); + ref AciHeader aci = ref Unsafe.As(ref Unsafe.Add(ref npdmBuffer, meta.AciOffset)); - ref AcidHeaderData acid = ref Unsafe.As(ref MemoryMarshal.GetReference(acidBuffer)); + // Set Npdm members. + npdm.Meta = new ReadOnlyRef(in meta); + npdm.Acid = new ReadOnlyRef(in acid); + npdm.Aci = new ReadOnlyRef(in aci); - // Validate magic. - if (acid.Magic != AcidHeaderData.MagicValue) - return ResultLoader.InvalidMeta.Log(); + npdm.FsAccessControlDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.FsAccessControlOffset), acid.FsAccessControlSize); + npdm.ServiceAccessControlDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.ServiceAccessControlOffset), acid.ServiceAccessControlSize); + npdm.KernelCapabilityDescriptor = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref acid), acid.KernelCapabilityOffset), acid.KernelCapabilitySize); - // Validate Fac, Sac, Kac. - Result rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.FsAccessControlOffset, - acid.FsAccessControlSize); - if (rc.IsFailure()) return rc; + npdm.FsAccessControlData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.FsAccessControlOffset), aci.FsAccessControlSize); + npdm.ServiceAccessControlData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.ServiceAccessControlOffset), aci.ServiceAccessControlSize); + npdm.KernelCapabilityData = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref Unsafe.As(ref aci), aci.KernelCapabilityOffset), aci.KernelCapabilitySize); - rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.ServiceAccessControlOffset, - acid.ServiceAccessControlSize); - if (rc.IsFailure()) return rc; + return npdm; + } - rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.KernelCapabilityOffset, - acid.KernelCapabilitySize); - if (rc.IsFailure()) return rc; + private static Result ValidateSubregion(int allowedStart, int allowedEnd, int start, int size, int minSize = 0) + { + if (size < minSize) return ResultLoader.InvalidMeta.Log(); + if (allowedStart > start) return ResultLoader.InvalidMeta.Log(); + if (start > allowedEnd) return ResultLoader.InvalidMeta.Log(); + if (start + size > allowedEnd) return ResultLoader.InvalidMeta.Log(); + return Result.Success; + } - return Result.Success; - } + private static Result ValidateMeta(ReadOnlySpan metaBuffer) + { + // Validate the buffer is large enough + if (metaBuffer.Length < Unsafe.SizeOf()) + return ResultLoader.InvalidMeta.Log(); - private static Result ValidateAci(ReadOnlySpan aciBuffer) - { - // Validate the buffer is large enough - if (aciBuffer.Length < Unsafe.SizeOf()) - return ResultLoader.InvalidMeta.Log(); + ref Meta meta = ref Unsafe.As(ref MemoryMarshal.GetReference(metaBuffer)); - ref AciHeader aci = ref Unsafe.As(ref MemoryMarshal.GetReference(aciBuffer)); + // Validate magic. + if (meta.Magic != Meta.MagicValue) + return ResultLoader.InvalidMeta.Log(); - // Validate magic. - if (aci.Magic != AciHeader.MagicValue) - return ResultLoader.InvalidMeta.Log(); + // Validate flags. + uint invalidFlagsMask = ~0x3Fu; - // Validate Fac, Sac, Kac. - Result rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.FsAccessControlOffset, - aci.FsAccessControlSize); - if (rc.IsFailure()) return rc; + if ((meta.Flags & invalidFlagsMask) != 0) + return ResultLoader.InvalidMeta.Log(); - rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.ServiceAccessControlOffset, - aci.ServiceAccessControlSize); - if (rc.IsFailure()) return rc; + // Validate Acid extents. + Result rc = ValidateSubregion(Unsafe.SizeOf(), metaBuffer.Length, meta.AcidOffset, + Unsafe.SizeOf()); + if (rc.IsFailure()) return rc; - rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.KernelCapabilityOffset, - aci.KernelCapabilitySize); - if (rc.IsFailure()) return rc; + // Validate Aci extends. + rc = ValidateSubregion(Unsafe.SizeOf(), metaBuffer.Length, meta.AciOffset, Unsafe.SizeOf()); + if (rc.IsFailure()) return rc; - return Result.Success; - } + return Result.Success; + } + + private static Result ValidateAcid(ReadOnlySpan acidBuffer) + { + // Validate the buffer is large enough + if (acidBuffer.Length < Unsafe.SizeOf()) + return ResultLoader.InvalidMeta.Log(); + + ref AcidHeaderData acid = ref Unsafe.As(ref MemoryMarshal.GetReference(acidBuffer)); + + // Validate magic. + if (acid.Magic != AcidHeaderData.MagicValue) + return ResultLoader.InvalidMeta.Log(); + + // Validate Fac, Sac, Kac. + Result rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.FsAccessControlOffset, + acid.FsAccessControlSize); + if (rc.IsFailure()) return rc; + + rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.ServiceAccessControlOffset, + acid.ServiceAccessControlSize); + if (rc.IsFailure()) return rc; + + rc = ValidateSubregion(Unsafe.SizeOf(), acidBuffer.Length, acid.KernelCapabilityOffset, + acid.KernelCapabilitySize); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private static Result ValidateAci(ReadOnlySpan aciBuffer) + { + // Validate the buffer is large enough + if (aciBuffer.Length < Unsafe.SizeOf()) + return ResultLoader.InvalidMeta.Log(); + + ref AciHeader aci = ref Unsafe.As(ref MemoryMarshal.GetReference(aciBuffer)); + + // Validate magic. + if (aci.Magic != AciHeader.MagicValue) + return ResultLoader.InvalidMeta.Log(); + + // Validate Fac, Sac, Kac. + Result rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.FsAccessControlOffset, + aci.FsAccessControlSize); + if (rc.IsFailure()) return rc; + + rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.ServiceAccessControlOffset, + aci.ServiceAccessControlSize); + if (rc.IsFailure()) return rc; + + rc = ValidateSubregion(Unsafe.SizeOf(), aciBuffer.Length, aci.KernelCapabilityOffset, + aci.KernelCapabilitySize); + if (rc.IsFailure()) return rc; + + return Result.Success; } } diff --git a/src/LibHac/Loader/NsoHeader.cs b/src/LibHac/Loader/NsoHeader.cs index 87223b8e..259de222 100644 --- a/src/LibHac/Loader/NsoHeader.cs +++ b/src/LibHac/Loader/NsoHeader.cs @@ -3,82 +3,81 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Loader +namespace LibHac.Loader; + +[StructLayout(LayoutKind.Explicit, Size = 0x100)] +public struct NsoHeader { - [StructLayout(LayoutKind.Explicit, Size = 0x100)] - public struct NsoHeader + public const int SegmentCount = 3; + + [FieldOffset(0x00)] public uint Magic; + [FieldOffset(0x04)] public uint Version; + [FieldOffset(0x08)] public uint Reserved08; + [FieldOffset(0x0C)] public Flag Flags; + + [FieldOffset(0x10)] public uint TextFileOffset; + [FieldOffset(0x14)] public uint TextMemoryOffset; + [FieldOffset(0x18)] public uint TextSize; + + [FieldOffset(0x1C)] public uint ModuleNameOffset; + + [FieldOffset(0x20)] public uint RoFileOffset; + [FieldOffset(0x24)] public uint RoMemoryOffset; + [FieldOffset(0x28)] public uint RoSize; + + [FieldOffset(0x2C)] public uint ModuleNameSize; + + [FieldOffset(0x30)] public uint DataFileOffset; + [FieldOffset(0x34)] public uint DataMemoryOffset; + [FieldOffset(0x38)] public uint DataSize; + + [FieldOffset(0x3C)] public uint BssSize; + + [FieldOffset(0x40)] public Buffer32 ModuleId; + + // Size of the sections in the NSO file + [FieldOffset(0x60)] public uint TextFileSize; + [FieldOffset(0x64)] public uint RoFileSize; + [FieldOffset(0x68)] public uint DataFileSize; + + [FieldOffset(0x6C)] private byte _reserved6C; + + [FieldOffset(0x88)] public uint ApiInfoOffset; + [FieldOffset(0x8C)] public uint ApiInfoSize; + [FieldOffset(0x90)] public uint DynStrOffset; + [FieldOffset(0x94)] public uint DynStrSize; + [FieldOffset(0x98)] public uint DynSymOffset; + [FieldOffset(0x9C)] public uint DynSymSize; + + [FieldOffset(0xA0)] public Buffer32 TextHash; + [FieldOffset(0xC0)] public Buffer32 RoHash; + [FieldOffset(0xE0)] public Buffer32 DataHash; + + public Span Segments => + SpanHelpers.CreateSpan(ref Unsafe.As(ref TextFileOffset), SegmentCount); + + public Span CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount); + + public Span SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount); + + public Span Reserved6C => SpanHelpers.CreateSpan(ref _reserved6C, 0x1C); + + [Flags] + public enum Flag { - public const int SegmentCount = 3; + TextCompress = 1 << 0, + RoCompress = 1 << 1, + DataCompress = 1 << 2, + TextHash = 1 << 3, + RoHash = 1 << 4, + DataHash = 1 << 5 + } - [FieldOffset(0x00)] public uint Magic; - [FieldOffset(0x04)] public uint Version; - [FieldOffset(0x08)] public uint Reserved08; - [FieldOffset(0x0C)] public Flag Flags; - - [FieldOffset(0x10)] public uint TextFileOffset; - [FieldOffset(0x14)] public uint TextMemoryOffset; - [FieldOffset(0x18)] public uint TextSize; - - [FieldOffset(0x1C)] public uint ModuleNameOffset; - - [FieldOffset(0x20)] public uint RoFileOffset; - [FieldOffset(0x24)] public uint RoMemoryOffset; - [FieldOffset(0x28)] public uint RoSize; - - [FieldOffset(0x2C)] public uint ModuleNameSize; - - [FieldOffset(0x30)] public uint DataFileOffset; - [FieldOffset(0x34)] public uint DataMemoryOffset; - [FieldOffset(0x38)] public uint DataSize; - - [FieldOffset(0x3C)] public uint BssSize; - - [FieldOffset(0x40)] public Buffer32 ModuleId; - - // Size of the sections in the NSO file - [FieldOffset(0x60)] public uint TextFileSize; - [FieldOffset(0x64)] public uint RoFileSize; - [FieldOffset(0x68)] public uint DataFileSize; - - [FieldOffset(0x6C)] private byte _reserved6C; - - [FieldOffset(0x88)] public uint ApiInfoOffset; - [FieldOffset(0x8C)] public uint ApiInfoSize; - [FieldOffset(0x90)] public uint DynStrOffset; - [FieldOffset(0x94)] public uint DynStrSize; - [FieldOffset(0x98)] public uint DynSymOffset; - [FieldOffset(0x9C)] public uint DynSymSize; - - [FieldOffset(0xA0)] public Buffer32 TextHash; - [FieldOffset(0xC0)] public Buffer32 RoHash; - [FieldOffset(0xE0)] public Buffer32 DataHash; - - public Span Segments => - SpanHelpers.CreateSpan(ref Unsafe.As(ref TextFileOffset), SegmentCount); - - public Span CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount); - - public Span SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount); - - public Span Reserved6C => SpanHelpers.CreateSpan(ref _reserved6C, 0x1C); - - [Flags] - public enum Flag - { - TextCompress = 1 << 0, - RoCompress = 1 << 1, - DataCompress = 1 << 2, - TextHash = 1 << 3, - RoHash = 1 << 4, - DataHash = 1 << 5 - } - - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct SegmentHeader - { - public uint FileOffset; - public uint MemoryOffset; - public uint Size; - } + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct SegmentHeader + { + public uint FileOffset; + public uint MemoryOffset; + public uint Size; } } diff --git a/src/LibHac/Loader/NsoReader.cs b/src/LibHac/Loader/NsoReader.cs index 7523418b..a48d4c0d 100644 --- a/src/LibHac/Loader/NsoReader.cs +++ b/src/LibHac/Loader/NsoReader.cs @@ -4,106 +4,105 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.Loader +namespace LibHac.Loader; + +public class NsoReader { - public class NsoReader + private IFile NsoFile { get; set; } + + public NsoHeader Header; + + public Result Initialize(IFile nsoFile) { - private IFile NsoFile { get; set; } + Result rc = nsoFile.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref Header), ReadOption.None); + if (rc.IsFailure()) return rc; - public NsoHeader Header; + if (bytesRead != Unsafe.SizeOf()) + return ResultLoader.InvalidNso.Log(); - public Result Initialize(IFile nsoFile) + NsoFile = nsoFile; + return Result.Success; + } + + public Result GetSegmentSize(SegmentType segment, out uint size) + { + UnsafeHelpers.SkipParamInit(out size); + + switch (segment) { - Result rc = nsoFile.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref Header), ReadOption.None); - if (rc.IsFailure()) return rc; - - if (bytesRead != Unsafe.SizeOf()) - return ResultLoader.InvalidNso.Log(); - - NsoFile = nsoFile; - return Result.Success; - } - - public Result GetSegmentSize(SegmentType segment, out uint size) - { - UnsafeHelpers.SkipParamInit(out size); - - switch (segment) - { - case SegmentType.Text: - case SegmentType.Ro: - case SegmentType.Data: - size = Header.Segments[(int)segment].Size; - return Result.Success; - default: - return ResultLibHac.ArgumentOutOfRange.Log(); - } - } - - public Result ReadSegment(SegmentType segment, Span buffer) - { - Result rc = GetSegmentSize(segment, out uint segmentSize); - if (rc.IsFailure()) return rc; - - if (buffer.Length < segmentSize) - return ResultLibHac.BufferTooSmall.Log(); - - bool isCompressed = (((int)Header.Flags >> (int)segment) & 1) != 0; - bool checkHash = (((int)Header.Flags >> (int)segment) & 8) != 0; - - return ReadSegmentImpl(ref Header.Segments[(int)segment], Header.CompressedSizes[(int)segment], - Header.SegmentHashes[(int)segment], isCompressed, checkHash, buffer); - } - - private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Buffer32 fileHash, - bool isCompressed, bool checkHash, Span buffer) - { - // Select read size based on compression. - if (!isCompressed) - { - fileSize = segment.Size; - } - - // Validate size. - if (fileSize > segment.Size) - return ResultLoader.InvalidNso.Log(); - - // Load data from file. - uint loadAddress = isCompressed ? (uint)buffer.Length - fileSize : 0; - - Result rc = NsoFile.Read(out long bytesRead, segment.FileOffset, buffer.Slice((int)loadAddress), ReadOption.None); - if (rc.IsFailure()) return rc; - - if (bytesRead != fileSize) - return ResultLoader.InvalidNso.Log(); - - // Uncompress if necessary. - if (isCompressed) - { - // todo: Fix in-place decompression - // Lz4.Decompress(buffer.Slice((int)loadAddress), buffer); - byte[] decomp = Lz4.Decompress(buffer.Slice((int)loadAddress).ToArray(), buffer.Length); - decomp.CopyTo(buffer); - } - - // Check hash if necessary. - if (checkHash) - { - var hash = new Buffer32(); - Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Bytes); - - if (hash.Bytes.SequenceCompareTo(fileHash.Bytes) != 0) - return ResultLoader.InvalidNso.Log(); - } - - return Result.Success; - } - - public enum SegmentType - { - Text = 0, - Ro = 1, - Data = 2 + case SegmentType.Text: + case SegmentType.Ro: + case SegmentType.Data: + size = Header.Segments[(int)segment].Size; + return Result.Success; + default: + return ResultLibHac.ArgumentOutOfRange.Log(); } } + + public Result ReadSegment(SegmentType segment, Span buffer) + { + Result rc = GetSegmentSize(segment, out uint segmentSize); + if (rc.IsFailure()) return rc; + + if (buffer.Length < segmentSize) + return ResultLibHac.BufferTooSmall.Log(); + + bool isCompressed = (((int)Header.Flags >> (int)segment) & 1) != 0; + bool checkHash = (((int)Header.Flags >> (int)segment) & 8) != 0; + + return ReadSegmentImpl(ref Header.Segments[(int)segment], Header.CompressedSizes[(int)segment], + Header.SegmentHashes[(int)segment], isCompressed, checkHash, buffer); + } + + private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Buffer32 fileHash, + bool isCompressed, bool checkHash, Span buffer) + { + // Select read size based on compression. + if (!isCompressed) + { + fileSize = segment.Size; + } + + // Validate size. + if (fileSize > segment.Size) + return ResultLoader.InvalidNso.Log(); + + // Load data from file. + uint loadAddress = isCompressed ? (uint)buffer.Length - fileSize : 0; + + Result rc = NsoFile.Read(out long bytesRead, segment.FileOffset, buffer.Slice((int)loadAddress), ReadOption.None); + if (rc.IsFailure()) return rc; + + if (bytesRead != fileSize) + return ResultLoader.InvalidNso.Log(); + + // Uncompress if necessary. + if (isCompressed) + { + // todo: Fix in-place decompression + // Lz4.Decompress(buffer.Slice((int)loadAddress), buffer); + byte[] decomp = Lz4.Decompress(buffer.Slice((int)loadAddress).ToArray(), buffer.Length); + decomp.CopyTo(buffer); + } + + // Check hash if necessary. + if (checkHash) + { + var hash = new Buffer32(); + Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Bytes); + + if (hash.Bytes.SequenceCompareTo(fileHash.Bytes) != 0) + return ResultLoader.InvalidNso.Log(); + } + + return Result.Success; + } + + public enum SegmentType + { + Text = 0, + Ro = 1, + Data = 2 + } } diff --git a/src/LibHac/Loader/ResultLoader.cs b/src/LibHac/Loader/ResultLoader.cs index 9161294c..d040d34d 100644 --- a/src/LibHac/Loader/ResultLoader.cs +++ b/src/LibHac/Loader/ResultLoader.cs @@ -9,83 +9,82 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Loader -{ - public static class ResultLoader - { - public const int ModuleLoader = 9; +namespace LibHac.Loader; - /// Error code: 2009-0001; Inner value: 0x209 - public static Result.Base TooLongArgument => new Result.Base(ModuleLoader, 1); - /// Error code: 2009-0002; Inner value: 0x409 - public static Result.Base TooManyArguments => new Result.Base(ModuleLoader, 2); - /// Error code: 2009-0003; Inner value: 0x609 - public static Result.Base TooLargeMeta => new Result.Base(ModuleLoader, 3); - /// Error code: 2009-0004; Inner value: 0x809 - public static Result.Base InvalidMeta => new Result.Base(ModuleLoader, 4); - /// Error code: 2009-0005; Inner value: 0xa09 - public static Result.Base InvalidNso => new Result.Base(ModuleLoader, 5); - /// Error code: 2009-0006; Inner value: 0xc09 - public static Result.Base InvalidPath => new Result.Base(ModuleLoader, 6); - /// Error code: 2009-0007; Inner value: 0xe09 - public static Result.Base TooManyProcesses => new Result.Base(ModuleLoader, 7); - /// Error code: 2009-0008; Inner value: 0x1009 - public static Result.Base NotPinned => new Result.Base(ModuleLoader, 8); - /// Error code: 2009-0009; Inner value: 0x1209 - public static Result.Base InvalidProgramId => new Result.Base(ModuleLoader, 9); - /// Error code: 2009-0010; Inner value: 0x1409 - public static Result.Base InvalidVersion => new Result.Base(ModuleLoader, 10); - /// Error code: 2009-0011; Inner value: 0x1609 - public static Result.Base InvalidAcidSignature => new Result.Base(ModuleLoader, 11); - /// Error code: 2009-0012; Inner value: 0x1809 - public static Result.Base InvalidNcaSignature => new Result.Base(ModuleLoader, 12); - /// Error code: 2009-0051; Inner value: 0x6609 - public static Result.Base InsufficientAddressSpace => new Result.Base(ModuleLoader, 51); - /// Error code: 2009-0052; Inner value: 0x6809 - public static Result.Base InvalidNro => new Result.Base(ModuleLoader, 52); - /// Error code: 2009-0053; Inner value: 0x6a09 - public static Result.Base InvalidNrr => new Result.Base(ModuleLoader, 53); - /// Error code: 2009-0054; Inner value: 0x6c09 - public static Result.Base InvalidSignature => new Result.Base(ModuleLoader, 54); - /// Error code: 2009-0055; Inner value: 0x6e09 - public static Result.Base InsufficientNroRegistrations => new Result.Base(ModuleLoader, 55); - /// Error code: 2009-0056; Inner value: 0x7009 - public static Result.Base InsufficientNrrRegistrations => new Result.Base(ModuleLoader, 56); - /// Error code: 2009-0057; Inner value: 0x7209 - public static Result.Base NroAlreadyLoaded => new Result.Base(ModuleLoader, 57); - /// Error code: 2009-0081; Inner value: 0xa209 - public static Result.Base InvalidAddress => new Result.Base(ModuleLoader, 81); - /// Error code: 2009-0082; Inner value: 0xa409 - public static Result.Base InvalidSize => new Result.Base(ModuleLoader, 82); - /// Error code: 2009-0084; Inner value: 0xa809 - public static Result.Base NotLoaded => new Result.Base(ModuleLoader, 84); - /// Error code: 2009-0085; Inner value: 0xaa09 - public static Result.Base NotRegistered => new Result.Base(ModuleLoader, 85); - /// Error code: 2009-0086; Inner value: 0xac09 - public static Result.Base InvalidSession => new Result.Base(ModuleLoader, 86); - /// Error code: 2009-0087; Inner value: 0xae09 - public static Result.Base InvalidProcess => new Result.Base(ModuleLoader, 87); - /// Error code: 2009-0100; Inner value: 0xc809 - public static Result.Base UnknownCapability => new Result.Base(ModuleLoader, 100); - /// Error code: 2009-0103; Inner value: 0xce09 - public static Result.Base InvalidCapabilityKernelFlags => new Result.Base(ModuleLoader, 103); - /// Error code: 2009-0104; Inner value: 0xd009 - public static Result.Base InvalidCapabilitySyscallMask => new Result.Base(ModuleLoader, 104); - /// Error code: 2009-0106; Inner value: 0xd409 - public static Result.Base InvalidCapabilityMapRange => new Result.Base(ModuleLoader, 106); - /// Error code: 2009-0107; Inner value: 0xd609 - public static Result.Base InvalidCapabilityMapPage => new Result.Base(ModuleLoader, 107); - /// Error code: 2009-0111; Inner value: 0xde09 - public static Result.Base InvalidCapabilityInterruptPair => new Result.Base(ModuleLoader, 111); - /// Error code: 2009-0113; Inner value: 0xe209 - public static Result.Base InvalidCapabilityApplicationType => new Result.Base(ModuleLoader, 113); - /// Error code: 2009-0114; Inner value: 0xe409 - public static Result.Base InvalidCapabilityKernelVersion => new Result.Base(ModuleLoader, 114); - /// Error code: 2009-0115; Inner value: 0xe609 - public static Result.Base InvalidCapabilityHandleTable => new Result.Base(ModuleLoader, 115); - /// Error code: 2009-0116; Inner value: 0xe809 - public static Result.Base InvalidCapabilityDebugFlags => new Result.Base(ModuleLoader, 116); - /// Error code: 2009-0200; Inner value: 0x19009 - public static Result.Base InternalError => new Result.Base(ModuleLoader, 200); - } +public static class ResultLoader +{ + public const int ModuleLoader = 9; + + /// Error code: 2009-0001; Inner value: 0x209 + public static Result.Base TooLongArgument => new Result.Base(ModuleLoader, 1); + /// Error code: 2009-0002; Inner value: 0x409 + public static Result.Base TooManyArguments => new Result.Base(ModuleLoader, 2); + /// Error code: 2009-0003; Inner value: 0x609 + public static Result.Base TooLargeMeta => new Result.Base(ModuleLoader, 3); + /// Error code: 2009-0004; Inner value: 0x809 + public static Result.Base InvalidMeta => new Result.Base(ModuleLoader, 4); + /// Error code: 2009-0005; Inner value: 0xa09 + public static Result.Base InvalidNso => new Result.Base(ModuleLoader, 5); + /// Error code: 2009-0006; Inner value: 0xc09 + public static Result.Base InvalidPath => new Result.Base(ModuleLoader, 6); + /// Error code: 2009-0007; Inner value: 0xe09 + public static Result.Base TooManyProcesses => new Result.Base(ModuleLoader, 7); + /// Error code: 2009-0008; Inner value: 0x1009 + public static Result.Base NotPinned => new Result.Base(ModuleLoader, 8); + /// Error code: 2009-0009; Inner value: 0x1209 + public static Result.Base InvalidProgramId => new Result.Base(ModuleLoader, 9); + /// Error code: 2009-0010; Inner value: 0x1409 + public static Result.Base InvalidVersion => new Result.Base(ModuleLoader, 10); + /// Error code: 2009-0011; Inner value: 0x1609 + public static Result.Base InvalidAcidSignature => new Result.Base(ModuleLoader, 11); + /// Error code: 2009-0012; Inner value: 0x1809 + public static Result.Base InvalidNcaSignature => new Result.Base(ModuleLoader, 12); + /// Error code: 2009-0051; Inner value: 0x6609 + public static Result.Base InsufficientAddressSpace => new Result.Base(ModuleLoader, 51); + /// Error code: 2009-0052; Inner value: 0x6809 + public static Result.Base InvalidNro => new Result.Base(ModuleLoader, 52); + /// Error code: 2009-0053; Inner value: 0x6a09 + public static Result.Base InvalidNrr => new Result.Base(ModuleLoader, 53); + /// Error code: 2009-0054; Inner value: 0x6c09 + public static Result.Base InvalidSignature => new Result.Base(ModuleLoader, 54); + /// Error code: 2009-0055; Inner value: 0x6e09 + public static Result.Base InsufficientNroRegistrations => new Result.Base(ModuleLoader, 55); + /// Error code: 2009-0056; Inner value: 0x7009 + public static Result.Base InsufficientNrrRegistrations => new Result.Base(ModuleLoader, 56); + /// Error code: 2009-0057; Inner value: 0x7209 + public static Result.Base NroAlreadyLoaded => new Result.Base(ModuleLoader, 57); + /// Error code: 2009-0081; Inner value: 0xa209 + public static Result.Base InvalidAddress => new Result.Base(ModuleLoader, 81); + /// Error code: 2009-0082; Inner value: 0xa409 + public static Result.Base InvalidSize => new Result.Base(ModuleLoader, 82); + /// Error code: 2009-0084; Inner value: 0xa809 + public static Result.Base NotLoaded => new Result.Base(ModuleLoader, 84); + /// Error code: 2009-0085; Inner value: 0xaa09 + public static Result.Base NotRegistered => new Result.Base(ModuleLoader, 85); + /// Error code: 2009-0086; Inner value: 0xac09 + public static Result.Base InvalidSession => new Result.Base(ModuleLoader, 86); + /// Error code: 2009-0087; Inner value: 0xae09 + public static Result.Base InvalidProcess => new Result.Base(ModuleLoader, 87); + /// Error code: 2009-0100; Inner value: 0xc809 + public static Result.Base UnknownCapability => new Result.Base(ModuleLoader, 100); + /// Error code: 2009-0103; Inner value: 0xce09 + public static Result.Base InvalidCapabilityKernelFlags => new Result.Base(ModuleLoader, 103); + /// Error code: 2009-0104; Inner value: 0xd009 + public static Result.Base InvalidCapabilitySyscallMask => new Result.Base(ModuleLoader, 104); + /// Error code: 2009-0106; Inner value: 0xd409 + public static Result.Base InvalidCapabilityMapRange => new Result.Base(ModuleLoader, 106); + /// Error code: 2009-0107; Inner value: 0xd609 + public static Result.Base InvalidCapabilityMapPage => new Result.Base(ModuleLoader, 107); + /// Error code: 2009-0111; Inner value: 0xde09 + public static Result.Base InvalidCapabilityInterruptPair => new Result.Base(ModuleLoader, 111); + /// Error code: 2009-0113; Inner value: 0xe209 + public static Result.Base InvalidCapabilityApplicationType => new Result.Base(ModuleLoader, 113); + /// Error code: 2009-0114; Inner value: 0xe409 + public static Result.Base InvalidCapabilityKernelVersion => new Result.Base(ModuleLoader, 114); + /// Error code: 2009-0115; Inner value: 0xe609 + public static Result.Base InvalidCapabilityHandleTable => new Result.Base(ModuleLoader, 115); + /// Error code: 2009-0116; Inner value: 0xe809 + public static Result.Base InvalidCapabilityDebugFlags => new Result.Base(ModuleLoader, 116); + /// Error code: 2009-0200; Inner value: 0x19009 + public static Result.Base InternalError => new Result.Base(ModuleLoader, 200); } diff --git a/src/LibHac/Loader/Types.cs b/src/LibHac/Loader/Types.cs index bdc0d4d4..123cd5ef 100644 --- a/src/LibHac/Loader/Types.cs +++ b/src/LibHac/Loader/Types.cs @@ -4,89 +4,88 @@ using LibHac.Common.FixedArrays; using LibHac.Ncm; #pragma warning disable 169 // Unused private fields -namespace LibHac.Loader +namespace LibHac.Loader; + +public ref struct Npdm { - public ref struct Npdm - { - public ReadOnlyRef Meta; - public ReadOnlyRef Acid; - public ReadOnlyRef Aci; + public ReadOnlyRef Meta; + public ReadOnlyRef Acid; + public ReadOnlyRef Aci; - public ReadOnlySpan FsAccessControlDescriptor; - public ReadOnlySpan ServiceAccessControlDescriptor; - public ReadOnlySpan KernelCapabilityDescriptor; + public ReadOnlySpan FsAccessControlDescriptor; + public ReadOnlySpan ServiceAccessControlDescriptor; + public ReadOnlySpan KernelCapabilityDescriptor; - public ReadOnlySpan FsAccessControlData; - public ReadOnlySpan ServiceAccessControlData; - public ReadOnlySpan KernelCapabilityData; - } - - public struct Meta - { - public static readonly uint MagicValue = 0x4154454D; // META - - public uint Magic; - public int SignatureKeyGeneration; - private Array4 _reserved08; - public byte Flags; - private byte _reserved0D; - public byte MainThreadPriority; - public byte DefaultCpuId; - private Array4 _reserved10; - public uint SystemResourceSize; - public uint Version; - public uint MainThreadStackSize; - private Array16 _programName; - private Array16 _productCode; - private Array32 _reserved40; - private Array16 _reserved60; - public int AciOffset; - public int AciSize; - public int AcidOffset; - public int AcidSize; - - public readonly ReadOnlySpan ProgramName => _programName.ItemsRo; - public readonly ReadOnlySpan ProductCode => _productCode.ItemsRo; - } - - public struct AciHeader - { - public static readonly uint MagicValue = 0x30494341; // ACI0 - - public uint Magic; - private Array12 _reserved04; - public ProgramId ProgramId; - private Array8 _reserved18; - public int FsAccessControlOffset; - public int FsAccessControlSize; - public int ServiceAccessControlOffset; - public int ServiceAccessControlSize; - public int KernelCapabilityOffset; - public int KernelCapabilitySize; - private Array4 _reserved38; - } - - public struct AcidHeaderData - { - public static readonly uint MagicValue = 0x44494341; // ACID - - private Array256 _signature; - private Array256 _modulus; - public uint Magic; - public int Size; - public byte Version; - public uint Flags; - public ProgramId ProgramIdMin; - public ProgramId ProgramIdMax; - public int FsAccessControlOffset; - public int FsAccessControlSize; - public int ServiceAccessControlOffset; - public int ServiceAccessControlSize; - public int KernelCapabilityOffset; - public int KernelCapabilitySize; - private Array4 _reserved238; - - public readonly ReadOnlySpan Signature => _signature.ItemsRo; - public readonly ReadOnlySpan Modulus => _modulus.ItemsRo; - } + public ReadOnlySpan FsAccessControlData; + public ReadOnlySpan ServiceAccessControlData; + public ReadOnlySpan KernelCapabilityData; +} + +public struct Meta +{ + public static readonly uint MagicValue = 0x4154454D; // META + + public uint Magic; + public int SignatureKeyGeneration; + private Array4 _reserved08; + public byte Flags; + private byte _reserved0D; + public byte MainThreadPriority; + public byte DefaultCpuId; + private Array4 _reserved10; + public uint SystemResourceSize; + public uint Version; + public uint MainThreadStackSize; + private Array16 _programName; + private Array16 _productCode; + private Array32 _reserved40; + private Array16 _reserved60; + public int AciOffset; + public int AciSize; + public int AcidOffset; + public int AcidSize; + + public readonly ReadOnlySpan ProgramName => _programName.ItemsRo; + public readonly ReadOnlySpan ProductCode => _productCode.ItemsRo; +} + +public struct AciHeader +{ + public static readonly uint MagicValue = 0x30494341; // ACI0 + + public uint Magic; + private Array12 _reserved04; + public ProgramId ProgramId; + private Array8 _reserved18; + public int FsAccessControlOffset; + public int FsAccessControlSize; + public int ServiceAccessControlOffset; + public int ServiceAccessControlSize; + public int KernelCapabilityOffset; + public int KernelCapabilitySize; + private Array4 _reserved38; +} + +public struct AcidHeaderData +{ + public static readonly uint MagicValue = 0x44494341; // ACID + + private Array256 _signature; + private Array256 _modulus; + public uint Magic; + public int Size; + public byte Version; + public uint Flags; + public ProgramId ProgramIdMin; + public ProgramId ProgramIdMax; + public int FsAccessControlOffset; + public int FsAccessControlSize; + public int ServiceAccessControlOffset; + public int ServiceAccessControlSize; + public int KernelCapabilityOffset; + public int KernelCapabilitySize; + private Array4 _reserved238; + + public readonly ReadOnlySpan Signature => _signature.ItemsRo; + public readonly ReadOnlySpan Modulus => _modulus.ItemsRo; } diff --git a/src/LibHac/Lr/AddOnContentLocationResolver.cs b/src/LibHac/Lr/AddOnContentLocationResolver.cs index 4f961e37..fa84d8d3 100644 --- a/src/LibHac/Lr/AddOnContentLocationResolver.cs +++ b/src/LibHac/Lr/AddOnContentLocationResolver.cs @@ -3,35 +3,34 @@ using LibHac.Common; using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.Lr +namespace LibHac.Lr; + +public class AddOnContentLocationResolver : IDisposable { - public class AddOnContentLocationResolver : IDisposable + private SharedRef _interface; + + public AddOnContentLocationResolver(ref SharedRef baseInterface) { - private SharedRef _interface; - - public AddOnContentLocationResolver(ref SharedRef baseInterface) - { - _interface = SharedRef.CreateMove(ref baseInterface); - } - - public void Dispose() - { - _interface.Destroy(); - } - - public Result ResolveAddOnContentPath(out Path path, DataId id) => - _interface.Get.ResolveAddOnContentPath(out path, id); - - public Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId) => - _interface.Get.RegisterAddOnContentStorage(id, applicationId, storageId); - - public Result UnregisterAllAddOnContentPath() => - _interface.Get.UnregisterAllAddOnContentPath(); - - public Result RefreshApplicationAddOnContent(InArray ids) => - _interface.Get.RefreshApplicationAddOnContent(ids); - - public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) => - _interface.Get.UnregisterApplicationAddOnContent(id); + _interface = SharedRef.CreateMove(ref baseInterface); } + + public void Dispose() + { + _interface.Destroy(); + } + + public Result ResolveAddOnContentPath(out Path path, DataId id) => + _interface.Get.ResolveAddOnContentPath(out path, id); + + public Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId) => + _interface.Get.RegisterAddOnContentStorage(id, applicationId, storageId); + + public Result UnregisterAllAddOnContentPath() => + _interface.Get.UnregisterAllAddOnContentPath(); + + public Result RefreshApplicationAddOnContent(InArray ids) => + _interface.Get.RefreshApplicationAddOnContent(ids); + + public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) => + _interface.Get.UnregisterApplicationAddOnContent(id); } diff --git a/src/LibHac/Lr/IAddOnContentLocationResolver.cs b/src/LibHac/Lr/IAddOnContentLocationResolver.cs index a199d9bd..a8a0c440 100644 --- a/src/LibHac/Lr/IAddOnContentLocationResolver.cs +++ b/src/LibHac/Lr/IAddOnContentLocationResolver.cs @@ -2,14 +2,13 @@ using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.Lr +namespace LibHac.Lr; + +public interface IAddOnContentLocationResolver : IDisposable { - public interface IAddOnContentLocationResolver : IDisposable - { - Result ResolveAddOnContentPath(out Path path, DataId id); - Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId); - Result UnregisterAllAddOnContentPath(); - Result RefreshApplicationAddOnContent(InArray ids); - Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id); - } + Result ResolveAddOnContentPath(out Path path, DataId id); + Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId); + Result UnregisterAllAddOnContentPath(); + Result RefreshApplicationAddOnContent(InArray ids); + Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id); } diff --git a/src/LibHac/Lr/ILocationResolver.cs b/src/LibHac/Lr/ILocationResolver.cs index c54f5953..2e2f625d 100644 --- a/src/LibHac/Lr/ILocationResolver.cs +++ b/src/LibHac/Lr/ILocationResolver.cs @@ -2,29 +2,28 @@ using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.Lr +namespace LibHac.Lr; + +public interface ILocationResolver : IDisposable { - public interface ILocationResolver : IDisposable - { - Result ResolveProgramPath(out Path path, ProgramId id); - Result RedirectProgramPath(in Path path, ProgramId id); - Result ResolveApplicationControlPath(out Path path, ProgramId id); - Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id); - Result ResolveDataPath(out Path path, DataId id); - Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId); - Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); - Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id); - Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId); - Result Refresh(); - Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId); - Result ClearApplicationRedirection(InArray excludingIds); - Result EraseProgramRedirection(ProgramId id); - Result EraseApplicationControlRedirection(ProgramId id); - Result EraseApplicationHtmlDocumentRedirection(ProgramId id); - Result EraseApplicationLegalInformationRedirection(ProgramId id); - Result ResolveProgramPathForDebug(out Path path, ProgramId id); - Result RedirectProgramPathForDebug(in Path path, ProgramId id); - Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId); - Result EraseProgramRedirectionForDebug(ProgramId id); - } + Result ResolveProgramPath(out Path path, ProgramId id); + Result RedirectProgramPath(in Path path, ProgramId id); + Result ResolveApplicationControlPath(out Path path, ProgramId id); + Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id); + Result ResolveDataPath(out Path path, DataId id); + Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId); + Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); + Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id); + Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId); + Result Refresh(); + Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result ClearApplicationRedirection(InArray excludingIds); + Result EraseProgramRedirection(ProgramId id); + Result EraseApplicationControlRedirection(ProgramId id); + Result EraseApplicationHtmlDocumentRedirection(ProgramId id); + Result EraseApplicationLegalInformationRedirection(ProgramId id); + Result ResolveProgramPathForDebug(out Path path, ProgramId id); + Result RedirectProgramPathForDebug(in Path path, ProgramId id); + Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId); + Result EraseProgramRedirectionForDebug(ProgramId id); } diff --git a/src/LibHac/Lr/ILocationResolverManager.cs b/src/LibHac/Lr/ILocationResolverManager.cs index 5f591373..2ef6e5d2 100644 --- a/src/LibHac/Lr/ILocationResolverManager.cs +++ b/src/LibHac/Lr/ILocationResolverManager.cs @@ -2,13 +2,12 @@ using LibHac.Common; using LibHac.Ncm; -namespace LibHac.Lr +namespace LibHac.Lr; + +public interface ILocationResolverManager : IDisposable { - public interface ILocationResolverManager : IDisposable - { - Result OpenLocationResolver(ref SharedRef outResolver, StorageId storageId); - Result OpenRegisteredLocationResolver(ref SharedRef outResolver); - Result RefreshLocationResolver(StorageId storageId); - Result OpenAddOnContentLocationResolver(ref SharedRef outResolver); - } + Result OpenLocationResolver(ref SharedRef outResolver, StorageId storageId); + Result OpenRegisteredLocationResolver(ref SharedRef outResolver); + Result RefreshLocationResolver(StorageId storageId); + Result OpenAddOnContentLocationResolver(ref SharedRef outResolver); } diff --git a/src/LibHac/Lr/IRegisteredLocationResolver.cs b/src/LibHac/Lr/IRegisteredLocationResolver.cs index 4aa7c543..91756ecc 100644 --- a/src/LibHac/Lr/IRegisteredLocationResolver.cs +++ b/src/LibHac/Lr/IRegisteredLocationResolver.cs @@ -1,19 +1,18 @@ using System; using LibHac.Ncm; -namespace LibHac.Lr +namespace LibHac.Lr; + +public interface IRegisteredLocationResolver : IDisposable { - public interface IRegisteredLocationResolver : IDisposable - { - Result ResolveProgramPath(out Path path, ProgramId id); - Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId); - Result UnregisterProgramPath(ProgramId id); - Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId); - Result ResolveHtmlDocumentPath(out Path path, ProgramId id); - Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); - Result UnregisterHtmlDocumentPath(ProgramId id); - Result RedirectHtmlDocumentPath(in Path path, ProgramId id); - Result Refresh(); - Result RefreshExcluding(ReadOnlySpan ids); - } + Result ResolveProgramPath(out Path path, ProgramId id); + Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result UnregisterProgramPath(ProgramId id); + Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result ResolveHtmlDocumentPath(out Path path, ProgramId id); + Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); + Result UnregisterHtmlDocumentPath(ProgramId id); + Result RedirectHtmlDocumentPath(in Path path, ProgramId id); + Result Refresh(); + Result RefreshExcluding(ReadOnlySpan ids); } diff --git a/src/LibHac/Lr/LocationResolver.cs b/src/LibHac/Lr/LocationResolver.cs index 48366328..50cbf33d 100644 --- a/src/LibHac/Lr/LocationResolver.cs +++ b/src/LibHac/Lr/LocationResolver.cs @@ -3,80 +3,79 @@ using LibHac.Common; using LibHac.Ncm; using LibHac.Sf; -namespace LibHac.Lr +namespace LibHac.Lr; + +public class LocationResolver : IDisposable { - public class LocationResolver : IDisposable + private SharedRef _interface; + + public LocationResolver(ref SharedRef baseInterface) { - private SharedRef _interface; - - public LocationResolver(ref SharedRef baseInterface) - { - _interface = SharedRef.CreateMove(ref baseInterface); - } - - public void Dispose() - { - _interface.Destroy(); - } - - public Result ResolveProgramPath(out Path path, ProgramId id) => - _interface.Get.ResolveProgramPath(out path, id); - - public Result RedirectProgramPath(in Path path, ProgramId id) => - _interface.Get.RedirectProgramPath(in path, id); - - public Result ResolveApplicationControlPath(out Path path, ProgramId id) => - _interface.Get.ResolveApplicationControlPath(out path, id); - - public Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id) => - _interface.Get.ResolveApplicationHtmlDocumentPath(out path, id); - - public Result ResolveDataPath(out Path path, DataId id) => - _interface.Get.ResolveDataPath(out path, id); - - public Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectApplicationControlPath(in path, id, ownerId); - - public Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectApplicationHtmlDocumentPath(in path, id, ownerId); - - public Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id) => - _interface.Get.ResolveApplicationLegalInformationPath(out path, id); - - public Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectApplicationLegalInformationPath(in path, id, ownerId); - - public Result Refresh() => - _interface.Get.Refresh(); - - public Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectApplicationProgramPath(in path, id, ownerId); - - public Result ClearApplicationRedirection(InArray excludingIds) => - _interface.Get.ClearApplicationRedirection(excludingIds); - - public Result EraseProgramRedirection(ProgramId id) => - _interface.Get.EraseProgramRedirection(id); - - public Result EraseApplicationControlRedirection(ProgramId id) => - _interface.Get.EraseApplicationControlRedirection(id); - - public Result EraseApplicationHtmlDocumentRedirection(ProgramId id) => - _interface.Get.EraseApplicationHtmlDocumentRedirection(id); - - public Result EraseApplicationLegalInformationRedirection(ProgramId id) => - _interface.Get.EraseApplicationLegalInformationRedirection(id); - - public Result ResolveProgramPathForDebug(out Path path, ProgramId id) => - _interface.Get.ResolveProgramPathForDebug(out path, id); - - public Result RedirectProgramPathForDebug(in Path path, ProgramId id) => - _interface.Get.RedirectProgramPathForDebug(in path, id); - - public Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectApplicationProgramPathForDebug(in path, id, ownerId); - - public Result EraseProgramRedirectionForDebug(ProgramId id) => - _interface.Get.EraseProgramRedirectionForDebug(id); + _interface = SharedRef.CreateMove(ref baseInterface); } + + public void Dispose() + { + _interface.Destroy(); + } + + public Result ResolveProgramPath(out Path path, ProgramId id) => + _interface.Get.ResolveProgramPath(out path, id); + + public Result RedirectProgramPath(in Path path, ProgramId id) => + _interface.Get.RedirectProgramPath(in path, id); + + public Result ResolveApplicationControlPath(out Path path, ProgramId id) => + _interface.Get.ResolveApplicationControlPath(out path, id); + + public Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id) => + _interface.Get.ResolveApplicationHtmlDocumentPath(out path, id); + + public Result ResolveDataPath(out Path path, DataId id) => + _interface.Get.ResolveDataPath(out path, id); + + public Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectApplicationControlPath(in path, id, ownerId); + + public Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectApplicationHtmlDocumentPath(in path, id, ownerId); + + public Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id) => + _interface.Get.ResolveApplicationLegalInformationPath(out path, id); + + public Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectApplicationLegalInformationPath(in path, id, ownerId); + + public Result Refresh() => + _interface.Get.Refresh(); + + public Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectApplicationProgramPath(in path, id, ownerId); + + public Result ClearApplicationRedirection(InArray excludingIds) => + _interface.Get.ClearApplicationRedirection(excludingIds); + + public Result EraseProgramRedirection(ProgramId id) => + _interface.Get.EraseProgramRedirection(id); + + public Result EraseApplicationControlRedirection(ProgramId id) => + _interface.Get.EraseApplicationControlRedirection(id); + + public Result EraseApplicationHtmlDocumentRedirection(ProgramId id) => + _interface.Get.EraseApplicationHtmlDocumentRedirection(id); + + public Result EraseApplicationLegalInformationRedirection(ProgramId id) => + _interface.Get.EraseApplicationLegalInformationRedirection(id); + + public Result ResolveProgramPathForDebug(out Path path, ProgramId id) => + _interface.Get.ResolveProgramPathForDebug(out path, id); + + public Result RedirectProgramPathForDebug(in Path path, ProgramId id) => + _interface.Get.RedirectProgramPathForDebug(in path, id); + + public Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectApplicationProgramPathForDebug(in path, id, ownerId); + + public Result EraseProgramRedirectionForDebug(ProgramId id) => + _interface.Get.EraseProgramRedirectionForDebug(id); } diff --git a/src/LibHac/Lr/LrClient.cs b/src/LibHac/Lr/LrClient.cs index d71759dc..d6955346 100644 --- a/src/LibHac/Lr/LrClient.cs +++ b/src/LibHac/Lr/LrClient.cs @@ -1,39 +1,38 @@ using System; using LibHac.Common; -namespace LibHac.Lr +namespace LibHac.Lr; + +public class LrClient : IDisposable { - public class LrClient : IDisposable + internal LrClientGlobals Globals; + internal HorizonClient Hos => Globals.Hos; + + public LrClient(HorizonClient horizonClient) { - internal LrClientGlobals Globals; - internal HorizonClient Hos => Globals.Hos; - - public LrClient(HorizonClient horizonClient) - { - Globals.Initialize(this, horizonClient); - } - - public void Dispose() - { - Globals.Dispose(); - } + Globals.Initialize(this, horizonClient); } - [NonCopyable] - internal struct LrClientGlobals : IDisposable + public void Dispose() { - public HorizonClient Hos; - public LrServiceGlobals LrService; - - public void Initialize(LrClient lrClient, HorizonClient horizonClient) - { - Hos = horizonClient; - LrService.Initialize(); - } - - public void Dispose() - { - LrService.Dispose(); - } + Globals.Dispose(); + } +} + +[NonCopyable] +internal struct LrClientGlobals : IDisposable +{ + public HorizonClient Hos; + public LrServiceGlobals LrService; + + public void Initialize(LrClient lrClient, HorizonClient horizonClient) + { + Hos = horizonClient; + LrService.Initialize(); + } + + public void Dispose() + { + LrService.Dispose(); } } diff --git a/src/LibHac/Lr/LrService.cs b/src/LibHac/Lr/LrService.cs index bf30744b..14f3edb9 100644 --- a/src/LibHac/Lr/LrService.cs +++ b/src/LibHac/Lr/LrService.cs @@ -4,99 +4,98 @@ using LibHac.Diag; using LibHac.Ncm; using LibHac.Os; -namespace LibHac.Lr +namespace LibHac.Lr; + +[NonCopyable] +internal struct LrServiceGlobals : IDisposable { - [NonCopyable] - internal struct LrServiceGlobals : IDisposable + public SharedRef LocationResolver; + public SdkMutexType InitializationMutex; + + public void Initialize() { - public SharedRef LocationResolver; - public SdkMutexType InitializationMutex; - - public void Initialize() - { - InitializationMutex.Initialize(); - } - - public void Dispose() - { - LocationResolver.Destroy(); - } + InitializationMutex.Initialize(); } - public static class LrService + public void Dispose() { - public static void Initialize(this LrClient lr) - { - ref LrServiceGlobals globals = ref lr.Globals.LrService; - Assert.SdkRequiresNotNull(globals.LocationResolver.Get); - - // The lock over getting the service object is a LibHac addition. - using ScopedLock scopedLock = ScopedLock.Lock(ref lr.Globals.LrService.InitializationMutex); - - if (globals.LocationResolver.HasValue) - return; - - using SharedRef serviceObject = lr.GetLocationResolverManagerServiceObject(); - globals.LocationResolver.SetByMove(ref serviceObject.Ref()); - } - - public static Result OpenLocationResolver(this LrClient lr, out LocationResolver outResolver, StorageId storageId) - { - UnsafeHelpers.SkipParamInit(out outResolver); - - using var resolver = new SharedRef(); - Result rc = lr.Globals.LrService.LocationResolver.Get.OpenLocationResolver(ref resolver.Ref(), storageId); - if (rc.IsFailure()) return rc; - - outResolver = new LocationResolver(ref resolver.Ref()); - return Result.Success; - } - - public static Result OpenRegisteredLocationResolver(this LrClient lr, out RegisteredLocationResolver outResolver) - { - UnsafeHelpers.SkipParamInit(out outResolver); - - using var resolver = new SharedRef(); - Result rc = lr.Globals.LrService.LocationResolver.Get.OpenRegisteredLocationResolver(ref resolver.Ref()); - if (rc.IsFailure()) return rc; - - outResolver = new RegisteredLocationResolver(ref resolver.Ref()); - return Result.Success; - } - - public static Result OpenAddOnContentLocationResolver(this LrClient lr, out AddOnContentLocationResolver outResolver) - { - UnsafeHelpers.SkipParamInit(out outResolver); - - using var resolver = new SharedRef(); - Result rc = lr.Globals.LrService.LocationResolver.Get.OpenAddOnContentLocationResolver(ref resolver.Ref()); - if (rc.IsFailure()) return rc; - - outResolver = new AddOnContentLocationResolver(ref resolver.Ref()); - return Result.Success; - } - - public static Result RefreshLocationResolver(this LrClient lr, StorageId storageId) - { - Result rc = lr.Globals.LrService.LocationResolver.Get.RefreshLocationResolver(storageId); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - // Official lr puts this function along with memory allocation for - // lr IPC objects into a separate file, LocationResolverManagerFactory. - private static SharedRef GetLocationResolverManagerServiceObject(this LrClient lr) - { - using var manager = new SharedRef(); - Result rc = lr.Hos.Sm.GetService(ref manager.Ref(), "lr"); - - if (rc.IsFailure()) - { - throw new HorizonResultException(rc, "Failed to get lr service object."); - } - - return SharedRef.CreateMove(ref manager.Ref()); - } + LocationResolver.Destroy(); + } +} + +public static class LrService +{ + public static void Initialize(this LrClient lr) + { + ref LrServiceGlobals globals = ref lr.Globals.LrService; + Assert.SdkRequiresNotNull(globals.LocationResolver.Get); + + // The lock over getting the service object is a LibHac addition. + using ScopedLock scopedLock = ScopedLock.Lock(ref lr.Globals.LrService.InitializationMutex); + + if (globals.LocationResolver.HasValue) + return; + + using SharedRef serviceObject = lr.GetLocationResolverManagerServiceObject(); + globals.LocationResolver.SetByMove(ref serviceObject.Ref()); + } + + public static Result OpenLocationResolver(this LrClient lr, out LocationResolver outResolver, StorageId storageId) + { + UnsafeHelpers.SkipParamInit(out outResolver); + + using var resolver = new SharedRef(); + Result rc = lr.Globals.LrService.LocationResolver.Get.OpenLocationResolver(ref resolver.Ref(), storageId); + if (rc.IsFailure()) return rc; + + outResolver = new LocationResolver(ref resolver.Ref()); + return Result.Success; + } + + public static Result OpenRegisteredLocationResolver(this LrClient lr, out RegisteredLocationResolver outResolver) + { + UnsafeHelpers.SkipParamInit(out outResolver); + + using var resolver = new SharedRef(); + Result rc = lr.Globals.LrService.LocationResolver.Get.OpenRegisteredLocationResolver(ref resolver.Ref()); + if (rc.IsFailure()) return rc; + + outResolver = new RegisteredLocationResolver(ref resolver.Ref()); + return Result.Success; + } + + public static Result OpenAddOnContentLocationResolver(this LrClient lr, out AddOnContentLocationResolver outResolver) + { + UnsafeHelpers.SkipParamInit(out outResolver); + + using var resolver = new SharedRef(); + Result rc = lr.Globals.LrService.LocationResolver.Get.OpenAddOnContentLocationResolver(ref resolver.Ref()); + if (rc.IsFailure()) return rc; + + outResolver = new AddOnContentLocationResolver(ref resolver.Ref()); + return Result.Success; + } + + public static Result RefreshLocationResolver(this LrClient lr, StorageId storageId) + { + Result rc = lr.Globals.LrService.LocationResolver.Get.RefreshLocationResolver(storageId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + // Official lr puts this function along with memory allocation for + // lr IPC objects into a separate file, LocationResolverManagerFactory. + private static SharedRef GetLocationResolverManagerServiceObject(this LrClient lr) + { + using var manager = new SharedRef(); + Result rc = lr.Hos.Sm.GetService(ref manager.Ref(), "lr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to get lr service object."); + } + + return SharedRef.CreateMove(ref manager.Ref()); } } diff --git a/src/LibHac/Lr/Path.cs b/src/LibHac/Lr/Path.cs index 0196420d..bbb30172 100644 --- a/src/LibHac/Lr/Path.cs +++ b/src/LibHac/Lr/Path.cs @@ -6,31 +6,30 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Util; -namespace LibHac.Lr +namespace LibHac.Lr; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax)] +public struct Path { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax)] - public struct Path - { #if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; #endif - public readonly ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); - public Span StrMutable => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + public Span StrMutable => SpanHelpers.AsByteSpan(ref this); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitEmpty(out Path path) - { - UnsafeHelpers.SkipParamInit(out path); - SpanHelpers.AsByteSpan(ref path)[0] = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator U8Span(in Path value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); - - public override readonly string ToString() => StringUtils.Utf8ZToString(Str); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitEmpty(out Path path) + { + UnsafeHelpers.SkipParamInit(out path); + SpanHelpers.AsByteSpan(ref path)[0] = 0; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator U8Span(in Path value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); + + public override readonly string ToString() => StringUtils.Utf8ZToString(Str); } diff --git a/src/LibHac/Lr/RegisteredLocationResolver.cs b/src/LibHac/Lr/RegisteredLocationResolver.cs index 9abaa040..c79ee901 100644 --- a/src/LibHac/Lr/RegisteredLocationResolver.cs +++ b/src/LibHac/Lr/RegisteredLocationResolver.cs @@ -2,50 +2,49 @@ using LibHac.Common; using LibHac.Ncm; -namespace LibHac.Lr +namespace LibHac.Lr; + +public class RegisteredLocationResolver : IDisposable { - public class RegisteredLocationResolver : IDisposable + private SharedRef _interface; + + public RegisteredLocationResolver(ref SharedRef baseInterface) { - private SharedRef _interface; - - public RegisteredLocationResolver(ref SharedRef baseInterface) - { - _interface = SharedRef.CreateMove(ref baseInterface); - } - - public void Dispose() - { - _interface.Destroy(); - } - - public Result ResolveProgramPath(out Path path, ProgramId id) => - _interface.Get.ResolveProgramPath(out path, id); - - public Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RegisterProgramPath(in path, id, ownerId); - - public Result UnregisterProgramPath(ProgramId id) => - _interface.Get.UnregisterProgramPath(id); - - public Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RedirectProgramPath(in path, id, ownerId); - - public Result ResolveHtmlDocumentPath(out Path path, ProgramId id) => - _interface.Get.ResolveHtmlDocumentPath(out path, id); - - public Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => - _interface.Get.RegisterHtmlDocumentPath(in path, id, ownerId); - - public Result UnregisterHtmlDocumentPath(ProgramId id) => - _interface.Get.UnregisterHtmlDocumentPath(id); - - public Result RedirectHtmlDocumentPath(in Path path, ProgramId id) => - _interface.Get.RedirectHtmlDocumentPath(in path, id); - - public Result Refresh() => - _interface.Get.Refresh(); - - public Result RefreshExcluding(ReadOnlySpan ids) => - _interface.Get.RefreshExcluding(ids); + _interface = SharedRef.CreateMove(ref baseInterface); } + + public void Dispose() + { + _interface.Destroy(); + } + + public Result ResolveProgramPath(out Path path, ProgramId id) => + _interface.Get.ResolveProgramPath(out path, id); + + public Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RegisterProgramPath(in path, id, ownerId); + + public Result UnregisterProgramPath(ProgramId id) => + _interface.Get.UnregisterProgramPath(id); + + public Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RedirectProgramPath(in path, id, ownerId); + + public Result ResolveHtmlDocumentPath(out Path path, ProgramId id) => + _interface.Get.ResolveHtmlDocumentPath(out path, id); + + public Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Get.RegisterHtmlDocumentPath(in path, id, ownerId); + + public Result UnregisterHtmlDocumentPath(ProgramId id) => + _interface.Get.UnregisterHtmlDocumentPath(id); + + public Result RedirectHtmlDocumentPath(in Path path, ProgramId id) => + _interface.Get.RedirectHtmlDocumentPath(in path, id); + + public Result Refresh() => + _interface.Get.Refresh(); + + public Result RefreshExcluding(ReadOnlySpan ids) => + _interface.Get.RefreshExcluding(ids); } diff --git a/src/LibHac/Lr/ResultLr.cs b/src/LibHac/Lr/ResultLr.cs index 7dbb6001..311e9488 100644 --- a/src/LibHac/Lr/ResultLr.cs +++ b/src/LibHac/Lr/ResultLr.cs @@ -9,33 +9,32 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Lr -{ - public static class ResultLr - { - public const int ModuleLr = 8; +namespace LibHac.Lr; - /// Error code: 2008-0001; Inner value: 0x208 - public static Result.Base ResolverNotFound => new Result.Base(ModuleLr, 1); - /// Error code: 2008-0002; Inner value: 0x408 - public static Result.Base ProgramNotFound => new Result.Base(ModuleLr, 2); - /// Error code: 2008-0003; Inner value: 0x608 - public static Result.Base DataNotFound => new Result.Base(ModuleLr, 3); - /// Error code: 2008-0004; Inner value: 0x808 - public static Result.Base UnknownResolver => new Result.Base(ModuleLr, 4); - /// Error code: 2008-0005; Inner value: 0xa08 - public static Result.Base ApplicationNotFound => new Result.Base(ModuleLr, 5); - /// Error code: 2008-0006; Inner value: 0xc08 - public static Result.Base HtmlDocumentNotFound => new Result.Base(ModuleLr, 6); - /// Error code: 2008-0007; Inner value: 0xe08 - public static Result.Base AddOnContentNotFound => new Result.Base(ModuleLr, 7); - /// Error code: 2008-0008; Inner value: 0x1008 - public static Result.Base ControlNotFound => new Result.Base(ModuleLr, 8); - /// Error code: 2008-0009; Inner value: 0x1208 - public static Result.Base LegalInformationNotFound => new Result.Base(ModuleLr, 9); - /// Error code: 2008-0010; Inner value: 0x1408 - public static Result.Base DebugProgramNotFound => new Result.Base(ModuleLr, 10); - /// Error code: 2008-0090; Inner value: 0xb408 - public static Result.Base TooManyRegisteredPaths => new Result.Base(ModuleLr, 90); - } +public static class ResultLr +{ + public const int ModuleLr = 8; + + /// Error code: 2008-0001; Inner value: 0x208 + public static Result.Base ResolverNotFound => new Result.Base(ModuleLr, 1); + /// Error code: 2008-0002; Inner value: 0x408 + public static Result.Base ProgramNotFound => new Result.Base(ModuleLr, 2); + /// Error code: 2008-0003; Inner value: 0x608 + public static Result.Base DataNotFound => new Result.Base(ModuleLr, 3); + /// Error code: 2008-0004; Inner value: 0x808 + public static Result.Base UnknownResolver => new Result.Base(ModuleLr, 4); + /// Error code: 2008-0005; Inner value: 0xa08 + public static Result.Base ApplicationNotFound => new Result.Base(ModuleLr, 5); + /// Error code: 2008-0006; Inner value: 0xc08 + public static Result.Base HtmlDocumentNotFound => new Result.Base(ModuleLr, 6); + /// Error code: 2008-0007; Inner value: 0xe08 + public static Result.Base AddOnContentNotFound => new Result.Base(ModuleLr, 7); + /// Error code: 2008-0008; Inner value: 0x1008 + public static Result.Base ControlNotFound => new Result.Base(ModuleLr, 8); + /// Error code: 2008-0009; Inner value: 0x1208 + public static Result.Base LegalInformationNotFound => new Result.Base(ModuleLr, 9); + /// Error code: 2008-0010; Inner value: 0x1408 + public static Result.Base DebugProgramNotFound => new Result.Base(ModuleLr, 10); + /// Error code: 2008-0090; Inner value: 0xb408 + public static Result.Base TooManyRegisteredPaths => new Result.Base(ModuleLr, 90); } diff --git a/src/LibHac/Lz4.cs b/src/LibHac/Lz4.cs index 4ad130a0..f3515ef0 100644 --- a/src/LibHac/Lz4.cs +++ b/src/LibHac/Lz4.cs @@ -2,147 +2,146 @@ using System; -namespace LibHac +namespace LibHac; + +public static class Lz4 { - public static class Lz4 + public static byte[] Decompress(byte[] cmp, int decLength) { - public static byte[] Decompress(byte[] cmp, int decLength) + byte[] dec = new byte[decLength]; + + int cmpPos = 0; + int decPos = 0; + + int GetLength(int length) { - byte[] dec = new byte[decLength]; + byte sum; - int cmpPos = 0; - int decPos = 0; - - int GetLength(int length) + if (length == 0xf) { - byte sum; - - if (length == 0xf) + do { - do - { - length += sum = cmp[cmpPos++]; - } - while (sum == 0xff); + length += sum = cmp[cmpPos++]; } - - return length; + while (sum == 0xff); } - do - { - byte token = cmp[cmpPos++]; - - int encCount = (token >> 0) & 0xf; - int litCount = (token >> 4) & 0xf; - - //Copy literal chunk - litCount = GetLength(litCount); - - Buffer.BlockCopy(cmp, cmpPos, dec, decPos, litCount); - - cmpPos += litCount; - decPos += litCount; - - if (cmpPos >= cmp.Length) - { - break; - } - - //Copy compressed chunk - int back = cmp[cmpPos++] << 0 | - cmp[cmpPos++] << 8; - - encCount = GetLength(encCount) + 4; - - int encPos = decPos - back; - - if (encCount <= back) - { - Buffer.BlockCopy(dec, encPos, dec, decPos, encCount); - - decPos += encCount; - } - else - { - while (encCount-- > 0) - { - dec[decPos++] = dec[encPos++]; - } - } - } - while (cmpPos < cmp.Length && - decPos < dec.Length); - - return dec; + return length; } - public static void Decompress(ReadOnlySpan cmp, Span dec) + do { - int cmpPos = 0; - int decPos = 0; + byte token = cmp[cmpPos++]; - // ReSharper disable once VariableHidesOuterVariable - int GetLength(int length, ReadOnlySpan cmp) + int encCount = (token >> 0) & 0xf; + int litCount = (token >> 4) & 0xf; + + //Copy literal chunk + litCount = GetLength(litCount); + + Buffer.BlockCopy(cmp, cmpPos, dec, decPos, litCount); + + cmpPos += litCount; + decPos += litCount; + + if (cmpPos >= cmp.Length) { - byte sum; - - if (length == 0xf) - { - do - { - length += sum = cmp[cmpPos++]; - } - while (sum == 0xff); - } - - return length; + break; } - do + //Copy compressed chunk + int back = cmp[cmpPos++] << 0 | + cmp[cmpPos++] << 8; + + encCount = GetLength(encCount) + 4; + + int encPos = decPos - back; + + if (encCount <= back) { - byte token = cmp[cmpPos++]; + Buffer.BlockCopy(dec, encPos, dec, decPos, encCount); - int encCount = (token >> 0) & 0xf; - int litCount = (token >> 4) & 0xf; - - //Copy literal chunk - litCount = GetLength(litCount, cmp); - - cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos)); - - cmpPos += litCount; - decPos += litCount; - - if (cmpPos >= cmp.Length) + decPos += encCount; + } + else + { + while (encCount-- > 0) { - break; - } - - //Copy compressed chunk - int back = cmp[cmpPos++] << 0 | - cmp[cmpPos++] << 8; - - encCount = GetLength(encCount, cmp) + 4; - - int encPos = decPos - back; - - if (encCount <= back) - { - dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos)); - - decPos += encCount; - } - else - { - while (encCount-- > 0) - { - dec[decPos++] = dec[encPos++]; - } + dec[decPos++] = dec[encPos++]; } } - while (cmpPos < cmp.Length && - decPos < dec.Length); } + while (cmpPos < cmp.Length && + decPos < dec.Length); + + return dec; } -} \ No newline at end of file + + public static void Decompress(ReadOnlySpan cmp, Span dec) + { + int cmpPos = 0; + int decPos = 0; + + // ReSharper disable once VariableHidesOuterVariable + int GetLength(int length, ReadOnlySpan cmp) + { + byte sum; + + if (length == 0xf) + { + do + { + length += sum = cmp[cmpPos++]; + } + while (sum == 0xff); + } + + return length; + } + + do + { + byte token = cmp[cmpPos++]; + + int encCount = (token >> 0) & 0xf; + int litCount = (token >> 4) & 0xf; + + //Copy literal chunk + litCount = GetLength(litCount, cmp); + + cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos)); + + cmpPos += litCount; + decPos += litCount; + + if (cmpPos >= cmp.Length) + { + break; + } + + //Copy compressed chunk + int back = cmp[cmpPos++] << 0 | + cmp[cmpPos++] << 8; + + encCount = GetLength(encCount, cmp) + 4; + + int encPos = decPos - back; + + if (encCount <= back) + { + dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos)); + + decPos += encCount; + } + else + { + while (encCount-- > 0) + { + dec[decPos++] = dec[encPos++]; + } + } + } + while (cmpPos < cmp.Length && + decPos < dec.Length); + } +} diff --git a/src/LibHac/MemoryResource.cs b/src/LibHac/MemoryResource.cs index bff51c8c..568457ff 100644 --- a/src/LibHac/MemoryResource.cs +++ b/src/LibHac/MemoryResource.cs @@ -2,92 +2,91 @@ using System.Buffers; using System.Runtime.CompilerServices; -namespace LibHac +namespace LibHac; + +public abstract class MemoryResource { - public abstract class MemoryResource + private const int DefaultAlignment = 8; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer Allocate(long size, int alignment = DefaultAlignment) => + DoAllocate(size, alignment); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Deallocate(ref Buffer buffer, int alignment = DefaultAlignment) { - private const int DefaultAlignment = 8; + DoDeallocate(buffer, alignment); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer Allocate(long size, int alignment = DefaultAlignment) => - DoAllocate(size, alignment); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Deallocate(ref Buffer buffer, int alignment = DefaultAlignment) - { - DoDeallocate(buffer, alignment); - - // Clear the references to the deallocated buffer. - buffer = new Buffer(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsEqual(MemoryResource other) => - DoIsEqual(other); - - protected abstract Buffer DoAllocate(long size, int alignment); - protected abstract void DoDeallocate(Buffer buffer, int alignment); - protected abstract bool DoIsEqual(MemoryResource other); - - /// - /// Represents a region of memory allocated by a . - /// - public struct Buffer - { - private Memory _memory; - - /// - /// A field where implementers can store info about the . - /// - internal object Extra { get; } - - /// - /// The length of the buffer in bytes. - /// - public readonly int Length => _memory.Length; - - /// - /// Gets a span from the . - /// - public readonly Span Get() => _memory.Span; - - /// - /// Returns if the is valid. - /// - public readonly bool IsValid => !_memory.Equals(default); - - internal Buffer(Memory memory, object extra = null) - { - _memory = memory; - Extra = extra; - } - } + // Clear the references to the deallocated buffer. + buffer = new Buffer(); } - public class ArrayPoolMemoryResource : MemoryResource + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEqual(MemoryResource other) => + DoIsEqual(other); + + protected abstract Buffer DoAllocate(long size, int alignment); + protected abstract void DoDeallocate(Buffer buffer, int alignment); + protected abstract bool DoIsEqual(MemoryResource other); + + /// + /// Represents a region of memory allocated by a . + /// + public struct Buffer { - protected override Buffer DoAllocate(long size, int alignment) - { - byte[] array = ArrayPool.Shared.Rent((int)size); + private Memory _memory; - return new Buffer(array.AsMemory(0, (int)size), array); - } + /// + /// A field where implementers can store info about the . + /// + internal object Extra { get; } - protected override void DoDeallocate(Buffer buffer, int alignment) - { - if (buffer.Extra is byte[] array) - { - ArrayPool.Shared.Return(array); - } - else - { - throw new LibHacException("Buffer was not allocated by this MemoryResource."); - } - } + /// + /// The length of the buffer in bytes. + /// + public readonly int Length => _memory.Length; - protected override bool DoIsEqual(MemoryResource other) + /// + /// Gets a span from the . + /// + public readonly Span Get() => _memory.Span; + + /// + /// Returns if the is valid. + /// + public readonly bool IsValid => !_memory.Equals(default); + + internal Buffer(Memory memory, object extra = null) { - return ReferenceEquals(this, other); + _memory = memory; + Extra = extra; } } } + +public class ArrayPoolMemoryResource : MemoryResource +{ + protected override Buffer DoAllocate(long size, int alignment) + { + byte[] array = ArrayPool.Shared.Rent((int)size); + + return new Buffer(array.AsMemory(0, (int)size), array); + } + + protected override void DoDeallocate(Buffer buffer, int alignment) + { + if (buffer.Extra is byte[] array) + { + ArrayPool.Shared.Return(array); + } + else + { + throw new LibHacException("Buffer was not allocated by this MemoryResource."); + } + } + + protected override bool DoIsEqual(MemoryResource other) + { + return ReferenceEquals(this, other); + } +} diff --git a/src/LibHac/MissingKeyException.cs b/src/LibHac/MissingKeyException.cs index 1bb05dcf..e2348ff4 100644 --- a/src/LibHac/MissingKeyException.cs +++ b/src/LibHac/MissingKeyException.cs @@ -1,81 +1,80 @@ using System; -namespace LibHac +namespace LibHac; + +/// +/// This is the exception that is thrown when an action requires a key that is not found in the provided keyset. +/// +public class MissingKeyException : LibHacException { /// - /// This is the exception that is thrown when an action requires a key that is not found in the provided keyset. + /// The name of the key that is missing. /// - public class MissingKeyException : LibHacException + public string Name { get; } + + /// + /// The type of the key that is missing. + /// + public KeyType Type { get; } + + /// + /// Initializes a new instance of the class with a specified error message, + /// information about the missing key and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The name of the key that is missing, or the rights ID of the missing key if is + /// The of the key that is missing. + public MissingKeyException(string message, string name, KeyType keyType) + : base(message) { - /// - /// The name of the key that is missing. - /// - public string Name { get; } + Name = name; + Type = keyType; + } - /// - /// The type of the key that is missing. - /// - public KeyType Type { get; } + /// + /// Initializes a new instance of the class. + /// + public MissingKeyException() + { + } - /// - /// Initializes a new instance of the class with a specified error message, - /// information about the missing key and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The name of the key that is missing, or the rights ID of the missing key if is - /// The of the key that is missing. - public MissingKeyException(string message, string name, KeyType keyType) - : base(message) + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public MissingKeyException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public MissingKeyException(string message, Exception innerException) + : base(message, innerException) + { + } + + public override string Message + { + get { - Name = name; - Type = keyType; - } + string s = base.Message; - /// - /// Initializes a new instance of the class. - /// - public MissingKeyException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The error message that explains the reason for the exception. - public MissingKeyException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public MissingKeyException(string message, Exception innerException) - : base(message, innerException) - { - } - - public override string Message - { - get + if (Type != KeyType.None) { - string s = base.Message; - - if (Type != KeyType.None) - { - s += $"{Environment.NewLine}Key Type: {Type}"; - } - - if (Name != null) - { - s += $"{Environment.NewLine}Key Name: {Name}"; - } - - return s; + s += $"{Environment.NewLine}Key Type: {Type}"; } + + if (Name != null) + { + s += $"{Environment.NewLine}Key Name: {Name}"; + } + + return s; } } } diff --git a/src/LibHac/Ncm/ContentEnums.cs b/src/LibHac/Ncm/ContentEnums.cs index e9197e82..792ef977 100644 --- a/src/LibHac/Ncm/ContentEnums.cs +++ b/src/LibHac/Ncm/ContentEnums.cs @@ -1,40 +1,39 @@ -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public enum ContentType : byte { - public enum ContentType : byte - { - Meta = 0, - Program = 1, - Data = 2, - Control = 3, - HtmlDocument = 4, - LegalInformation = 5, - DeltaFragment = 6 - } - - public enum ContentMetaType : byte - { - SystemProgram = 1, - SystemData = 2, - SystemUpdate = 3, - BootImagePackage = 4, - BootImagePackageSafe = 5, - Application = 0x80, - Patch = 0x81, - AddOnContent = 0x82, - Delta = 0x83 - } - - public enum ContentMetaAttribute : byte - { - None = 0, - IncludesExFatDriver = 1, - Rebootless = 2 - } - - public enum UpdateType : byte - { - ApplyAsDelta = 0, - Overwrite = 1, - Create = 2 - } + Meta = 0, + Program = 1, + Data = 2, + Control = 3, + HtmlDocument = 4, + LegalInformation = 5, + DeltaFragment = 6 +} + +public enum ContentMetaType : byte +{ + SystemProgram = 1, + SystemData = 2, + SystemUpdate = 3, + BootImagePackage = 4, + BootImagePackageSafe = 5, + Application = 0x80, + Patch = 0x81, + AddOnContent = 0x82, + Delta = 0x83 +} + +public enum ContentMetaAttribute : byte +{ + None = 0, + IncludesExFatDriver = 1, + Rebootless = 2 +} + +public enum UpdateType : byte +{ + ApplyAsDelta = 0, + Overwrite = 1, + Create = 2 } diff --git a/src/LibHac/Ncm/ContentId.cs b/src/LibHac/Ncm/ContentId.cs index 7df8ac93..1cfb93b4 100644 --- a/src/LibHac/Ncm/ContentId.cs +++ b/src/LibHac/Ncm/ContentId.cs @@ -3,52 +3,51 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct ContentId : IEquatable, IComparable, IComparable { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct ContentId : IEquatable, IComparable, IComparable + public readonly Id128 Id; + + public ContentId(ulong high, ulong low) { - public readonly Id128 Id; - - public ContentId(ulong high, ulong low) - { - Id = new Id128(high, low); - } - - public ContentId(ReadOnlySpan uid) - { - Id = new Id128(uid); - } - - public override string ToString() => Id.ToString(); - - public bool Equals(ContentId other) => Id == other.Id; - public override bool Equals(object obj) => obj is ContentId other && Equals(other); - - public override int GetHashCode() => Id.GetHashCode(); - - public int CompareTo(ContentId other) => Id.CompareTo(other.Id); - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is ContentId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentId)}"); - } - - public void ToBytes(Span output) => Id.ToBytes(output); - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(ContentId left, ContentId right) => left.Equals(right); - public static bool operator !=(ContentId left, ContentId right) => !left.Equals(right); - - public static bool operator <(ContentId left, ContentId right) => left.CompareTo(right) < 0; - public static bool operator >(ContentId left, ContentId right) => left.CompareTo(right) > 0; - public static bool operator <=(ContentId left, ContentId right) => left.CompareTo(right) <= 0; - public static bool operator >=(ContentId left, ContentId right) => left.CompareTo(right) >= 0; + Id = new Id128(high, low); } -} \ No newline at end of file + + public ContentId(ReadOnlySpan uid) + { + Id = new Id128(uid); + } + + public override string ToString() => Id.ToString(); + + public bool Equals(ContentId other) => Id == other.Id; + public override bool Equals(object obj) => obj is ContentId other && Equals(other); + + public override int GetHashCode() => Id.GetHashCode(); + + public int CompareTo(ContentId other) => Id.CompareTo(other.Id); + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is ContentId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentId)}"); + } + + public void ToBytes(Span output) => Id.ToBytes(output); + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(ContentId left, ContentId right) => left.Equals(right); + public static bool operator !=(ContentId left, ContentId right) => !left.Equals(right); + + public static bool operator <(ContentId left, ContentId right) => left.CompareTo(right) < 0; + public static bool operator >(ContentId left, ContentId right) => left.CompareTo(right) > 0; + public static bool operator <=(ContentId left, ContentId right) => left.CompareTo(right) <= 0; + public static bool operator >=(ContentId left, ContentId right) => left.CompareTo(right) >= 0; +} diff --git a/src/LibHac/Ncm/ContentMetaId.cs b/src/LibHac/Ncm/ContentMetaId.cs index 66d6c10e..85b5fa1e 100644 --- a/src/LibHac/Ncm/ContentMetaId.cs +++ b/src/LibHac/Ncm/ContentMetaId.cs @@ -1,57 +1,56 @@ using System; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public readonly struct ApplicationId : IEquatable { - public readonly struct ApplicationId : IEquatable + public readonly ulong Value; + + public ApplicationId(ulong value) { - public readonly ulong Value; - - public ApplicationId(ulong value) - { - Value = value; - } - - public static ApplicationId InvalidId => default; - - public static ApplicationId Start => new ApplicationId(0x0100000000010000); - public static ApplicationId End => new ApplicationId(0x01FFFFFFFFFFFFFF); - - public static implicit operator ProgramId(ApplicationId id) => new ProgramId(id.Value); - - public static bool IsApplicationId(ProgramId programId) - { - return Start <= programId && programId <= End; - } - - public bool Equals(ApplicationId other) => Value == other.Value; - public override bool Equals(object obj) => obj is ApplicationId id && Equals(id); - public override int GetHashCode() => Value.GetHashCode(); - - public static bool operator ==(ApplicationId left, ApplicationId right) => left.Equals(right); - public static bool operator !=(ApplicationId left, ApplicationId right) => !left.Equals(right); + Value = value; } - public readonly struct PatchId + public static ApplicationId InvalidId => default; + + public static ApplicationId Start => new ApplicationId(0x0100000000010000); + public static ApplicationId End => new ApplicationId(0x01FFFFFFFFFFFFFF); + + public static implicit operator ProgramId(ApplicationId id) => new ProgramId(id.Value); + + public static bool IsApplicationId(ProgramId programId) { - public readonly ulong Value; - - public PatchId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(PatchId id) => new ProgramId(id.Value); + return Start <= programId && programId <= End; } - public readonly struct DeltaId - { - public readonly ulong Value; + public bool Equals(ApplicationId other) => Value == other.Value; + public override bool Equals(object obj) => obj is ApplicationId id && Equals(id); + public override int GetHashCode() => Value.GetHashCode(); - public DeltaId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(DeltaId id) => new ProgramId(id.Value); - } + public static bool operator ==(ApplicationId left, ApplicationId right) => left.Equals(right); + public static bool operator !=(ApplicationId left, ApplicationId right) => !left.Equals(right); +} + +public readonly struct PatchId +{ + public readonly ulong Value; + + public PatchId(ulong value) + { + Value = value; + } + + public static implicit operator ProgramId(PatchId id) => new ProgramId(id.Value); +} + +public readonly struct DeltaId +{ + public readonly ulong Value; + + public DeltaId(ulong value) + { + Value = value; + } + + public static implicit operator ProgramId(DeltaId id) => new ProgramId(id.Value); } diff --git a/src/LibHac/Ncm/ContentMetaKey.cs b/src/LibHac/Ncm/ContentMetaKey.cs index bcd562bf..b93fc47f 100644 --- a/src/LibHac/Ncm/ContentMetaKey.cs +++ b/src/LibHac/Ncm/ContentMetaKey.cs @@ -1,76 +1,75 @@ using System; using System.Buffers.Binary; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public class ContentMetaKey : IComparable, IComparable, IEquatable { - public class ContentMetaKey : IComparable, IComparable, IEquatable + public ulong TitleId { get; private set; } + public uint Version { get; private set; } + public ContentMetaType Type { get; private set; } + public ContentMetaAttribute Attributes { get; private set; } + + public int ExportSize => 0x10; + private bool _isFrozen; + + public void ToBytes(Span output) { - public ulong TitleId { get; private set; } - public uint Version { get; private set; } - public ContentMetaType Type { get; private set; } - public ContentMetaAttribute Attributes { get; private set; } + if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small."); - public int ExportSize => 0x10; - private bool _isFrozen; + BinaryPrimitives.WriteUInt64LittleEndian(output, TitleId); + BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(8), Version); + output[0xC] = (byte)Type; + output[0xD] = (byte)Attributes; + } - public void ToBytes(Span output) - { - if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small."); + public void FromBytes(ReadOnlySpan input) + { + if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object."); + if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short."); - BinaryPrimitives.WriteUInt64LittleEndian(output, TitleId); - BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(8), Version); - output[0xC] = (byte)Type; - output[0xD] = (byte)Attributes; - } + TitleId = BinaryPrimitives.ReadUInt64LittleEndian(input); + Version = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(8)); + Type = (ContentMetaType)input[0xC]; + Attributes = (ContentMetaAttribute)input[0xD]; + } - public void FromBytes(ReadOnlySpan input) - { - if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object."); - if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short."); + public void Freeze() => _isFrozen = true; - TitleId = BinaryPrimitives.ReadUInt64LittleEndian(input); - Version = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(8)); - Type = (ContentMetaType)input[0xC]; - Attributes = (ContentMetaAttribute)input[0xD]; - } + public bool Equals(ContentMetaKey other) + { + return other != null && TitleId == other.TitleId && Version == other.Version && + Type == other.Type && Attributes == other.Attributes; + } - public void Freeze() => _isFrozen = true; + public override bool Equals(object obj) + { + return obj is ContentMetaKey other && Equals(other); + } - public bool Equals(ContentMetaKey other) - { - return other != null && TitleId == other.TitleId && Version == other.Version && - Type == other.Type && Attributes == other.Attributes; - } + public override int GetHashCode() + { + // ReSharper disable NonReadonlyMemberInGetHashCode + return HashCode.Combine(TitleId, Version, Type, Attributes); + // ReSharper restore NonReadonlyMemberInGetHashCode + } - public override bool Equals(object obj) - { - return obj is ContentMetaKey other && Equals(other); - } + public int CompareTo(ContentMetaKey other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + int titleIdComparison = TitleId.CompareTo(other.TitleId); + if (titleIdComparison != 0) return titleIdComparison; + int versionComparison = Version.CompareTo(other.Version); + if (versionComparison != 0) return versionComparison; + int typeComparison = Type.CompareTo(other.Type); + if (typeComparison != 0) return typeComparison; + return Attributes.CompareTo(other.Attributes); + } - public override int GetHashCode() - { - // ReSharper disable NonReadonlyMemberInGetHashCode - return HashCode.Combine(TitleId, Version, Type, Attributes); - // ReSharper restore NonReadonlyMemberInGetHashCode - } - - public int CompareTo(ContentMetaKey other) - { - if (ReferenceEquals(this, other)) return 0; - if (ReferenceEquals(null, other)) return 1; - int titleIdComparison = TitleId.CompareTo(other.TitleId); - if (titleIdComparison != 0) return titleIdComparison; - int versionComparison = Version.CompareTo(other.Version); - if (versionComparison != 0) return versionComparison; - int typeComparison = Type.CompareTo(other.Type); - if (typeComparison != 0) return typeComparison; - return Attributes.CompareTo(other.Attributes); - } - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is ContentMetaKey other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentMetaKey)}"); - } + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is ContentMetaKey other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentMetaKey)}"); } } diff --git a/src/LibHac/Ncm/ContentMetaStructs.cs b/src/LibHac/Ncm/ContentMetaStructs.cs index 992b115d..28180315 100644 --- a/src/LibHac/Ncm/ContentMetaStructs.cs +++ b/src/LibHac/Ncm/ContentMetaStructs.cs @@ -1,20 +1,19 @@ using System.Runtime.InteropServices; -namespace LibHac.Ncm -{ - [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 1)] - public struct ContentInfo - { - public ContentId contentId; - public uint size1; - public ushort size2; - private ContentType contentType; - private byte IdOffset; - } +namespace LibHac.Ncm; - public class ApplicationContentMetaKey - { - public ContentMetaKey Key { get; set; } - public ulong TitleId { get; set; } - } +[StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 1)] +public struct ContentInfo +{ + public ContentId contentId; + public uint size1; + public ushort size2; + private ContentType contentType; + private byte IdOffset; +} + +public class ApplicationContentMetaKey +{ + public ContentMetaKey Key { get; set; } + public ulong TitleId { get; set; } } diff --git a/src/LibHac/Ncm/DataId.cs b/src/LibHac/Ncm/DataId.cs index acfe6930..0d5e8b0c 100644 --- a/src/LibHac/Ncm/DataId.cs +++ b/src/LibHac/Ncm/DataId.cs @@ -1,32 +1,31 @@ using System; using System.Diagnostics; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +[DebuggerDisplay("{" + nameof(Value) + "}")] +public readonly struct DataId : IEquatable, IComparable { - [DebuggerDisplay("{" + nameof(Value) + "}")] - public readonly struct DataId : IEquatable, IComparable + public readonly ulong Value; + + public DataId(ulong value) { - public readonly ulong Value; - - public DataId(ulong value) - { - Value = value; - } - - public static DataId InvalidId => default; - - public override string ToString() => $"{Value:X16}"; - - public override bool Equals(object obj) => obj is DataId id && Equals(id); - public bool Equals(DataId other) => Value == other.Value; - public int CompareTo(DataId other) => Value.CompareTo(other.Value); - public override int GetHashCode() => Value.GetHashCode(); - - public static bool operator ==(DataId left, DataId right) => left.Equals(right); - public static bool operator !=(DataId left, DataId right) => !(left == right); - public static bool operator <(DataId left, DataId right) => left.CompareTo(right) < 0; - public static bool operator >(DataId left, DataId right) => left.CompareTo(right) > 0; - public static bool operator <=(DataId left, DataId right) => left.CompareTo(right) <= 0; - public static bool operator >=(DataId left, DataId right) => left.CompareTo(right) >= 0; + Value = value; } + + public static DataId InvalidId => default; + + public override string ToString() => $"{Value:X16}"; + + public override bool Equals(object obj) => obj is DataId id && Equals(id); + public bool Equals(DataId other) => Value == other.Value; + public int CompareTo(DataId other) => Value.CompareTo(other.Value); + public override int GetHashCode() => Value.GetHashCode(); + + public static bool operator ==(DataId left, DataId right) => left.Equals(right); + public static bool operator !=(DataId left, DataId right) => !(left == right); + public static bool operator <(DataId left, DataId right) => left.CompareTo(right) < 0; + public static bool operator >(DataId left, DataId right) => left.CompareTo(right) > 0; + public static bool operator <=(DataId left, DataId right) => left.CompareTo(right) <= 0; + public static bool operator >=(DataId left, DataId right) => left.CompareTo(right) >= 0; } diff --git a/src/LibHac/Ncm/IContentMetaDatabase.cs b/src/LibHac/Ncm/IContentMetaDatabase.cs index 90307aee..f1a08ed2 100644 --- a/src/LibHac/Ncm/IContentMetaDatabase.cs +++ b/src/LibHac/Ncm/IContentMetaDatabase.cs @@ -1,32 +1,31 @@ using System; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public interface IContentMetaDatabase { - public interface IContentMetaDatabase - { - Result Set(ContentMetaKey key, ReadOnlySpan value); - Result Get(out long valueSize, ContentMetaKey key, Span valueBuffer); - Result Remove(ContentMetaKey key); - Result GetContentIdByType(out ContentId contentId, ContentMetaKey key, ContentType type); - Result ListContentInfo(out int count, Span outInfo, ContentMetaKey key, int startIndex); + Result Set(ContentMetaKey key, ReadOnlySpan value); + Result Get(out long valueSize, ContentMetaKey key, Span valueBuffer); + Result Remove(ContentMetaKey key); + Result GetContentIdByType(out ContentId contentId, ContentMetaKey key, ContentType type); + Result ListContentInfo(out int count, Span outInfo, ContentMetaKey key, int startIndex); - Result List(out int totalEntryCount, out int matchedEntryCount, Span keys, ContentMetaType type, - ulong applicationTitleId, ulong minTitleId, ulong maxTitleId, ContentMetaAttribute attributes); + Result List(out int totalEntryCount, out int matchedEntryCount, Span keys, ContentMetaType type, + ulong applicationTitleId, ulong minTitleId, ulong maxTitleId, ContentMetaAttribute attributes); - Result GetLatestContentMetaKey(out ContentMetaKey key, ulong titleId); - Result ListApplication(out int totalEntryCount, out int matchedEntryCount, Span keys, ContentMetaType type); - Result Has(out bool hasKey, ContentMetaKey key); - Result HasAll(out bool hasAllKeys, ReadOnlySpan key); - Result GetSize(out long size, ContentMetaKey key); - Result GetRequiredSystemVersion(out int version, ContentMetaKey key); - Result GetPatchId(out ulong titleId, ContentMetaKey key); - Result DisableForcibly(); - Result LookupOrphanContent(Span outOrphaned, ReadOnlySpan contentIds); - Result Commit(); - Result HasContent(out bool hasContent, ContentMetaKey key, ContentId contentId); - Result ListContentMetaInfo(out int entryCount, Span outInfo, ContentMetaKey key, int startIndex); - Result GetAttributes(out ContentMetaAttribute attributes, ContentMetaKey key); - Result GetRequiredApplicationVersion(out int version, ContentMetaKey key); - //Result GetContentIdByTypeAndIdOffset(out ContentId contentId, ContentMetaKey key, ContentType type, byte idOffset); - } -} \ No newline at end of file + Result GetLatestContentMetaKey(out ContentMetaKey key, ulong titleId); + Result ListApplication(out int totalEntryCount, out int matchedEntryCount, Span keys, ContentMetaType type); + Result Has(out bool hasKey, ContentMetaKey key); + Result HasAll(out bool hasAllKeys, ReadOnlySpan key); + Result GetSize(out long size, ContentMetaKey key); + Result GetRequiredSystemVersion(out int version, ContentMetaKey key); + Result GetPatchId(out ulong titleId, ContentMetaKey key); + Result DisableForcibly(); + Result LookupOrphanContent(Span outOrphaned, ReadOnlySpan contentIds); + Result Commit(); + Result HasContent(out bool hasContent, ContentMetaKey key, ContentId contentId); + Result ListContentMetaInfo(out int entryCount, Span outInfo, ContentMetaKey key, int startIndex); + Result GetAttributes(out ContentMetaAttribute attributes, ContentMetaKey key); + Result GetRequiredApplicationVersion(out int version, ContentMetaKey key); + //Result GetContentIdByTypeAndIdOffset(out ContentId contentId, ContentMetaKey key, ContentType type, byte idOffset); +} diff --git a/src/LibHac/Ncm/IContentStorage.cs b/src/LibHac/Ncm/IContentStorage.cs index 2cd17ea9..aa4eb798 100644 --- a/src/LibHac/Ncm/IContentStorage.cs +++ b/src/LibHac/Ncm/IContentStorage.cs @@ -1,37 +1,36 @@ using System; using LibHac.Fs; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public interface IContentStorage { - public interface IContentStorage - { - Result GeneratePlaceHolderId(out PlaceHolderId placeHolderId); - Result CreatePlaceHolder(PlaceHolderId placeHolderId, ContentId contentId, long fileSize); - Result DeletePlaceHolder(PlaceHolderId placeHolderId); - Result HasPlaceHolder(out bool hasPlaceHolder, PlaceHolderId placeHolderId); - Result WritePlaceHolder(PlaceHolderId placeHolderId, long offset, ReadOnlySpan buffer); - Result Register(PlaceHolderId placeHolderId, ContentId contentId); - Result Delete(ContentId contentId); - Result Has(out bool hasContent, ContentId contentId); - Result GetPath(Span outPath, ContentId contentId); - Result GetPlaceHolderPath(Span outPath, PlaceHolderId placeHolderId); - Result CleanupAllPlaceHolder(); - Result ListPlaceHolder(out int count, Span placeHolderIds); - Result GetContentCount(out int count); - Result ListContentId(out int count, Span contentIds, int startOffset); - Result GetSizeFromContentId(out long size, ContentId contentId); - Result DisableForcibly(); - Result RevertToPlaceHolder(PlaceHolderId placeHolderId, ContentId oldContentId, ContentId newContentId); - Result SetPlaceHolderSize(PlaceHolderId placeHolderId, long size); - Result ReadContentIdFile(Span buffer, long size, ContentId contentId, long offset); - Result GetRightsIdFromPlaceHolderId(out RightsId rightsId, out byte keyGeneration, PlaceHolderId placeHolderId); - Result GetRightsIdFromContentId(out RightsId rightsId, out byte keyGeneration, ContentId contentId); - Result WriteContentForDebug(ContentId contentId, long offset, ReadOnlySpan buffer); - Result GetFreeSpaceSize(out long size); - Result GetTotalSpaceSize(out long size); - Result FlushPlaceHolder(); - //Result GetSizeFromPlaceHolderId(out long size, PlaceHolderId placeHolderId); - //Result RepairInvalidFileAttribute(); - //Result GetRightsIdFromPlaceHolderIdWithCache(out RightsId rightsId, out byte keyGeneration, PlaceHolderId placeHolderId, out ContentId cacheContentId); - } -} \ No newline at end of file + Result GeneratePlaceHolderId(out PlaceHolderId placeHolderId); + Result CreatePlaceHolder(PlaceHolderId placeHolderId, ContentId contentId, long fileSize); + Result DeletePlaceHolder(PlaceHolderId placeHolderId); + Result HasPlaceHolder(out bool hasPlaceHolder, PlaceHolderId placeHolderId); + Result WritePlaceHolder(PlaceHolderId placeHolderId, long offset, ReadOnlySpan buffer); + Result Register(PlaceHolderId placeHolderId, ContentId contentId); + Result Delete(ContentId contentId); + Result Has(out bool hasContent, ContentId contentId); + Result GetPath(Span outPath, ContentId contentId); + Result GetPlaceHolderPath(Span outPath, PlaceHolderId placeHolderId); + Result CleanupAllPlaceHolder(); + Result ListPlaceHolder(out int count, Span placeHolderIds); + Result GetContentCount(out int count); + Result ListContentId(out int count, Span contentIds, int startOffset); + Result GetSizeFromContentId(out long size, ContentId contentId); + Result DisableForcibly(); + Result RevertToPlaceHolder(PlaceHolderId placeHolderId, ContentId oldContentId, ContentId newContentId); + Result SetPlaceHolderSize(PlaceHolderId placeHolderId, long size); + Result ReadContentIdFile(Span buffer, long size, ContentId contentId, long offset); + Result GetRightsIdFromPlaceHolderId(out RightsId rightsId, out byte keyGeneration, PlaceHolderId placeHolderId); + Result GetRightsIdFromContentId(out RightsId rightsId, out byte keyGeneration, ContentId contentId); + Result WriteContentForDebug(ContentId contentId, long offset, ReadOnlySpan buffer); + Result GetFreeSpaceSize(out long size); + Result GetTotalSpaceSize(out long size); + Result FlushPlaceHolder(); + //Result GetSizeFromPlaceHolderId(out long size, PlaceHolderId placeHolderId); + //Result RepairInvalidFileAttribute(); + //Result GetRightsIdFromPlaceHolderIdWithCache(out RightsId rightsId, out byte keyGeneration, PlaceHolderId placeHolderId, out ContentId cacheContentId); +} diff --git a/src/LibHac/Ncm/PlaceHolderId.cs b/src/LibHac/Ncm/PlaceHolderId.cs index 27ed1631..f0aac726 100644 --- a/src/LibHac/Ncm/PlaceHolderId.cs +++ b/src/LibHac/Ncm/PlaceHolderId.cs @@ -3,52 +3,51 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct PlaceHolderId : IEquatable, IComparable, IComparable { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct PlaceHolderId : IEquatable, IComparable, IComparable + public readonly Id128 Id; + + public PlaceHolderId(ulong high, ulong low) { - public readonly Id128 Id; - - public PlaceHolderId(ulong high, ulong low) - { - Id = new Id128(high, low); - } - - public PlaceHolderId(ReadOnlySpan uid) - { - Id = new Id128(uid); - } - - public override string ToString() => Id.ToString(); - - public bool Equals(PlaceHolderId other) => Id == other.Id; - public override bool Equals(object obj) => obj is PlaceHolderId other && Equals(other); - - public override int GetHashCode() => Id.GetHashCode(); - - public int CompareTo(PlaceHolderId other) => Id.CompareTo(other.Id); - - public int CompareTo(object obj) - { - if (obj is null) return 1; - return obj is PlaceHolderId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(PlaceHolderId)}"); - } - - public void ToBytes(Span output) => Id.ToBytes(output); - - public ReadOnlySpan AsBytes() - { - return SpanHelpers.AsByteSpan(ref this); - } - - public static bool operator ==(PlaceHolderId left, PlaceHolderId right) => left.Equals(right); - public static bool operator !=(PlaceHolderId left, PlaceHolderId right) => !left.Equals(right); - - public static bool operator <(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) < 0; - public static bool operator >(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) > 0; - public static bool operator <=(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) <= 0; - public static bool operator >=(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) >= 0; + Id = new Id128(high, low); } -} \ No newline at end of file + + public PlaceHolderId(ReadOnlySpan uid) + { + Id = new Id128(uid); + } + + public override string ToString() => Id.ToString(); + + public bool Equals(PlaceHolderId other) => Id == other.Id; + public override bool Equals(object obj) => obj is PlaceHolderId other && Equals(other); + + public override int GetHashCode() => Id.GetHashCode(); + + public int CompareTo(PlaceHolderId other) => Id.CompareTo(other.Id); + + public int CompareTo(object obj) + { + if (obj is null) return 1; + return obj is PlaceHolderId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(PlaceHolderId)}"); + } + + public void ToBytes(Span output) => Id.ToBytes(output); + + public ReadOnlySpan AsBytes() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public static bool operator ==(PlaceHolderId left, PlaceHolderId right) => left.Equals(right); + public static bool operator !=(PlaceHolderId left, PlaceHolderId right) => !left.Equals(right); + + public static bool operator <(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) < 0; + public static bool operator >(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) > 0; + public static bool operator <=(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) <= 0; + public static bool operator >=(PlaceHolderId left, PlaceHolderId right) => left.CompareTo(right) >= 0; +} diff --git a/src/LibHac/Ncm/ProgramId.cs b/src/LibHac/Ncm/ProgramId.cs index ab54198e..09fbe259 100644 --- a/src/LibHac/Ncm/ProgramId.cs +++ b/src/LibHac/Ncm/ProgramId.cs @@ -1,32 +1,31 @@ using System; using System.Diagnostics; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +[DebuggerDisplay("{" + nameof(Value) + "}")] +public readonly struct ProgramId : IEquatable, IComparable { - [DebuggerDisplay("{" + nameof(Value) + "}")] - public readonly struct ProgramId : IEquatable, IComparable + public readonly ulong Value; + + public ProgramId(ulong value) { - public readonly ulong Value; - - public ProgramId(ulong value) - { - Value = value; - } - - public static ProgramId InvalidId => default; - - public override string ToString() => $"{Value:X16}"; - - public override bool Equals(object obj) => obj is ProgramId id && Equals(id); - public bool Equals(ProgramId other) => Value == other.Value; - public int CompareTo(ProgramId other) => Value.CompareTo(other.Value); - public override int GetHashCode() => Value.GetHashCode(); - - public static bool operator ==(ProgramId left, ProgramId right) => left.Equals(right); - public static bool operator !=(ProgramId left, ProgramId right) => !(left == right); - public static bool operator <(ProgramId left, ProgramId right) => left.CompareTo(right) < 0; - public static bool operator >(ProgramId left, ProgramId right) => left.CompareTo(right) > 0; - public static bool operator <=(ProgramId left, ProgramId right) => left.CompareTo(right) <= 0; - public static bool operator >=(ProgramId left, ProgramId right) => left.CompareTo(right) >= 0; + Value = value; } + + public static ProgramId InvalidId => default; + + public override string ToString() => $"{Value:X16}"; + + public override bool Equals(object obj) => obj is ProgramId id && Equals(id); + public bool Equals(ProgramId other) => Value == other.Value; + public int CompareTo(ProgramId other) => Value.CompareTo(other.Value); + public override int GetHashCode() => Value.GetHashCode(); + + public static bool operator ==(ProgramId left, ProgramId right) => left.Equals(right); + public static bool operator !=(ProgramId left, ProgramId right) => !(left == right); + public static bool operator <(ProgramId left, ProgramId right) => left.CompareTo(right) < 0; + public static bool operator >(ProgramId left, ProgramId right) => left.CompareTo(right) > 0; + public static bool operator <=(ProgramId left, ProgramId right) => left.CompareTo(right) <= 0; + public static bool operator >=(ProgramId left, ProgramId right) => left.CompareTo(right) >= 0; } diff --git a/src/LibHac/Ncm/ProgramLocation.cs b/src/LibHac/Ncm/ProgramLocation.cs index b755c437..e24f01a1 100644 --- a/src/LibHac/Ncm/ProgramLocation.cs +++ b/src/LibHac/Ncm/ProgramLocation.cs @@ -1,14 +1,13 @@ -namespace LibHac.Ncm -{ - public readonly struct ProgramLocation - { - public readonly ProgramId ProgramId; - public readonly StorageId StorageId; +namespace LibHac.Ncm; - public ProgramLocation(ProgramId programId, StorageId storageId) - { - ProgramId = programId; - StorageId = storageId; - } +public readonly struct ProgramLocation +{ + public readonly ProgramId ProgramId; + public readonly StorageId StorageId; + + public ProgramLocation(ProgramId programId, StorageId storageId) + { + ProgramId = programId; + StorageId = storageId; } -} \ No newline at end of file +} diff --git a/src/LibHac/Ncm/ResultNcm.cs b/src/LibHac/Ncm/ResultNcm.cs index a145527f..3ba7b831 100644 --- a/src/LibHac/Ncm/ResultNcm.cs +++ b/src/LibHac/Ncm/ResultNcm.cs @@ -11,103 +11,102 @@ using System.Runtime.CompilerServices; -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public static class ResultNcm { - public static class ResultNcm - { - public const int ModuleNcm = 5; + public const int ModuleNcm = 5; - /// Error code: 2005-0001; Inner value: 0x205 - public static Result.Base InvalidContentStorageBase => new Result.Base(ModuleNcm, 1); - /// Error code: 2005-0002; Inner value: 0x405 - public static Result.Base PlaceHolderAlreadyExists => new Result.Base(ModuleNcm, 2); - /// Error code: 2005-0003; Inner value: 0x605 - public static Result.Base PlaceHolderNotFound => new Result.Base(ModuleNcm, 3); - /// Error code: 2005-0004; Inner value: 0x805 - public static Result.Base ContentAlreadyExists => new Result.Base(ModuleNcm, 4); - /// Error code: 2005-0005; Inner value: 0xa05 - public static Result.Base ContentNotFound => new Result.Base(ModuleNcm, 5); - /// Error code: 2005-0007; Inner value: 0xe05 - public static Result.Base ContentMetaNotFound => new Result.Base(ModuleNcm, 7); - /// Error code: 2005-0008; Inner value: 0x1005 - public static Result.Base AllocationFailed => new Result.Base(ModuleNcm, 8); - /// Error code: 2005-0012; Inner value: 0x1805 - public static Result.Base UnknownStorage => new Result.Base(ModuleNcm, 12); - /// Error code: 2005-0100; Inner value: 0xc805 - public static Result.Base InvalidContentStorage => new Result.Base(ModuleNcm, 100); - /// Error code: 2005-0110; Inner value: 0xdc05 - public static Result.Base InvalidContentMetaDatabase => new Result.Base(ModuleNcm, 110); - /// Error code: 2005-0130; Inner value: 0x10405 - public static Result.Base InvalidPackageFormat => new Result.Base(ModuleNcm, 130); - /// Error code: 2005-0140; Inner value: 0x11805 - public static Result.Base InvalidContentHash => new Result.Base(ModuleNcm, 140); - /// Error code: 2005-0160; Inner value: 0x14005 - public static Result.Base InvalidInstallTaskState => new Result.Base(ModuleNcm, 160); - /// Error code: 2005-0170; Inner value: 0x15405 - public static Result.Base InvalidPlaceHolderFile => new Result.Base(ModuleNcm, 170); - /// Error code: 2005-0180; Inner value: 0x16805 - public static Result.Base BufferInsufficient => new Result.Base(ModuleNcm, 180); - /// Error code: 2005-0190; Inner value: 0x17c05 - public static Result.Base WriteToReadOnlyContentStorage => new Result.Base(ModuleNcm, 190); - /// Error code: 2005-0200; Inner value: 0x19005 - public static Result.Base NotEnoughInstallSpace => new Result.Base(ModuleNcm, 200); - /// Error code: 2005-0210; Inner value: 0x1a405 - public static Result.Base SystemUpdateNotFoundInPackage => new Result.Base(ModuleNcm, 210); - /// Error code: 2005-0220; Inner value: 0x1b805 - public static Result.Base ContentInfoNotFound => new Result.Base(ModuleNcm, 220); - /// Error code: 2005-0237; Inner value: 0x1da05 - public static Result.Base DeltaNotFound => new Result.Base(ModuleNcm, 237); - /// Error code: 2005-0240; Inner value: 0x1e005 - public static Result.Base InvalidContentMetaKey => new Result.Base(ModuleNcm, 240); + /// Error code: 2005-0001; Inner value: 0x205 + public static Result.Base InvalidContentStorageBase => new Result.Base(ModuleNcm, 1); + /// Error code: 2005-0002; Inner value: 0x405 + public static Result.Base PlaceHolderAlreadyExists => new Result.Base(ModuleNcm, 2); + /// Error code: 2005-0003; Inner value: 0x605 + public static Result.Base PlaceHolderNotFound => new Result.Base(ModuleNcm, 3); + /// Error code: 2005-0004; Inner value: 0x805 + public static Result.Base ContentAlreadyExists => new Result.Base(ModuleNcm, 4); + /// Error code: 2005-0005; Inner value: 0xa05 + public static Result.Base ContentNotFound => new Result.Base(ModuleNcm, 5); + /// Error code: 2005-0007; Inner value: 0xe05 + public static Result.Base ContentMetaNotFound => new Result.Base(ModuleNcm, 7); + /// Error code: 2005-0008; Inner value: 0x1005 + public static Result.Base AllocationFailed => new Result.Base(ModuleNcm, 8); + /// Error code: 2005-0012; Inner value: 0x1805 + public static Result.Base UnknownStorage => new Result.Base(ModuleNcm, 12); + /// Error code: 2005-0100; Inner value: 0xc805 + public static Result.Base InvalidContentStorage => new Result.Base(ModuleNcm, 100); + /// Error code: 2005-0110; Inner value: 0xdc05 + public static Result.Base InvalidContentMetaDatabase => new Result.Base(ModuleNcm, 110); + /// Error code: 2005-0130; Inner value: 0x10405 + public static Result.Base InvalidPackageFormat => new Result.Base(ModuleNcm, 130); + /// Error code: 2005-0140; Inner value: 0x11805 + public static Result.Base InvalidContentHash => new Result.Base(ModuleNcm, 140); + /// Error code: 2005-0160; Inner value: 0x14005 + public static Result.Base InvalidInstallTaskState => new Result.Base(ModuleNcm, 160); + /// Error code: 2005-0170; Inner value: 0x15405 + public static Result.Base InvalidPlaceHolderFile => new Result.Base(ModuleNcm, 170); + /// Error code: 2005-0180; Inner value: 0x16805 + public static Result.Base BufferInsufficient => new Result.Base(ModuleNcm, 180); + /// Error code: 2005-0190; Inner value: 0x17c05 + public static Result.Base WriteToReadOnlyContentStorage => new Result.Base(ModuleNcm, 190); + /// Error code: 2005-0200; Inner value: 0x19005 + public static Result.Base NotEnoughInstallSpace => new Result.Base(ModuleNcm, 200); + /// Error code: 2005-0210; Inner value: 0x1a405 + public static Result.Base SystemUpdateNotFoundInPackage => new Result.Base(ModuleNcm, 210); + /// Error code: 2005-0220; Inner value: 0x1b805 + public static Result.Base ContentInfoNotFound => new Result.Base(ModuleNcm, 220); + /// Error code: 2005-0237; Inner value: 0x1da05 + public static Result.Base DeltaNotFound => new Result.Base(ModuleNcm, 237); + /// Error code: 2005-0240; Inner value: 0x1e005 + public static Result.Base InvalidContentMetaKey => new Result.Base(ModuleNcm, 240); - /// Error code: 2005-0250; Range: 250-258; Inner value: 0x1f405 - public static Result.Base ContentStorageNotActive { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 250, 258); } - /// Error code: 2005-0251; Inner value: 0x1f605 - public static Result.Base GameCardContentStorageNotActive => new Result.Base(ModuleNcm, 251); - /// Error code: 2005-0252; Inner value: 0x1f805 - public static Result.Base BuiltInSystemContentStorageNotActive => new Result.Base(ModuleNcm, 252); - /// Error code: 2005-0253; Inner value: 0x1fa05 - public static Result.Base BuiltInUserContentStorageNotActive => new Result.Base(ModuleNcm, 253); - /// Error code: 2005-0254; Inner value: 0x1fc05 - public static Result.Base SdCardContentStorageNotActive => new Result.Base(ModuleNcm, 254); - /// Error code: 2005-0258; Inner value: 0x20405 - public static Result.Base UnknownContentStorageNotActive => new Result.Base(ModuleNcm, 258); + /// Error code: 2005-0250; Range: 250-258; Inner value: 0x1f405 + public static Result.Base ContentStorageNotActive { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 250, 258); } + /// Error code: 2005-0251; Inner value: 0x1f605 + public static Result.Base GameCardContentStorageNotActive => new Result.Base(ModuleNcm, 251); + /// Error code: 2005-0252; Inner value: 0x1f805 + public static Result.Base BuiltInSystemContentStorageNotActive => new Result.Base(ModuleNcm, 252); + /// Error code: 2005-0253; Inner value: 0x1fa05 + public static Result.Base BuiltInUserContentStorageNotActive => new Result.Base(ModuleNcm, 253); + /// Error code: 2005-0254; Inner value: 0x1fc05 + public static Result.Base SdCardContentStorageNotActive => new Result.Base(ModuleNcm, 254); + /// Error code: 2005-0258; Inner value: 0x20405 + public static Result.Base UnknownContentStorageNotActive => new Result.Base(ModuleNcm, 258); - /// Error code: 2005-0260; Range: 260-268; Inner value: 0x20805 - public static Result.Base ContentMetaDatabaseNotActive { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 260, 268); } - /// Error code: 2005-0261; Inner value: 0x20a05 - public static Result.Base GameCardContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 261); - /// Error code: 2005-0262; Inner value: 0x20c05 - public static Result.Base BuiltInSystemContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 262); - /// Error code: 2005-0263; Inner value: 0x20e05 - public static Result.Base BuiltInUserContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 263); - /// Error code: 2005-0264; Inner value: 0x21005 - public static Result.Base SdCardContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 264); - /// Error code: 2005-0268; Inner value: 0x21805 - public static Result.Base UnknownContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 268); + /// Error code: 2005-0260; Range: 260-268; Inner value: 0x20805 + public static Result.Base ContentMetaDatabaseNotActive { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 260, 268); } + /// Error code: 2005-0261; Inner value: 0x20a05 + public static Result.Base GameCardContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 261); + /// Error code: 2005-0262; Inner value: 0x20c05 + public static Result.Base BuiltInSystemContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 262); + /// Error code: 2005-0263; Inner value: 0x20e05 + public static Result.Base BuiltInUserContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 263); + /// Error code: 2005-0264; Inner value: 0x21005 + public static Result.Base SdCardContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 264); + /// Error code: 2005-0268; Inner value: 0x21805 + public static Result.Base UnknownContentMetaDatabaseNotActive => new Result.Base(ModuleNcm, 268); - /// Error code: 2005-0280; Inner value: 0x23005 - public static Result.Base IgnorableInstallTicketFailure => new Result.Base(ModuleNcm, 280); + /// Error code: 2005-0280; Inner value: 0x23005 + public static Result.Base IgnorableInstallTicketFailure => new Result.Base(ModuleNcm, 280); - /// Error code: 2005-0290; Range: 290-299; Inner value: 0x24405 - public static Result.Base InstallTaskCancelled { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 290, 299); } - /// Error code: 2005-0291; Inner value: 0x24605 - public static Result.Base CreatePlaceHolderCancelled => new Result.Base(ModuleNcm, 291); - /// Error code: 2005-0292; Inner value: 0x24805 - public static Result.Base WritePlaceHolderCancelled => new Result.Base(ModuleNcm, 292); + /// Error code: 2005-0290; Range: 290-299; Inner value: 0x24405 + public static Result.Base InstallTaskCancelled { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 290, 299); } + /// Error code: 2005-0291; Inner value: 0x24605 + public static Result.Base CreatePlaceHolderCancelled => new Result.Base(ModuleNcm, 291); + /// Error code: 2005-0292; Inner value: 0x24805 + public static Result.Base WritePlaceHolderCancelled => new Result.Base(ModuleNcm, 292); - /// Error code: 2005-0310; Inner value: 0x26c05 - public static Result.Base ContentStorageBaseNotFound => new Result.Base(ModuleNcm, 310); - /// Error code: 2005-0330; Inner value: 0x29405 - public static Result.Base ListPartiallyNotCommitted => new Result.Base(ModuleNcm, 330); - /// Error code: 2005-0360; Inner value: 0x2d005 - public static Result.Base UnexpectedContentMetaPrepared => new Result.Base(ModuleNcm, 360); - /// Error code: 2005-0380; Inner value: 0x2f805 - public static Result.Base InvalidFirmwareVariation => new Result.Base(ModuleNcm, 380); + /// Error code: 2005-0310; Inner value: 0x26c05 + public static Result.Base ContentStorageBaseNotFound => new Result.Base(ModuleNcm, 310); + /// Error code: 2005-0330; Inner value: 0x29405 + public static Result.Base ListPartiallyNotCommitted => new Result.Base(ModuleNcm, 330); + /// Error code: 2005-0360; Inner value: 0x2d005 + public static Result.Base UnexpectedContentMetaPrepared => new Result.Base(ModuleNcm, 360); + /// Error code: 2005-0380; Inner value: 0x2f805 + public static Result.Base InvalidFirmwareVariation => new Result.Base(ModuleNcm, 380); - /// Error code: 2005-8181; Range: 8181-8191; Inner value: 0x3fea05 - public static Result.Base InvalidArgument { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 8181, 8191); } - /// Error code: 2005-8182; Inner value: 0x3fec05 - public static Result.Base InvalidOffset => new Result.Base(ModuleNcm, 8182); - } + /// Error code: 2005-8181; Range: 8181-8191; Inner value: 0x3fea05 + public static Result.Base InvalidArgument { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleNcm, 8181, 8191); } + /// Error code: 2005-8182; Inner value: 0x3fec05 + public static Result.Base InvalidOffset => new Result.Base(ModuleNcm, 8182); } diff --git a/src/LibHac/Ncm/StorageId.cs b/src/LibHac/Ncm/StorageId.cs index 1fd4339a..5e99563f 100644 --- a/src/LibHac/Ncm/StorageId.cs +++ b/src/LibHac/Ncm/StorageId.cs @@ -1,12 +1,11 @@ -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public enum StorageId : byte { - public enum StorageId : byte - { - None = 0, - Host = 1, - GameCard = 2, - BuiltInSystem = 3, - BuiltInUser = 4, - SdCard = 5 - } + None = 0, + Host = 1, + GameCard = 2, + BuiltInSystem = 3, + BuiltInUser = 4, + SdCard = 5 } diff --git a/src/LibHac/Ncm/SystemContentMetaId.cs b/src/LibHac/Ncm/SystemContentMetaId.cs index ae375f38..b588e1c0 100644 --- a/src/LibHac/Ncm/SystemContentMetaId.cs +++ b/src/LibHac/Ncm/SystemContentMetaId.cs @@ -1,335 +1,334 @@ -namespace LibHac.Ncm +namespace LibHac.Ncm; + +public readonly struct SystemProgramId { - public readonly struct SystemProgramId + public readonly ulong Value; + + public SystemProgramId(ulong value) { - public readonly ulong Value; - - public SystemProgramId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(SystemProgramId id) => new ProgramId(id.Value); - - public static bool IsSystemProgramId(ProgramId programId) - { - return Start <= programId && programId <= End; - } - - public static bool IsSystemProgramId(SystemProgramId id) => true; - - public static SystemProgramId Start => new SystemProgramId(0x0100000000000000); - - public static SystemProgramId Fs => new SystemProgramId(0x0100000000000000); - public static SystemProgramId Loader => new SystemProgramId(0x0100000000000001); - public static SystemProgramId Ncm => new SystemProgramId(0x0100000000000002); - public static SystemProgramId Pm => new SystemProgramId(0x0100000000000003); - public static SystemProgramId Sm => new SystemProgramId(0x0100000000000004); - public static SystemProgramId Boot => new SystemProgramId(0x0100000000000005); - public static SystemProgramId Usb => new SystemProgramId(0x0100000000000006); - public static SystemProgramId Tma => new SystemProgramId(0x0100000000000007); - public static SystemProgramId Boot2 => new SystemProgramId(0x0100000000000008); - public static SystemProgramId Settings => new SystemProgramId(0x0100000000000009); - public static SystemProgramId Bus => new SystemProgramId(0x010000000000000A); - public static SystemProgramId Bluetooth => new SystemProgramId(0x010000000000000B); - public static SystemProgramId Bcat => new SystemProgramId(0x010000000000000C); - public static SystemProgramId Dmnt => new SystemProgramId(0x010000000000000D); - public static SystemProgramId Friends => new SystemProgramId(0x010000000000000E); - public static SystemProgramId Nifm => new SystemProgramId(0x010000000000000F); - public static SystemProgramId Ptm => new SystemProgramId(0x0100000000000010); - public static SystemProgramId Shell => new SystemProgramId(0x0100000000000011); - public static SystemProgramId BsdSockets => new SystemProgramId(0x0100000000000012); - public static SystemProgramId Hid => new SystemProgramId(0x0100000000000013); - public static SystemProgramId Audio => new SystemProgramId(0x0100000000000014); - public static SystemProgramId LogManager => new SystemProgramId(0x0100000000000015); - public static SystemProgramId Wlan => new SystemProgramId(0x0100000000000016); - public static SystemProgramId Cs => new SystemProgramId(0x0100000000000017); - public static SystemProgramId Ldn => new SystemProgramId(0x0100000000000018); - public static SystemProgramId NvServices => new SystemProgramId(0x0100000000000019); - public static SystemProgramId Pcv => new SystemProgramId(0x010000000000001A); - public static SystemProgramId Ppc => new SystemProgramId(0x010000000000001B); - public static SystemProgramId NvnFlinger => new SystemProgramId(0x010000000000001C); - public static SystemProgramId Pcie => new SystemProgramId(0x010000000000001D); - public static SystemProgramId Account => new SystemProgramId(0x010000000000001E); - public static SystemProgramId Ns => new SystemProgramId(0x010000000000001F); - public static SystemProgramId Nfc => new SystemProgramId(0x0100000000000020); - public static SystemProgramId Psc => new SystemProgramId(0x0100000000000021); - public static SystemProgramId CapSrv => new SystemProgramId(0x0100000000000022); - public static SystemProgramId Am => new SystemProgramId(0x0100000000000023); - public static SystemProgramId Ssl => new SystemProgramId(0x0100000000000024); - public static SystemProgramId Nim => new SystemProgramId(0x0100000000000025); - public static SystemProgramId Cec => new SystemProgramId(0x0100000000000026); - public static SystemProgramId Tspm => new SystemProgramId(0x0100000000000027); - public static SystemProgramId Spl => new SystemProgramId(0x0100000000000028); - public static SystemProgramId Lbl => new SystemProgramId(0x0100000000000029); - public static SystemProgramId Btm => new SystemProgramId(0x010000000000002A); - public static SystemProgramId Erpt => new SystemProgramId(0x010000000000002B); - public static SystemProgramId Time => new SystemProgramId(0x010000000000002C); - public static SystemProgramId Vi => new SystemProgramId(0x010000000000002D); - public static SystemProgramId Pctl => new SystemProgramId(0x010000000000002E); - public static SystemProgramId Npns => new SystemProgramId(0x010000000000002F); - public static SystemProgramId Eupld => new SystemProgramId(0x0100000000000030); - public static SystemProgramId Arp => new SystemProgramId(0x0100000000000031); - public static SystemProgramId Glue => new SystemProgramId(0x0100000000000031); - public static SystemProgramId Eclct => new SystemProgramId(0x0100000000000032); - public static SystemProgramId Es => new SystemProgramId(0x0100000000000033); - public static SystemProgramId Fatal => new SystemProgramId(0x0100000000000034); - public static SystemProgramId Grc => new SystemProgramId(0x0100000000000035); - public static SystemProgramId Creport => new SystemProgramId(0x0100000000000036); - public static SystemProgramId Ro => new SystemProgramId(0x0100000000000037); - public static SystemProgramId Profiler => new SystemProgramId(0x0100000000000038); - public static SystemProgramId Sdb => new SystemProgramId(0x0100000000000039); - public static SystemProgramId Migration => new SystemProgramId(0x010000000000003A); - public static SystemProgramId Jit => new SystemProgramId(0x010000000000003B); - public static SystemProgramId JpegDec => new SystemProgramId(0x010000000000003C); - public static SystemProgramId SafeMode => new SystemProgramId(0x010000000000003D); - public static SystemProgramId Olsc => new SystemProgramId(0x010000000000003E); - public static SystemProgramId Dt => new SystemProgramId(0x010000000000003F); - public static SystemProgramId Nd => new SystemProgramId(0x0100000000000040); - public static SystemProgramId Ngct => new SystemProgramId(0x0100000000000041); - public static SystemProgramId Pgl => new SystemProgramId(0x0100000000000042); - - public static SystemProgramId End => new SystemProgramId(0x01000000000007FF); + Value = value; } - public readonly struct SystemDataId + public static implicit operator ProgramId(SystemProgramId id) => new ProgramId(id.Value); + + public static bool IsSystemProgramId(ProgramId programId) { - public readonly ulong Value; - - public SystemDataId(ulong value) - { - Value = value; - } - - public static implicit operator DataId(SystemDataId id) => new DataId(id.Value); - - public static bool IsSystemDataId(DataId dataId) - { - return Start <= dataId && dataId <= End; - } - - public static bool IsSystemDataId(SystemDataId id) => true; - - public static SystemDataId Start => new SystemDataId(0x0100000000000800); - - public static SystemDataId CertStore => new SystemDataId(0x0100000000000800); - public static SystemDataId ErrorMessage => new SystemDataId(0x0100000000000801); - public static SystemDataId MiiModel => new SystemDataId(0x0100000000000802); - public static SystemDataId BrowserDll => new SystemDataId(0x0100000000000803); - public static SystemDataId Help => new SystemDataId(0x0100000000000804); - public static SystemDataId SharedFont => new SystemDataId(0x0100000000000805); - public static SystemDataId NgWord => new SystemDataId(0x0100000000000806); - public static SystemDataId SsidList => new SystemDataId(0x0100000000000807); - public static SystemDataId Dictionary => new SystemDataId(0x0100000000000808); - public static SystemDataId SystemVersion => new SystemDataId(0x0100000000000809); - public static SystemDataId AvatarImage => new SystemDataId(0x010000000000080A); - public static SystemDataId LocalNews => new SystemDataId(0x010000000000080B); - public static SystemDataId Eula => new SystemDataId(0x010000000000080C); - public static SystemDataId UrlBlackList => new SystemDataId(0x010000000000080D); - public static SystemDataId TimeZoneBinar => new SystemDataId(0x010000000000080E); - public static SystemDataId CertStoreCruiser => new SystemDataId(0x010000000000080F); - public static SystemDataId FontNintendoExtension => new SystemDataId(0x0100000000000810); - public static SystemDataId FontStandard => new SystemDataId(0x0100000000000811); - public static SystemDataId FontKorean => new SystemDataId(0x0100000000000812); - public static SystemDataId FontChineseTraditional => new SystemDataId(0x0100000000000813); - public static SystemDataId FontChineseSimple => new SystemDataId(0x0100000000000814); - public static SystemDataId FontBfcpx => new SystemDataId(0x0100000000000815); - public static SystemDataId SystemUpdate => new SystemDataId(0x0100000000000816); - - public static SystemDataId FirmwareDebugSettings => new SystemDataId(0x0100000000000818); - public static SystemDataId BootImagePackage => new SystemDataId(0x0100000000000819); - public static SystemDataId BootImagePackageSafe => new SystemDataId(0x010000000000081A); - public static SystemDataId BootImagePackageExFat => new SystemDataId(0x010000000000081B); - public static SystemDataId BootImagePackageExFatSafe => new SystemDataId(0x010000000000081C); - public static SystemDataId FatalMessage => new SystemDataId(0x010000000000081D); - public static SystemDataId ControllerIcon => new SystemDataId(0x010000000000081E); - public static SystemDataId PlatformConfigIcosa => new SystemDataId(0x010000000000081F); - public static SystemDataId PlatformConfigCopper => new SystemDataId(0x0100000000000820); - public static SystemDataId PlatformConfigHoag => new SystemDataId(0x0100000000000821); - public static SystemDataId ControllerFirmware => new SystemDataId(0x0100000000000822); - public static SystemDataId NgWord2 => new SystemDataId(0x0100000000000823); - public static SystemDataId PlatformConfigIcosaMariko => new SystemDataId(0x0100000000000824); - public static SystemDataId ApplicationBlackList => new SystemDataId(0x0100000000000825); - public static SystemDataId RebootlessSystemUpdateVersion => new SystemDataId(0x0100000000000826); - public static SystemDataId ContentActionTable => new SystemDataId(0x0100000000000827); - - public static SystemDataId End => new SystemDataId(0x0100000000000FFF); + return Start <= programId && programId <= End; } - public readonly struct SystemUpdateId - { - public readonly ulong Value; + public static bool IsSystemProgramId(SystemProgramId id) => true; - public SystemUpdateId(ulong value) - { - Value = value; - } + public static SystemProgramId Start => new SystemProgramId(0x0100000000000000); - public static implicit operator DataId(SystemUpdateId id) => new DataId(id.Value); - } + public static SystemProgramId Fs => new SystemProgramId(0x0100000000000000); + public static SystemProgramId Loader => new SystemProgramId(0x0100000000000001); + public static SystemProgramId Ncm => new SystemProgramId(0x0100000000000002); + public static SystemProgramId Pm => new SystemProgramId(0x0100000000000003); + public static SystemProgramId Sm => new SystemProgramId(0x0100000000000004); + public static SystemProgramId Boot => new SystemProgramId(0x0100000000000005); + public static SystemProgramId Usb => new SystemProgramId(0x0100000000000006); + public static SystemProgramId Tma => new SystemProgramId(0x0100000000000007); + public static SystemProgramId Boot2 => new SystemProgramId(0x0100000000000008); + public static SystemProgramId Settings => new SystemProgramId(0x0100000000000009); + public static SystemProgramId Bus => new SystemProgramId(0x010000000000000A); + public static SystemProgramId Bluetooth => new SystemProgramId(0x010000000000000B); + public static SystemProgramId Bcat => new SystemProgramId(0x010000000000000C); + public static SystemProgramId Dmnt => new SystemProgramId(0x010000000000000D); + public static SystemProgramId Friends => new SystemProgramId(0x010000000000000E); + public static SystemProgramId Nifm => new SystemProgramId(0x010000000000000F); + public static SystemProgramId Ptm => new SystemProgramId(0x0100000000000010); + public static SystemProgramId Shell => new SystemProgramId(0x0100000000000011); + public static SystemProgramId BsdSockets => new SystemProgramId(0x0100000000000012); + public static SystemProgramId Hid => new SystemProgramId(0x0100000000000013); + public static SystemProgramId Audio => new SystemProgramId(0x0100000000000014); + public static SystemProgramId LogManager => new SystemProgramId(0x0100000000000015); + public static SystemProgramId Wlan => new SystemProgramId(0x0100000000000016); + public static SystemProgramId Cs => new SystemProgramId(0x0100000000000017); + public static SystemProgramId Ldn => new SystemProgramId(0x0100000000000018); + public static SystemProgramId NvServices => new SystemProgramId(0x0100000000000019); + public static SystemProgramId Pcv => new SystemProgramId(0x010000000000001A); + public static SystemProgramId Ppc => new SystemProgramId(0x010000000000001B); + public static SystemProgramId NvnFlinger => new SystemProgramId(0x010000000000001C); + public static SystemProgramId Pcie => new SystemProgramId(0x010000000000001D); + public static SystemProgramId Account => new SystemProgramId(0x010000000000001E); + public static SystemProgramId Ns => new SystemProgramId(0x010000000000001F); + public static SystemProgramId Nfc => new SystemProgramId(0x0100000000000020); + public static SystemProgramId Psc => new SystemProgramId(0x0100000000000021); + public static SystemProgramId CapSrv => new SystemProgramId(0x0100000000000022); + public static SystemProgramId Am => new SystemProgramId(0x0100000000000023); + public static SystemProgramId Ssl => new SystemProgramId(0x0100000000000024); + public static SystemProgramId Nim => new SystemProgramId(0x0100000000000025); + public static SystemProgramId Cec => new SystemProgramId(0x0100000000000026); + public static SystemProgramId Tspm => new SystemProgramId(0x0100000000000027); + public static SystemProgramId Spl => new SystemProgramId(0x0100000000000028); + public static SystemProgramId Lbl => new SystemProgramId(0x0100000000000029); + public static SystemProgramId Btm => new SystemProgramId(0x010000000000002A); + public static SystemProgramId Erpt => new SystemProgramId(0x010000000000002B); + public static SystemProgramId Time => new SystemProgramId(0x010000000000002C); + public static SystemProgramId Vi => new SystemProgramId(0x010000000000002D); + public static SystemProgramId Pctl => new SystemProgramId(0x010000000000002E); + public static SystemProgramId Npns => new SystemProgramId(0x010000000000002F); + public static SystemProgramId Eupld => new SystemProgramId(0x0100000000000030); + public static SystemProgramId Arp => new SystemProgramId(0x0100000000000031); + public static SystemProgramId Glue => new SystemProgramId(0x0100000000000031); + public static SystemProgramId Eclct => new SystemProgramId(0x0100000000000032); + public static SystemProgramId Es => new SystemProgramId(0x0100000000000033); + public static SystemProgramId Fatal => new SystemProgramId(0x0100000000000034); + public static SystemProgramId Grc => new SystemProgramId(0x0100000000000035); + public static SystemProgramId Creport => new SystemProgramId(0x0100000000000036); + public static SystemProgramId Ro => new SystemProgramId(0x0100000000000037); + public static SystemProgramId Profiler => new SystemProgramId(0x0100000000000038); + public static SystemProgramId Sdb => new SystemProgramId(0x0100000000000039); + public static SystemProgramId Migration => new SystemProgramId(0x010000000000003A); + public static SystemProgramId Jit => new SystemProgramId(0x010000000000003B); + public static SystemProgramId JpegDec => new SystemProgramId(0x010000000000003C); + public static SystemProgramId SafeMode => new SystemProgramId(0x010000000000003D); + public static SystemProgramId Olsc => new SystemProgramId(0x010000000000003E); + public static SystemProgramId Dt => new SystemProgramId(0x010000000000003F); + public static SystemProgramId Nd => new SystemProgramId(0x0100000000000040); + public static SystemProgramId Ngct => new SystemProgramId(0x0100000000000041); + public static SystemProgramId Pgl => new SystemProgramId(0x0100000000000042); - public readonly struct SystemAppletId - { - public readonly ulong Value; - - public SystemAppletId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(SystemAppletId id) => new ProgramId(id.Value); - - public static bool IsSystemAppletId(ProgramId programId) - { - return Start <= programId && programId <= End; - } - - public static bool IsSystemAppletId(SystemAppletId id) => true; - - public static SystemAppletId Start => new SystemAppletId(0x0100000000001000); - - public static SystemAppletId Qlaunch => new SystemAppletId(0x0100000000001000); - public static SystemAppletId Auth => new SystemAppletId(0x0100000000001001); - public static SystemAppletId Cabinet => new SystemAppletId(0x0100000000001002); - public static SystemAppletId Controller => new SystemAppletId(0x0100000000001003); - public static SystemAppletId DataErase => new SystemAppletId(0x0100000000001004); - public static SystemAppletId Error => new SystemAppletId(0x0100000000001005); - public static SystemAppletId NetConnect => new SystemAppletId(0x0100000000001006); - public static SystemAppletId PlayerSelect => new SystemAppletId(0x0100000000001007); - public static SystemAppletId Swkbd => new SystemAppletId(0x0100000000001008); - public static SystemAppletId MiiEdit => new SystemAppletId(0x0100000000001009); - public static SystemAppletId Web => new SystemAppletId(0x010000000000100A); - public static SystemAppletId Shop => new SystemAppletId(0x010000000000100B); - public static SystemAppletId OverlayDisp => new SystemAppletId(0x010000000000100C); - public static SystemAppletId PhotoViewer => new SystemAppletId(0x010000000000100D); - public static SystemAppletId Set => new SystemAppletId(0x010000000000100E); - public static SystemAppletId OfflineWeb => new SystemAppletId(0x010000000000100F); - public static SystemAppletId LoginShare => new SystemAppletId(0x0100000000001010); - public static SystemAppletId WifiWebAuth => new SystemAppletId(0x0100000000001011); - public static SystemAppletId Starter => new SystemAppletId(0x0100000000001012); - public static SystemAppletId MyPage => new SystemAppletId(0x0100000000001013); - public static SystemAppletId PlayReport => new SystemAppletId(0x0100000000001014); - public static SystemAppletId MaintenanceMenu => new SystemAppletId(0x0100000000001015); - - public static SystemAppletId Gift => new SystemAppletId(0x010000000000101A); - public static SystemAppletId DummyShop => new SystemAppletId(0x010000000000101B); - public static SystemAppletId UserMigration => new SystemAppletId(0x010000000000101C); - public static SystemAppletId Encounter => new SystemAppletId(0x010000000000101D); - - public static SystemAppletId Story => new SystemAppletId(0x0100000000001020); - - public static SystemAppletId End => new SystemAppletId(0x0100000000001FFF); - } - - public readonly struct SystemDebugAppletId - { - public readonly ulong Value; - - public SystemDebugAppletId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(SystemDebugAppletId id) => new ProgramId(id.Value); - - public static bool IsSystemDebugAppletId(ProgramId programId) - { - return Start <= programId && programId <= End; - } - - public static bool IsSystemDebugAppletId(SystemDebugAppletId id) => true; - - public static SystemDebugAppletId Start => new SystemDebugAppletId(0x0100000000002000); - - public static SystemDebugAppletId SnapShotDumper => new SystemDebugAppletId(0x0100000000002071); - - public static SystemDebugAppletId End => new SystemDebugAppletId(0x0100000000002FFF); - } - - public readonly struct LibraryAppletId - { - public readonly ulong Value; - - public LibraryAppletId(ulong value) - { - Value = value; - } - - public static implicit operator SystemAppletId(LibraryAppletId id) => new SystemAppletId(id.Value); - public static implicit operator ProgramId(LibraryAppletId id) => new ProgramId(id.Value); - - public static bool IsLibraryAppletId(ProgramId programId) - { - return programId == Auth || - programId == Controller || - programId == Error || - programId == PlayerSelect || - programId == Swkbd || - programId == Web || - programId == Shop || - programId == PhotoViewer || - programId == OfflineWeb || - programId == LoginShare || - programId == WifiWebAuth || - programId == MyPage; - } - - public static bool IsLibraryAppletId(LibraryAppletId id) => true; - - public static LibraryAppletId Auth => new LibraryAppletId(SystemAppletId.Auth.Value); - public static LibraryAppletId Controller => new LibraryAppletId(SystemAppletId.Controller.Value); - public static LibraryAppletId Error => new LibraryAppletId(SystemAppletId.Error.Value); - public static LibraryAppletId PlayerSelect => new LibraryAppletId(SystemAppletId.PlayerSelect.Value); - public static LibraryAppletId Swkbd => new LibraryAppletId(SystemAppletId.Swkbd.Value); - public static LibraryAppletId Web => new LibraryAppletId(SystemAppletId.Web.Value); - public static LibraryAppletId Shop => new LibraryAppletId(SystemAppletId.Shop.Value); - public static LibraryAppletId PhotoViewer => new LibraryAppletId(SystemAppletId.PhotoViewer.Value); - public static LibraryAppletId OfflineWeb => new LibraryAppletId(SystemAppletId.OfflineWeb.Value); - public static LibraryAppletId LoginShare => new LibraryAppletId(SystemAppletId.LoginShare.Value); - public static LibraryAppletId WifiWebAuth => new LibraryAppletId(SystemAppletId.WifiWebAuth.Value); - public static LibraryAppletId MyPage => new LibraryAppletId(SystemAppletId.MyPage.Value); - } - - public readonly struct WebAppletId - { - public readonly ulong Value; - - public WebAppletId(ulong value) - { - Value = value; - } - - public static implicit operator LibraryAppletId(WebAppletId id) => new LibraryAppletId(id.Value); - public static implicit operator SystemAppletId(WebAppletId id) => new SystemAppletId(id.Value); - public static implicit operator ProgramId(WebAppletId id) => new ProgramId(id.Value); - - public static bool IsWebAppletId(ProgramId programId) - { - return programId == Web || - programId == Shop || - programId == OfflineWeb || - programId == LoginShare || - programId == WifiWebAuth; - } - - public static bool IsWebAppletId(WebAppletId id) => true; - - public static WebAppletId Web => new WebAppletId(LibraryAppletId.Web.Value); - public static WebAppletId Shop => new WebAppletId(LibraryAppletId.Shop.Value); - public static WebAppletId OfflineWeb => new WebAppletId(LibraryAppletId.OfflineWeb.Value); - public static WebAppletId LoginShare => new WebAppletId(LibraryAppletId.LoginShare.Value); - public static WebAppletId WifiWebAuth => new WebAppletId(LibraryAppletId.WifiWebAuth.Value); - } - - public readonly struct SystemApplicationId - { - public readonly ulong Value; - - public SystemApplicationId(ulong value) - { - Value = value; - } - - public static implicit operator ProgramId(SystemApplicationId id) => new ProgramId(id.Value); - } + public static SystemProgramId End => new SystemProgramId(0x01000000000007FF); +} + +public readonly struct SystemDataId +{ + public readonly ulong Value; + + public SystemDataId(ulong value) + { + Value = value; + } + + public static implicit operator DataId(SystemDataId id) => new DataId(id.Value); + + public static bool IsSystemDataId(DataId dataId) + { + return Start <= dataId && dataId <= End; + } + + public static bool IsSystemDataId(SystemDataId id) => true; + + public static SystemDataId Start => new SystemDataId(0x0100000000000800); + + public static SystemDataId CertStore => new SystemDataId(0x0100000000000800); + public static SystemDataId ErrorMessage => new SystemDataId(0x0100000000000801); + public static SystemDataId MiiModel => new SystemDataId(0x0100000000000802); + public static SystemDataId BrowserDll => new SystemDataId(0x0100000000000803); + public static SystemDataId Help => new SystemDataId(0x0100000000000804); + public static SystemDataId SharedFont => new SystemDataId(0x0100000000000805); + public static SystemDataId NgWord => new SystemDataId(0x0100000000000806); + public static SystemDataId SsidList => new SystemDataId(0x0100000000000807); + public static SystemDataId Dictionary => new SystemDataId(0x0100000000000808); + public static SystemDataId SystemVersion => new SystemDataId(0x0100000000000809); + public static SystemDataId AvatarImage => new SystemDataId(0x010000000000080A); + public static SystemDataId LocalNews => new SystemDataId(0x010000000000080B); + public static SystemDataId Eula => new SystemDataId(0x010000000000080C); + public static SystemDataId UrlBlackList => new SystemDataId(0x010000000000080D); + public static SystemDataId TimeZoneBinar => new SystemDataId(0x010000000000080E); + public static SystemDataId CertStoreCruiser => new SystemDataId(0x010000000000080F); + public static SystemDataId FontNintendoExtension => new SystemDataId(0x0100000000000810); + public static SystemDataId FontStandard => new SystemDataId(0x0100000000000811); + public static SystemDataId FontKorean => new SystemDataId(0x0100000000000812); + public static SystemDataId FontChineseTraditional => new SystemDataId(0x0100000000000813); + public static SystemDataId FontChineseSimple => new SystemDataId(0x0100000000000814); + public static SystemDataId FontBfcpx => new SystemDataId(0x0100000000000815); + public static SystemDataId SystemUpdate => new SystemDataId(0x0100000000000816); + + public static SystemDataId FirmwareDebugSettings => new SystemDataId(0x0100000000000818); + public static SystemDataId BootImagePackage => new SystemDataId(0x0100000000000819); + public static SystemDataId BootImagePackageSafe => new SystemDataId(0x010000000000081A); + public static SystemDataId BootImagePackageExFat => new SystemDataId(0x010000000000081B); + public static SystemDataId BootImagePackageExFatSafe => new SystemDataId(0x010000000000081C); + public static SystemDataId FatalMessage => new SystemDataId(0x010000000000081D); + public static SystemDataId ControllerIcon => new SystemDataId(0x010000000000081E); + public static SystemDataId PlatformConfigIcosa => new SystemDataId(0x010000000000081F); + public static SystemDataId PlatformConfigCopper => new SystemDataId(0x0100000000000820); + public static SystemDataId PlatformConfigHoag => new SystemDataId(0x0100000000000821); + public static SystemDataId ControllerFirmware => new SystemDataId(0x0100000000000822); + public static SystemDataId NgWord2 => new SystemDataId(0x0100000000000823); + public static SystemDataId PlatformConfigIcosaMariko => new SystemDataId(0x0100000000000824); + public static SystemDataId ApplicationBlackList => new SystemDataId(0x0100000000000825); + public static SystemDataId RebootlessSystemUpdateVersion => new SystemDataId(0x0100000000000826); + public static SystemDataId ContentActionTable => new SystemDataId(0x0100000000000827); + + public static SystemDataId End => new SystemDataId(0x0100000000000FFF); +} + +public readonly struct SystemUpdateId +{ + public readonly ulong Value; + + public SystemUpdateId(ulong value) + { + Value = value; + } + + public static implicit operator DataId(SystemUpdateId id) => new DataId(id.Value); +} + +public readonly struct SystemAppletId +{ + public readonly ulong Value; + + public SystemAppletId(ulong value) + { + Value = value; + } + + public static implicit operator ProgramId(SystemAppletId id) => new ProgramId(id.Value); + + public static bool IsSystemAppletId(ProgramId programId) + { + return Start <= programId && programId <= End; + } + + public static bool IsSystemAppletId(SystemAppletId id) => true; + + public static SystemAppletId Start => new SystemAppletId(0x0100000000001000); + + public static SystemAppletId Qlaunch => new SystemAppletId(0x0100000000001000); + public static SystemAppletId Auth => new SystemAppletId(0x0100000000001001); + public static SystemAppletId Cabinet => new SystemAppletId(0x0100000000001002); + public static SystemAppletId Controller => new SystemAppletId(0x0100000000001003); + public static SystemAppletId DataErase => new SystemAppletId(0x0100000000001004); + public static SystemAppletId Error => new SystemAppletId(0x0100000000001005); + public static SystemAppletId NetConnect => new SystemAppletId(0x0100000000001006); + public static SystemAppletId PlayerSelect => new SystemAppletId(0x0100000000001007); + public static SystemAppletId Swkbd => new SystemAppletId(0x0100000000001008); + public static SystemAppletId MiiEdit => new SystemAppletId(0x0100000000001009); + public static SystemAppletId Web => new SystemAppletId(0x010000000000100A); + public static SystemAppletId Shop => new SystemAppletId(0x010000000000100B); + public static SystemAppletId OverlayDisp => new SystemAppletId(0x010000000000100C); + public static SystemAppletId PhotoViewer => new SystemAppletId(0x010000000000100D); + public static SystemAppletId Set => new SystemAppletId(0x010000000000100E); + public static SystemAppletId OfflineWeb => new SystemAppletId(0x010000000000100F); + public static SystemAppletId LoginShare => new SystemAppletId(0x0100000000001010); + public static SystemAppletId WifiWebAuth => new SystemAppletId(0x0100000000001011); + public static SystemAppletId Starter => new SystemAppletId(0x0100000000001012); + public static SystemAppletId MyPage => new SystemAppletId(0x0100000000001013); + public static SystemAppletId PlayReport => new SystemAppletId(0x0100000000001014); + public static SystemAppletId MaintenanceMenu => new SystemAppletId(0x0100000000001015); + + public static SystemAppletId Gift => new SystemAppletId(0x010000000000101A); + public static SystemAppletId DummyShop => new SystemAppletId(0x010000000000101B); + public static SystemAppletId UserMigration => new SystemAppletId(0x010000000000101C); + public static SystemAppletId Encounter => new SystemAppletId(0x010000000000101D); + + public static SystemAppletId Story => new SystemAppletId(0x0100000000001020); + + public static SystemAppletId End => new SystemAppletId(0x0100000000001FFF); +} + +public readonly struct SystemDebugAppletId +{ + public readonly ulong Value; + + public SystemDebugAppletId(ulong value) + { + Value = value; + } + + public static implicit operator ProgramId(SystemDebugAppletId id) => new ProgramId(id.Value); + + public static bool IsSystemDebugAppletId(ProgramId programId) + { + return Start <= programId && programId <= End; + } + + public static bool IsSystemDebugAppletId(SystemDebugAppletId id) => true; + + public static SystemDebugAppletId Start => new SystemDebugAppletId(0x0100000000002000); + + public static SystemDebugAppletId SnapShotDumper => new SystemDebugAppletId(0x0100000000002071); + + public static SystemDebugAppletId End => new SystemDebugAppletId(0x0100000000002FFF); +} + +public readonly struct LibraryAppletId +{ + public readonly ulong Value; + + public LibraryAppletId(ulong value) + { + Value = value; + } + + public static implicit operator SystemAppletId(LibraryAppletId id) => new SystemAppletId(id.Value); + public static implicit operator ProgramId(LibraryAppletId id) => new ProgramId(id.Value); + + public static bool IsLibraryAppletId(ProgramId programId) + { + return programId == Auth || + programId == Controller || + programId == Error || + programId == PlayerSelect || + programId == Swkbd || + programId == Web || + programId == Shop || + programId == PhotoViewer || + programId == OfflineWeb || + programId == LoginShare || + programId == WifiWebAuth || + programId == MyPage; + } + + public static bool IsLibraryAppletId(LibraryAppletId id) => true; + + public static LibraryAppletId Auth => new LibraryAppletId(SystemAppletId.Auth.Value); + public static LibraryAppletId Controller => new LibraryAppletId(SystemAppletId.Controller.Value); + public static LibraryAppletId Error => new LibraryAppletId(SystemAppletId.Error.Value); + public static LibraryAppletId PlayerSelect => new LibraryAppletId(SystemAppletId.PlayerSelect.Value); + public static LibraryAppletId Swkbd => new LibraryAppletId(SystemAppletId.Swkbd.Value); + public static LibraryAppletId Web => new LibraryAppletId(SystemAppletId.Web.Value); + public static LibraryAppletId Shop => new LibraryAppletId(SystemAppletId.Shop.Value); + public static LibraryAppletId PhotoViewer => new LibraryAppletId(SystemAppletId.PhotoViewer.Value); + public static LibraryAppletId OfflineWeb => new LibraryAppletId(SystemAppletId.OfflineWeb.Value); + public static LibraryAppletId LoginShare => new LibraryAppletId(SystemAppletId.LoginShare.Value); + public static LibraryAppletId WifiWebAuth => new LibraryAppletId(SystemAppletId.WifiWebAuth.Value); + public static LibraryAppletId MyPage => new LibraryAppletId(SystemAppletId.MyPage.Value); +} + +public readonly struct WebAppletId +{ + public readonly ulong Value; + + public WebAppletId(ulong value) + { + Value = value; + } + + public static implicit operator LibraryAppletId(WebAppletId id) => new LibraryAppletId(id.Value); + public static implicit operator SystemAppletId(WebAppletId id) => new SystemAppletId(id.Value); + public static implicit operator ProgramId(WebAppletId id) => new ProgramId(id.Value); + + public static bool IsWebAppletId(ProgramId programId) + { + return programId == Web || + programId == Shop || + programId == OfflineWeb || + programId == LoginShare || + programId == WifiWebAuth; + } + + public static bool IsWebAppletId(WebAppletId id) => true; + + public static WebAppletId Web => new WebAppletId(LibraryAppletId.Web.Value); + public static WebAppletId Shop => new WebAppletId(LibraryAppletId.Shop.Value); + public static WebAppletId OfflineWeb => new WebAppletId(LibraryAppletId.OfflineWeb.Value); + public static WebAppletId LoginShare => new WebAppletId(LibraryAppletId.LoginShare.Value); + public static WebAppletId WifiWebAuth => new WebAppletId(LibraryAppletId.WifiWebAuth.Value); +} + +public readonly struct SystemApplicationId +{ + public readonly ulong Value; + + public SystemApplicationId(ulong value) + { + Value = value; + } + + public static implicit operator ProgramId(SystemApplicationId id) => new ProgramId(id.Value); } diff --git a/src/LibHac/Npdm/Aci0.cs b/src/LibHac/Npdm/Aci0.cs index 60d993c1..13d49956 100644 --- a/src/LibHac/Npdm/Aci0.cs +++ b/src/LibHac/Npdm/Aci0.cs @@ -2,63 +2,62 @@ using System; using System.IO; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class Aci0 { - public class Aci0 + public string Magic; + public long TitleId { get; } + public int FsVersion { get; } + public ulong FsPermissionsBitmask { get; } + public ServiceAccessControl ServiceAccess { get; } + public KernelAccessControl KernelAccess { get; } + + public Aci0(Stream stream, int offset) { - public string Magic; - public long TitleId { get; } - public int FsVersion { get; } - public ulong FsPermissionsBitmask { get; } - public ServiceAccessControl ServiceAccess { get; } - public KernelAccessControl KernelAccess { get; } + stream.Seek(offset, SeekOrigin.Begin); - public Aci0(Stream stream, int offset) + var reader = new BinaryReader(stream); + + Magic = reader.ReadAscii(0x4); + + if (Magic != "ACI0") { - stream.Seek(offset, SeekOrigin.Begin); + throw new Exception("ACI0 Stream doesn't contain ACI0 section!"); + } - var reader = new BinaryReader(stream); + stream.Seek(0xc, SeekOrigin.Current); - Magic = reader.ReadAscii(0x4); + TitleId = reader.ReadInt64(); - if (Magic != "ACI0") - { - throw new Exception("ACI0 Stream doesn't contain ACI0 section!"); - } + //Reserved. + stream.Seek(8, SeekOrigin.Current); - stream.Seek(0xc, SeekOrigin.Current); + int fsAccessHeaderOffset = reader.ReadInt32(); + int fsAccessHeaderSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); - TitleId = reader.ReadInt64(); + if (fsAccessHeaderSize > 0) + { + var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset); - //Reserved. - stream.Seek(8, SeekOrigin.Current); + FsVersion = accessHeader.Version; + FsPermissionsBitmask = accessHeader.PermissionsBitmask; + } - int fsAccessHeaderOffset = reader.ReadInt32(); - int fsAccessHeaderSize = reader.ReadInt32(); - int serviceAccessControlOffset = reader.ReadInt32(); - int serviceAccessControlSize = reader.ReadInt32(); - int kernelAccessControlOffset = reader.ReadInt32(); - int kernelAccessControlSize = reader.ReadInt32(); + if (serviceAccessControlSize > 0) + { + ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, + serviceAccessControlSize); + } - if (fsAccessHeaderSize > 0) - { - var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset); - - FsVersion = accessHeader.Version; - FsPermissionsBitmask = accessHeader.PermissionsBitmask; - } - - if (serviceAccessControlSize > 0) - { - ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, - serviceAccessControlSize); - } - - if (kernelAccessControlSize > 0) - { - KernelAccess = - new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); - } + if (kernelAccessControlSize > 0) + { + KernelAccess = + new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); } } } diff --git a/src/LibHac/Npdm/Acid.cs b/src/LibHac/Npdm/Acid.cs index be7017a6..4b839467 100644 --- a/src/LibHac/Npdm/Acid.cs +++ b/src/LibHac/Npdm/Acid.cs @@ -3,73 +3,72 @@ using System; using System.IO; using LibHac.Common.Keys; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class Acid { - public class Acid + public string Magic; + public byte[] Rsa2048Signature { get; } + public byte[] Rsa2048Modulus { get; } + public int Size { get; } + public int Flags { get; } + + public long TitleIdRangeMin { get; } + public long TitleIdRangeMax { get; } + + public FsAccessControl FsAccess { get; } + public ServiceAccessControl ServiceAccess { get; } + public KernelAccessControl KernelAccess { get; } + + public Validity SignatureValidity { get; } + + public Acid(Stream stream, int offset) : this(stream, offset, null) { } + + public Acid(Stream stream, int offset, KeySet keySet) { - public string Magic; - public byte[] Rsa2048Signature { get; } - public byte[] Rsa2048Modulus { get; } - public int Size { get; } - public int Flags { get; } + stream.Seek(offset, SeekOrigin.Begin); - public long TitleIdRangeMin { get; } - public long TitleIdRangeMax { get; } + var reader = new BinaryReader(stream); - public FsAccessControl FsAccess { get; } - public ServiceAccessControl ServiceAccess { get; } - public KernelAccessControl KernelAccess { get; } + Rsa2048Signature = reader.ReadBytes(0x100); + Rsa2048Modulus = reader.ReadBytes(0x100); - public Validity SignatureValidity { get; } - - public Acid(Stream stream, int offset) : this(stream, offset, null) { } - - public Acid(Stream stream, int offset, KeySet keySet) + Magic = reader.ReadAscii(0x4); + if (Magic != "ACID") { - stream.Seek(offset, SeekOrigin.Begin); - - var reader = new BinaryReader(stream); - - Rsa2048Signature = reader.ReadBytes(0x100); - Rsa2048Modulus = reader.ReadBytes(0x100); - - Magic = reader.ReadAscii(0x4); - if (Magic != "ACID") - { - throw new Exception("ACID Stream doesn't contain ACID section!"); - } - - Size = reader.ReadInt32(); - - if (keySet != null) - { - reader.BaseStream.Position = offset + 0x100; - byte[] signatureData = reader.ReadBytes(Size); - SignatureValidity = - CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeyParams[0].Modulus); - } - - reader.BaseStream.Position = offset + 0x208; - reader.ReadInt32(); - - //Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown. - Flags = reader.ReadInt32(); - - TitleIdRangeMin = reader.ReadInt64(); - TitleIdRangeMax = reader.ReadInt64(); - - int fsAccessControlOffset = reader.ReadInt32(); - int fsAccessControlSize = reader.ReadInt32(); - int serviceAccessControlOffset = reader.ReadInt32(); - int serviceAccessControlSize = reader.ReadInt32(); - int kernelAccessControlOffset = reader.ReadInt32(); - int kernelAccessControlSize = reader.ReadInt32(); - - FsAccess = new FsAccessControl(stream, offset + fsAccessControlOffset); - - ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); - - KernelAccess = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); + throw new Exception("ACID Stream doesn't contain ACID section!"); } + + Size = reader.ReadInt32(); + + if (keySet != null) + { + reader.BaseStream.Position = offset + 0x100; + byte[] signatureData = reader.ReadBytes(Size); + SignatureValidity = + CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeyParams[0].Modulus); + } + + reader.BaseStream.Position = offset + 0x208; + reader.ReadInt32(); + + //Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown. + Flags = reader.ReadInt32(); + + TitleIdRangeMin = reader.ReadInt64(); + TitleIdRangeMax = reader.ReadInt64(); + + int fsAccessControlOffset = reader.ReadInt32(); + int fsAccessControlSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); + + FsAccess = new FsAccessControl(stream, offset + fsAccessControlOffset); + + ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); + + KernelAccess = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); } } diff --git a/src/LibHac/Npdm/ApplicationType.cs b/src/LibHac/Npdm/ApplicationType.cs index 5719cb05..03f9fa3f 100644 --- a/src/LibHac/Npdm/ApplicationType.cs +++ b/src/LibHac/Npdm/ApplicationType.cs @@ -1,9 +1,8 @@ -namespace LibHac.Npdm +namespace LibHac.Npdm; + +internal enum ApplicationType { - internal enum ApplicationType - { - SystemModule, - Application, - Applet - } + SystemModule, + Application, + Applet } diff --git a/src/LibHac/Npdm/FSAccessControl.cs b/src/LibHac/Npdm/FSAccessControl.cs index 92ec0c77..70cdb070 100644 --- a/src/LibHac/Npdm/FSAccessControl.cs +++ b/src/LibHac/Npdm/FSAccessControl.cs @@ -1,28 +1,27 @@ using System.IO; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class FsAccessControl { - public class FsAccessControl + public int Version { get; } + public ulong PermissionsBitmask { get; } + public int Unknown1 { get; } + public int Unknown2 { get; } + public int Unknown3 { get; } + public int Unknown4 { get; } + + public FsAccessControl(Stream stream, int offset) { - public int Version { get; } - public ulong PermissionsBitmask { get; } - public int Unknown1 { get; } - public int Unknown2 { get; } - public int Unknown3 { get; } - public int Unknown4 { get; } + stream.Seek(offset, SeekOrigin.Begin); - public FsAccessControl(Stream stream, int offset) - { - stream.Seek(offset, SeekOrigin.Begin); + var reader = new BinaryReader(stream); - var reader = new BinaryReader(stream); - - Version = reader.ReadInt32(); - PermissionsBitmask = reader.ReadUInt64(); - Unknown1 = reader.ReadInt32(); - Unknown2 = reader.ReadInt32(); - Unknown3 = reader.ReadInt32(); - Unknown4 = reader.ReadInt32(); - } + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + Unknown1 = reader.ReadInt32(); + Unknown2 = reader.ReadInt32(); + Unknown3 = reader.ReadInt32(); + Unknown4 = reader.ReadInt32(); } } diff --git a/src/LibHac/Npdm/FSAccessHeader.cs b/src/LibHac/Npdm/FSAccessHeader.cs index 6b9b3f31..101a7f95 100644 --- a/src/LibHac/Npdm/FSAccessHeader.cs +++ b/src/LibHac/Npdm/FSAccessHeader.cs @@ -2,36 +2,35 @@ using System; using System.IO; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class FsAccessHeader { - public class FsAccessHeader + public int Version { get; } + public ulong PermissionsBitmask { get; } + + public FsAccessHeader(Stream stream, int offset) { - public int Version { get; } - public ulong PermissionsBitmask { get; } + stream.Seek(offset, SeekOrigin.Begin); - public FsAccessHeader(Stream stream, int offset) + var reader = new BinaryReader(stream); + + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + + int dataSize = reader.ReadInt32(); + + if (dataSize != 0x1c) { - stream.Seek(offset, SeekOrigin.Begin); + throw new Exception("FsAccessHeader is corrupted!"); + } - var reader = new BinaryReader(stream); + int contentOwnerIdSize = reader.ReadInt32(); + int dataAndContentOwnerIdSize = reader.ReadInt32(); - Version = reader.ReadInt32(); - PermissionsBitmask = reader.ReadUInt64(); - - int dataSize = reader.ReadInt32(); - - if (dataSize != 0x1c) - { - throw new Exception("FsAccessHeader is corrupted!"); - } - - int contentOwnerIdSize = reader.ReadInt32(); - int dataAndContentOwnerIdSize = reader.ReadInt32(); - - if (dataAndContentOwnerIdSize != 0x1c) - { - throw new NotImplementedException("ContentOwnerId section is not implemented!"); - } + if (dataAndContentOwnerIdSize != 0x1c) + { + throw new NotImplementedException("ContentOwnerId section is not implemented!"); } } } diff --git a/src/LibHac/Npdm/FsPermissionBool.cs b/src/LibHac/Npdm/FsPermissionBool.cs index f95823d8..e67c2526 100644 --- a/src/LibHac/Npdm/FsPermissionBool.cs +++ b/src/LibHac/Npdm/FsPermissionBool.cs @@ -1,34 +1,33 @@ // ReSharper disable InconsistentNaming -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public enum FsPermissionBool : ulong { - public enum FsPermissionBool : ulong - { - BisCache = 0x8000000000000080, - EraseMmc = 0x8000000000000080, - GameCardCertificate = 0x8000000000000010, - GameCardIdSet = 0x8000000000000010, - GameCardDriver = 0x8000000000000200, - GameCardAsic = 0x8000000000000200, - SaveDataCreate = 0x8000000000002020, - SaveDataDelete0 = 0x8000000000000060, - SystemSaveDataCreate0 = 0x8000000000000028, - SystemSaveDataCreate1 = 0x8000000000000020, - SaveDataDelete1 = 0x8000000000004028, - SaveDataIterators0 = 0x8000000000000060, - SaveDataIterators1 = 0x8000000000004020, - SaveThumbnails = 0x8000000000020000, - PosixTime = 0x8000000000000400, - SaveDataExtraData = 0x8000000000004060, - GlobalMode = 0x8000000000080000, - SpeedEmulation = 0x8000000000080000, - NULL = 0, - PaddingFiles = 0xC000000000800000, - SaveData_Debug = 0xC000000001000000, - SaveData_SystemManagement = 0xC000000002000000, - Unknown0x16 = 0x8000000004000000, - Unknown0x17 = 0x8000000008000000, - Unknown0x18 = 0x8000000010000000, - Unknown0x19 = 0x8000000000000800, - Unknown0x1A = 0x8000000000004020 - } + BisCache = 0x8000000000000080, + EraseMmc = 0x8000000000000080, + GameCardCertificate = 0x8000000000000010, + GameCardIdSet = 0x8000000000000010, + GameCardDriver = 0x8000000000000200, + GameCardAsic = 0x8000000000000200, + SaveDataCreate = 0x8000000000002020, + SaveDataDelete0 = 0x8000000000000060, + SystemSaveDataCreate0 = 0x8000000000000028, + SystemSaveDataCreate1 = 0x8000000000000020, + SaveDataDelete1 = 0x8000000000004028, + SaveDataIterators0 = 0x8000000000000060, + SaveDataIterators1 = 0x8000000000004020, + SaveThumbnails = 0x8000000000020000, + PosixTime = 0x8000000000000400, + SaveDataExtraData = 0x8000000000004060, + GlobalMode = 0x8000000000080000, + SpeedEmulation = 0x8000000000080000, + NULL = 0, + PaddingFiles = 0xC000000000800000, + SaveData_Debug = 0xC000000001000000, + SaveData_SystemManagement = 0xC000000002000000, + Unknown0x16 = 0x8000000004000000, + Unknown0x17 = 0x8000000008000000, + Unknown0x18 = 0x8000000010000000, + Unknown0x19 = 0x8000000000000800, + Unknown0x1A = 0x8000000000004020 } diff --git a/src/LibHac/Npdm/FsPermissionRw.cs b/src/LibHac/Npdm/FsPermissionRw.cs index 69518e65..d8122899 100644 --- a/src/LibHac/Npdm/FsPermissionRw.cs +++ b/src/LibHac/Npdm/FsPermissionRw.cs @@ -1,46 +1,45 @@ // ReSharper disable InconsistentNaming -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public enum FsPermissionRw : ulong { - public enum FsPermissionRw : ulong - { - MountContentType2 = 0x8000000000000801, - MountContentType5 = 0x8000000000000801, - MountContentType3 = 0x8000000000000801, - MountContentType4 = 0x8000000000000801, - MountContentType6 = 0x8000000000000801, - MountContentType7 = 0x8000000000000801, - Unknown0x6 = 0x8000000000000000, - ContentStorageAccess = 0x8000000000000800, - ImageDirectoryAccess = 0x8000000000001000, - MountBisType28 = 0x8000000000000084, - MountBisType29 = 0x8000000000000080, - MountBisType30 = 0x8000000000008080, - MountBisType31 = 0x8000000000008080, - Unknown0xD = 0x8000000000000080, - SdCardAccess = 0xC000000000200000, - GameCardUser = 0x8000000000000010, - SaveDataAccess0 = 0x8000000000040020, - SystemSaveDataAccess0 = 0x8000000000000028, - SaveDataAccess1 = 0x8000000000000020, - SystemSaveDataAccess1 = 0x8000000000000020, - BisPartition0 = 0x8000000000010082, - BisPartition10 = 0x8000000000010080, - BisPartition20 = 0x8000000000010080, - BisPartition21 = 0x8000000000010080, - BisPartition22 = 0x8000000000010080, - BisPartition23 = 0x8000000000010080, - BisPartition24 = 0x8000000000010080, - BisPartition25 = 0x8000000000010080, - BisPartition26 = 0x8000000000000080, - BisPartition27 = 0x8000000000000084, - BisPartition28 = 0x8000000000000084, - BisPartition29 = 0x8000000000000080, - BisPartition30 = 0x8000000000000080, - BisPartition31 = 0x8000000000000080, - BisPartition32 = 0x8000000000000080, - Unknown0x23 = 0xC000000000200000, - GameCard_System = 0x8000000000000100, - MountContent_System = 0x8000000000100008, - HostAccess = 0xC000000000400000 - } + MountContentType2 = 0x8000000000000801, + MountContentType5 = 0x8000000000000801, + MountContentType3 = 0x8000000000000801, + MountContentType4 = 0x8000000000000801, + MountContentType6 = 0x8000000000000801, + MountContentType7 = 0x8000000000000801, + Unknown0x6 = 0x8000000000000000, + ContentStorageAccess = 0x8000000000000800, + ImageDirectoryAccess = 0x8000000000001000, + MountBisType28 = 0x8000000000000084, + MountBisType29 = 0x8000000000000080, + MountBisType30 = 0x8000000000008080, + MountBisType31 = 0x8000000000008080, + Unknown0xD = 0x8000000000000080, + SdCardAccess = 0xC000000000200000, + GameCardUser = 0x8000000000000010, + SaveDataAccess0 = 0x8000000000040020, + SystemSaveDataAccess0 = 0x8000000000000028, + SaveDataAccess1 = 0x8000000000000020, + SystemSaveDataAccess1 = 0x8000000000000020, + BisPartition0 = 0x8000000000010082, + BisPartition10 = 0x8000000000010080, + BisPartition20 = 0x8000000000010080, + BisPartition21 = 0x8000000000010080, + BisPartition22 = 0x8000000000010080, + BisPartition23 = 0x8000000000010080, + BisPartition24 = 0x8000000000010080, + BisPartition25 = 0x8000000000010080, + BisPartition26 = 0x8000000000000080, + BisPartition27 = 0x8000000000000084, + BisPartition28 = 0x8000000000000084, + BisPartition29 = 0x8000000000000080, + BisPartition30 = 0x8000000000000080, + BisPartition31 = 0x8000000000000080, + BisPartition32 = 0x8000000000000080, + Unknown0x23 = 0xC000000000200000, + GameCard_System = 0x8000000000000100, + MountContent_System = 0x8000000000100008, + HostAccess = 0xC000000000400000 } diff --git a/src/LibHac/Npdm/KernelAccessControl.cs b/src/LibHac/Npdm/KernelAccessControl.cs index e6eeef26..7fb6f38c 100644 --- a/src/LibHac/Npdm/KernelAccessControl.cs +++ b/src/LibHac/Npdm/KernelAccessControl.cs @@ -3,171 +3,170 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class KernelAccessControl { - public class KernelAccessControl + public List Items { get; } + + public KernelAccessControl(Stream stream, int offset, int size) { - public List Items { get; } + stream.Seek(offset, SeekOrigin.Begin); - public KernelAccessControl(Stream stream, int offset, int size) + var reader = new BinaryReader(stream); + + var items = new KernelAccessControlItem[size / 4]; + + for (int index = 0; index < size / 4; index++) { - stream.Seek(offset, SeekOrigin.Begin); + uint descriptor = reader.ReadUInt32(); - var reader = new BinaryReader(stream); - - var items = new KernelAccessControlItem[size / 4]; - - for (int index = 0; index < size / 4; index++) + //Ignore the descriptor. + if (descriptor == 0xffffffff) { - uint descriptor = reader.ReadUInt32(); - - //Ignore the descriptor. - if (descriptor == 0xffffffff) - { - continue; - } - - items[index] = new KernelAccessControlItem(); - - int lowBits = 0; - - while ((descriptor & 1) != 0) - { - descriptor >>= 1; - - lowBits++; - } - - descriptor >>= 1; - - switch (lowBits) - { - //Kernel flags. - case 3: - { - items[index].HasKernelFlags = true; - - items[index].HighestThreadPriority = (descriptor >> 0) & 0x3f; - items[index].LowestThreadPriority = (descriptor >> 6) & 0x3f; - items[index].LowestCpuId = (descriptor >> 12) & 0xff; - items[index].HighestCpuId = (descriptor >> 20) & 0xff; - - break; - } - - //Syscall mask. - case 4: - { - items[index].HasSvcFlags = true; - - items[index].AllowedSvcs = new bool[0x80]; - - int sysCallBase = (int)(descriptor >> 24) * 0x18; - - for (int sysCall = 0; sysCall < 0x18 && sysCallBase + sysCall < 0x80; sysCall++) - { - items[index].AllowedSvcs[sysCallBase + sysCall] = (descriptor & 1) != 0; - - descriptor >>= 1; - } - - break; - } - - //Map IO/Normal. - case 6: - { - ulong address = (descriptor & 0xffffff) << 12; - bool isRo = (descriptor >> 24) != 0; - - if (index == size / 4 - 1) - { - throw new Exception("Invalid Kernel Access Control Descriptors!"); - } - - descriptor = reader.ReadUInt32(); - - if ((descriptor & 0x7f) != 0x3f) - { - throw new Exception("Invalid Kernel Access Control Descriptors!"); - } - - descriptor >>= 7; - - ulong mmioSize = (descriptor & 0xffffff) << 12; - bool isNormal = (descriptor >> 24) != 0; - - items[index].NormalMmio.Add(new KernelAccessControlMmio(address, mmioSize, isRo, isNormal)); - - index++; - - break; - } - - //Map Normal Page. - case 7: - { - ulong address = descriptor << 12; - - items[index].PageMmio.Add(new KernelAccessControlMmio(address, 0x1000, false, false)); - - break; - } - - //IRQ Pair. - case 11: - { - items[index].Irq.Add(new KernelAccessControlIrq( - (descriptor >> 0) & 0x3ff, - (descriptor >> 10) & 0x3ff)); - - break; - } - - //Application Type. - case 13: - { - items[index].HasApplicationType = true; - - items[index].ApplicationType = (int)descriptor & 7; - - break; - } - - //Kernel Release Version. - case 14: - { - items[index].HasKernelVersion = true; - - items[index].KernelVersionRelease = (int)descriptor; - - break; - } - - //Handle Table Size. - case 15: - { - items[index].HasHandleTableSize = true; - - items[index].HandleTableSize = (int)descriptor; - - break; - } - - //Debug Flags. - case 16: - { - items[index].HasDebugFlags = true; - - items[index].AllowDebug = ((descriptor >> 0) & 1) != 0; - items[index].ForceDebug = ((descriptor >> 1) & 1) != 0; - - break; - } - } + continue; } - Items = items.ToList(); + items[index] = new KernelAccessControlItem(); + + int lowBits = 0; + + while ((descriptor & 1) != 0) + { + descriptor >>= 1; + + lowBits++; + } + + descriptor >>= 1; + + switch (lowBits) + { + //Kernel flags. + case 3: + { + items[index].HasKernelFlags = true; + + items[index].HighestThreadPriority = (descriptor >> 0) & 0x3f; + items[index].LowestThreadPriority = (descriptor >> 6) & 0x3f; + items[index].LowestCpuId = (descriptor >> 12) & 0xff; + items[index].HighestCpuId = (descriptor >> 20) & 0xff; + + break; + } + + //Syscall mask. + case 4: + { + items[index].HasSvcFlags = true; + + items[index].AllowedSvcs = new bool[0x80]; + + int sysCallBase = (int)(descriptor >> 24) * 0x18; + + for (int sysCall = 0; sysCall < 0x18 && sysCallBase + sysCall < 0x80; sysCall++) + { + items[index].AllowedSvcs[sysCallBase + sysCall] = (descriptor & 1) != 0; + + descriptor >>= 1; + } + + break; + } + + //Map IO/Normal. + case 6: + { + ulong address = (descriptor & 0xffffff) << 12; + bool isRo = (descriptor >> 24) != 0; + + if (index == size / 4 - 1) + { + throw new Exception("Invalid Kernel Access Control Descriptors!"); + } + + descriptor = reader.ReadUInt32(); + + if ((descriptor & 0x7f) != 0x3f) + { + throw new Exception("Invalid Kernel Access Control Descriptors!"); + } + + descriptor >>= 7; + + ulong mmioSize = (descriptor & 0xffffff) << 12; + bool isNormal = (descriptor >> 24) != 0; + + items[index].NormalMmio.Add(new KernelAccessControlMmio(address, mmioSize, isRo, isNormal)); + + index++; + + break; + } + + //Map Normal Page. + case 7: + { + ulong address = descriptor << 12; + + items[index].PageMmio.Add(new KernelAccessControlMmio(address, 0x1000, false, false)); + + break; + } + + //IRQ Pair. + case 11: + { + items[index].Irq.Add(new KernelAccessControlIrq( + (descriptor >> 0) & 0x3ff, + (descriptor >> 10) & 0x3ff)); + + break; + } + + //Application Type. + case 13: + { + items[index].HasApplicationType = true; + + items[index].ApplicationType = (int)descriptor & 7; + + break; + } + + //Kernel Release Version. + case 14: + { + items[index].HasKernelVersion = true; + + items[index].KernelVersionRelease = (int)descriptor; + + break; + } + + //Handle Table Size. + case 15: + { + items[index].HasHandleTableSize = true; + + items[index].HandleTableSize = (int)descriptor; + + break; + } + + //Debug Flags. + case 16: + { + items[index].HasDebugFlags = true; + + items[index].AllowDebug = ((descriptor >> 0) & 1) != 0; + items[index].ForceDebug = ((descriptor >> 1) & 1) != 0; + + break; + } + } } + + Items = items.ToList(); } } diff --git a/src/LibHac/Npdm/KernelAccessControlIrq.cs b/src/LibHac/Npdm/KernelAccessControlIrq.cs index aed2f980..91c437f7 100644 --- a/src/LibHac/Npdm/KernelAccessControlIrq.cs +++ b/src/LibHac/Npdm/KernelAccessControlIrq.cs @@ -1,14 +1,13 @@ -namespace LibHac.Npdm -{ - public struct KernelAccessControlIrq - { - public uint Irq0 { get; } - public uint Irq1 { get; } +namespace LibHac.Npdm; - public KernelAccessControlIrq(uint irq0, uint irq1) - { - Irq0 = irq0; - Irq1 = irq1; - } +public struct KernelAccessControlIrq +{ + public uint Irq0 { get; } + public uint Irq1 { get; } + + public KernelAccessControlIrq(uint irq0, uint irq1) + { + Irq0 = irq0; + Irq1 = irq1; } -} \ No newline at end of file +} diff --git a/src/LibHac/Npdm/KernelAccessControlMmio.cs b/src/LibHac/Npdm/KernelAccessControlMmio.cs index 2aea07d1..a0071e94 100644 --- a/src/LibHac/Npdm/KernelAccessControlMmio.cs +++ b/src/LibHac/Npdm/KernelAccessControlMmio.cs @@ -1,22 +1,21 @@ -namespace LibHac.Npdm -{ - public struct KernelAccessControlMmio - { - public ulong Address { get; } - public ulong Size { get; private set; } - public bool IsRo { get; private set; } - public bool IsNormal { get; private set; } +namespace LibHac.Npdm; - public KernelAccessControlMmio( - ulong address, - ulong size, - bool isro, - bool isnormal) - { - Address = address; - Size = size; - IsRo = isro; - IsNormal = isnormal; - } +public struct KernelAccessControlMmio +{ + public ulong Address { get; } + public ulong Size { get; private set; } + public bool IsRo { get; private set; } + public bool IsNormal { get; private set; } + + public KernelAccessControlMmio( + ulong address, + ulong size, + bool isro, + bool isnormal) + { + Address = address; + Size = size; + IsRo = isro; + IsNormal = isnormal; } -} \ No newline at end of file +} diff --git a/src/LibHac/Npdm/KernelAccessItem.cs b/src/LibHac/Npdm/KernelAccessItem.cs index fd5199f2..1600349b 100644 --- a/src/LibHac/Npdm/KernelAccessItem.cs +++ b/src/LibHac/Npdm/KernelAccessItem.cs @@ -1,41 +1,39 @@ using System.Collections.Generic; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class KernelAccessControlItem { - public class KernelAccessControlItem + public bool HasKernelFlags { get; set; } + public uint LowestThreadPriority { get; set; } + public uint HighestThreadPriority { get; set; } + public uint LowestCpuId { get; set; } + public uint HighestCpuId { get; set; } + + public bool HasSvcFlags { get; set; } + public bool[] AllowedSvcs { get; set; } + + public List NormalMmio { get; set; } + public List PageMmio { get; set; } + public List Irq { get; set; } + + public bool HasApplicationType { get; set; } + public int ApplicationType { get; set; } + + public bool HasKernelVersion { get; set; } + public int KernelVersionRelease { get; set; } + + public bool HasHandleTableSize { get; set; } + public int HandleTableSize { get; set; } + + public bool HasDebugFlags { get; set; } + public bool AllowDebug { get; set; } + public bool ForceDebug { get; set; } + + public KernelAccessControlItem() { - public bool HasKernelFlags { get; set; } - public uint LowestThreadPriority { get; set; } - public uint HighestThreadPriority { get; set; } - public uint LowestCpuId { get; set; } - public uint HighestCpuId { get; set; } - - public bool HasSvcFlags { get; set; } - public bool[] AllowedSvcs { get; set; } - - public List NormalMmio { get; set; } - public List PageMmio { get; set; } - public List Irq { get; set; } - - public bool HasApplicationType { get; set; } - public int ApplicationType { get; set; } - - public bool HasKernelVersion { get; set; } - public int KernelVersionRelease { get; set; } - - public bool HasHandleTableSize { get; set; } - public int HandleTableSize { get; set; } - - public bool HasDebugFlags { get; set; } - public bool AllowDebug { get; set; } - public bool ForceDebug { get; set; } - - public KernelAccessControlItem() - { - NormalMmio = new List(); - PageMmio = new List(); - Irq = new List(); - } + NormalMmio = new List(); + PageMmio = new List(); + Irq = new List(); } - } diff --git a/src/LibHac/Npdm/NpdmBinary.cs b/src/LibHac/Npdm/NpdmBinary.cs index ef298f53..5c8c8c95 100644 --- a/src/LibHac/Npdm/NpdmBinary.cs +++ b/src/LibHac/Npdm/NpdmBinary.cs @@ -4,78 +4,77 @@ using System.Buffers.Binary; using System.IO; using LibHac.Common.Keys; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +//https://github.com/Ryujinx/Ryujinx/blob/master/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +//https://github.com/SciresM/hactool/blob/master/npdm.c +//https://github.com/SciresM/hactool/blob/master/npdm.h +//http://switchbrew.org/index.php?title=NPDM +public class NpdmBinary { - //https://github.com/Ryujinx/Ryujinx/blob/master/Ryujinx.HLE/Loaders/Npdm/Npdm.cs - //https://github.com/SciresM/hactool/blob/master/npdm.c - //https://github.com/SciresM/hactool/blob/master/npdm.h - //http://switchbrew.org/index.php?title=NPDM - public class NpdmBinary + public string Magic; + public bool Is64Bits { get; } + public int AddressSpaceWidth { get; } + public byte MainThreadPriority { get; } + public byte DefaultCpuId { get; } + public int SystemResourceSize { get; } + public int ProcessCategory { get; } + public int MainEntrypointStackSize { get; } + public string TitleName { get; } + public byte[] ProductCode { get; } + + public Aci0 Aci0 { get; } + public Acid AciD { get; } + + public NpdmBinary(Stream stream) : this(stream, null) { } + + public NpdmBinary(Stream stream, KeySet keySet) { - public string Magic; - public bool Is64Bits { get; } - public int AddressSpaceWidth { get; } - public byte MainThreadPriority { get; } - public byte DefaultCpuId { get; } - public int SystemResourceSize { get; } - public int ProcessCategory { get; } - public int MainEntrypointStackSize { get; } - public string TitleName { get; } - public byte[] ProductCode { get; } + var reader = new BinaryReader(stream); - public Aci0 Aci0 { get; } - public Acid AciD { get; } + Magic = reader.ReadAscii(0x4); - public NpdmBinary(Stream stream) : this(stream, null) { } - - public NpdmBinary(Stream stream, KeySet keySet) + if (Magic != "META") { - var reader = new BinaryReader(stream); - - Magic = reader.ReadAscii(0x4); - - if (Magic != "META") - { - throw new Exception("NPDM Stream doesn't contain NPDM file!"); - } - - reader.ReadInt64(); - - //MmuFlags, bit0: 64-bit instructions, bits1-3: address space width (1=64-bit, 2=32-bit). Needs to be <= 0xF. - byte mmuflags = reader.ReadByte(); - - Is64Bits = (mmuflags & 1) != 0; - AddressSpaceWidth = (mmuflags >> 1) & 7; - - reader.ReadByte(); - - MainThreadPriority = reader.ReadByte(); //(0-63). - DefaultCpuId = reader.ReadByte(); - - reader.ReadInt32(); - - //System resource size (max size as of 5.x: 534773760). - SystemResourceSize = BinaryPrimitives.ReverseEndianness(reader.ReadInt32()); - - //ProcessCategory (0: regular title, 1: kernel built-in). Should be 0 here. - ProcessCategory = BinaryPrimitives.ReverseEndianness(reader.ReadInt32()); - - //Main entrypoint stack size. - MainEntrypointStackSize = reader.ReadInt32(); - - TitleName = reader.ReadUtf8(0x10).Trim('\0'); - - ProductCode = reader.ReadBytes(0x10); - - stream.Seek(0x30, SeekOrigin.Current); - - int aci0Offset = reader.ReadInt32(); - int aci0Size = reader.ReadInt32(); - int acidOffset = reader.ReadInt32(); - int acidSize = reader.ReadInt32(); - - Aci0 = new Aci0(stream, aci0Offset); - AciD = new Acid(stream, acidOffset, keySet); + throw new Exception("NPDM Stream doesn't contain NPDM file!"); } + + reader.ReadInt64(); + + //MmuFlags, bit0: 64-bit instructions, bits1-3: address space width (1=64-bit, 2=32-bit). Needs to be <= 0xF. + byte mmuflags = reader.ReadByte(); + + Is64Bits = (mmuflags & 1) != 0; + AddressSpaceWidth = (mmuflags >> 1) & 7; + + reader.ReadByte(); + + MainThreadPriority = reader.ReadByte(); //(0-63). + DefaultCpuId = reader.ReadByte(); + + reader.ReadInt32(); + + //System resource size (max size as of 5.x: 534773760). + SystemResourceSize = BinaryPrimitives.ReverseEndianness(reader.ReadInt32()); + + //ProcessCategory (0: regular title, 1: kernel built-in). Should be 0 here. + ProcessCategory = BinaryPrimitives.ReverseEndianness(reader.ReadInt32()); + + //Main entrypoint stack size. + MainEntrypointStackSize = reader.ReadInt32(); + + TitleName = reader.ReadUtf8(0x10).Trim('\0'); + + ProductCode = reader.ReadBytes(0x10); + + stream.Seek(0x30, SeekOrigin.Current); + + int aci0Offset = reader.ReadInt32(); + int aci0Size = reader.ReadInt32(); + int acidOffset = reader.ReadInt32(); + int acidSize = reader.ReadInt32(); + + Aci0 = new Aci0(stream, aci0Offset); + AciD = new Acid(stream, acidOffset, keySet); } } diff --git a/src/LibHac/Npdm/ServiceAccessControl.cs b/src/LibHac/Npdm/ServiceAccessControl.cs index b6e323ee..285bda79 100644 --- a/src/LibHac/Npdm/ServiceAccessControl.cs +++ b/src/LibHac/Npdm/ServiceAccessControl.cs @@ -2,36 +2,35 @@ using System.Collections.Generic; using System.IO; -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public class ServiceAccessControl { - public class ServiceAccessControl + public List> Services { get; } = new List>(); + + public ServiceAccessControl(Stream stream, int offset, int size) { - public List> Services { get; } = new List>(); + stream.Seek(offset, SeekOrigin.Begin); - public ServiceAccessControl(Stream stream, int offset, int size) + var reader = new BinaryReader(stream); + + int bytesRead = 0; + + while (bytesRead != size) { - stream.Seek(offset, SeekOrigin.Begin); + byte controlByte = reader.ReadByte(); - var reader = new BinaryReader(stream); - - int bytesRead = 0; - - while (bytesRead != size) + if (controlByte == 0) { - byte controlByte = reader.ReadByte(); - - if (controlByte == 0) - { - break; - } - - int length = ((controlByte & 0x07)) + 1; - bool registerAllowed = ((controlByte & 0x80) != 0); - - Services.Add(new Tuple(reader.ReadAscii(length), registerAllowed)); - - bytesRead += length + 1; + break; } + + int length = ((controlByte & 0x07)) + 1; + bool registerAllowed = ((controlByte & 0x80) != 0); + + Services.Add(new Tuple(reader.ReadAscii(length), registerAllowed)); + + bytesRead += length + 1; } } } diff --git a/src/LibHac/Npdm/SvcName.cs b/src/LibHac/Npdm/SvcName.cs index 52d5db76..bd363757 100644 --- a/src/LibHac/Npdm/SvcName.cs +++ b/src/LibHac/Npdm/SvcName.cs @@ -1,134 +1,133 @@ -namespace LibHac.Npdm +namespace LibHac.Npdm; + +public enum SvcName { - public enum SvcName - { - Reserved0, - SetHeapSize, - SetMemoryPermission, - SetMemoryAttribute, - MapMemory, - UnmapMemory, - QueryMemory, - ExitProcess, - CreateThread, - StartThread, - ExitThread, - SleepThread, - GetThreadPriority, - SetThreadPriority, - GetThreadCoreMask, - SetThreadCoreMask, - GetCurrentProcessorNumber, - SignalEvent, - ClearEvent, - MapSharedMemory, - UnmapSharedMemory, - CreateTransferMemory, - CloseHandle, - ResetSignal, - WaitSynchronization, - CancelSynchronization, - ArbitrateLock, - ArbitrateUnlock, - WaitProcessWideKeyAtomic, - SignalProcessWideKey, - GetSystemTick, - ConnectToNamedPort, - SendSyncRequestLight, - SendSyncRequest, - SendSyncRequestWithUserBuffer, - SendAsyncRequestWithUserBuffer, - GetProcessId, - GetThreadId, - Break, - OutputDebugString, - ReturnFromException, - GetInfo, - FlushEntireDataCache, - FlushDataCache, - MapPhysicalMemory, - UnmapPhysicalMemory, - GetFutureThreadInfo, - GetLastThreadInfo, - GetResourceLimitLimitValue, - GetResourceLimitCurrentValue, - SetThreadActivity, - GetThreadContext3, - WaitForAddress, - SignalToAddress, - Reserved1, - Reserved2, - Reserved3, - Reserved4, - Reserved5, - Reserved6, - DumpInfo, - DumpInfoNew, - Reserved7, - Reserved8, - CreateSession, - AcceptSession, - ReplyAndReceiveLight, - ReplyAndReceive, - ReplyAndReceiveWithUserBuffer, - CreateEvent, - Reserved9, - Reserved10, - MapPhysicalMemoryUnsafe, - UnmapPhysicalMemoryUnsafe, - SetUnsafeLimit, - CreateCodeMemory, - ControlCodeMemory, - SleepSystem, - ReadWriteRegister, - SetProcessActivity, - CreateSharedMemory, - MapTransferMemory, - UnmapTransferMemory, - CreateInterruptEvent, - QueryPhysicalAddress, - QueryIoMapping, - CreateDeviceAddressSpace, - AttachDeviceAddressSpace, - DetachDeviceAddressSpace, - MapDeviceAddressSpaceByForce, - MapDeviceAddressSpaceAligned, - MapDeviceAddressSpace, - UnmapDeviceAddressSpace, - InvalidateProcessDataCache, - StoreProcessDataCache, - FlushProcessDataCache, - DebugActiveProcess, - BreakDebugProcess, - TerminateDebugProcess, - GetDebugEvent, - ContinueDebugEvent, - GetProcessList, - GetThreadList, - GetDebugThreadContext, - SetDebugThreadContext, - QueryDebugProcessMemory, - ReadDebugProcessMemory, - WriteDebugProcessMemory, - SetHardwareBreakPoint, - GetDebugThreadParam, - Reserved11, - GetSystemInfo, - CreatePort, - ManageNamedPort, - ConnectToPort, - SetProcessMemoryPermission, - MapProcessMemory, - UnmapProcessMemory, - QueryProcessMemory, - MapProcessCodeMemory, - UnmapProcessCodeMemory, - CreateProcess, - StartProcess, - TerminateProcess, - GetProcessInfo, - CreateResourceLimit, - SetResourceLimitLimitValue, - CallSecureMonitor - } + Reserved0, + SetHeapSize, + SetMemoryPermission, + SetMemoryAttribute, + MapMemory, + UnmapMemory, + QueryMemory, + ExitProcess, + CreateThread, + StartThread, + ExitThread, + SleepThread, + GetThreadPriority, + SetThreadPriority, + GetThreadCoreMask, + SetThreadCoreMask, + GetCurrentProcessorNumber, + SignalEvent, + ClearEvent, + MapSharedMemory, + UnmapSharedMemory, + CreateTransferMemory, + CloseHandle, + ResetSignal, + WaitSynchronization, + CancelSynchronization, + ArbitrateLock, + ArbitrateUnlock, + WaitProcessWideKeyAtomic, + SignalProcessWideKey, + GetSystemTick, + ConnectToNamedPort, + SendSyncRequestLight, + SendSyncRequest, + SendSyncRequestWithUserBuffer, + SendAsyncRequestWithUserBuffer, + GetProcessId, + GetThreadId, + Break, + OutputDebugString, + ReturnFromException, + GetInfo, + FlushEntireDataCache, + FlushDataCache, + MapPhysicalMemory, + UnmapPhysicalMemory, + GetFutureThreadInfo, + GetLastThreadInfo, + GetResourceLimitLimitValue, + GetResourceLimitCurrentValue, + SetThreadActivity, + GetThreadContext3, + WaitForAddress, + SignalToAddress, + Reserved1, + Reserved2, + Reserved3, + Reserved4, + Reserved5, + Reserved6, + DumpInfo, + DumpInfoNew, + Reserved7, + Reserved8, + CreateSession, + AcceptSession, + ReplyAndReceiveLight, + ReplyAndReceive, + ReplyAndReceiveWithUserBuffer, + CreateEvent, + Reserved9, + Reserved10, + MapPhysicalMemoryUnsafe, + UnmapPhysicalMemoryUnsafe, + SetUnsafeLimit, + CreateCodeMemory, + ControlCodeMemory, + SleepSystem, + ReadWriteRegister, + SetProcessActivity, + CreateSharedMemory, + MapTransferMemory, + UnmapTransferMemory, + CreateInterruptEvent, + QueryPhysicalAddress, + QueryIoMapping, + CreateDeviceAddressSpace, + AttachDeviceAddressSpace, + DetachDeviceAddressSpace, + MapDeviceAddressSpaceByForce, + MapDeviceAddressSpaceAligned, + MapDeviceAddressSpace, + UnmapDeviceAddressSpace, + InvalidateProcessDataCache, + StoreProcessDataCache, + FlushProcessDataCache, + DebugActiveProcess, + BreakDebugProcess, + TerminateDebugProcess, + GetDebugEvent, + ContinueDebugEvent, + GetProcessList, + GetThreadList, + GetDebugThreadContext, + SetDebugThreadContext, + QueryDebugProcessMemory, + ReadDebugProcessMemory, + WriteDebugProcessMemory, + SetHardwareBreakPoint, + GetDebugThreadParam, + Reserved11, + GetSystemInfo, + CreatePort, + ManageNamedPort, + ConnectToPort, + SetProcessMemoryPermission, + MapProcessMemory, + UnmapProcessMemory, + QueryProcessMemory, + MapProcessCodeMemory, + UnmapProcessCodeMemory, + CreateProcess, + StartProcess, + TerminateProcess, + GetProcessInfo, + CreateResourceLimit, + SetResourceLimitLimitValue, + CallSecureMonitor } diff --git a/src/LibHac/Nro.cs b/src/LibHac/Nro.cs index d5cca492..0f8ef6bf 100644 --- a/src/LibHac/Nro.cs +++ b/src/LibHac/Nro.cs @@ -2,156 +2,155 @@ using LibHac.Fs; using LibHac.FsSystem; -namespace LibHac +namespace LibHac; + +public class Nro { - public class Nro + public NroStart Start { get; } + public NroHeader Header { get; } + public NroAssetHeader AssetHeader { get; } + private IStorage Storage { get; } + private IStorage AssetStorage { get; } + + public Nro(IStorage storage) { - public NroStart Start { get; } - public NroHeader Header { get; } - public NroAssetHeader AssetHeader { get; } - private IStorage Storage { get; } - private IStorage AssetStorage { get; } + Storage = storage; + var reader = new BinaryReader(Storage.AsStream()); + Start = new NroStart(reader); + Header = new NroHeader(reader); - public Nro(IStorage storage) + if (Header.Magic != "NRO0") + throw new InvalidDataException("NRO0 magic is incorrect!"); + + Storage.GetSize(out long storageSize).ThrowIfFailure(); + + if (Header.Size < storageSize) { - Storage = storage; - var reader = new BinaryReader(Storage.AsStream()); - Start = new NroStart(reader); - Header = new NroHeader(reader); + AssetStorage = Storage.Slice(Header.Size); + var assetReader = new BinaryReader(AssetStorage.AsStream()); - if (Header.Magic != "NRO0") - throw new InvalidDataException("NRO0 magic is incorrect!"); - - Storage.GetSize(out long storageSize).ThrowIfFailure(); - - if (Header.Size < storageSize) - { - AssetStorage = Storage.Slice(Header.Size); - var assetReader = new BinaryReader(AssetStorage.AsStream()); - - AssetHeader = new NroAssetHeader(assetReader); - if (AssetHeader.Magic != "ASET") - throw new InvalidDataException("ASET magic is incorrect!"); - } - } - - public IStorage OpenNroSegment(NroSegmentType type, bool leaveOpen) - { - NroSegment segment = Header.NroSegments[(int)type]; - - if (segment.Size <= 0) return new NullStorage(0); - - return Storage.Slice(segment.FileOffset, segment.Size, leaveOpen); - } - - public IStorage OpenNroAssetSection(NroAssetType type, bool leaveOpen) - { - NroAssetSection header = AssetHeader.NroAssetSections[(int)type]; - - if (header.Size <= 0) return new NullStorage(0); - - return AssetStorage.Slice(header.FileOffset, header.Size, leaveOpen); - } - - } - - public class NroStart - { - public int Mod0Offset { get; } - - public NroStart(BinaryReader reader) - { - reader.BaseStream.Position += 4; - Mod0Offset = reader.ReadInt32(); - reader.BaseStream.Position += 8; + AssetHeader = new NroAssetHeader(assetReader); + if (AssetHeader.Magic != "ASET") + throw new InvalidDataException("ASET magic is incorrect!"); } } - public class NroHeader + public IStorage OpenNroSegment(NroSegmentType type, bool leaveOpen) { - public string Magic { get; } - public uint Version { get; } - public uint Size { get; } - public uint BssSize { get; } - public byte[] BuildId { get; } + NroSegment segment = Header.NroSegments[(int)type]; - public NroSegment[] NroSegments { get; } = new NroSegment[0x3]; + if (segment.Size <= 0) return new NullStorage(0); - public NroHeader(BinaryReader reader) + return Storage.Slice(segment.FileOffset, segment.Size, leaveOpen); + } + + public IStorage OpenNroAssetSection(NroAssetType type, bool leaveOpen) + { + NroAssetSection header = AssetHeader.NroAssetSections[(int)type]; + + if (header.Size <= 0) return new NullStorage(0); + + return AssetStorage.Slice(header.FileOffset, header.Size, leaveOpen); + } + +} + +public class NroStart +{ + public int Mod0Offset { get; } + + public NroStart(BinaryReader reader) + { + reader.BaseStream.Position += 4; + Mod0Offset = reader.ReadInt32(); + reader.BaseStream.Position += 8; + } +} + +public class NroHeader +{ + public string Magic { get; } + public uint Version { get; } + public uint Size { get; } + public uint BssSize { get; } + public byte[] BuildId { get; } + + public NroSegment[] NroSegments { get; } = new NroSegment[0x3]; + + public NroHeader(BinaryReader reader) + { + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + Size = reader.ReadUInt32(); + reader.BaseStream.Position += 4; + + for (int i = 0; i < 3; i++) { - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - Size = reader.ReadUInt32(); - reader.BaseStream.Position += 4; - - for (int i = 0; i < 3; i++) - { - NroSegments[i] = new NroSegment(reader, (NroSegmentType)i); - } - - BssSize = reader.ReadUInt32(); - reader.BaseStream.Position += 4; - BuildId = reader.ReadBytes(0x20); - reader.BaseStream.Position += 0x20; + NroSegments[i] = new NroSegment(reader, (NroSegmentType)i); } + + BssSize = reader.ReadUInt32(); + reader.BaseStream.Position += 4; + BuildId = reader.ReadBytes(0x20); + reader.BaseStream.Position += 0x20; } +} - public enum NroSegmentType +public enum NroSegmentType +{ + Text = 0, + Ro, + Data +} + +public class NroSegment +{ + public NroSegmentType Type { get; } + public uint FileOffset { get; } + public uint Size { get; } + + public NroSegment(BinaryReader reader, NroSegmentType type) { - Text = 0, - Ro, - Data + Type = type; + FileOffset = reader.ReadUInt32(); + Size = reader.ReadUInt32(); } +} - public class NroSegment +public class NroAssetHeader +{ + public string Magic { get; } + public uint Version { get; } + public NroAssetSection[] NroAssetSections { get; } = new NroAssetSection[0x3]; + + public NroAssetHeader(BinaryReader reader) { - public NroSegmentType Type { get; } - public uint FileOffset { get; } - public uint Size { get; } - - public NroSegment(BinaryReader reader, NroSegmentType type) + Magic = reader.ReadAscii(4); + Version = reader.ReadUInt32(); + for (int i = 0; i < 3; i++) { - Type = type; - FileOffset = reader.ReadUInt32(); - Size = reader.ReadUInt32(); - } - } - - public class NroAssetHeader - { - public string Magic { get; } - public uint Version { get; } - public NroAssetSection[] NroAssetSections { get; } = new NroAssetSection[0x3]; - - public NroAssetHeader(BinaryReader reader) - { - Magic = reader.ReadAscii(4); - Version = reader.ReadUInt32(); - for (int i = 0; i < 3; i++) - { - NroAssetSections[i] = new NroAssetSection(reader, (NroAssetType)i); - } - } - } - - public enum NroAssetType - { - Icon = 0, - Nacp, - RomFs - } - - public class NroAssetSection - { - public NroAssetType Type { get; } - public uint FileOffset { get; } - public uint Size { get; } - - public NroAssetSection(BinaryReader reader, NroAssetType type) - { - Type = type; - FileOffset = (uint)reader.ReadUInt64(); - Size = (uint)reader.ReadUInt64(); + NroAssetSections[i] = new NroAssetSection(reader, (NroAssetType)i); } } } + +public enum NroAssetType +{ + Icon = 0, + Nacp, + RomFs +} + +public class NroAssetSection +{ + public NroAssetType Type { get; } + public uint FileOffset { get; } + public uint Size { get; } + + public NroAssetSection(BinaryReader reader, NroAssetType type) + { + Type = type; + FileOffset = (uint)reader.ReadUInt64(); + Size = (uint)reader.ReadUInt64(); + } +} diff --git a/src/LibHac/Ns/ApplicationControlProperty.cs b/src/LibHac/Ns/ApplicationControlProperty.cs index 595dab26..a0bc6eb7 100644 --- a/src/LibHac/Ns/ApplicationControlProperty.cs +++ b/src/LibHac/Ns/ApplicationControlProperty.cs @@ -5,185 +5,184 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Ncm; -namespace LibHac.Ns +namespace LibHac.Ns; + +[StructLayout(LayoutKind.Explicit, Size = 0x4000)] +public struct ApplicationControlProperty { - [StructLayout(LayoutKind.Explicit, Size = 0x4000)] - public struct ApplicationControlProperty - { - private const int TitleCount = 0x10; - private const int IsbnSize = 0x25; - private const int RatingAgeCount = 0x20; - private const int DisplayVersionSize = 0x10; - private const int ApplicationErrorCodeCategorySize = 8; - private const int LocalCommunicationIdCount = 8; - private const int Reserved30F3Size = 3; - private const int BcatPassphraseSize = 0x41; - private const int ReservedForUserAccountSaveDataOperationSize = 6; - private const int PlayLogQueryableApplicationIdCount = 0x10; - private const int ReceivableDataConfigurationCount = 0x10; + private const int TitleCount = 0x10; + private const int IsbnSize = 0x25; + private const int RatingAgeCount = 0x20; + private const int DisplayVersionSize = 0x10; + private const int ApplicationErrorCodeCategorySize = 8; + private const int LocalCommunicationIdCount = 8; + private const int Reserved30F3Size = 3; + private const int BcatPassphraseSize = 0x41; + private const int ReservedForUserAccountSaveDataOperationSize = 6; + private const int PlayLogQueryableApplicationIdCount = 0x10; + private const int ReceivableDataConfigurationCount = 0x10; - [FieldOffset(0x0000)] private byte _titles; - [FieldOffset(0x3000)] private byte _isbn; - [FieldOffset(0x3025)] public StartupUserAccount StartupUserAccount; - [FieldOffset(0x3026)] public byte UserAccountSwitchLock; - [FieldOffset(0x3027)] public byte AddOnContentRegistrationType; - [FieldOffset(0x3028)] public ApplicationAttribute ApplicationAttribute; - [FieldOffset(0x302C)] public uint SupportedLanguages; - [FieldOffset(0x3030)] public ParentalControlFlagValue ParentalControl; - [FieldOffset(0x3034)] public ScreenshotValue Screenshot; - [FieldOffset(0x3035)] public VideoCaptureValue VideoCaptureMode; - [FieldOffset(0x3036)] public byte DataLossConfirmation; - [FieldOffset(0x3037)] public byte PlayLogPolicy; - [FieldOffset(0x3038)] public ulong PresenceGroupId; - [FieldOffset(0x3040)] private sbyte _ratingAge; - [FieldOffset(0x3060)] private byte _displayVersion; - [FieldOffset(0x3070)] public ulong AddOnContentBaseId; - [FieldOffset(0x3078)] public ProgramId SaveDataOwnerId; - [FieldOffset(0x3080)] public long UserAccountSaveDataSize; - [FieldOffset(0x3088)] public long UserAccountSaveDataJournalSize; - [FieldOffset(0x3090)] public long DeviceSaveDataSize; - [FieldOffset(0x3098)] public long DeviceSaveDataJournalSize; - [FieldOffset(0x30A0)] public long BcatDeliveryCacheStorageSize; - [FieldOffset(0x30A8)] private byte _applicationErrorCodeCategory; - [FieldOffset(0x30B0)] private ulong _localCommunicationIds; - [FieldOffset(0x30F0)] public LogoType LogoType; - [FieldOffset(0x30F1)] public LogoHandling LogoHandling; - [FieldOffset(0x30F2)] public byte RuntimeAddOnContentInstall; - [FieldOffset(0x30F3)] public byte _reserved30F3; - [FieldOffset(0x30F6)] public byte CrashReport; - [FieldOffset(0x30F7)] public byte Hdcp; - [FieldOffset(0x30F8)] public ulong SeedForPseudoDeviceId; - [FieldOffset(0x3100)] private byte _bcatPassphrase; - [FieldOffset(0x3141)] public byte StartupUserAccountOption; - [FieldOffset(0x3142)] private byte _reservedForUserAccountSaveDataOperation; - [FieldOffset(0x3148)] public long UserAccountSaveDataMaxSize; - [FieldOffset(0x3150)] public long UserAccountSaveDataMaxJournalSize; - [FieldOffset(0x3158)] public long DeviceSaveDataMaxSize; - [FieldOffset(0x3160)] public long DeviceSaveDataMaxJournalSize; - [FieldOffset(0x3168)] public long TemporaryStorageSize; - [FieldOffset(0x3170)] public long CacheStorageSize; - [FieldOffset(0x3178)] public long CacheStorageJournalSize; - [FieldOffset(0x3180)] public long CacheStorageMaxSizeAndMaxJournalSize; - [FieldOffset(0x3188)] public long CacheStorageMaxIndex; - [FieldOffset(0x3190)] private ulong _playLogQueryableApplicationId; - [FieldOffset(0x3210)] public PlayLogQueryCapability PlayLogQueryCapability; - [FieldOffset(0x3211)] public byte RepairFlag; - [FieldOffset(0x3212)] public byte ProgramIndex; - [FieldOffset(0x3213)] public byte RequiredNetworkServiceLicenseOnLaunchFlag; - [FieldOffset(0x3214)] public uint Reserved3214; - [FieldOffset(0x3218)] public ApplicationControlDataConfiguration SendDataConfiguration; - [FieldOffset(0x3230)] private ApplicationControlDataConfiguration _receivableDataConfigurations; - [FieldOffset(0x32B0)] public ulong JitConfigurationFlag; - [FieldOffset(0x32B8)] public long MemorySize; + [FieldOffset(0x0000)] private byte _titles; + [FieldOffset(0x3000)] private byte _isbn; + [FieldOffset(0x3025)] public StartupUserAccount StartupUserAccount; + [FieldOffset(0x3026)] public byte UserAccountSwitchLock; + [FieldOffset(0x3027)] public byte AddOnContentRegistrationType; + [FieldOffset(0x3028)] public ApplicationAttribute ApplicationAttribute; + [FieldOffset(0x302C)] public uint SupportedLanguages; + [FieldOffset(0x3030)] public ParentalControlFlagValue ParentalControl; + [FieldOffset(0x3034)] public ScreenshotValue Screenshot; + [FieldOffset(0x3035)] public VideoCaptureValue VideoCaptureMode; + [FieldOffset(0x3036)] public byte DataLossConfirmation; + [FieldOffset(0x3037)] public byte PlayLogPolicy; + [FieldOffset(0x3038)] public ulong PresenceGroupId; + [FieldOffset(0x3040)] private sbyte _ratingAge; + [FieldOffset(0x3060)] private byte _displayVersion; + [FieldOffset(0x3070)] public ulong AddOnContentBaseId; + [FieldOffset(0x3078)] public ProgramId SaveDataOwnerId; + [FieldOffset(0x3080)] public long UserAccountSaveDataSize; + [FieldOffset(0x3088)] public long UserAccountSaveDataJournalSize; + [FieldOffset(0x3090)] public long DeviceSaveDataSize; + [FieldOffset(0x3098)] public long DeviceSaveDataJournalSize; + [FieldOffset(0x30A0)] public long BcatDeliveryCacheStorageSize; + [FieldOffset(0x30A8)] private byte _applicationErrorCodeCategory; + [FieldOffset(0x30B0)] private ulong _localCommunicationIds; + [FieldOffset(0x30F0)] public LogoType LogoType; + [FieldOffset(0x30F1)] public LogoHandling LogoHandling; + [FieldOffset(0x30F2)] public byte RuntimeAddOnContentInstall; + [FieldOffset(0x30F3)] public byte _reserved30F3; + [FieldOffset(0x30F6)] public byte CrashReport; + [FieldOffset(0x30F7)] public byte Hdcp; + [FieldOffset(0x30F8)] public ulong SeedForPseudoDeviceId; + [FieldOffset(0x3100)] private byte _bcatPassphrase; + [FieldOffset(0x3141)] public byte StartupUserAccountOption; + [FieldOffset(0x3142)] private byte _reservedForUserAccountSaveDataOperation; + [FieldOffset(0x3148)] public long UserAccountSaveDataMaxSize; + [FieldOffset(0x3150)] public long UserAccountSaveDataMaxJournalSize; + [FieldOffset(0x3158)] public long DeviceSaveDataMaxSize; + [FieldOffset(0x3160)] public long DeviceSaveDataMaxJournalSize; + [FieldOffset(0x3168)] public long TemporaryStorageSize; + [FieldOffset(0x3170)] public long CacheStorageSize; + [FieldOffset(0x3178)] public long CacheStorageJournalSize; + [FieldOffset(0x3180)] public long CacheStorageMaxSizeAndMaxJournalSize; + [FieldOffset(0x3188)] public long CacheStorageMaxIndex; + [FieldOffset(0x3190)] private ulong _playLogQueryableApplicationId; + [FieldOffset(0x3210)] public PlayLogQueryCapability PlayLogQueryCapability; + [FieldOffset(0x3211)] public byte RepairFlag; + [FieldOffset(0x3212)] public byte ProgramIndex; + [FieldOffset(0x3213)] public byte RequiredNetworkServiceLicenseOnLaunchFlag; + [FieldOffset(0x3214)] public uint Reserved3214; + [FieldOffset(0x3218)] public ApplicationControlDataConfiguration SendDataConfiguration; + [FieldOffset(0x3230)] private ApplicationControlDataConfiguration _receivableDataConfigurations; + [FieldOffset(0x32B0)] public ulong JitConfigurationFlag; + [FieldOffset(0x32B8)] public long MemorySize; - [FieldOffset(0x3000), DebuggerBrowsable(DebuggerBrowsableState.Never)] private Padding200 _padding1; - [FieldOffset(0x3200), DebuggerBrowsable(DebuggerBrowsableState.Never)] private Padding100 _padding2; + [FieldOffset(0x3000), DebuggerBrowsable(DebuggerBrowsableState.Never)] private Padding200 _padding1; + [FieldOffset(0x3200), DebuggerBrowsable(DebuggerBrowsableState.Never)] private Padding100 _padding2; - public Span Titles => SpanHelpers.CreateSpan(ref Unsafe.As(ref _titles), TitleCount); - public U8SpanMutable Isbn => new U8SpanMutable(SpanHelpers.CreateSpan(ref _isbn, IsbnSize)); - public Span RatingAge => SpanHelpers.CreateSpan(ref _ratingAge, RatingAgeCount); - public U8SpanMutable DisplayVersion => new U8SpanMutable(SpanHelpers.CreateSpan(ref _displayVersion, DisplayVersionSize)); + public Span Titles => SpanHelpers.CreateSpan(ref Unsafe.As(ref _titles), TitleCount); + public U8SpanMutable Isbn => new U8SpanMutable(SpanHelpers.CreateSpan(ref _isbn, IsbnSize)); + public Span RatingAge => SpanHelpers.CreateSpan(ref _ratingAge, RatingAgeCount); + public U8SpanMutable DisplayVersion => new U8SpanMutable(SpanHelpers.CreateSpan(ref _displayVersion, DisplayVersionSize)); - public U8SpanMutable ApplicationErrorCodeCategory => - new U8SpanMutable(SpanHelpers.CreateSpan(ref _applicationErrorCodeCategory, - ApplicationErrorCodeCategorySize)); + public U8SpanMutable ApplicationErrorCodeCategory => + new U8SpanMutable(SpanHelpers.CreateSpan(ref _applicationErrorCodeCategory, + ApplicationErrorCodeCategorySize)); - public Span LocalCommunicationIds => SpanHelpers.CreateSpan(ref _localCommunicationIds, LocalCommunicationIdCount); - public Span Reserved30F3 => SpanHelpers.CreateSpan(ref _reserved30F3, Reserved30F3Size); - public U8SpanMutable BcatPassphrase => new U8SpanMutable(SpanHelpers.CreateSpan(ref _bcatPassphrase, BcatPassphraseSize)); + public Span LocalCommunicationIds => SpanHelpers.CreateSpan(ref _localCommunicationIds, LocalCommunicationIdCount); + public Span Reserved30F3 => SpanHelpers.CreateSpan(ref _reserved30F3, Reserved30F3Size); + public U8SpanMutable BcatPassphrase => new U8SpanMutable(SpanHelpers.CreateSpan(ref _bcatPassphrase, BcatPassphraseSize)); - public Span ReservedForUserAccountSaveDataOperation => - SpanHelpers.CreateSpan(ref _reservedForUserAccountSaveDataOperation, - ReservedForUserAccountSaveDataOperationSize); + public Span ReservedForUserAccountSaveDataOperation => + SpanHelpers.CreateSpan(ref _reservedForUserAccountSaveDataOperation, + ReservedForUserAccountSaveDataOperationSize); - public Span PlayLogQueryableApplicationId => - SpanHelpers.CreateSpan(ref _playLogQueryableApplicationId, PlayLogQueryableApplicationIdCount); + public Span PlayLogQueryableApplicationId => + SpanHelpers.CreateSpan(ref _playLogQueryableApplicationId, PlayLogQueryableApplicationIdCount); - public Span ReceivableDataConfigurations => - SpanHelpers.CreateSpan(ref _receivableDataConfigurations, ReceivableDataConfigurationCount); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x300)] - public struct ApplicationControlTitle - { - private const int NameLength = 0x200; - private const int PublisherLength = 0x100; - - [FieldOffset(0x000)] private byte _name; - [FieldOffset(0x200)] private byte _publisher; - - [FieldOffset(0x000), DebuggerBrowsable(DebuggerBrowsableState.Never)] - private Padding200 _padding0; - - [FieldOffset(0x200), DebuggerBrowsable(DebuggerBrowsableState.Never)] - private Padding100 _padding200; - - public U8SpanMutable Name => new U8SpanMutable(SpanHelpers.CreateSpan(ref _name, NameLength)); - public U8SpanMutable Publisher => new U8SpanMutable(SpanHelpers.CreateSpan(ref _publisher, PublisherLength)); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x18)] - public struct ApplicationControlDataConfiguration - { - [FieldOffset(0)] public ulong Id; - [FieldOffset(8)] private byte _key; - - [FieldOffset(8), DebuggerBrowsable(DebuggerBrowsableState.Never)] - private Padding10 _keyPadding; - - public Span Key => SpanHelpers.CreateSpan(ref _key, 0x10); - } - - public enum StartupUserAccount : byte - { - None = 0, - Required = 1, - RequiredWithNetworkServiceAccountAvailable = 2 - } - - public enum LogoHandling : byte - { - Auto = 0, - Manual = 1 - } - - public enum LogoType : byte - { - LicensedByNintendo = 0, - DistributedByNintendo = 1, - Nintendo = 2 - } - - [Flags] - public enum ApplicationAttribute - { - None = 0, - Demo = 1 - } - - public enum PlayLogQueryCapability : byte - { - None = 0, - WhiteList = 1, - All = 2 - } - - public enum ParentalControlFlagValue - { - None = 0, - FreeCommunication = 1 - } - - public enum ScreenshotValue : byte - { - Allow = 0, - Deny = 1 - } - - public enum VideoCaptureValue : byte - { - Deny = 0, - Allow = 1, - Automatic = 2 - } + public Span ReceivableDataConfigurations => + SpanHelpers.CreateSpan(ref _receivableDataConfigurations, ReceivableDataConfigurationCount); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x300)] +public struct ApplicationControlTitle +{ + private const int NameLength = 0x200; + private const int PublisherLength = 0x100; + + [FieldOffset(0x000)] private byte _name; + [FieldOffset(0x200)] private byte _publisher; + + [FieldOffset(0x000), DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Padding200 _padding0; + + [FieldOffset(0x200), DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Padding100 _padding200; + + public U8SpanMutable Name => new U8SpanMutable(SpanHelpers.CreateSpan(ref _name, NameLength)); + public U8SpanMutable Publisher => new U8SpanMutable(SpanHelpers.CreateSpan(ref _publisher, PublisherLength)); +} + +[StructLayout(LayoutKind.Explicit, Size = 0x18)] +public struct ApplicationControlDataConfiguration +{ + [FieldOffset(0)] public ulong Id; + [FieldOffset(8)] private byte _key; + + [FieldOffset(8), DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Padding10 _keyPadding; + + public Span Key => SpanHelpers.CreateSpan(ref _key, 0x10); +} + +public enum StartupUserAccount : byte +{ + None = 0, + Required = 1, + RequiredWithNetworkServiceAccountAvailable = 2 +} + +public enum LogoHandling : byte +{ + Auto = 0, + Manual = 1 +} + +public enum LogoType : byte +{ + LicensedByNintendo = 0, + DistributedByNintendo = 1, + Nintendo = 2 +} + +[Flags] +public enum ApplicationAttribute +{ + None = 0, + Demo = 1 +} + +public enum PlayLogQueryCapability : byte +{ + None = 0, + WhiteList = 1, + All = 2 +} + +public enum ParentalControlFlagValue +{ + None = 0, + FreeCommunication = 1 +} + +public enum ScreenshotValue : byte +{ + Allow = 0, + Deny = 1 +} + +public enum VideoCaptureValue : byte +{ + Deny = 0, + Allow = 1, + Automatic = 2 } diff --git a/src/LibHac/Os/ConditionVariable.cs b/src/LibHac/Os/ConditionVariable.cs index 3048e0bc..5dd71b87 100644 --- a/src/LibHac/Os/ConditionVariable.cs +++ b/src/LibHac/Os/ConditionVariable.cs @@ -1,8 +1,7 @@ -namespace LibHac.Os +namespace LibHac.Os; + +public enum ConditionVariableStatus { - public enum ConditionVariableStatus - { - TimedOut = 0, - Success = 1 - } + TimedOut = 0, + Success = 1 } diff --git a/src/LibHac/Os/ILockable.cs b/src/LibHac/Os/ILockable.cs index 6000607b..11b42025 100644 --- a/src/LibHac/Os/ILockable.cs +++ b/src/LibHac/Os/ILockable.cs @@ -1,20 +1,19 @@ -namespace LibHac.Os +namespace LibHac.Os; + +public interface IBasicLockable { - public interface IBasicLockable - { - void Lock(); - void Unlock(); - } - - public interface ILockable : IBasicLockable - { - bool TryLock(); - } - - public interface ISharedMutex : ILockable - { - void LockShared(); - bool TryLockShared(); - void UnlockShared(); - } + void Lock(); + void Unlock(); +} + +public interface ILockable : IBasicLockable +{ + bool TryLock(); +} + +public interface ISharedMutex : ILockable +{ + void LockShared(); + bool TryLockShared(); + void UnlockShared(); } diff --git a/src/LibHac/Os/ITickGenerator.cs b/src/LibHac/Os/ITickGenerator.cs index 050d7c67..975821d0 100644 --- a/src/LibHac/Os/ITickGenerator.cs +++ b/src/LibHac/Os/ITickGenerator.cs @@ -1,24 +1,23 @@ using System.Diagnostics; -namespace LibHac.Os +namespace LibHac.Os; + +public interface ITickGenerator { - public interface ITickGenerator + Tick GetCurrentTick(); +} + +internal class DefaultTickGenerator : ITickGenerator +{ + private readonly long _initialTick; + + public DefaultTickGenerator() { - Tick GetCurrentTick(); + _initialTick = Stopwatch.GetTimestamp(); } - internal class DefaultTickGenerator : ITickGenerator + public Tick GetCurrentTick() { - private readonly long _initialTick; - - public DefaultTickGenerator() - { - _initialTick = Stopwatch.GetTimestamp(); - } - - public Tick GetCurrentTick() - { - return new Tick(Stopwatch.GetTimestamp() - _initialTick); - } + return new Tick(Stopwatch.GetTimestamp() - _initialTick); } } diff --git a/src/LibHac/Os/Impl/InternalConditionVariable-os.net.cs b/src/LibHac/Os/Impl/InternalConditionVariable-os.net.cs index a8c64a41..c02144ac 100644 --- a/src/LibHac/Os/Impl/InternalConditionVariable-os.net.cs +++ b/src/LibHac/Os/Impl/InternalConditionVariable-os.net.cs @@ -1,77 +1,76 @@ using System.Threading; using LibHac.Diag; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal struct InternalConditionVariableImpl { - internal struct InternalConditionVariableImpl + private object _obj; + + public InternalConditionVariableImpl(nint _ = 0) => _obj = new object(); + public void Initialize() => _obj = new object(); + + public void Signal() { - private object _obj; + Assert.SdkRequires(!Monitor.IsEntered(_obj)); - public InternalConditionVariableImpl(nint _ = 0) => _obj = new object(); - public void Initialize() => _obj = new object(); + Monitor.Enter(_obj); + Monitor.Pulse(_obj); + Monitor.Exit(_obj); + } - public void Signal() - { - Assert.SdkRequires(!Monitor.IsEntered(_obj)); + public void Broadcast() + { + Assert.SdkRequires(!Monitor.IsEntered(_obj)); - Monitor.Enter(_obj); - Monitor.Pulse(_obj); - Monitor.Exit(_obj); - } + Monitor.Enter(_obj); + Monitor.PulseAll(_obj); + Monitor.Exit(_obj); + } - public void Broadcast() - { - Assert.SdkRequires(!Monitor.IsEntered(_obj)); + public void Wait(ref InternalCriticalSection cs) + { + Assert.SdkRequires(!Monitor.IsEntered(_obj)); + Abort.DoAbortUnless(cs.IsLockedByCurrentThread()); - Monitor.Enter(_obj); - Monitor.PulseAll(_obj); - Monitor.Exit(_obj); - } + // Monitor.Wait doesn't allow specifying a separate mutex object. Workaround this by manually + // unlocking and locking the separate mutex object. Due to this, the order the waiting threads + // will resume is not guaranteed, and 5 Monitor calls are required instead of 1. + cs.Leave(); + Monitor.Enter(_obj); + Monitor.Wait(_obj); + Monitor.Exit(_obj); + cs.Enter(); + } - public void Wait(ref InternalCriticalSection cs) - { - Assert.SdkRequires(!Monitor.IsEntered(_obj)); - Abort.DoAbortUnless(cs.IsLockedByCurrentThread()); + public ConditionVariableStatus TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper) + { + Assert.SdkRequires(!Monitor.IsEntered(_obj)); + Abort.DoAbortUnless(cs.IsLockedByCurrentThread()); - // Monitor.Wait doesn't allow specifying a separate mutex object. Workaround this by manually - // unlocking and locking the separate mutex object. Due to this, the order the waiting threads - // will resume is not guaranteed, and 5 Monitor calls are required instead of 1. - cs.Leave(); - Monitor.Enter(_obj); - Monitor.Wait(_obj); - Monitor.Exit(_obj); - cs.Enter(); - } + TimeSpan remainingTime = timeoutHelper.GetTimeLeftOnTarget(); - public ConditionVariableStatus TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper) - { - Assert.SdkRequires(!Monitor.IsEntered(_obj)); - Abort.DoAbortUnless(cs.IsLockedByCurrentThread()); + if (remainingTime <= new TimeSpan(0)) + return ConditionVariableStatus.TimedOut; - TimeSpan remainingTime = timeoutHelper.GetTimeLeftOnTarget(); + // Casting to an int won't lose any data because the .NET implementation of + // GetTimeLeftOnTarget always returns a value that fits in an int. + int remainingTimeMs = (int)remainingTime.GetMilliSeconds(); - if (remainingTime <= new TimeSpan(0)) - return ConditionVariableStatus.TimedOut; + cs.Leave(); + Monitor.Enter(_obj); + bool acquiredBeforeTimeout = Monitor.Wait(_obj, remainingTimeMs); + Monitor.Exit(_obj); + cs.Enter(); - // Casting to an int won't lose any data because the .NET implementation of - // GetTimeLeftOnTarget always returns a value that fits in an int. - int remainingTimeMs = (int)remainingTime.GetMilliSeconds(); + // Short code path if we timed out even before waiting on the mutex. + if (!acquiredBeforeTimeout) + return ConditionVariableStatus.TimedOut; - cs.Leave(); - Monitor.Enter(_obj); - bool acquiredBeforeTimeout = Monitor.Wait(_obj, remainingTimeMs); - Monitor.Exit(_obj); - cs.Enter(); + // We may have timed out waiting to Enter the mutex. Check the time left again. + if (timeoutHelper.GetTimeLeftOnTarget() <= new TimeSpan(0)) + return ConditionVariableStatus.TimedOut; - // Short code path if we timed out even before waiting on the mutex. - if (!acquiredBeforeTimeout) - return ConditionVariableStatus.TimedOut; - - // We may have timed out waiting to Enter the mutex. Check the time left again. - if (timeoutHelper.GetTimeLeftOnTarget() <= new TimeSpan(0)) - return ConditionVariableStatus.TimedOut; - - return ConditionVariableStatus.Success; - } + return ConditionVariableStatus.Success; } } diff --git a/src/LibHac/Os/Impl/InternalConditionVariable.cs b/src/LibHac/Os/Impl/InternalConditionVariable.cs index 8415f4eb..594cc681 100644 --- a/src/LibHac/Os/Impl/InternalConditionVariable.cs +++ b/src/LibHac/Os/Impl/InternalConditionVariable.cs @@ -1,20 +1,19 @@ -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal struct InternalConditionVariable { - internal struct InternalConditionVariable + private InternalConditionVariableImpl _impl; + + public InternalConditionVariable(nint _ = 0) { - private InternalConditionVariableImpl _impl; - - public InternalConditionVariable(nint _ = 0) - { - _impl = new InternalConditionVariableImpl(); - } - - public void Initialize() => _impl.Initialize(); - public void Signal() => _impl.Signal(); - public void Broadcast() => _impl.Broadcast(); - public void Wait(ref InternalCriticalSection cs) => _impl.Wait(ref cs); - - public void TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper) => - _impl.TimedWait(ref cs, in timeoutHelper); + _impl = new InternalConditionVariableImpl(); } + + public void Initialize() => _impl.Initialize(); + public void Signal() => _impl.Signal(); + public void Broadcast() => _impl.Broadcast(); + public void Wait(ref InternalCriticalSection cs) => _impl.Wait(ref cs); + + public void TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper) => + _impl.TimedWait(ref cs, in timeoutHelper); } diff --git a/src/LibHac/Os/Impl/InternalCriticalSection-os.net.cs b/src/LibHac/Os/Impl/InternalCriticalSection-os.net.cs index 761fa7df..9d3590b8 100644 --- a/src/LibHac/Os/Impl/InternalCriticalSection-os.net.cs +++ b/src/LibHac/Os/Impl/InternalCriticalSection-os.net.cs @@ -1,36 +1,35 @@ using System.Threading; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal struct InternalCriticalSectionImpl { - internal struct InternalCriticalSectionImpl + private object _obj; + + public void Initialize() { - private object _obj; + _obj = new object(); + } - public void Initialize() - { - _obj = new object(); - } + public void FinalizeObject() { } - public void FinalizeObject() { } + public void Enter() + { + Monitor.Enter(_obj); + } - public void Enter() - { - Monitor.Enter(_obj); - } + public bool TryEnter() + { + return Monitor.TryEnter(_obj); + } - public bool TryEnter() - { - return Monitor.TryEnter(_obj); - } + public void Leave() + { + Monitor.Exit(_obj); + } - public void Leave() - { - Monitor.Exit(_obj); - } - - public bool IsLockedByCurrentThread() - { - return Monitor.IsEntered(_obj); - } + public bool IsLockedByCurrentThread() + { + return Monitor.IsEntered(_obj); } } diff --git a/src/LibHac/Os/Impl/InternalCriticalSection.cs b/src/LibHac/Os/Impl/InternalCriticalSection.cs index 448b8452..9daa0e59 100644 --- a/src/LibHac/Os/Impl/InternalCriticalSection.cs +++ b/src/LibHac/Os/Impl/InternalCriticalSection.cs @@ -1,20 +1,19 @@ -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +public struct InternalCriticalSection : ILockable { - public struct InternalCriticalSection : ILockable - { - private InternalCriticalSectionImpl _impl; + private InternalCriticalSectionImpl _impl; - public void Initialize() => _impl.Initialize(); - public void FinalizeObject() => _impl.FinalizeObject(); + public void Initialize() => _impl.Initialize(); + public void FinalizeObject() => _impl.FinalizeObject(); - public void Enter() => _impl.Enter(); - public bool TryEnter() => _impl.TryEnter(); - public void Leave() => _impl.Leave(); + public void Enter() => _impl.Enter(); + public bool TryEnter() => _impl.TryEnter(); + public void Leave() => _impl.Leave(); - public void Lock() => _impl.Enter(); - public bool TryLock() => _impl.TryEnter(); - public void Unlock() => _impl.Leave(); + public void Lock() => _impl.Enter(); + public bool TryLock() => _impl.TryEnter(); + public void Unlock() => _impl.Leave(); - public bool IsLockedByCurrentThread() => _impl.IsLockedByCurrentThread(); - } + public bool IsLockedByCurrentThread() => _impl.IsLockedByCurrentThread(); } diff --git a/src/LibHac/Os/Impl/OsResourceManager.cs b/src/LibHac/Os/Impl/OsResourceManager.cs index a80a6912..3c707bfb 100644 --- a/src/LibHac/Os/Impl/OsResourceManager.cs +++ b/src/LibHac/Os/Impl/OsResourceManager.cs @@ -1,28 +1,27 @@ using System; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal class OsResourceManager : IDisposable { - internal class OsResourceManager : IDisposable + public TickManager TickManager { get; } + + // Todo: Use configuration object if/when more options are added + public OsResourceManager(ITickGenerator tickGenerator) { - public TickManager TickManager { get; } - - // Todo: Use configuration object if/when more options are added - public OsResourceManager(ITickGenerator tickGenerator) - { - TickManager = new TickManager(tickGenerator); - } - - public void Dispose() - { - TickManager.Dispose(); - } + TickManager = new TickManager(tickGenerator); } - internal static class OsResourceManagerApi + public void Dispose() { - public static OsResourceManager GetOsResourceManager(this OsState os) - { - return os.ResourceManager; - } + TickManager.Dispose(); + } +} + +internal static class OsResourceManagerApi +{ + public static OsResourceManager GetOsResourceManager(this OsState os) + { + return os.ResourceManager; } } diff --git a/src/LibHac/Os/Impl/ReaderWriterLockImpl-os.net.cs b/src/LibHac/Os/Impl/ReaderWriterLockImpl-os.net.cs index 1f39bd2a..176bbbef 100644 --- a/src/LibHac/Os/Impl/ReaderWriterLockImpl-os.net.cs +++ b/src/LibHac/Os/Impl/ReaderWriterLockImpl-os.net.cs @@ -1,194 +1,81 @@ using System; using LibHac.Diag; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal static partial class ReaderWriterLockImpl { - internal static partial class ReaderWriterLockImpl + public static void AcquireReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) { - public static void AcquireReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + ref InternalCriticalSection cs = ref GetLockCount(ref rwLock).Cs; + using ScopedLock lk = ScopedLock.Lock(ref cs); + + // If we already own the lock, no additional action is needed + if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) { - ref InternalCriticalSection cs = ref GetLockCount(ref rwLock).Cs; - using ScopedLock lk = ScopedLock.Lock(ref cs); - - // If we already own the lock, no additional action is needed - if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) - { - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); - } - // Otherwise we might need to block until we can acquire the read lock - else - { - // Wait until there aren't any writers or waiting writers - while (GetWriteLocked(in GetLockCount(ref rwLock)) == 1 || - GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) - { - IncReadLockWaiterCount(ref GetLockCount(ref rwLock)); - rwLock.CvReadLockWaiter.Wait(ref cs); - DecReadLockWaiterCount(ref GetLockCount(ref rwLock)); - } - - Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); - Assert.SdkEqual(rwLock.OwnerThread, 0); - } - - IncReadLockCount(ref GetLockCount(ref rwLock)); + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); } - - public static bool TryAcquireReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + // Otherwise we might need to block until we can acquire the read lock + else { - using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); - - // Acquire the lock if we already have write access - if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) + // Wait until there aren't any writers or waiting writers + while (GetWriteLocked(in GetLockCount(ref rwLock)) == 1 || + GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) { - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); - - IncReadLockCount(ref GetLockCount(ref rwLock)); - return true; + IncReadLockWaiterCount(ref GetLockCount(ref rwLock)); + rwLock.CvReadLockWaiter.Wait(ref cs); + DecReadLockWaiterCount(ref GetLockCount(ref rwLock)); } - // Fail to acquire if there are any writers or waiting writers - if (GetWriteLocked(in GetLockCount(ref rwLock)) == 1 || - GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) - { - return false; - } - - // Otherwise acquire the lock Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); Assert.SdkEqual(rwLock.OwnerThread, 0); + } + + IncReadLockCount(ref GetLockCount(ref rwLock)); + } + + public static bool TryAcquireReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + { + using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); + + // Acquire the lock if we already have write access + if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) + { + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); IncReadLockCount(ref GetLockCount(ref rwLock)); return true; } - public static void ReleaseReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + // Fail to acquire if there are any writers or waiting writers + if (GetWriteLocked(in GetLockCount(ref rwLock)) == 1 || + GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) { - using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); - - Assert.SdkLess(0u, GetReadLockCount(in GetLockCount(ref rwLock))); - DecReadLockCount(ref GetLockCount(ref rwLock)); - - // If we own the lock, check if we need to release ownership and signal any waiting threads - if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) - { - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); - - // Return if we still hold any locks - if (GetWriteLockCount(in rwLock) != 0 || GetReadLockCount(in GetLockCount(ref rwLock)) != 0) - { - return; - } - - // We don't hold any more locks. Release our ownership of the lock - rwLock.OwnerThread = 0; - ClearWriteLocked(ref GetLockCount(ref rwLock)); - - // Signal the next writer if any are waiting - if (GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) - { - rwLock.CvWriteLockWaiter.Signal(); - } - // Otherwise signal any waiting readers - else if (GetReadLockWaiterCount(in GetLockCount(ref rwLock)) != 0) - { - rwLock.CvReadLockWaiter.Broadcast(); - } - } - // Otherwise we need to signal the next writer if we were the only reader - else - { - Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 0u); - Assert.SdkEqual(rwLock.OwnerThread, 0); - - // Signal the next writer if no readers are left - if (GetReadLockCount(in GetLockCount(ref rwLock)) == 0 && - GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) - { - rwLock.CvWriteLockWaiter.Signal(); - } - } + return false; } - public static void AcquireWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + // Otherwise acquire the lock + Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); + Assert.SdkEqual(rwLock.OwnerThread, 0); + + IncReadLockCount(ref GetLockCount(ref rwLock)); + return true; + } + + public static void ReleaseReadLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + { + using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); + + Assert.SdkLess(0u, GetReadLockCount(in GetLockCount(ref rwLock))); + DecReadLockCount(ref GetLockCount(ref rwLock)); + + // If we own the lock, check if we need to release ownership and signal any waiting threads + if (rwLock.OwnerThread == Environment.CurrentManagedThreadId) { - ref InternalCriticalSection cs = ref GetLockCount(ref rwLock).Cs; - using ScopedLock lk = ScopedLock.Lock(ref cs); - - int currentThread = Environment.CurrentManagedThreadId; - - // Increase the write lock count if we already own the lock - if (rwLock.OwnerThread == currentThread) - { - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); - - IncWriteLockCount(ref rwLock); - return; - } - - // Otherwise wait until there aren't any readers or writers - while (GetReadLockCount(in GetLockCount(ref rwLock)) != 0 || - GetWriteLocked(in GetLockCount(ref rwLock)) == 1) - { - IncWriteLockWaiterCount(ref GetLockCount(ref rwLock)); - rwLock.CvWriteLockWaiter.Wait(ref cs); - DecWriteLockWaiterCount(ref GetLockCount(ref rwLock)); - } - - Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); - Assert.SdkEqual(rwLock.OwnerThread, 0); - - // Acquire the lock - IncWriteLockCount(ref rwLock); - SetWriteLocked(ref GetLockCount(ref rwLock)); - rwLock.OwnerThread = currentThread; - } - - public static bool TryAcquireWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) - { - using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); - - int currentThread = Environment.CurrentManagedThreadId; - - // Acquire the lock if we already have write access - if (rwLock.OwnerThread == currentThread) - { - Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); - - IncWriteLockCount(ref rwLock); - return true; - } - - // Fail to acquire if there are any readers or writers - if (GetReadLockCount(in GetLockCount(ref rwLock)) != 0 || - GetWriteLocked(in GetLockCount(ref rwLock)) == 1) - { - return false; - } - - // Otherwise acquire the lock - Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); - Assert.SdkEqual(rwLock.OwnerThread, 0); - - IncWriteLockCount(ref rwLock); - SetWriteLocked(ref GetLockCount(ref rwLock)); - rwLock.OwnerThread = currentThread; - return true; - } - - public static void ReleaseWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) - { - using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); - - Assert.SdkRequiresGreater(GetWriteLockCount(in rwLock), 0u); - Assert.SdkNotEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 0u); - Assert.SdkEqual(rwLock.OwnerThread, Environment.CurrentManagedThreadId); - - DecWriteLockCount(ref rwLock); + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); // Return if we still hold any locks - if (GetWriteLockCount(in rwLock) != 0 || GetReadLockCount(in GetLockCountRo(in rwLock)) != 0) + if (GetWriteLockCount(in rwLock) != 0 || GetReadLockCount(in GetLockCount(ref rwLock)) != 0) { return; } @@ -208,5 +95,117 @@ namespace LibHac.Os.Impl rwLock.CvReadLockWaiter.Broadcast(); } } + // Otherwise we need to signal the next writer if we were the only reader + else + { + Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 0u); + Assert.SdkEqual(rwLock.OwnerThread, 0); + + // Signal the next writer if no readers are left + if (GetReadLockCount(in GetLockCount(ref rwLock)) == 0 && + GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) + { + rwLock.CvWriteLockWaiter.Signal(); + } + } + } + + public static void AcquireWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + { + ref InternalCriticalSection cs = ref GetLockCount(ref rwLock).Cs; + using ScopedLock lk = ScopedLock.Lock(ref cs); + + int currentThread = Environment.CurrentManagedThreadId; + + // Increase the write lock count if we already own the lock + if (rwLock.OwnerThread == currentThread) + { + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); + + IncWriteLockCount(ref rwLock); + return; + } + + // Otherwise wait until there aren't any readers or writers + while (GetReadLockCount(in GetLockCount(ref rwLock)) != 0 || + GetWriteLocked(in GetLockCount(ref rwLock)) == 1) + { + IncWriteLockWaiterCount(ref GetLockCount(ref rwLock)); + rwLock.CvWriteLockWaiter.Wait(ref cs); + DecWriteLockWaiterCount(ref GetLockCount(ref rwLock)); + } + + Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); + Assert.SdkEqual(rwLock.OwnerThread, 0); + + // Acquire the lock + IncWriteLockCount(ref rwLock); + SetWriteLocked(ref GetLockCount(ref rwLock)); + rwLock.OwnerThread = currentThread; + } + + public static bool TryAcquireWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + { + using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); + + int currentThread = Environment.CurrentManagedThreadId; + + // Acquire the lock if we already have write access + if (rwLock.OwnerThread == currentThread) + { + Assert.SdkEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 1u); + + IncWriteLockCount(ref rwLock); + return true; + } + + // Fail to acquire if there are any readers or writers + if (GetReadLockCount(in GetLockCount(ref rwLock)) != 0 || + GetWriteLocked(in GetLockCount(ref rwLock)) == 1) + { + return false; + } + + // Otherwise acquire the lock + Assert.SdkEqual(GetWriteLockCount(in rwLock), 0u); + Assert.SdkEqual(rwLock.OwnerThread, 0); + + IncWriteLockCount(ref rwLock); + SetWriteLocked(ref GetLockCount(ref rwLock)); + rwLock.OwnerThread = currentThread; + return true; + } + + public static void ReleaseWriteLockImpl(this OsStateImpl os, ref ReaderWriterLockType rwLock) + { + using ScopedLock lk = ScopedLock.Lock(ref GetLockCount(ref rwLock).Cs); + + Assert.SdkRequiresGreater(GetWriteLockCount(in rwLock), 0u); + Assert.SdkNotEqual(GetWriteLocked(in GetLockCount(ref rwLock)), 0u); + Assert.SdkEqual(rwLock.OwnerThread, Environment.CurrentManagedThreadId); + + DecWriteLockCount(ref rwLock); + + // Return if we still hold any locks + if (GetWriteLockCount(in rwLock) != 0 || GetReadLockCount(in GetLockCountRo(in rwLock)) != 0) + { + return; + } + + // We don't hold any more locks. Release our ownership of the lock + rwLock.OwnerThread = 0; + ClearWriteLocked(ref GetLockCount(ref rwLock)); + + // Signal the next writer if any are waiting + if (GetWriteLockWaiterCount(in GetLockCount(ref rwLock)) != 0) + { + rwLock.CvWriteLockWaiter.Signal(); + } + // Otherwise signal any waiting readers + else if (GetReadLockWaiterCount(in GetLockCount(ref rwLock)) != 0) + { + rwLock.CvReadLockWaiter.Broadcast(); + } } } diff --git a/src/LibHac/Os/Impl/ReaderWriterLockImpl.cs b/src/LibHac/Os/Impl/ReaderWriterLockImpl.cs index 6d751a3a..86f39654 100644 --- a/src/LibHac/Os/Impl/ReaderWriterLockImpl.cs +++ b/src/LibHac/Os/Impl/ReaderWriterLockImpl.cs @@ -1,128 +1,127 @@ using LibHac.Diag; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal static partial class ReaderWriterLockImpl { - internal static partial class ReaderWriterLockImpl + public static void ClearReadLockCount(ref ReaderWriterLockType.LockCountType lc) { - public static void ClearReadLockCount(ref ReaderWriterLockType.LockCountType lc) - { - lc.Counter.ReadLockCount = 0; - } + lc.Counter.ReadLockCount = 0; + } - public static void ClearWriteLocked(ref ReaderWriterLockType.LockCountType lc) - { - lc.Counter.WriteLocked = 0; - } + public static void ClearWriteLocked(ref ReaderWriterLockType.LockCountType lc) + { + lc.Counter.WriteLocked = 0; + } - public static void ClearReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - lc.Counter.ReadLockWaiterCount = 0; - } + public static void ClearReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + lc.Counter.ReadLockWaiterCount = 0; + } - public static void ClearWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - lc.Counter.WriteLockWaiterCount = 0; - } + public static void ClearWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + lc.Counter.WriteLockWaiterCount = 0; + } - public static void ClearWriteLockCount(ref ReaderWriterLockType rwLock) - { - rwLock.LockCount.WriteLockCount = 0; - } + public static void ClearWriteLockCount(ref ReaderWriterLockType rwLock) + { + rwLock.LockCount.WriteLockCount = 0; + } - public static ref ReaderWriterLockType.LockCountType GetLockCount(ref ReaderWriterLockType rwLock) - { - return ref rwLock.LockCount; - } + public static ref ReaderWriterLockType.LockCountType GetLockCount(ref ReaderWriterLockType rwLock) + { + return ref rwLock.LockCount; + } - public static ref readonly ReaderWriterLockType.LockCountType GetLockCountRo(in ReaderWriterLockType rwLock) - { - return ref rwLock.LockCount; - } + public static ref readonly ReaderWriterLockType.LockCountType GetLockCountRo(in ReaderWriterLockType rwLock) + { + return ref rwLock.LockCount; + } - public static uint GetReadLockCount(in ReaderWriterLockType.LockCountType lc) - { - return lc.Counter.ReadLockCount; - } + public static uint GetReadLockCount(in ReaderWriterLockType.LockCountType lc) + { + return lc.Counter.ReadLockCount; + } - public static uint GetWriteLocked(in ReaderWriterLockType.LockCountType lc) - { - return lc.Counter.WriteLocked; - } + public static uint GetWriteLocked(in ReaderWriterLockType.LockCountType lc) + { + return lc.Counter.WriteLocked; + } - public static uint GetReadLockWaiterCount(in ReaderWriterLockType.LockCountType lc) - { - return lc.Counter.ReadLockWaiterCount; - } + public static uint GetReadLockWaiterCount(in ReaderWriterLockType.LockCountType lc) + { + return lc.Counter.ReadLockWaiterCount; + } - public static uint GetWriteLockWaiterCount(in ReaderWriterLockType.LockCountType lc) - { - return lc.Counter.WriteLockWaiterCount; - } + public static uint GetWriteLockWaiterCount(in ReaderWriterLockType.LockCountType lc) + { + return lc.Counter.WriteLockWaiterCount; + } - public static uint GetWriteLockCount(in ReaderWriterLockType rwLock) - { - return rwLock.LockCount.WriteLockCount; - } + public static uint GetWriteLockCount(in ReaderWriterLockType rwLock) + { + return rwLock.LockCount.WriteLockCount; + } - public static void IncReadLockCount(ref ReaderWriterLockType.LockCountType lc) - { - uint readLockCount = lc.Counter.ReadLockCount; - Assert.SdkLess(readLockCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); - lc.Counter.ReadLockCount = readLockCount + 1; - } + public static void IncReadLockCount(ref ReaderWriterLockType.LockCountType lc) + { + uint readLockCount = lc.Counter.ReadLockCount; + Assert.SdkLess(readLockCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); + lc.Counter.ReadLockCount = readLockCount + 1; + } - public static void DecReadLockCount(ref ReaderWriterLockType.LockCountType lc) - { - uint readLockCount = lc.Counter.ReadLockCount; - Assert.SdkGreater(readLockCount, 0u); - lc.Counter.ReadLockCount = readLockCount - 1; - } + public static void DecReadLockCount(ref ReaderWriterLockType.LockCountType lc) + { + uint readLockCount = lc.Counter.ReadLockCount; + Assert.SdkGreater(readLockCount, 0u); + lc.Counter.ReadLockCount = readLockCount - 1; + } - public static void IncReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - uint readLockWaiterCount = lc.Counter.ReadLockWaiterCount; - Assert.SdkLess(readLockWaiterCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); - lc.Counter.ReadLockWaiterCount = readLockWaiterCount + 1; - } + public static void IncReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + uint readLockWaiterCount = lc.Counter.ReadLockWaiterCount; + Assert.SdkLess(readLockWaiterCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); + lc.Counter.ReadLockWaiterCount = readLockWaiterCount + 1; + } - public static void DecReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - uint readLockWaiterCount = lc.Counter.ReadLockWaiterCount; - Assert.SdkGreater(readLockWaiterCount, 0u); - lc.Counter.ReadLockWaiterCount = readLockWaiterCount - 1; - } + public static void DecReadLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + uint readLockWaiterCount = lc.Counter.ReadLockWaiterCount; + Assert.SdkGreater(readLockWaiterCount, 0u); + lc.Counter.ReadLockWaiterCount = readLockWaiterCount - 1; + } - public static void IncWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - uint writeLockWaiterCount = lc.Counter.WriteLockWaiterCount; - Assert.SdkLess(writeLockWaiterCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); - lc.Counter.WriteLockWaiterCount = writeLockWaiterCount + 1; - } + public static void IncWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + uint writeLockWaiterCount = lc.Counter.WriteLockWaiterCount; + Assert.SdkLess(writeLockWaiterCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); + lc.Counter.WriteLockWaiterCount = writeLockWaiterCount + 1; + } - public static void DecWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) - { - uint writeLockWaiterCount = lc.Counter.WriteLockWaiterCount; - Assert.SdkGreater(writeLockWaiterCount, 0u); - lc.Counter.WriteLockWaiterCount = writeLockWaiterCount - 1; - } + public static void DecWriteLockWaiterCount(ref ReaderWriterLockType.LockCountType lc) + { + uint writeLockWaiterCount = lc.Counter.WriteLockWaiterCount; + Assert.SdkGreater(writeLockWaiterCount, 0u); + lc.Counter.WriteLockWaiterCount = writeLockWaiterCount - 1; + } - public static void IncWriteLockCount(ref ReaderWriterLockType rwLock) - { - uint writeLockCount = rwLock.LockCount.WriteLockCount; - Assert.SdkLess(writeLockCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); - rwLock.LockCount.WriteLockCount = writeLockCount + 1; - } + public static void IncWriteLockCount(ref ReaderWriterLockType rwLock) + { + uint writeLockCount = rwLock.LockCount.WriteLockCount; + Assert.SdkLess(writeLockCount, (uint)ReaderWriterLock.ReaderWriterLockCountMax); + rwLock.LockCount.WriteLockCount = writeLockCount + 1; + } - public static void DecWriteLockCount(ref ReaderWriterLockType rwLock) - { - uint writeLockCount = rwLock.LockCount.WriteLockCount; - Assert.SdkGreater(writeLockCount, 0u); - rwLock.LockCount.WriteLockCount = writeLockCount - 1; - } + public static void DecWriteLockCount(ref ReaderWriterLockType rwLock) + { + uint writeLockCount = rwLock.LockCount.WriteLockCount; + Assert.SdkGreater(writeLockCount, 0u); + rwLock.LockCount.WriteLockCount = writeLockCount - 1; + } - public static void SetWriteLocked(ref ReaderWriterLockType.LockCountType lc) - { - lc.Counter.WriteLocked = 1; - } + public static void SetWriteLocked(ref ReaderWriterLockType.LockCountType lc) + { + lc.Counter.WriteLocked = 1; } } diff --git a/src/LibHac/Os/Impl/TickManager-os.net.cs b/src/LibHac/Os/Impl/TickManager-os.net.cs index a588b12c..cde07bcf 100644 --- a/src/LibHac/Os/Impl/TickManager-os.net.cs +++ b/src/LibHac/Os/Impl/TickManager-os.net.cs @@ -2,58 +2,57 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal struct TickManagerImpl : IDisposable { - internal struct TickManagerImpl : IDisposable + private long _tickFrequency; + private ITickGenerator _tickGenerator; + private TimeSpan _maxTimeSpan; + private long _maxTick; + + public TickManagerImpl(ITickGenerator tickGenerator) { - private long _tickFrequency; - private ITickGenerator _tickGenerator; - private TimeSpan _maxTimeSpan; - private long _maxTick; - - public TickManagerImpl(ITickGenerator tickGenerator) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - TimeBeginPeriod(1); - } - - _tickFrequency = Stopwatch.Frequency; - _tickGenerator = tickGenerator; - - long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); - - if (_tickFrequency <= nanoSecondsPerSecond) - { - _maxTick = _tickFrequency * (long.MaxValue / nanoSecondsPerSecond); - _maxTimeSpan = TimeSpan.FromNanoSeconds(long.MaxValue); - } - else - { - _maxTick = long.MaxValue; - _maxTimeSpan = TimeSpan.FromSeconds(long.MaxValue / _tickFrequency); - } + TimeBeginPeriod(1); } - public void Dispose() + _tickFrequency = Stopwatch.Frequency; + _tickGenerator = tickGenerator; + + long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); + + if (_tickFrequency <= nanoSecondsPerSecond) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - TimeEndPeriod(1); - } + _maxTick = _tickFrequency * (long.MaxValue / nanoSecondsPerSecond); + _maxTimeSpan = TimeSpan.FromNanoSeconds(long.MaxValue); + } + else + { + _maxTick = long.MaxValue; + _maxTimeSpan = TimeSpan.FromSeconds(long.MaxValue / _tickFrequency); } - - public Tick GetTick() => _tickGenerator.GetCurrentTick(); - public Tick GetSystemTickOrdered() => _tickGenerator.GetCurrentTick(); - public long GetTickFrequency() => _tickFrequency; - public long GetMaxTick() => _maxTick; - public long GetMaxTimeSpanNs() => _maxTimeSpan.GetNanoSeconds(); - - - [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] - private static extern uint TimeBeginPeriod(uint milliseconds); - - [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] - private static extern uint TimeEndPeriod(uint milliseconds); } + + public void Dispose() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + TimeEndPeriod(1); + } + } + + public Tick GetTick() => _tickGenerator.GetCurrentTick(); + public Tick GetSystemTickOrdered() => _tickGenerator.GetCurrentTick(); + public long GetTickFrequency() => _tickFrequency; + public long GetMaxTick() => _maxTick; + public long GetMaxTimeSpanNs() => _maxTimeSpan.GetNanoSeconds(); + + + [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static extern uint TimeBeginPeriod(uint milliseconds); + + [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static extern uint TimeEndPeriod(uint milliseconds); } diff --git a/src/LibHac/Os/Impl/TickManager.cs b/src/LibHac/Os/Impl/TickManager.cs index 03909399..4e6d77e2 100644 --- a/src/LibHac/Os/Impl/TickManager.cs +++ b/src/LibHac/Os/Impl/TickManager.cs @@ -1,118 +1,117 @@ using System; using LibHac.Diag; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal static class TickManagerApi { - internal static class TickManagerApi + public static TickManager GetTickManager(this OsState os) => os.GetOsResourceManager().TickManager; + public static Tick GetCurrentTick(this OsState os) => os.GetTickManager().GetTick(); + public static Tick GetCurrentTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered(); +} + +public class TickManager : IDisposable +{ + private static readonly long MaxTickFrequency = long.MaxValue / TimeSpan.FromSeconds(1).GetNanoSeconds() - 1; + + private TickManagerImpl _impl; + + public TickManager(ITickGenerator tickGenerator) => _impl = new TickManagerImpl(tickGenerator); + + ~TickManager() { - public static TickManager GetTickManager(this OsState os) => os.GetOsResourceManager().TickManager; - public static Tick GetCurrentTick(this OsState os) => os.GetTickManager().GetTick(); - public static Tick GetCurrentTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered(); + Dispose(); } - public class TickManager : IDisposable + public void Dispose() { - private static readonly long MaxTickFrequency = long.MaxValue / TimeSpan.FromSeconds(1).GetNanoSeconds() - 1; + _impl.Dispose(); + GC.SuppressFinalize(this); + } - private TickManagerImpl _impl; + public Tick GetTick() => _impl.GetTick(); + public Tick GetSystemTickOrdered() => _impl.GetSystemTickOrdered(); + public long GetTickFrequency() => _impl.GetTickFrequency(); + public long GetMaxTick() => _impl.GetMaxTick(); + public long GetMaxTimeSpanNs() => _impl.GetMaxTimeSpanNs(); - public TickManager(ITickGenerator tickGenerator) => _impl = new TickManagerImpl(tickGenerator); + public TimeSpan ConvertToTimespan(Tick tick) + { + // Get the tick value. + long ticks = tick.GetInt64Value(); - ~TickManager() + // Get the tick frequency. + long tickFreq = GetTickFrequency(); + Assert.SdkLess(tickFreq, MaxTickFrequency); + + // Clamp tick to range. + if (ticks > GetMaxTick()) { - Dispose(); + return TimeSpan.FromNanoSeconds(long.MaxValue); } - - public void Dispose() + else if (ticks < -GetMaxTick()) { - _impl.Dispose(); - GC.SuppressFinalize(this); + return TimeSpan.FromNanoSeconds(long.MinValue); } - - public Tick GetTick() => _impl.GetTick(); - public Tick GetSystemTickOrdered() => _impl.GetSystemTickOrdered(); - public long GetTickFrequency() => _impl.GetTickFrequency(); - public long GetMaxTick() => _impl.GetMaxTick(); - public long GetMaxTimeSpanNs() => _impl.GetMaxTimeSpanNs(); - - public TimeSpan ConvertToTimespan(Tick tick) + else { - // Get the tick value. - long ticks = tick.GetInt64Value(); + // Convert to timespan. + long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); + long seconds = ticks / tickFreq; + long frac = ticks % tickFreq; + TimeSpan ts = TimeSpan.FromSeconds(seconds) + + TimeSpan.FromNanoSeconds(frac * nanoSecondsPerSecond / tickFreq); + + Assert.SdkAssert(!(ticks > 0 && ts < default(TimeSpan) || ticks < 0 && ts > default(TimeSpan))); + + return ts; + } + } + + public Tick ConvertToTick(TimeSpan ts) + { + // Get the TimeSpan in nanoseconds. + long ns = ts.GetNanoSeconds(); + + // Clamp ns to range. + if (ns > GetMaxTimeSpanNs()) + { + return new Tick(long.MaxValue); + } + else if (ns < -GetMaxTimeSpanNs()) + { + return new Tick(long.MinValue); + } + else + { // Get the tick frequency. long tickFreq = GetTickFrequency(); Assert.SdkLess(tickFreq, MaxTickFrequency); - // Clamp tick to range. - if (ticks > GetMaxTick()) + // Convert to tick. + long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); + bool isNegative = ns < 0; + long seconds = ns / nanoSecondsPerSecond; + long frac = ns % nanoSecondsPerSecond; + + // If negative, negate seconds/frac. + if (isNegative) { - return TimeSpan.FromNanoSeconds(long.MaxValue); + seconds = -seconds; + frac = -frac; } - else if (ticks < -GetMaxTick()) + + // Calculate the tick, and invert back to negative if needed. + long ticks = (seconds * tickFreq) + + ((frac * tickFreq + nanoSecondsPerSecond - 1) / nanoSecondsPerSecond); + + if (isNegative) { - return TimeSpan.FromNanoSeconds(long.MinValue); + ticks = -ticks; } - else - { - // Convert to timespan. - long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); - long seconds = ticks / tickFreq; - long frac = ticks % tickFreq; - TimeSpan ts = TimeSpan.FromSeconds(seconds) + - TimeSpan.FromNanoSeconds(frac * nanoSecondsPerSecond / tickFreq); - - Assert.SdkAssert(!(ticks > 0 && ts < default(TimeSpan) || ticks < 0 && ts > default(TimeSpan))); - - return ts; - } - } - - public Tick ConvertToTick(TimeSpan ts) - { - // Get the TimeSpan in nanoseconds. - long ns = ts.GetNanoSeconds(); - - // Clamp ns to range. - if (ns > GetMaxTimeSpanNs()) - { - return new Tick(long.MaxValue); - } - else if (ns < -GetMaxTimeSpanNs()) - { - return new Tick(long.MinValue); - } - else - { - // Get the tick frequency. - long tickFreq = GetTickFrequency(); - Assert.SdkLess(tickFreq, MaxTickFrequency); - - // Convert to tick. - long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds(); - bool isNegative = ns < 0; - long seconds = ns / nanoSecondsPerSecond; - long frac = ns % nanoSecondsPerSecond; - - // If negative, negate seconds/frac. - if (isNegative) - { - seconds = -seconds; - frac = -frac; - } - - // Calculate the tick, and invert back to negative if needed. - long ticks = (seconds * tickFreq) + - ((frac * tickFreq + nanoSecondsPerSecond - 1) / nanoSecondsPerSecond); - - if (isNegative) - { - ticks = -ticks; - } - - return new Tick(ticks); - } + return new Tick(ticks); } } } diff --git a/src/LibHac/Os/Impl/TimeoutHelper-os.net.cs b/src/LibHac/Os/Impl/TimeoutHelper-os.net.cs index d4a2c93a..2eb8e8ad 100644 --- a/src/LibHac/Os/Impl/TimeoutHelper-os.net.cs +++ b/src/LibHac/Os/Impl/TimeoutHelper-os.net.cs @@ -1,45 +1,44 @@ using System; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal static class TimeoutHelperImpl { - internal static class TimeoutHelperImpl + public static void Sleep(OsState os, TimeSpan time) { - public static void Sleep(OsState os, TimeSpan time) + if (time == new TimeSpan(0)) + return; + + TickManager tickManager = os.GetTickManager(); + + // Attempt to avoid overflow by doing the addition unsigned + ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value(); + ulong timeoutTick = (ulong)tickManager.ConvertToTick(time).GetInt64Value(); + ulong absoluteEndTick = currentTick + timeoutTick + 1; + + var endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick)); + + Tick curTick = tickManager.GetTick(); + + // Sleep in a loop until the requested time has past. + while (curTick < endTick) { - if (time == new TimeSpan(0)) - return; + Tick remaining = endTick - curTick; + int sleepTimeMs = (int)ConvertToImplTime(os, remaining).GetMilliSeconds(); - TickManager tickManager = os.GetTickManager(); + System.Threading.Thread.Sleep(sleepTimeMs); - // Attempt to avoid overflow by doing the addition unsigned - ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value(); - ulong timeoutTick = (ulong)tickManager.ConvertToTick(time).GetInt64Value(); - ulong absoluteEndTick = currentTick + timeoutTick + 1; - - var endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick)); - - Tick curTick = tickManager.GetTick(); - - // Sleep in a loop until the requested time has past. - while (curTick < endTick) - { - Tick remaining = endTick - curTick; - int sleepTimeMs = (int)ConvertToImplTime(os, remaining).GetMilliSeconds(); - - System.Threading.Thread.Sleep(sleepTimeMs); - - curTick = tickManager.GetTick(); - } - } - - public static TimeSpan ConvertToImplTime(OsState os, Tick tick) - { - TickManager tickManager = os.GetTickManager(); - TimeSpan ts = tickManager.ConvertToTimespan(tick); - - // .NET allows sleeping up to int.MaxValue milliseconds at a time. - long timeMs = Math.Min(int.MaxValue, ts.GetMilliSeconds()); - return TimeSpan.FromMilliSeconds(timeMs); + curTick = tickManager.GetTick(); } } + + public static TimeSpan ConvertToImplTime(OsState os, Tick tick) + { + TickManager tickManager = os.GetTickManager(); + TimeSpan ts = tickManager.ConvertToTimespan(tick); + + // .NET allows sleeping up to int.MaxValue milliseconds at a time. + long timeMs = Math.Min(int.MaxValue, ts.GetMilliSeconds()); + return TimeSpan.FromMilliSeconds(timeMs); + } } diff --git a/src/LibHac/Os/Impl/TimeoutHelper.cs b/src/LibHac/Os/Impl/TimeoutHelper.cs index 354c69db..7b66b697 100644 --- a/src/LibHac/Os/Impl/TimeoutHelper.cs +++ b/src/LibHac/Os/Impl/TimeoutHelper.cs @@ -1,62 +1,61 @@ using System; -namespace LibHac.Os.Impl +namespace LibHac.Os.Impl; + +internal readonly struct TimeoutHelper { - internal readonly struct TimeoutHelper + private readonly Tick _endTick; + private readonly OsState _os; + + public TimeoutHelper(OsState os, TimeSpan timeout) { - private readonly Tick _endTick; - private readonly OsState _os; + _os = os; - public TimeoutHelper(OsState os, TimeSpan timeout) + if (timeout == new TimeSpan(0)) { - _os = os; - - if (timeout == new TimeSpan(0)) - { - // If timeout is zero, don't do relative tick calculations. - _endTick = new Tick(0); - } - else - { - TickManager tickManager = os.GetTickManager(); - - // Attempt to avoid overflow by doing the addition unsigned - ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value(); - ulong timeoutTick = (ulong)tickManager.ConvertToTick(timeout).GetInt64Value(); - ulong absoluteEndTick = currentTick + timeoutTick + 1; - - _endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick)); - } + // If timeout is zero, don't do relative tick calculations. + _endTick = new Tick(0); } - - public bool TimedOut() + else { - if (_endTick.GetInt64Value() == 0) - return true; + TickManager tickManager = os.GetTickManager(); - Tick currentTick = _os.GetTickManager().GetTick(); + // Attempt to avoid overflow by doing the addition unsigned + ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value(); + ulong timeoutTick = (ulong)tickManager.ConvertToTick(timeout).GetInt64Value(); + ulong absoluteEndTick = currentTick + timeoutTick + 1; - return currentTick >= _endTick; - } - - public TimeSpan GetTimeLeftOnTarget() - { - // If the end tick is zero, we're expired. - if (_endTick.GetInt64Value() == 0) - return new TimeSpan(0); - - // Check if we've expired. - Tick currentTick = _os.GetTickManager().GetTick(); - if (currentTick >= _endTick) - return new TimeSpan(0); - - // Return the converted difference as a timespan. - return TimeoutHelperImpl.ConvertToImplTime(_os, _endTick - currentTick); - } - - public static void Sleep(OsState os, TimeSpan timeout) - { - TimeoutHelperImpl.Sleep(os, timeout); + _endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick)); } } + + public bool TimedOut() + { + if (_endTick.GetInt64Value() == 0) + return true; + + Tick currentTick = _os.GetTickManager().GetTick(); + + return currentTick >= _endTick; + } + + public TimeSpan GetTimeLeftOnTarget() + { + // If the end tick is zero, we're expired. + if (_endTick.GetInt64Value() == 0) + return new TimeSpan(0); + + // Check if we've expired. + Tick currentTick = _os.GetTickManager().GetTick(); + if (currentTick >= _endTick) + return new TimeSpan(0); + + // Return the converted difference as a timespan. + return TimeoutHelperImpl.ConvertToImplTime(_os, _endTick - currentTick); + } + + public static void Sleep(OsState os, TimeSpan timeout) + { + TimeoutHelperImpl.Sleep(os, timeout); + } } diff --git a/src/LibHac/Os/NativeHandle.cs b/src/LibHac/Os/NativeHandle.cs index d6b587a2..e91736ba 100644 --- a/src/LibHac/Os/NativeHandle.cs +++ b/src/LibHac/Os/NativeHandle.cs @@ -1,10 +1,9 @@ -namespace LibHac.Os -{ - public static class NativeHandleApi - { - public static void CloseNativeHandle(this OsState os, object handle) - { +namespace LibHac.Os; + +public static class NativeHandleApi +{ + public static void CloseNativeHandle(this OsState os, object handle) + { - } } } diff --git a/src/LibHac/Os/OsState.cs b/src/LibHac/Os/OsState.cs index d96fac4a..3a1d27a5 100644 --- a/src/LibHac/Os/OsState.cs +++ b/src/LibHac/Os/OsState.cs @@ -1,38 +1,37 @@ using System; using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public class OsState : IDisposable { - public class OsState : IDisposable + public OsStateImpl Impl => new OsStateImpl(this); + internal HorizonClient Hos { get; } + internal OsResourceManager ResourceManager { get; } + + // Todo: Use configuration object if/when more options are added + internal OsState(HorizonClient horizonClient, ITickGenerator tickGenerator) { - public OsStateImpl Impl => new OsStateImpl(this); - internal HorizonClient Hos { get; } - internal OsResourceManager ResourceManager { get; } - - // Todo: Use configuration object if/when more options are added - internal OsState(HorizonClient horizonClient, ITickGenerator tickGenerator) - { - Hos = horizonClient; - ResourceManager = new OsResourceManager(tickGenerator); - } - - public ProcessId GetCurrentProcessId() - { - return Hos.ProcessId; - } - - public void Dispose() - { - ResourceManager.Dispose(); - } + Hos = horizonClient; + ResourceManager = new OsResourceManager(tickGenerator); } - // Functions in the nn::os::detail namespace use this struct. - public readonly struct OsStateImpl + public ProcessId GetCurrentProcessId() { - internal readonly OsState Os; - internal HorizonClient Hos => Os.Hos; + return Hos.ProcessId; + } - internal OsStateImpl(OsState parent) => Os = parent; + public void Dispose() + { + ResourceManager.Dispose(); } } + +// Functions in the nn::os::detail namespace use this struct. +public readonly struct OsStateImpl +{ + internal readonly OsState Os; + internal HorizonClient Hos => Os.Hos; + + internal OsStateImpl(OsState parent) => Os = parent; +} diff --git a/src/LibHac/Os/ProcessId.cs b/src/LibHac/Os/ProcessId.cs index 6f77a0d4..3e083264 100644 --- a/src/LibHac/Os/ProcessId.cs +++ b/src/LibHac/Os/ProcessId.cs @@ -1,11 +1,10 @@ -namespace LibHac.Os +namespace LibHac.Os; + +public readonly struct ProcessId { - public readonly struct ProcessId - { - public static ProcessId InvalidId => new ProcessId(ulong.MaxValue); + public static ProcessId InvalidId => new ProcessId(ulong.MaxValue); - public readonly ulong Value; + public readonly ulong Value; - public ProcessId(ulong value) => Value = value; - } + public ProcessId(ulong value) => Value = value; } diff --git a/src/LibHac/Os/ReaderWriterLock.cs b/src/LibHac/Os/ReaderWriterLock.cs index 4021df8f..3a2eb19b 100644 --- a/src/LibHac/Os/ReaderWriterLock.cs +++ b/src/LibHac/Os/ReaderWriterLock.cs @@ -2,194 +2,193 @@ using LibHac.Diag; using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public static class ReaderWriterLockApi { - public static class ReaderWriterLockApi + public static void InitializeReaderWriterLock(this OsState os, ref ReaderWriterLockType rwLock) { - public static void InitializeReaderWriterLock(this OsState os, ref ReaderWriterLockType rwLock) - { - // Create objects. - ReaderWriterLockImpl.GetLockCount(ref rwLock).Cs.Initialize(); - rwLock.CvReadLockWaiter.Initialize(); - rwLock.CvWriteLockWaiter.Initialize(); + // Create objects. + ReaderWriterLockImpl.GetLockCount(ref rwLock).Cs.Initialize(); + rwLock.CvReadLockWaiter.Initialize(); + rwLock.CvWriteLockWaiter.Initialize(); - // Set member variables. - ReaderWriterLockImpl.ClearReadLockCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); - ReaderWriterLockImpl.ClearWriteLocked(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); - ReaderWriterLockImpl.ClearReadLockWaiterCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); - ReaderWriterLockImpl.ClearWriteLockWaiterCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); - ReaderWriterLockImpl.ClearWriteLockCount(ref rwLock); - rwLock.OwnerThread = 0; + // Set member variables. + ReaderWriterLockImpl.ClearReadLockCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); + ReaderWriterLockImpl.ClearWriteLocked(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); + ReaderWriterLockImpl.ClearReadLockWaiterCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); + ReaderWriterLockImpl.ClearWriteLockWaiterCount(ref ReaderWriterLockImpl.GetLockCount(ref rwLock)); + ReaderWriterLockImpl.ClearWriteLockCount(ref rwLock); + rwLock.OwnerThread = 0; - // Mark initialized. - rwLock.LockState = ReaderWriterLockType.State.Initialized; - } - - public static void FinalizeReaderWriterLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - - // Don't allow finalizing a locked lock. - Assert.SdkRequires(ReaderWriterLockImpl.GetReadLockCount(in ReaderWriterLockImpl.GetLockCount(ref rwLock)) == 0); - Assert.SdkRequires(ReaderWriterLockImpl.GetWriteLocked(in ReaderWriterLockImpl.GetLockCount(ref rwLock)) == 0); - - // Mark not initialized. - rwLock.LockState = ReaderWriterLockType.State.NotInitialized; - - // Destroy objects. - ReaderWriterLockImpl.GetLockCount(ref rwLock).Cs.FinalizeObject(); - } - - public static void AcquireReadLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - os.Impl.AcquireReadLockImpl(ref rwLock); - } - - public static bool TryAcquireReadLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - return os.Impl.TryAcquireReadLockImpl(ref rwLock); - } - - public static void ReleaseReadLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - os.Impl.ReleaseReadLockImpl(ref rwLock); - } - - public static void AcquireWriteLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - os.Impl.AcquireWriteLockImpl(ref rwLock); - } - - public static bool TryAcquireWriteLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - return os.Impl.TryAcquireWriteLockImpl(ref rwLock); - } - - public static void ReleaseWriteLock(this OsState os, ref ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - os.Impl.ReleaseWriteLockImpl(ref rwLock); - } - - public static bool IsReadLockHeld(this OsState os, in ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - return ReaderWriterLockImpl.GetReadLockCount(in ReaderWriterLockImpl.GetLockCountRo(in rwLock)) != 0; - - } - - // Todo: Use Horizon thread APIs - public static bool IsWriteLockHeldByCurrentThread(this OsState os, in ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - return rwLock.OwnerThread == Environment.CurrentManagedThreadId && - ReaderWriterLockImpl.GetWriteLockCount(in rwLock) != 0; - } - - public static bool IsReaderWriterLockOwnerThread(this OsState os, in ReaderWriterLockType rwLock) - { - Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - return rwLock.OwnerThread == Environment.CurrentManagedThreadId; - } + // Mark initialized. + rwLock.LockState = ReaderWriterLockType.State.Initialized; } - public class ReaderWriterLock : ISharedMutex + public static void FinalizeReaderWriterLock(this OsState os, ref ReaderWriterLockType rwLock) { - public const int ReaderWriterLockCountMax = (1 << 15) - 1; - public const int ReadWriteLockWaiterCountMax = (1 << 8) - 1; + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); - private readonly OsState _os; - private ReaderWriterLockType _rwLock; + // Don't allow finalizing a locked lock. + Assert.SdkRequires(ReaderWriterLockImpl.GetReadLockCount(in ReaderWriterLockImpl.GetLockCount(ref rwLock)) == 0); + Assert.SdkRequires(ReaderWriterLockImpl.GetWriteLocked(in ReaderWriterLockImpl.GetLockCount(ref rwLock)) == 0); - public ReaderWriterLock(OsState os) - { - _os = os; - _os.InitializeReaderWriterLock(ref _rwLock); - } + // Mark not initialized. + rwLock.LockState = ReaderWriterLockType.State.NotInitialized; - public void AcquireReadLock() - { - _os.AcquireReadLock(ref _rwLock); - } + // Destroy objects. + ReaderWriterLockImpl.GetLockCount(ref rwLock).Cs.FinalizeObject(); + } - public bool TryAcquireReadLock() - { - return _os.TryAcquireReadLock(ref _rwLock); - } + public static void AcquireReadLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + os.Impl.AcquireReadLockImpl(ref rwLock); + } - public void ReleaseReadLock() - { - _os.ReleaseReadLock(ref _rwLock); - } + public static bool TryAcquireReadLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + return os.Impl.TryAcquireReadLockImpl(ref rwLock); + } - public void AcquireWriteLock() - { - _os.AcquireWriteLock(ref _rwLock); - } + public static void ReleaseReadLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + os.Impl.ReleaseReadLockImpl(ref rwLock); + } - public bool TryAcquireWriteLock() - { - return _os.TryAcquireWriteLock(ref _rwLock); - } + public static void AcquireWriteLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + os.Impl.AcquireWriteLockImpl(ref rwLock); + } - public void ReleaseWriteLock() - { - _os.ReleaseWriteLock(ref _rwLock); - } + public static bool TryAcquireWriteLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + return os.Impl.TryAcquireWriteLockImpl(ref rwLock); + } - public bool IsReadLockHeld() - { - return _os.IsReadLockHeld(in _rwLock); - } + public static void ReleaseWriteLock(this OsState os, ref ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + os.Impl.ReleaseWriteLockImpl(ref rwLock); + } - public bool IsWriteLockHeldByCurrentThread() - { - return _os.IsWriteLockHeldByCurrentThread(in _rwLock); - } + public static bool IsReadLockHeld(this OsState os, in ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + return ReaderWriterLockImpl.GetReadLockCount(in ReaderWriterLockImpl.GetLockCountRo(in rwLock)) != 0; - public bool IsLockOwner() - { - return _os.IsReaderWriterLockOwnerThread(in _rwLock); - } + } - public void LockShared() - { - AcquireReadLock(); - } + // Todo: Use Horizon thread APIs + public static bool IsWriteLockHeldByCurrentThread(this OsState os, in ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + return rwLock.OwnerThread == Environment.CurrentManagedThreadId && + ReaderWriterLockImpl.GetWriteLockCount(in rwLock) != 0; + } - public bool TryLockShared() - { - return TryAcquireReadLock(); - } - - public void UnlockShared() - { - ReleaseReadLock(); - } - - public void Lock() - { - AcquireWriteLock(); - } - - public bool TryLock() - { - return TryAcquireWriteLock(); - } - - public void Unlock() - { - ReleaseWriteLock(); - } - - public ref ReaderWriterLockType GetBase() - { - return ref _rwLock; - } + public static bool IsReaderWriterLockOwnerThread(this OsState os, in ReaderWriterLockType rwLock) + { + Assert.SdkRequires(rwLock.LockState == ReaderWriterLockType.State.Initialized); + return rwLock.OwnerThread == Environment.CurrentManagedThreadId; + } +} + +public class ReaderWriterLock : ISharedMutex +{ + public const int ReaderWriterLockCountMax = (1 << 15) - 1; + public const int ReadWriteLockWaiterCountMax = (1 << 8) - 1; + + private readonly OsState _os; + private ReaderWriterLockType _rwLock; + + public ReaderWriterLock(OsState os) + { + _os = os; + _os.InitializeReaderWriterLock(ref _rwLock); + } + + public void AcquireReadLock() + { + _os.AcquireReadLock(ref _rwLock); + } + + public bool TryAcquireReadLock() + { + return _os.TryAcquireReadLock(ref _rwLock); + } + + public void ReleaseReadLock() + { + _os.ReleaseReadLock(ref _rwLock); + } + + public void AcquireWriteLock() + { + _os.AcquireWriteLock(ref _rwLock); + } + + public bool TryAcquireWriteLock() + { + return _os.TryAcquireWriteLock(ref _rwLock); + } + + public void ReleaseWriteLock() + { + _os.ReleaseWriteLock(ref _rwLock); + } + + public bool IsReadLockHeld() + { + return _os.IsReadLockHeld(in _rwLock); + } + + public bool IsWriteLockHeldByCurrentThread() + { + return _os.IsWriteLockHeldByCurrentThread(in _rwLock); + } + + public bool IsLockOwner() + { + return _os.IsReaderWriterLockOwnerThread(in _rwLock); + } + + public void LockShared() + { + AcquireReadLock(); + } + + public bool TryLockShared() + { + return TryAcquireReadLock(); + } + + public void UnlockShared() + { + ReleaseReadLock(); + } + + public void Lock() + { + AcquireWriteLock(); + } + + public bool TryLock() + { + return TryAcquireWriteLock(); + } + + public void Unlock() + { + ReleaseWriteLock(); + } + + public ref ReaderWriterLockType GetBase() + { + return ref _rwLock; } } diff --git a/src/LibHac/Os/ReaderWriterLockTypes.cs b/src/LibHac/Os/ReaderWriterLockTypes.cs index f3eba2e6..9d1e1f34 100644 --- a/src/LibHac/Os/ReaderWriterLockTypes.cs +++ b/src/LibHac/Os/ReaderWriterLockTypes.cs @@ -1,64 +1,63 @@ using System.Runtime.CompilerServices; using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public struct ReaderWriterLockType { - public struct ReaderWriterLockType + internal LockCountType LockCount; + internal State LockState; + internal int OwnerThread; + internal InternalConditionVariable CvReadLockWaiter; + internal InternalConditionVariable CvWriteLockWaiter; + + public enum State { - internal LockCountType LockCount; - internal State LockState; - internal int OwnerThread; - internal InternalConditionVariable CvReadLockWaiter; - internal InternalConditionVariable CvWriteLockWaiter; + NotInitialized, + Initialized + } - public enum State + public struct LockCountType + { + public InternalCriticalSection Cs; + public ReaderWriterLockCounter Counter; + public uint WriteLockCount; + } + + public struct ReaderWriterLockCounter + { + private uint _counter; + + public uint ReadLockCount { - NotInitialized, - Initialized + readonly get => GetBitsValue(_counter, 0, 15); + set => _counter = SetBitsValue(value, 0, 15); } - public struct LockCountType + public uint WriteLocked { - public InternalCriticalSection Cs; - public ReaderWriterLockCounter Counter; - public uint WriteLockCount; + readonly get => GetBitsValue(_counter, 15, 1); + set => _counter = SetBitsValue(value, 15, 1); } - public struct ReaderWriterLockCounter + public uint ReadLockWaiterCount { - private uint _counter; - - public uint ReadLockCount - { - readonly get => GetBitsValue(_counter, 0, 15); - set => _counter = SetBitsValue(value, 0, 15); - } - - public uint WriteLocked - { - readonly get => GetBitsValue(_counter, 15, 1); - set => _counter = SetBitsValue(value, 15, 1); - } - - public uint ReadLockWaiterCount - { - readonly get => GetBitsValue(_counter, 16, 8); - set => _counter = SetBitsValue(value, 16, 8); - } - - public uint WriteLockWaiterCount - { - readonly get => GetBitsValue(_counter, 24, 8); - set => _counter = SetBitsValue(value, 24, 8); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GetBitsValue(uint value, int bitsOffset, int bitsCount) => - (value >> bitsOffset) & ~(~default(uint) << bitsCount); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint SetBitsValue(uint value, int bitsOffset, int bitsCount) => - (value & ~(~default(uint) << bitsCount)) << bitsOffset; + readonly get => GetBitsValue(_counter, 16, 8); + set => _counter = SetBitsValue(value, 16, 8); } + + public uint WriteLockWaiterCount + { + readonly get => GetBitsValue(_counter, 24, 8); + set => _counter = SetBitsValue(value, 24, 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetBitsValue(uint value, int bitsOffset, int bitsCount) => + (value >> bitsOffset) & ~(~default(uint) << bitsCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint SetBitsValue(uint value, int bitsOffset, int bitsCount) => + (value & ~(~default(uint) << bitsCount)) << bitsOffset; } } diff --git a/src/LibHac/Os/ScopedLock.cs b/src/LibHac/Os/ScopedLock.cs index 09cff965..4e971b8e 100644 --- a/src/LibHac/Os/ScopedLock.cs +++ b/src/LibHac/Os/ScopedLock.cs @@ -1,32 +1,31 @@ using System.Runtime.CompilerServices; using LibHac.Common; -namespace LibHac.Os +namespace LibHac.Os; + +public static class ScopedLock { - public static class ScopedLock + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScopedLock Lock(ref TMutex lockable) where TMutex : IBasicLockable { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ScopedLock Lock(ref TMutex lockable) where TMutex : IBasicLockable - { - return new ScopedLock(ref lockable); - } - } - - public ref struct ScopedLock where TMutex : IBasicLockable - { - private Ref _mutex; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ScopedLock(ref TMutex mutex) - { - _mutex = new Ref(ref mutex); - mutex.Lock(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - _mutex.Value.Unlock(); - } + return new ScopedLock(ref lockable); + } +} + +public ref struct ScopedLock where TMutex : IBasicLockable +{ + private Ref _mutex; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ScopedLock(ref TMutex mutex) + { + _mutex = new Ref(ref mutex); + mutex.Lock(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + _mutex.Value.Unlock(); } } diff --git a/src/LibHac/Os/SdkMutex.cs b/src/LibHac/Os/SdkMutex.cs index 2110de65..310543f5 100644 --- a/src/LibHac/Os/SdkMutex.cs +++ b/src/LibHac/Os/SdkMutex.cs @@ -1,148 +1,147 @@ using LibHac.Diag; using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public class SdkMutex : ILockable { - public class SdkMutex : ILockable + private SdkMutexType _mutex; + + public void Initialize() { - private SdkMutexType _mutex; - - public void Initialize() - { - _mutex.Initialize(); - } - - public void Lock() - { - _mutex.Lock(); - } - - public bool TryLock() - { - return _mutex.TryLock(); - } - - public void Unlock() - { - _mutex.Unlock(); - } - - public bool IsLockedByCurrentThread() - { - return _mutex.IsLockedByCurrentThread(); - } + _mutex.Initialize(); } - public struct SdkMutexType : ILockable + public void Lock() { - private InternalCriticalSection _cs; + _mutex.Lock(); + } - public void Initialize() - { - _cs.Initialize(); - } + public bool TryLock() + { + return _mutex.TryLock(); + } - public void Lock() + public void Unlock() + { + _mutex.Unlock(); + } + + public bool IsLockedByCurrentThread() + { + return _mutex.IsLockedByCurrentThread(); + } +} + +public struct SdkMutexType : ILockable +{ + private InternalCriticalSection _cs; + + public void Initialize() + { + _cs.Initialize(); + } + + public void Lock() + { + Abort.DoAbortUnless(!IsLockedByCurrentThread()); + _cs.Enter(); + } + + public bool TryLock() + { + Abort.DoAbortUnless(!IsLockedByCurrentThread()); + return _cs.TryEnter(); + } + + public void Unlock() + { + Abort.DoAbortUnless(IsLockedByCurrentThread()); + _cs.Leave(); + } + + public bool IsLockedByCurrentThread() + { + return _cs.IsLockedByCurrentThread(); + } +} + +public class SdkRecursiveMutex : IBasicLockable +{ + private SdkRecursiveMutexType _impl; + + public SdkRecursiveMutex() + { + _impl.Initialize(); + } + + public void Lock() + { + _impl.Lock(); + } + + public void Unlock() + { + _impl.Unlock(); + } + + public bool IsLockedByCurrentThread() + { + return _impl.IsLockedByCurrentThread(); + } +} + +public struct SdkRecursiveMutexType : ILockable +{ + private InternalCriticalSection _cs; + private int _recursiveCount; + + public void Initialize() + { + _cs.Initialize(); + _recursiveCount = 0; + } + + public void Lock() + { + if (!IsLockedByCurrentThread()) { - Abort.DoAbortUnless(!IsLockedByCurrentThread()); _cs.Enter(); } - public bool TryLock() + _recursiveCount++; + Abort.DoAbortUnless(_recursiveCount != 0); + } + + public bool TryLock() + { + if (!IsLockedByCurrentThread()) { - Abort.DoAbortUnless(!IsLockedByCurrentThread()); - return _cs.TryEnter(); + if (!_cs.TryEnter()) + { + return false; + } } - public void Unlock() + _recursiveCount++; + Abort.DoAbortUnless(_recursiveCount != 0); + + return true; + } + + public void Unlock() + { + Abort.DoAbortUnless(IsLockedByCurrentThread()); + + _recursiveCount--; + if (_recursiveCount == 0) { - Abort.DoAbortUnless(IsLockedByCurrentThread()); _cs.Leave(); } - - public bool IsLockedByCurrentThread() - { - return _cs.IsLockedByCurrentThread(); - } } - public class SdkRecursiveMutex : IBasicLockable + public bool IsLockedByCurrentThread() { - private SdkRecursiveMutexType _impl; - - public SdkRecursiveMutex() - { - _impl.Initialize(); - } - - public void Lock() - { - _impl.Lock(); - } - - public void Unlock() - { - _impl.Unlock(); - } - - public bool IsLockedByCurrentThread() - { - return _impl.IsLockedByCurrentThread(); - } - } - - public struct SdkRecursiveMutexType : ILockable - { - private InternalCriticalSection _cs; - private int _recursiveCount; - - public void Initialize() - { - _cs.Initialize(); - _recursiveCount = 0; - } - - public void Lock() - { - if (!IsLockedByCurrentThread()) - { - _cs.Enter(); - } - - _recursiveCount++; - Abort.DoAbortUnless(_recursiveCount != 0); - } - - public bool TryLock() - { - if (!IsLockedByCurrentThread()) - { - if (!_cs.TryEnter()) - { - return false; - } - } - - _recursiveCount++; - Abort.DoAbortUnless(_recursiveCount != 0); - - return true; - } - - public void Unlock() - { - Abort.DoAbortUnless(IsLockedByCurrentThread()); - - _recursiveCount--; - if (_recursiveCount == 0) - { - _cs.Leave(); - } - } - - public bool IsLockedByCurrentThread() - { - return _cs.IsLockedByCurrentThread(); - } + return _cs.IsLockedByCurrentThread(); } } diff --git a/src/LibHac/Os/Thread.cs b/src/LibHac/Os/Thread.cs index 6bbc3bc5..da03be85 100644 --- a/src/LibHac/Os/Thread.cs +++ b/src/LibHac/Os/Thread.cs @@ -1,12 +1,11 @@ using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public static class Thread { - public static class Thread + public static void SleepThread(this OsState os, TimeSpan time) { - public static void SleepThread(this OsState os, TimeSpan time) - { - TimeoutHelperImpl.Sleep(os, time); - } + TimeoutHelperImpl.Sleep(os, time); } } diff --git a/src/LibHac/Os/Tick.cs b/src/LibHac/Os/Tick.cs index 1b93ee01..cbc26344 100644 --- a/src/LibHac/Os/Tick.cs +++ b/src/LibHac/Os/Tick.cs @@ -1,39 +1,38 @@ using System; using LibHac.Os.Impl; -namespace LibHac.Os +namespace LibHac.Os; + +public readonly struct Tick : IEquatable { - public readonly struct Tick : IEquatable - { - private readonly long _ticks; + private readonly long _ticks; - public Tick(long ticks) => _ticks = ticks; + public Tick(long ticks) => _ticks = ticks; - public long GetInt64Value() => _ticks; - public TimeSpan ToTimeSpan(OsState os) => os.ConvertToTimeSpan(this); + public long GetInt64Value() => _ticks; + public TimeSpan ToTimeSpan(OsState os) => os.ConvertToTimeSpan(this); - public static Tick operator +(Tick left, Tick right) => new(left._ticks + right._ticks); - public static Tick operator -(Tick left, Tick right) => new(left._ticks - right._ticks); + public static Tick operator +(Tick left, Tick right) => new(left._ticks + right._ticks); + public static Tick operator -(Tick left, Tick right) => new(left._ticks - right._ticks); - public static bool operator ==(Tick left, Tick right) => left._ticks == right._ticks; - public static bool operator !=(Tick left, Tick right) => left._ticks != right._ticks; - public static bool operator <(Tick left, Tick right) => left._ticks < right._ticks; - public static bool operator >(Tick left, Tick right) => left._ticks > right._ticks; - public static bool operator <=(Tick left, Tick right) => left._ticks <= right._ticks; - public static bool operator >=(Tick left, Tick right) => left._ticks >= right._ticks; + public static bool operator ==(Tick left, Tick right) => left._ticks == right._ticks; + public static bool operator !=(Tick left, Tick right) => left._ticks != right._ticks; + public static bool operator <(Tick left, Tick right) => left._ticks < right._ticks; + public static bool operator >(Tick left, Tick right) => left._ticks > right._ticks; + public static bool operator <=(Tick left, Tick right) => left._ticks <= right._ticks; + public static bool operator >=(Tick left, Tick right) => left._ticks >= right._ticks; - public override bool Equals(object obj) => obj is Tick other && Equals(other); - public bool Equals(Tick other) => _ticks == other._ticks; - public override int GetHashCode() => _ticks.GetHashCode(); - public override string ToString() => _ticks.ToString(); - } - - public static class TickApi - { - public static Tick GetSystemTick(this OsState os) => os.GetTickManager().GetTick(); - public static Tick GetSystemTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered(); - public static long GetSystemTickFrequency(this OsState os) => os.GetTickManager().GetTickFrequency(); - public static TimeSpan ConvertToTimeSpan(this OsState os, Tick tick) => os.GetTickManager().ConvertToTimespan(tick); - public static Tick ConvertToTick(this OsState os, TimeSpan ts) => os.GetTickManager().ConvertToTick(ts); - } + public override bool Equals(object obj) => obj is Tick other && Equals(other); + public bool Equals(Tick other) => _ticks == other._ticks; + public override int GetHashCode() => _ticks.GetHashCode(); + public override string ToString() => _ticks.ToString(); +} + +public static class TickApi +{ + public static Tick GetSystemTick(this OsState os) => os.GetTickManager().GetTick(); + public static Tick GetSystemTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered(); + public static long GetSystemTickFrequency(this OsState os) => os.GetTickManager().GetTickFrequency(); + public static TimeSpan ConvertToTimeSpan(this OsState os, Tick tick) => os.GetTickManager().ConvertToTimespan(tick); + public static Tick ConvertToTick(this OsState os, TimeSpan ts) => os.GetTickManager().ConvertToTick(ts); } diff --git a/src/LibHac/Os/UniqueLock.cs b/src/LibHac/Os/UniqueLock.cs index 66792097..e63b49dd 100644 --- a/src/LibHac/Os/UniqueLock.cs +++ b/src/LibHac/Os/UniqueLock.cs @@ -4,198 +4,197 @@ using System.Threading; using LibHac.Common; using static InlineIL.IL.Emit; -namespace LibHac.Os +namespace LibHac.Os; + +/// +/// Specifies that a constructed should not be automatically locked upon construction.
+/// Used only to differentiate between constructor signatures. +///
+public struct DeferLock { } + +public static class UniqueLock { - /// - /// Specifies that a constructed should not be automatically locked upon construction.
- /// Used only to differentiate between constructor signatures. - ///
- public struct DeferLock { } - - public static class UniqueLock + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UniqueLockRef Lock(ref TMutex lockable) where TMutex : struct, ILockable { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UniqueLockRef Lock(ref TMutex lockable) where TMutex : struct, ILockable - { - return new UniqueLockRef(ref lockable); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UniqueLock Lock(TMutex lockable) where TMutex : class, ILockable - { - return new UniqueLock(lockable); - } - - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref UniqueLockRef Ref(this in UniqueLockRef value) where T : struct, ILockable - { - Ldarg(nameof(value)); - Ret(); - throw InlineIL.IL.Unreachable(); - } - - // ReSharper disable once EntityNameCapturedOnly.Global - public static ref UniqueLock Ref(this in UniqueLock value) where T : class, ILockable - { - Ldarg(nameof(value)); - Ret(); - throw InlineIL.IL.Unreachable(); - } + return new UniqueLockRef(ref lockable); } - public ref struct UniqueLockRef where TMutex : struct, ILockable + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UniqueLock Lock(TMutex lockable) where TMutex : class, ILockable { - private Ref _mutex; - private bool _ownsLock; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLockRef(ref TMutex mutex) - { - _mutex = new Ref(ref mutex); - mutex.Lock(); - _ownsLock = true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLockRef(ref TMutex mutex, DeferLock tag) - { - _mutex = new Ref(ref mutex); - _ownsLock = false; - } - - public UniqueLockRef(ref UniqueLockRef other) - { - this = other; - other = default; - } - - public void Set(ref UniqueLockRef other) - { - if (_ownsLock) - _mutex.Value.Unlock(); - - this = other; - other = default; - } - - public void Lock() - { - if (_mutex.IsNull) - throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); - - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); - - _mutex.Value.Lock(); - _ownsLock = true; - } - - public bool TryLock() - { - if (_mutex.IsNull) - throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); - - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); - - _ownsLock = _mutex.Value.TryLock(); - return _ownsLock; - } - - public void Unlock() - { - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); - - _mutex.Value.Unlock(); - _ownsLock = false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (_ownsLock) - _mutex.Value.Unlock(); - - this = default; - } + return new UniqueLock(lockable); } - public struct UniqueLock : IDisposable where TMutex : class, ILockable + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref UniqueLockRef Ref(this in UniqueLockRef value) where T : struct, ILockable { - private TMutex _mutex; - private bool _ownsLock; + Ldarg(nameof(value)); + Ret(); + throw InlineIL.IL.Unreachable(); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLock(TMutex mutex) - { - _mutex = mutex; - mutex.Lock(); - _ownsLock = true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UniqueLock(TMutex mutex, DeferLock tag) - { - _mutex = mutex; - _ownsLock = false; - } - - public UniqueLock(ref UniqueLock other) - { - this = other; - other = default; - } - - public void Set(ref UniqueLock other) - { - if (_ownsLock) - _mutex.Unlock(); - - this = other; - other = default; - } - - public void Lock() - { - if (_mutex is null) - throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); - - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); - - _mutex.Lock(); - _ownsLock = true; - } - - public bool TryLock() - { - if (_mutex is null) - throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); - - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); - - _ownsLock = _mutex.TryLock(); - return _ownsLock; - } - - public void Unlock() - { - if (_ownsLock) - throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); - - _mutex.Unlock(); - _ownsLock = false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (_ownsLock) - _mutex.Unlock(); - - this = default; - } + // ReSharper disable once EntityNameCapturedOnly.Global + public static ref UniqueLock Ref(this in UniqueLock value) where T : class, ILockable + { + Ldarg(nameof(value)); + Ret(); + throw InlineIL.IL.Unreachable(); + } +} + +public ref struct UniqueLockRef where TMutex : struct, ILockable +{ + private Ref _mutex; + private bool _ownsLock; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLockRef(ref TMutex mutex) + { + _mutex = new Ref(ref mutex); + mutex.Lock(); + _ownsLock = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLockRef(ref TMutex mutex, DeferLock tag) + { + _mutex = new Ref(ref mutex); + _ownsLock = false; + } + + public UniqueLockRef(ref UniqueLockRef other) + { + this = other; + other = default; + } + + public void Set(ref UniqueLockRef other) + { + if (_ownsLock) + _mutex.Value.Unlock(); + + this = other; + other = default; + } + + public void Lock() + { + if (_mutex.IsNull) + throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); + + _mutex.Value.Lock(); + _ownsLock = true; + } + + public bool TryLock() + { + if (_mutex.IsNull) + throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); + + _ownsLock = _mutex.Value.TryLock(); + return _ownsLock; + } + + public void Unlock() + { + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); + + _mutex.Value.Unlock(); + _ownsLock = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_ownsLock) + _mutex.Value.Unlock(); + + this = default; + } +} + +public struct UniqueLock : IDisposable where TMutex : class, ILockable +{ + private TMutex _mutex; + private bool _ownsLock; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLock(TMutex mutex) + { + _mutex = mutex; + mutex.Lock(); + _ownsLock = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UniqueLock(TMutex mutex, DeferLock tag) + { + _mutex = mutex; + _ownsLock = false; + } + + public UniqueLock(ref UniqueLock other) + { + this = other; + other = default; + } + + public void Set(ref UniqueLock other) + { + if (_ownsLock) + _mutex.Unlock(); + + this = other; + other = default; + } + + public void Lock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.Lock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Lock: Already locked"); + + _mutex.Lock(); + _ownsLock = true; + } + + public bool TryLock() + { + if (_mutex is null) + throw new SynchronizationLockException("UniqueLock.TryLock: References null mutex"); + + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.TryLock: Already locked"); + + _ownsLock = _mutex.TryLock(); + return _ownsLock; + } + + public void Unlock() + { + if (_ownsLock) + throw new SynchronizationLockException("UniqueLock.Unlock: Not locked"); + + _mutex.Unlock(); + _ownsLock = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_ownsLock) + _mutex.Unlock(); + + this = default; } } diff --git a/src/LibHac/ProgressBar.cs b/src/LibHac/ProgressBar.cs index 0d239d7f..7648f3a4 100644 --- a/src/LibHac/ProgressBar.cs +++ b/src/LibHac/ProgressBar.cs @@ -5,170 +5,169 @@ using System.Diagnostics; using System.Text; using System.Threading; -namespace LibHac +namespace LibHac; + +public class ProgressBar : IDisposable, IProgressReport { - public class ProgressBar : IDisposable, IProgressReport + private const int BlockCount = 20; + private long _progress; + private long _total; + private readonly Timer _timer; + + private bool _isMeasuringSpeed; + private Stopwatch _watch; + private long _timedBytes; + + private readonly System.TimeSpan _animationInterval = System.TimeSpan.FromSeconds(1.0 / 30); + private const string Animation = @"|/-\"; + + private string _currentText = string.Empty; + private bool _disposed; + private int _animationIndex; + + private StringBuilder LogText { get; } = new StringBuilder(); + + public ProgressBar() { - private const int BlockCount = 20; - private long _progress; - private long _total; - private readonly Timer _timer; + var timerCallBack = new TimerCallback(TimerHandler); + _timer = new Timer(timerCallBack, 0, 0, 0); + } - private bool _isMeasuringSpeed; - private Stopwatch _watch; - private long _timedBytes; + public void Report(long value) + { + Interlocked.Exchange(ref _progress, value); + } - private readonly System.TimeSpan _animationInterval = System.TimeSpan.FromSeconds(1.0 / 30); - private const string Animation = @"|/-\"; + public void ReportAdd(long value) + { + Interlocked.Add(ref _progress, value); + if (_isMeasuringSpeed) Interlocked.Add(ref _timedBytes, value); + } - private string _currentText = string.Empty; - private bool _disposed; - private int _animationIndex; - - private StringBuilder LogText { get; } = new StringBuilder(); - - public ProgressBar() + public void LogMessage(string message) + { + lock (_timer) { - var timerCallBack = new TimerCallback(TimerHandler); - _timer = new Timer(timerCallBack, 0, 0, 0); + LogText.AppendLine(message); } + } - public void Report(long value) - { - Interlocked.Exchange(ref _progress, value); - } + public void SetTotal(long value) + { + Interlocked.Exchange(ref _total, value); + Report(0); + } - public void ReportAdd(long value) - { - Interlocked.Add(ref _progress, value); - if (_isMeasuringSpeed) Interlocked.Add(ref _timedBytes, value); - } + public void StartNewStopWatch() + { + _isMeasuringSpeed = true; + _timedBytes = 0; + _watch = Stopwatch.StartNew(); + } - public void LogMessage(string message) - { - lock (_timer) - { - LogText.AppendLine(message); - } - } + public void PauseStopWatch() + { + _isMeasuringSpeed = false; + _watch.Stop(); + } - public void SetTotal(long value) - { - Interlocked.Exchange(ref _total, value); - Report(0); - } + public void ResumeStopWatch() + { + _isMeasuringSpeed = true; - public void StartNewStopWatch() + if (_watch == null) { - _isMeasuringSpeed = true; - _timedBytes = 0; _watch = Stopwatch.StartNew(); } - - public void PauseStopWatch() + else { - _isMeasuringSpeed = false; - _watch.Stop(); + _watch.Start(); + } + } + + public string GetRateString() + { + return Utilities.GetBytesReadable((long)(_timedBytes / _watch.Elapsed.TotalSeconds)) + "/s"; + } + + private void TimerHandler(object state) + { + lock (_timer) + { + if (_disposed) return; + + string text = string.Empty; + string speed = string.Empty; + + if (_isMeasuringSpeed) + { + speed = $" {GetRateString()}"; + } + + if (_total > 0) + { + double progress = _total == 0 ? 0 : (double)_progress / _total; + int progressBlockCount = (int)Math.Min(progress * BlockCount, BlockCount); + text = $"[{new string('#', progressBlockCount)}{new string('-', BlockCount - progressBlockCount)}] {_progress}/{_total} {progress:P1} {Animation[_animationIndex++ % Animation.Length]}{speed}"; + } + UpdateText(text); + + ResetTimer(); + } + } + + private void UpdateText(string text) + { + var outputBuilder = new StringBuilder(); + + if (LogText.Length > 0) + { + // Erase current text + outputBuilder.Append("\r"); + outputBuilder.Append(' ', _currentText.Length); + outputBuilder.Append("\r"); + outputBuilder.Append(LogText); + _currentText = string.Empty; + LogText.Clear(); } - public void ResumeStopWatch() + // Get length of common portion + int commonPrefixLength = 0; + int commonLength = Math.Min(_currentText.Length, text.Length); + while (commonPrefixLength < commonLength && text[commonPrefixLength] == _currentText[commonPrefixLength]) { - _isMeasuringSpeed = true; - - if (_watch == null) - { - _watch = Stopwatch.StartNew(); - } - else - { - _watch.Start(); - } + commonPrefixLength++; } - public string GetRateString() + // Backtrack to the first differing character + outputBuilder.Append('\b', _currentText.Length - commonPrefixLength); + + // Output new suffix + outputBuilder.Append(text.Substring(commonPrefixLength)); + + // If the new text is shorter than the old one: delete overlapping characters + int overlapCount = _currentText.Length - text.Length; + if (overlapCount > 0) { - return Utilities.GetBytesReadable((long)(_timedBytes / _watch.Elapsed.TotalSeconds)) + "/s"; + outputBuilder.Append(' ', overlapCount); + outputBuilder.Append('\b', overlapCount); } - private void TimerHandler(object state) + Console.Write(outputBuilder); + _currentText = text; + } + + private void ResetTimer() + { + _timer.Change(_animationInterval, System.TimeSpan.FromMilliseconds(-1)); + } + + public void Dispose() + { + lock (_timer) { - lock (_timer) - { - if (_disposed) return; - - string text = string.Empty; - string speed = string.Empty; - - if (_isMeasuringSpeed) - { - speed = $" {GetRateString()}"; - } - - if (_total > 0) - { - double progress = _total == 0 ? 0 : (double)_progress / _total; - int progressBlockCount = (int)Math.Min(progress * BlockCount, BlockCount); - text = $"[{new string('#', progressBlockCount)}{new string('-', BlockCount - progressBlockCount)}] {_progress}/{_total} {progress:P1} {Animation[_animationIndex++ % Animation.Length]}{speed}"; - } - UpdateText(text); - - ResetTimer(); - } - } - - private void UpdateText(string text) - { - var outputBuilder = new StringBuilder(); - - if (LogText.Length > 0) - { - // Erase current text - outputBuilder.Append("\r"); - outputBuilder.Append(' ', _currentText.Length); - outputBuilder.Append("\r"); - outputBuilder.Append(LogText); - _currentText = string.Empty; - LogText.Clear(); - } - - // Get length of common portion - int commonPrefixLength = 0; - int commonLength = Math.Min(_currentText.Length, text.Length); - while (commonPrefixLength < commonLength && text[commonPrefixLength] == _currentText[commonPrefixLength]) - { - commonPrefixLength++; - } - - // Backtrack to the first differing character - outputBuilder.Append('\b', _currentText.Length - commonPrefixLength); - - // Output new suffix - outputBuilder.Append(text.Substring(commonPrefixLength)); - - // If the new text is shorter than the old one: delete overlapping characters - int overlapCount = _currentText.Length - text.Length; - if (overlapCount > 0) - { - outputBuilder.Append(' ', overlapCount); - outputBuilder.Append('\b', overlapCount); - } - - Console.Write(outputBuilder); - _currentText = text; - } - - private void ResetTimer() - { - _timer.Change(_animationInterval, System.TimeSpan.FromMilliseconds(-1)); - } - - public void Dispose() - { - lock (_timer) - { - _disposed = true; - UpdateText(string.Empty); - } + _disposed = true; + UpdateText(string.Empty); } } } diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index 1ec8a27a..fc12ec98 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -4,83 +4,305 @@ using System.Runtime.CompilerServices; using LibHac.Common; using BaseType = System.UInt32; -namespace LibHac +namespace LibHac; + +/// +/// Represents a code used to report the result of a returned function. +/// +[Serializable] +[DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] +public readonly struct Result : IEquatable { + private const BaseType SuccessValue = default; /// - /// Represents a code used to report the result of a returned function. + /// The signifying success. /// - [Serializable] - [DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] - public readonly struct Result : IEquatable + public static Result Success => new Result(SuccessValue); + + private static IResultLogger Logger { get; set; } + private static IResultNameResolver NameResolver { get; set; } = new ResultNameResolver(); + + private const int ModuleBitsOffset = 0; + private const int ModuleBitsCount = 9; + private const int ModuleBegin = 1; + private const int ModuleEnd = 1 << ModuleBitsCount; + private const int DescriptionBitsOffset = ModuleBitsOffset + ModuleBitsCount; + private const int DescriptionBitsCount = 13; + private const int DescriptionBegin = 0; + private const int DescriptionEnd = 1 << DescriptionBitsCount; + private const int ReservedBitsOffset = DescriptionBitsOffset + DescriptionBitsCount; + private const int ReservedBitsCount = sizeof(BaseType) * 8 - ReservedBitsOffset; + // ReSharper disable once UnusedMember.Local + private const int EndOffset = ReservedBitsOffset + ReservedBitsCount; + + private readonly BaseType _value; + + /// + /// Creates a new from the internal result value. + /// + /// The value used internally by . + public Result(BaseType value) { - private const BaseType SuccessValue = default; - /// - /// The signifying success. - /// - public static Result Success => new Result(SuccessValue); + _value = GetBitsValue(value, ModuleBitsOffset, ModuleBitsCount + DescriptionBitsCount); + } - private static IResultLogger Logger { get; set; } - private static IResultNameResolver NameResolver { get; set; } = new ResultNameResolver(); + /// + /// Creates a new from a module and description. + /// + /// The module this result is from. Must be in the range 1 through 511. + /// The description value of the result. Must be in the range 0 through 8191. + public Result(int module, int description) + { + Debug.Assert(ModuleBegin <= module && module < ModuleEnd, "Invalid Module"); + Debug.Assert(DescriptionBegin <= description && description < DescriptionEnd, "Invalid Description"); - private const int ModuleBitsOffset = 0; - private const int ModuleBitsCount = 9; - private const int ModuleBegin = 1; - private const int ModuleEnd = 1 << ModuleBitsCount; - private const int DescriptionBitsOffset = ModuleBitsOffset + ModuleBitsCount; - private const int DescriptionBitsCount = 13; - private const int DescriptionBegin = 0; - private const int DescriptionEnd = 1 << DescriptionBitsCount; - private const int ReservedBitsOffset = DescriptionBitsOffset + DescriptionBitsCount; - private const int ReservedBitsCount = sizeof(BaseType) * 8 - ReservedBitsOffset; - // ReSharper disable once UnusedMember.Local - private const int EndOffset = ReservedBitsOffset + ReservedBitsCount; + _value = SetBitsValue(module, ModuleBitsOffset, ModuleBitsCount) | + SetBitsValue(description, DescriptionBitsOffset, DescriptionBitsCount); + } - private readonly BaseType _value; + public BaseType Module => GetBitsValue(_value, ModuleBitsOffset, ModuleBitsCount); + public BaseType Description => GetBitsValue(_value, DescriptionBitsOffset, DescriptionBitsCount); - /// - /// Creates a new from the internal result value. - /// - /// The value used internally by . - public Result(BaseType value) + public BaseType Value => GetBitsValue(_value, ModuleBitsOffset, ModuleBitsCount + DescriptionBitsCount); + + public string ErrorCode => $"{2000 + Module:d4}-{Description:d4}"; + + public bool IsSuccess() => _value == SuccessValue; + public bool IsFailure() => !IsSuccess(); + + /// + /// Specifies that the from a returned function is explicitly ignored. + /// + public void IgnoreResult() { } + + public void ThrowIfFailure() + { + if (IsFailure()) { - _value = GetBitsValue(value, ModuleBitsOffset, ModuleBitsCount + DescriptionBitsCount); + ThrowHelper.ThrowResult(this); + } + } + + /// + /// Performs no action in release mode. + /// In debug mode, logs returned results using the set by . + ///
Intended to always be used when returning a non-zero . + ///

Example: + /// return result.Log(); + ///
+ /// The called value. + public Result Log() + { + LogImpl(); + + return this; + } + + /// + /// In debug mode, logs converted results using the set by . + /// + /// The original value. + /// The called value. + public Result LogConverted(Result originalResult) + { + LogConvertedImpl(originalResult); + + return this; + } + + public Result Handle() + { + return this; + } + + public Result Rethrow() + { + return this; + } + + public Result Catch() + { + return this; + } + + public Result Miss() + { + MissImpl(); + + return this; + } + + public bool TryGetResultName(out string name) + { + IResultNameResolver resolver = NameResolver; + + if (resolver == null) + { + UnsafeHelpers.SkipParamInit(out name); + return false; } + return resolver.TryResolveName(this, out name); + } + + /// + /// If a has been set via , attempts to + /// return the name and error code of this , otherwise it only returns . + /// + /// If a name was found, the name and error code, otherwise just the error code. + public string ToStringWithName() + { + if (TryGetResultName(out string name)) + { + return $"{name} ({ErrorCode})"; + } + + return ErrorCode; + } + + public override string ToString() => IsSuccess() ? "Success" : ToStringWithName(); + + public override bool Equals(object obj) => obj is Result result && Equals(result); + public bool Equals(Result other) => _value == other._value; + public override int GetHashCode() => _value.GetHashCode(); + + public static bool operator ==(Result left, Result right) => left.Equals(right); + public static bool operator !=(Result left, Result right) => !left.Equals(right); + + /// + /// Sets a to be called when is called in debug mode. + /// + public static void SetLogger(IResultLogger logger) + { + Logger = logger; + } + + /// + /// Sets a that will be used by methods like + /// or to resolve the names of s. + /// + public static void SetNameResolver(IResultNameResolver nameResolver) + { + NameResolver = nameResolver; + } + + [Conditional("DEBUG")] + private void LogImpl() + { + Logger?.LogResult(this); + } + + [Conditional("DEBUG")] + private void LogConvertedImpl(Result originalResult) + { + Logger?.LogConvertedResult(this, originalResult); + } + + [Conditional("DEBUG")] + private void MissImpl() + { + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) + { + return (value >> bitsOffset) & ~(~default(BaseType) << bitsCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static BaseType SetBitsValue(int value, int bitsOffset, int bitsCount) + { + return ((uint)value & ~(~default(BaseType) << bitsCount)) << bitsOffset; + } + + /// + /// Represents a range of s. + /// This range is defined by a single module value and a range of description values. + /// See the documentation remarks for additional information. + /// + /// + /// Due to C# not having templates, we can't define results like Horizon and Atmosphere do. + /// Compared to those Result classes, this struct generates identical code and uses identical syntax. + ///
A Result definition should look like this: public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1); + ///
Being a computed property like this will allow the compiler to do constant propagation to optimize comparisons. + ///

This is an example of how a Result should be returned from a function: return PathNotFound.Log(); + ///
The method will return the for the specified , and + /// will optionally log the returned Result when running in debug mode for easier debugging. All Result logging functionality + /// is removed from release builds. + /// If the is not being used as a return value, will get the Result without logging anything. + ///

is used to check if a provided is contained within the range of the . + /// If the is a computed property as shown above, the compiler will be able to properly optimize the code. + /// The following pairs of lines will produce the same code given Result result; + /// + /// bool a1 = ResultFs.TargetNotFound.Includes(result); // Comparing a single value + /// bool a2 = result.Value == 0x7D402; + /// + /// bool b1 = ResultFs.InsufficientFreeSpace.Includes(result); // Comparing a range of values + /// bool b2 = return result.Module == 2 && (result.Description - 30 <= 45 - 30); + /// + /// Unfortunately RyuJIT will not automatically inline the property when the compiled CIL is 16 bytes or larger as in cases like + /// new Result.Base(ModuleFs, 2000, 2499). The property will need to have the aggressive inlining flag set like so: + /// public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } + ///
+ [DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] + public readonly struct Base + { + private const int DescriptionEndBitsOffset = ReservedBitsOffset; + private readonly ulong _value; + /// - /// Creates a new from a module and description. + /// Creates a Result containing a single value. /// /// The module this result is from. Must be in the range 1 through 511. /// The description value of the result. Must be in the range 0 through 8191. - public Result(int module, int description) - { - Debug.Assert(ModuleBegin <= module && module < ModuleEnd, "Invalid Module"); - Debug.Assert(DescriptionBegin <= description && description < DescriptionEnd, "Invalid Description"); - - _value = SetBitsValue(module, ModuleBitsOffset, ModuleBitsCount) | - SetBitsValue(description, DescriptionBitsOffset, DescriptionBitsCount); - } - - public BaseType Module => GetBitsValue(_value, ModuleBitsOffset, ModuleBitsCount); - public BaseType Description => GetBitsValue(_value, DescriptionBitsOffset, DescriptionBitsCount); - - public BaseType Value => GetBitsValue(_value, ModuleBitsOffset, ModuleBitsCount + DescriptionBitsCount); - - public string ErrorCode => $"{2000 + Module:d4}-{Description:d4}"; - - public bool IsSuccess() => _value == SuccessValue; - public bool IsFailure() => !IsSuccess(); + public Base(int module, int description) : this(module, description, description) { } /// - /// Specifies that the from a returned function is explicitly ignored. + /// Creates a Result containing a range of values. /// - public void IgnoreResult() { } - - public void ThrowIfFailure() + /// The module this result is from. Must be in the range 1 through 511. + /// The inclusive start description value of the range. Must be in the range 0 through 8191. + /// The inclusive end description value of the range. Must be in the range 0 through 8191. + public Base(int module, int descriptionStart, int descriptionEnd) { - if (IsFailure()) + Debug.Assert(ModuleBegin <= module && module < ModuleEnd, "Invalid Module"); + Debug.Assert(DescriptionBegin <= descriptionStart && descriptionStart < DescriptionEnd, "Invalid Description Start"); + Debug.Assert(DescriptionBegin <= descriptionEnd && descriptionEnd < DescriptionEnd, "Invalid Description End"); + Debug.Assert(descriptionStart <= descriptionEnd, "descriptionStart must be <= descriptionEnd"); + + _value = SetBitsValueLong(module, ModuleBitsOffset, ModuleBitsCount) | + SetBitsValueLong(descriptionStart, DescriptionBitsOffset, DescriptionBitsCount) | + SetBitsValueLong(descriptionEnd, DescriptionEndBitsOffset, DescriptionBitsCount); + } + + public BaseType Module => GetBitsValueLong(_value, ModuleBitsOffset, ModuleBitsCount); + public BaseType DescriptionRangeStart => GetBitsValueLong(_value, DescriptionBitsOffset, DescriptionBitsCount); + public BaseType DescriptionRangeEnd => GetBitsValueLong(_value, DescriptionEndBitsOffset, DescriptionBitsCount); + + /// + /// The representing the start of this result range. + /// If returning a from a function, use instead. + /// + public Result Value => new Result((BaseType)_value); + + /// + /// Checks if the range of this includes the provided . + /// + /// The to check. + /// if the range includes . Otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Includes(Result result) + { + // 99% of the time the values in this struct will be constants. + // This check allows the compiler to optimize this method down to a simple comparison when possible. + if (DescriptionRangeStart == DescriptionRangeEnd) { - ThrowHelper.ThrowResult(this); + return result.Value == Value.Value; } + + return result.Module == Module && + result.Description - DescriptionRangeStart <= DescriptionRangeEnd - DescriptionRangeStart; } /// @@ -88,183 +310,67 @@ namespace LibHac /// In debug mode, logs returned results using the set by . ///
Intended to always be used when returning a non-zero . ///

Example: - /// return result.Log(); + /// return ResultFs.PathNotFound.Log(); ///
- /// The called value. + /// The representing the start of this result range. public Result Log() { - LogImpl(); - - return this; + return Value.Log(); } /// /// In debug mode, logs converted results using the set by . /// /// The original value. - /// The called value. + /// The representing the start of this result range. public Result LogConverted(Result originalResult) { - LogConvertedImpl(originalResult); - - return this; + return Value.LogConverted(originalResult); } public Result Handle() { - return this; + return Value.Handle(); } public Result Rethrow() { - return this; + return Value.Rethrow(); } public Result Catch() { - return this; + return Value.Catch(); } public Result Miss() { - MissImpl(); - - return this; - } - - public bool TryGetResultName(out string name) - { - IResultNameResolver resolver = NameResolver; - - if (resolver == null) - { - UnsafeHelpers.SkipParamInit(out name); - return false; - } - - return resolver.TryResolveName(this, out name); - } - - /// - /// If a has been set via , attempts to - /// return the name and error code of this , otherwise it only returns . - /// - /// If a name was found, the name and error code, otherwise just the error code. - public string ToStringWithName() - { - if (TryGetResultName(out string name)) - { - return $"{name} ({ErrorCode})"; - } - - return ErrorCode; - } - - public override string ToString() => IsSuccess() ? "Success" : ToStringWithName(); - - public override bool Equals(object obj) => obj is Result result && Equals(result); - public bool Equals(Result other) => _value == other._value; - public override int GetHashCode() => _value.GetHashCode(); - - public static bool operator ==(Result left, Result right) => left.Equals(right); - public static bool operator !=(Result left, Result right) => !left.Equals(right); - - /// - /// Sets a to be called when is called in debug mode. - /// - public static void SetLogger(IResultLogger logger) - { - Logger = logger; - } - - /// - /// Sets a that will be used by methods like - /// or to resolve the names of s. - /// - public static void SetNameResolver(IResultNameResolver nameResolver) - { - NameResolver = nameResolver; - } - - [Conditional("DEBUG")] - private void LogImpl() - { - Logger?.LogResult(this); - } - - [Conditional("DEBUG")] - private void LogConvertedImpl(Result originalResult) - { - Logger?.LogConvertedResult(this, originalResult); - } - - [Conditional("DEBUG")] - private void MissImpl() - { - + return Value.Miss(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) + private static BaseType GetBitsValueLong(ulong value, int bitsOffset, int bitsCount) { - return (value >> bitsOffset) & ~(~default(BaseType) << bitsCount); + return (BaseType)(value >> bitsOffset) & ~(~default(BaseType) << bitsCount); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BaseType SetBitsValue(int value, int bitsOffset, int bitsCount) + private static ulong SetBitsValueLong(int value, int bitsOffset, int bitsCount) { - return ((uint)value & ~(~default(BaseType) << bitsCount)) << bitsOffset; + return ((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset; } /// - /// Represents a range of s. - /// This range is defined by a single module value and a range of description values. - /// See the documentation remarks for additional information. + /// A that can't be used as a itself, but can check if its + /// range includes other s. /// - /// - /// Due to C# not having templates, we can't define results like Horizon and Atmosphere do. - /// Compared to those Result classes, this struct generates identical code and uses identical syntax. - ///
A Result definition should look like this: public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1); - ///
Being a computed property like this will allow the compiler to do constant propagation to optimize comparisons. - ///

This is an example of how a Result should be returned from a function: return PathNotFound.Log(); - ///
The method will return the for the specified , and - /// will optionally log the returned Result when running in debug mode for easier debugging. All Result logging functionality - /// is removed from release builds. - /// If the is not being used as a return value, will get the Result without logging anything. - ///

is used to check if a provided is contained within the range of the . - /// If the is a computed property as shown above, the compiler will be able to properly optimize the code. - /// The following pairs of lines will produce the same code given Result result; - /// - /// bool a1 = ResultFs.TargetNotFound.Includes(result); // Comparing a single value - /// bool a2 = result.Value == 0x7D402; - /// - /// bool b1 = ResultFs.InsufficientFreeSpace.Includes(result); // Comparing a range of values - /// bool b2 = return result.Module == 2 && (result.Description - 30 <= 45 - 30); - /// - /// Unfortunately RyuJIT will not automatically inline the property when the compiled CIL is 16 bytes or larger as in cases like - /// new Result.Base(ModuleFs, 2000, 2499). The property will need to have the aggressive inlining flag set like so: - /// public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } - ///
- [DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] - public readonly struct Base + public readonly struct Abstract { - private const int DescriptionEndBitsOffset = ReservedBitsOffset; private readonly ulong _value; - /// - /// Creates a Result containing a single value. - /// - /// The module this result is from. Must be in the range 1 through 511. - /// The description value of the result. Must be in the range 0 through 8191. - public Base(int module, int description) : this(module, description, description) { } + public Abstract(int module, int description) : this(module, description, description) { } - /// - /// Creates a Result containing a range of values. - /// - /// The module this result is from. Must be in the range 1 through 511. - /// The inclusive start description value of the range. Must be in the range 0 through 8191. - /// The inclusive end description value of the range. Must be in the range 0 through 8191. - public Base(int module, int descriptionStart, int descriptionEnd) + public Abstract(int module, int descriptionStart, int descriptionEnd) { Debug.Assert(ModuleBegin <= module && module < ModuleEnd, "Invalid Module"); Debug.Assert(DescriptionBegin <= descriptionStart && descriptionStart < DescriptionEnd, "Invalid Description Start"); @@ -280,11 +386,7 @@ namespace LibHac public BaseType DescriptionRangeStart => GetBitsValueLong(_value, DescriptionBitsOffset, DescriptionBitsCount); public BaseType DescriptionRangeEnd => GetBitsValueLong(_value, DescriptionEndBitsOffset, DescriptionBitsCount); - /// - /// The representing the start of this result range. - /// If returning a from a function, use instead. - /// - public Result Value => new Result((BaseType)_value); + private Result Value => new Result((BaseType)_value); /// /// Checks if the range of this includes the provided . @@ -304,120 +406,17 @@ namespace LibHac return result.Module == Module && result.Description - DescriptionRangeStart <= DescriptionRangeEnd - DescriptionRangeStart; } - - /// - /// Performs no action in release mode. - /// In debug mode, logs returned results using the set by . - ///
Intended to always be used when returning a non-zero . - ///

Example: - /// return ResultFs.PathNotFound.Log(); - ///
- /// The representing the start of this result range. - public Result Log() - { - return Value.Log(); - } - - /// - /// In debug mode, logs converted results using the set by . - /// - /// The original value. - /// The representing the start of this result range. - public Result LogConverted(Result originalResult) - { - return Value.LogConverted(originalResult); - } - - public Result Handle() - { - return Value.Handle(); - } - - public Result Rethrow() - { - return Value.Rethrow(); - } - - public Result Catch() - { - return Value.Catch(); - } - - public Result Miss() - { - return Value.Miss(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BaseType GetBitsValueLong(ulong value, int bitsOffset, int bitsCount) - { - return (BaseType)(value >> bitsOffset) & ~(~default(BaseType) << bitsCount); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong SetBitsValueLong(int value, int bitsOffset, int bitsCount) - { - return ((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset; - } - - /// - /// A that can't be used as a itself, but can check if its - /// range includes other s. - /// - public readonly struct Abstract - { - private readonly ulong _value; - - public Abstract(int module, int description) : this(module, description, description) { } - - public Abstract(int module, int descriptionStart, int descriptionEnd) - { - Debug.Assert(ModuleBegin <= module && module < ModuleEnd, "Invalid Module"); - Debug.Assert(DescriptionBegin <= descriptionStart && descriptionStart < DescriptionEnd, "Invalid Description Start"); - Debug.Assert(DescriptionBegin <= descriptionEnd && descriptionEnd < DescriptionEnd, "Invalid Description End"); - Debug.Assert(descriptionStart <= descriptionEnd, "descriptionStart must be <= descriptionEnd"); - - _value = SetBitsValueLong(module, ModuleBitsOffset, ModuleBitsCount) | - SetBitsValueLong(descriptionStart, DescriptionBitsOffset, DescriptionBitsCount) | - SetBitsValueLong(descriptionEnd, DescriptionEndBitsOffset, DescriptionBitsCount); - } - - public BaseType Module => GetBitsValueLong(_value, ModuleBitsOffset, ModuleBitsCount); - public BaseType DescriptionRangeStart => GetBitsValueLong(_value, DescriptionBitsOffset, DescriptionBitsCount); - public BaseType DescriptionRangeEnd => GetBitsValueLong(_value, DescriptionEndBitsOffset, DescriptionBitsCount); - - private Result Value => new Result((BaseType)_value); - - /// - /// Checks if the range of this includes the provided . - /// - /// The to check. - /// if the range includes . Otherwise, . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Includes(Result result) - { - // 99% of the time the values in this struct will be constants. - // This check allows the compiler to optimize this method down to a simple comparison when possible. - if (DescriptionRangeStart == DescriptionRangeEnd) - { - return result.Value == Value.Value; - } - - return result.Module == Module && - result.Description - DescriptionRangeStart <= DescriptionRangeEnd - DescriptionRangeStart; - } - } - } - - public interface IResultLogger - { - public void LogResult(Result result); - public void LogConvertedResult(Result result, Result originalResult); - } - - public interface IResultNameResolver - { - public bool TryResolveName(Result result, out string name); } } + + public interface IResultLogger + { + public void LogResult(Result result); + public void LogConvertedResult(Result result, Result originalResult); + } + + public interface IResultNameResolver + { + public bool TryResolveName(Result result, out string name); + } } diff --git a/src/LibHac/ResultNameResolver.Archive.cs b/src/LibHac/ResultNameResolver.Archive.cs index a426721d..75401bf4 100644 --- a/src/LibHac/ResultNameResolver.Archive.cs +++ b/src/LibHac/ResultNameResolver.Archive.cs @@ -1,15 +1,14 @@ using System; -namespace LibHac -{ - internal partial class ResultNameResolver - { - private static ReadOnlySpan ArchiveData => new byte[] - { - // This array will be populated when the build script is run. +namespace LibHac; - // The script can be run with the "codegen" option to run only the - // code generation portion of the build. - }; - } +internal partial class ResultNameResolver +{ + private static ReadOnlySpan ArchiveData => new byte[] + { + // This array will be populated when the build script is run. + + // The script can be run with the "codegen" option to run only the + // code generation portion of the build. + }; } diff --git a/src/LibHac/ResultNameResolver.cs b/src/LibHac/ResultNameResolver.cs index e7ba9795..bb2db98c 100644 --- a/src/LibHac/ResultNameResolver.cs +++ b/src/LibHac/ResultNameResolver.cs @@ -7,94 +7,93 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Util; -namespace LibHac +namespace LibHac; + +internal partial class ResultNameResolver : Result.IResultNameResolver { - internal partial class ResultNameResolver : Result.IResultNameResolver + private Lazy> ResultNames { get; } = new Lazy>(GetResultNames); + + public bool TryResolveName(Result result, out string name) { - private Lazy> ResultNames { get; } = new Lazy>(GetResultNames); + return ResultNames.Value.TryGetValue(result, out name); + } - public bool TryResolveName(Result result, out string name) + private static Dictionary GetResultNames() + { + var archiveReader = new ResultArchiveReader(DecompressArchive()); + return archiveReader.GetDictionary(); + } + + private static byte[] DecompressArchive() + { + var deflateStream = new DeflateStream(new MemoryStream(ArchiveData.ToArray()), CompressionMode.Decompress); + var archiveDataStream = new MemoryStream(); + deflateStream.CopyTo(archiveDataStream); + return archiveDataStream.ToArray(); + } + + // To save a bunch of space in the assembly, Results with their names are packed into + // an archive and unpacked at runtime. + private readonly ref struct ResultArchiveReader + { + private readonly ReadOnlySpan _data; + + private ref HeaderStruct Header => ref Unsafe.As(ref MemoryMarshal.GetReference(_data)); + private ReadOnlySpan NameTable => _data.Slice(Header.NameTableOffset); + private ReadOnlySpan Elements => MemoryMarshal.Cast( + _data.Slice(Unsafe.SizeOf(), Header.ElementCount * Unsafe.SizeOf())); + + public ResultArchiveReader(ReadOnlySpan archive) { - return ResultNames.Value.TryGetValue(result, out name); + _data = archive; } - private static Dictionary GetResultNames() + public Dictionary GetDictionary() { - var archiveReader = new ResultArchiveReader(DecompressArchive()); - return archiveReader.GetDictionary(); - } + var dict = new Dictionary(); + if (_data.Length < 8) return dict; - private static byte[] DecompressArchive() - { - var deflateStream = new DeflateStream(new MemoryStream(ArchiveData.ToArray()), CompressionMode.Decompress); - var archiveDataStream = new MemoryStream(); - deflateStream.CopyTo(archiveDataStream); - return archiveDataStream.ToArray(); - } + ReadOnlySpan elements = Elements; - // To save a bunch of space in the assembly, Results with their names are packed into - // an archive and unpacked at runtime. - private readonly ref struct ResultArchiveReader - { - private readonly ReadOnlySpan _data; - - private ref HeaderStruct Header => ref Unsafe.As(ref MemoryMarshal.GetReference(_data)); - private ReadOnlySpan NameTable => _data.Slice(Header.NameTableOffset); - private ReadOnlySpan Elements => MemoryMarshal.Cast( - _data.Slice(Unsafe.SizeOf(), Header.ElementCount * Unsafe.SizeOf())); - - public ResultArchiveReader(ReadOnlySpan archive) + foreach (ref readonly Element element in elements) { - _data = archive; - } + if (element.IsAbstract) + continue; - public Dictionary GetDictionary() - { - var dict = new Dictionary(); - if (_data.Length < 8) return dict; + var result = new Result(element.Module, element.DescriptionStart); - ReadOnlySpan elements = Elements; - - foreach (ref readonly Element element in elements) + if (!dict.TryAdd(result, GetName(element.NameOffset).ToString())) { - if (element.IsAbstract) - continue; - - var result = new Result(element.Module, element.DescriptionStart); - - if (!dict.TryAdd(result, GetName(element.NameOffset).ToString())) - { - throw new InvalidDataException("Invalid result name archive: Duplicate result found."); - } + throw new InvalidDataException("Invalid result name archive: Duplicate result found."); } - - return dict; } - private U8Span GetName(int offset) - { - ReadOnlySpan untrimmed = NameTable.Slice(offset); - int len = StringUtils.GetLength(untrimmed); + return dict; + } - return new U8Span(untrimmed.Slice(0, len)); - } + private U8Span GetName(int offset) + { + ReadOnlySpan untrimmed = NameTable.Slice(offset); + int len = StringUtils.GetLength(untrimmed); + + return new U8Span(untrimmed.Slice(0, len)); + } #pragma warning disable 649 - private struct HeaderStruct - { - public int ElementCount; - public int NameTableOffset; - } - - private struct Element - { - public int NameOffset; - public short Module; - public short DescriptionStart; - public short DescriptionEnd; - public bool IsAbstract; - } -#pragma warning restore 649 + private struct HeaderStruct + { + public int ElementCount; + public int NameTableOffset; } + + private struct Element + { + public int NameOffset; + public short Module; + public short DescriptionStart; + public short DescriptionEnd; + public bool IsAbstract; + } +#pragma warning restore 649 } } diff --git a/src/LibHac/Sdmmc/ResultSdmmc.cs b/src/LibHac/Sdmmc/ResultSdmmc.cs index 89a589e3..c0f37aae 100644 --- a/src/LibHac/Sdmmc/ResultSdmmc.cs +++ b/src/LibHac/Sdmmc/ResultSdmmc.cs @@ -11,175 +11,174 @@ using System.Runtime.CompilerServices; -namespace LibHac.Sdmmc +namespace LibHac.Sdmmc; + +public static class ResultSdmmc { - public static class ResultSdmmc - { - public const int ModuleSdmmc = 24; + public const int ModuleSdmmc = 24; - /// Error code: 2024-0001; Inner value: 0x218 - public static Result.Base NoDevice => new Result.Base(ModuleSdmmc, 1); - /// Error code: 2024-0002; Inner value: 0x418 - public static Result.Base NotActivated => new Result.Base(ModuleSdmmc, 2); - /// Error code: 2024-0003; Inner value: 0x618 - public static Result.Base DeviceRemoved => new Result.Base(ModuleSdmmc, 3); - /// Error code: 2024-0004; Inner value: 0x818 - public static Result.Base NotAwakened => new Result.Base(ModuleSdmmc, 4); + /// Error code: 2024-0001; Inner value: 0x218 + public static Result.Base NoDevice => new Result.Base(ModuleSdmmc, 1); + /// Error code: 2024-0002; Inner value: 0x418 + public static Result.Base NotActivated => new Result.Base(ModuleSdmmc, 2); + /// Error code: 2024-0003; Inner value: 0x618 + public static Result.Base DeviceRemoved => new Result.Base(ModuleSdmmc, 3); + /// Error code: 2024-0004; Inner value: 0x818 + public static Result.Base NotAwakened => new Result.Base(ModuleSdmmc, 4); - /// Error code: 2024-0032; Range: 32-126; Inner value: 0x4018 - public static Result.Base CommunicationError => new Result.Base(ModuleSdmmc, 32, 126); - /// Error code: 2024-0033; Range: 33-46; Inner value: 0x4218 - public static Result.Base CommunicationNotAttained => new Result.Base(ModuleSdmmc, 33, 46); - /// Error code: 2024-0034; Inner value: 0x4418 - public static Result.Base ResponseIndexError => new Result.Base(ModuleSdmmc, 34); - /// Error code: 2024-0035; Inner value: 0x4618 - public static Result.Base ResponseEndBitError => new Result.Base(ModuleSdmmc, 35); - /// Error code: 2024-0036; Inner value: 0x4818 - public static Result.Base ResponseCrcError => new Result.Base(ModuleSdmmc, 36); - /// Error code: 2024-0037; Inner value: 0x4a18 - public static Result.Base ResponseTimeoutError => new Result.Base(ModuleSdmmc, 37); - /// Error code: 2024-0038; Inner value: 0x4c18 - public static Result.Base DataEndBitError => new Result.Base(ModuleSdmmc, 38); - /// Error code: 2024-0039; Inner value: 0x4e18 - public static Result.Base DataCrcError => new Result.Base(ModuleSdmmc, 39); - /// Error code: 2024-0040; Inner value: 0x5018 - public static Result.Base DataTimeoutError => new Result.Base(ModuleSdmmc, 40); - /// Error code: 2024-0041; Inner value: 0x5218 - public static Result.Base AutoCommandResponseIndexError => new Result.Base(ModuleSdmmc, 41); - /// Error code: 2024-0042; Inner value: 0x5418 - public static Result.Base AutoCommandResponseEndBitError => new Result.Base(ModuleSdmmc, 42); - /// Error code: 2024-0043; Inner value: 0x5618 - public static Result.Base AutoCommandResponseCrcError => new Result.Base(ModuleSdmmc, 43); - /// Error code: 2024-0044; Inner value: 0x5818 - public static Result.Base AutoCommandResponseTimeoutError => new Result.Base(ModuleSdmmc, 44); - /// Error code: 2024-0045; Inner value: 0x5a18 - public static Result.Base CommandCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 45); - /// Error code: 2024-0046; Inner value: 0x5c18 - public static Result.Base TransferCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 46); + /// Error code: 2024-0032; Range: 32-126; Inner value: 0x4018 + public static Result.Base CommunicationError => new Result.Base(ModuleSdmmc, 32, 126); + /// Error code: 2024-0033; Range: 33-46; Inner value: 0x4218 + public static Result.Base CommunicationNotAttained => new Result.Base(ModuleSdmmc, 33, 46); + /// Error code: 2024-0034; Inner value: 0x4418 + public static Result.Base ResponseIndexError => new Result.Base(ModuleSdmmc, 34); + /// Error code: 2024-0035; Inner value: 0x4618 + public static Result.Base ResponseEndBitError => new Result.Base(ModuleSdmmc, 35); + /// Error code: 2024-0036; Inner value: 0x4818 + public static Result.Base ResponseCrcError => new Result.Base(ModuleSdmmc, 36); + /// Error code: 2024-0037; Inner value: 0x4a18 + public static Result.Base ResponseTimeoutError => new Result.Base(ModuleSdmmc, 37); + /// Error code: 2024-0038; Inner value: 0x4c18 + public static Result.Base DataEndBitError => new Result.Base(ModuleSdmmc, 38); + /// Error code: 2024-0039; Inner value: 0x4e18 + public static Result.Base DataCrcError => new Result.Base(ModuleSdmmc, 39); + /// Error code: 2024-0040; Inner value: 0x5018 + public static Result.Base DataTimeoutError => new Result.Base(ModuleSdmmc, 40); + /// Error code: 2024-0041; Inner value: 0x5218 + public static Result.Base AutoCommandResponseIndexError => new Result.Base(ModuleSdmmc, 41); + /// Error code: 2024-0042; Inner value: 0x5418 + public static Result.Base AutoCommandResponseEndBitError => new Result.Base(ModuleSdmmc, 42); + /// Error code: 2024-0043; Inner value: 0x5618 + public static Result.Base AutoCommandResponseCrcError => new Result.Base(ModuleSdmmc, 43); + /// Error code: 2024-0044; Inner value: 0x5818 + public static Result.Base AutoCommandResponseTimeoutError => new Result.Base(ModuleSdmmc, 44); + /// Error code: 2024-0045; Inner value: 0x5a18 + public static Result.Base CommandCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 45); + /// Error code: 2024-0046; Inner value: 0x5c18 + public static Result.Base TransferCompleteSoftwareTimeout => new Result.Base(ModuleSdmmc, 46); - /// Error code: 2024-0048; Range: 48-70; Inner value: 0x6018 - public static Result.Base DeviceStatusHasError => new Result.Base(ModuleSdmmc, 48, 70); - /// Error code: 2024-0049; Inner value: 0x6218 - public static Result.Base DeviceStatusAddressOutOfRange => new Result.Base(ModuleSdmmc, 49); - /// Error code: 2024-0050; Inner value: 0x6418 - public static Result.Base DeviceStatusAddressMisaligned => new Result.Base(ModuleSdmmc, 50); - /// Error code: 2024-0051; Inner value: 0x6618 - public static Result.Base DeviceStatusBlockLenError => new Result.Base(ModuleSdmmc, 51); - /// Error code: 2024-0052; Inner value: 0x6818 - public static Result.Base DeviceStatusEraseSeqError => new Result.Base(ModuleSdmmc, 52); - /// Error code: 2024-0053; Inner value: 0x6a18 - public static Result.Base DeviceStatusEraseParam => new Result.Base(ModuleSdmmc, 53); - /// Error code: 2024-0054; Inner value: 0x6c18 - public static Result.Base DeviceStatusWpViolation => new Result.Base(ModuleSdmmc, 54); - /// Error code: 2024-0055; Inner value: 0x6e18 - public static Result.Base DeviceStatusLockUnlockFailed => new Result.Base(ModuleSdmmc, 55); - /// Error code: 2024-0056; Inner value: 0x7018 - public static Result.Base DeviceStatusComCrcError => new Result.Base(ModuleSdmmc, 56); - /// Error code: 2024-0057; Inner value: 0x7218 - public static Result.Base DeviceStatusIllegalCommand => new Result.Base(ModuleSdmmc, 57); - /// Error code: 2024-0058; Inner value: 0x7418 - public static Result.Base DeviceStatusDeviceEccFailed => new Result.Base(ModuleSdmmc, 58); - /// Error code: 2024-0059; Inner value: 0x7618 - public static Result.Base DeviceStatusCcError => new Result.Base(ModuleSdmmc, 59); - /// Error code: 2024-0060; Inner value: 0x7818 - public static Result.Base DeviceStatusError => new Result.Base(ModuleSdmmc, 60); - /// Error code: 2024-0061; Inner value: 0x7a18 - public static Result.Base DeviceStatusCidCsdOverwrite => new Result.Base(ModuleSdmmc, 61); - /// Error code: 2024-0062; Inner value: 0x7c18 - public static Result.Base DeviceStatusWpEraseSkip => new Result.Base(ModuleSdmmc, 62); - /// Error code: 2024-0063; Inner value: 0x7e18 - public static Result.Base DeviceStatusEraseReset => new Result.Base(ModuleSdmmc, 63); - /// Error code: 2024-0064; Inner value: 0x8018 - public static Result.Base DeviceStatusSwitchError => new Result.Base(ModuleSdmmc, 64); + /// Error code: 2024-0048; Range: 48-70; Inner value: 0x6018 + public static Result.Base DeviceStatusHasError => new Result.Base(ModuleSdmmc, 48, 70); + /// Error code: 2024-0049; Inner value: 0x6218 + public static Result.Base DeviceStatusAddressOutOfRange => new Result.Base(ModuleSdmmc, 49); + /// Error code: 2024-0050; Inner value: 0x6418 + public static Result.Base DeviceStatusAddressMisaligned => new Result.Base(ModuleSdmmc, 50); + /// Error code: 2024-0051; Inner value: 0x6618 + public static Result.Base DeviceStatusBlockLenError => new Result.Base(ModuleSdmmc, 51); + /// Error code: 2024-0052; Inner value: 0x6818 + public static Result.Base DeviceStatusEraseSeqError => new Result.Base(ModuleSdmmc, 52); + /// Error code: 2024-0053; Inner value: 0x6a18 + public static Result.Base DeviceStatusEraseParam => new Result.Base(ModuleSdmmc, 53); + /// Error code: 2024-0054; Inner value: 0x6c18 + public static Result.Base DeviceStatusWpViolation => new Result.Base(ModuleSdmmc, 54); + /// Error code: 2024-0055; Inner value: 0x6e18 + public static Result.Base DeviceStatusLockUnlockFailed => new Result.Base(ModuleSdmmc, 55); + /// Error code: 2024-0056; Inner value: 0x7018 + public static Result.Base DeviceStatusComCrcError => new Result.Base(ModuleSdmmc, 56); + /// Error code: 2024-0057; Inner value: 0x7218 + public static Result.Base DeviceStatusIllegalCommand => new Result.Base(ModuleSdmmc, 57); + /// Error code: 2024-0058; Inner value: 0x7418 + public static Result.Base DeviceStatusDeviceEccFailed => new Result.Base(ModuleSdmmc, 58); + /// Error code: 2024-0059; Inner value: 0x7618 + public static Result.Base DeviceStatusCcError => new Result.Base(ModuleSdmmc, 59); + /// Error code: 2024-0060; Inner value: 0x7818 + public static Result.Base DeviceStatusError => new Result.Base(ModuleSdmmc, 60); + /// Error code: 2024-0061; Inner value: 0x7a18 + public static Result.Base DeviceStatusCidCsdOverwrite => new Result.Base(ModuleSdmmc, 61); + /// Error code: 2024-0062; Inner value: 0x7c18 + public static Result.Base DeviceStatusWpEraseSkip => new Result.Base(ModuleSdmmc, 62); + /// Error code: 2024-0063; Inner value: 0x7e18 + public static Result.Base DeviceStatusEraseReset => new Result.Base(ModuleSdmmc, 63); + /// Error code: 2024-0064; Inner value: 0x8018 + public static Result.Base DeviceStatusSwitchError => new Result.Base(ModuleSdmmc, 64); - /// Error code: 2024-0072; Inner value: 0x9018 - public static Result.Base UnexpectedDeviceState => new Result.Base(ModuleSdmmc, 72); - /// Error code: 2024-0073; Inner value: 0x9218 - public static Result.Base UnexpectedDeviceCsdValue => new Result.Base(ModuleSdmmc, 73); - /// Error code: 2024-0074; Inner value: 0x9418 - public static Result.Base AbortTransactionSoftwareTimeout => new Result.Base(ModuleSdmmc, 74); - /// Error code: 2024-0075; Inner value: 0x9618 - public static Result.Base CommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleSdmmc, 75); - /// Error code: 2024-0076; Inner value: 0x9818 - public static Result.Base CommandInhibitDatSoftwareTimeout => new Result.Base(ModuleSdmmc, 76); - /// Error code: 2024-0077; Inner value: 0x9a18 - public static Result.Base BusySoftwareTimeout => new Result.Base(ModuleSdmmc, 77); - /// Error code: 2024-0078; Inner value: 0x9c18 - public static Result.Base IssueTuningCommandSoftwareTimeout => new Result.Base(ModuleSdmmc, 78); - /// Error code: 2024-0079; Inner value: 0x9e18 - public static Result.Base TuningFailed => new Result.Base(ModuleSdmmc, 79); - /// Error code: 2024-0080; Inner value: 0xa018 - public static Result.Base MmcInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 80); - /// Error code: 2024-0081; Inner value: 0xa218 - public static Result.Base MmcNotSupportExtendedCsd => new Result.Base(ModuleSdmmc, 81); - /// Error code: 2024-0082; Inner value: 0xa418 - public static Result.Base UnexpectedMmcExtendedCsdValue => new Result.Base(ModuleSdmmc, 82); - /// Error code: 2024-0083; Inner value: 0xa618 - public static Result.Base MmcEraseSoftwareTimeout => new Result.Base(ModuleSdmmc, 83); - /// Error code: 2024-0084; Inner value: 0xa818 - public static Result.Base SdCardValidationError => new Result.Base(ModuleSdmmc, 84); - /// Error code: 2024-0085; Inner value: 0xaa18 - public static Result.Base SdCardInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 85); - /// Error code: 2024-0086; Inner value: 0xac18 - public static Result.Base SdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleSdmmc, 86); - /// Error code: 2024-0087; Inner value: 0xae18 - public static Result.Base UnexpectedSdCardAcmdDisabled => new Result.Base(ModuleSdmmc, 87); - /// Error code: 2024-0088; Inner value: 0xb018 - public static Result.Base SdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 88); - /// Error code: 2024-0089; Inner value: 0xb218 - public static Result.Base UnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 89); - /// Error code: 2024-0090; Inner value: 0xb418 - public static Result.Base SdCardNotSupportAccessMode => new Result.Base(ModuleSdmmc, 90); - /// Error code: 2024-0091; Inner value: 0xb618 - public static Result.Base SdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleSdmmc, 91); - /// Error code: 2024-0092; Inner value: 0xb818 - public static Result.Base SdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleSdmmc, 92); - /// Error code: 2024-0093; Inner value: 0xba18 - public static Result.Base SdCardCannotSwitchAccessMode => new Result.Base(ModuleSdmmc, 93); - /// Error code: 2024-0094; Inner value: 0xbc18 - public static Result.Base SdCardFailedSwitchAccessMode => new Result.Base(ModuleSdmmc, 94); - /// Error code: 2024-0095; Inner value: 0xbe18 - public static Result.Base SdCardUnacceptableCurrentConsumption => new Result.Base(ModuleSdmmc, 95); - /// Error code: 2024-0096; Inner value: 0xc018 - public static Result.Base SdCardNotReadyToVoltageSwitch => new Result.Base(ModuleSdmmc, 96); - /// Error code: 2024-0097; Inner value: 0xc218 - public static Result.Base SdCardNotCompleteVoltageSwitch => new Result.Base(ModuleSdmmc, 97); + /// Error code: 2024-0072; Inner value: 0x9018 + public static Result.Base UnexpectedDeviceState => new Result.Base(ModuleSdmmc, 72); + /// Error code: 2024-0073; Inner value: 0x9218 + public static Result.Base UnexpectedDeviceCsdValue => new Result.Base(ModuleSdmmc, 73); + /// Error code: 2024-0074; Inner value: 0x9418 + public static Result.Base AbortTransactionSoftwareTimeout => new Result.Base(ModuleSdmmc, 74); + /// Error code: 2024-0075; Inner value: 0x9618 + public static Result.Base CommandInhibitCmdSoftwareTimeout => new Result.Base(ModuleSdmmc, 75); + /// Error code: 2024-0076; Inner value: 0x9818 + public static Result.Base CommandInhibitDatSoftwareTimeout => new Result.Base(ModuleSdmmc, 76); + /// Error code: 2024-0077; Inner value: 0x9a18 + public static Result.Base BusySoftwareTimeout => new Result.Base(ModuleSdmmc, 77); + /// Error code: 2024-0078; Inner value: 0x9c18 + public static Result.Base IssueTuningCommandSoftwareTimeout => new Result.Base(ModuleSdmmc, 78); + /// Error code: 2024-0079; Inner value: 0x9e18 + public static Result.Base TuningFailed => new Result.Base(ModuleSdmmc, 79); + /// Error code: 2024-0080; Inner value: 0xa018 + public static Result.Base MmcInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 80); + /// Error code: 2024-0081; Inner value: 0xa218 + public static Result.Base MmcNotSupportExtendedCsd => new Result.Base(ModuleSdmmc, 81); + /// Error code: 2024-0082; Inner value: 0xa418 + public static Result.Base UnexpectedMmcExtendedCsdValue => new Result.Base(ModuleSdmmc, 82); + /// Error code: 2024-0083; Inner value: 0xa618 + public static Result.Base MmcEraseSoftwareTimeout => new Result.Base(ModuleSdmmc, 83); + /// Error code: 2024-0084; Inner value: 0xa818 + public static Result.Base SdCardValidationError => new Result.Base(ModuleSdmmc, 84); + /// Error code: 2024-0085; Inner value: 0xaa18 + public static Result.Base SdCardInitializationSoftwareTimeout => new Result.Base(ModuleSdmmc, 85); + /// Error code: 2024-0086; Inner value: 0xac18 + public static Result.Base SdCardGetValidRcaSoftwareTimeout => new Result.Base(ModuleSdmmc, 86); + /// Error code: 2024-0087; Inner value: 0xae18 + public static Result.Base UnexpectedSdCardAcmdDisabled => new Result.Base(ModuleSdmmc, 87); + /// Error code: 2024-0088; Inner value: 0xb018 + public static Result.Base SdCardNotSupportSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 88); + /// Error code: 2024-0089; Inner value: 0xb218 + public static Result.Base UnexpectedSdCardSwitchFunctionStatus => new Result.Base(ModuleSdmmc, 89); + /// Error code: 2024-0090; Inner value: 0xb418 + public static Result.Base SdCardNotSupportAccessMode => new Result.Base(ModuleSdmmc, 90); + /// Error code: 2024-0091; Inner value: 0xb618 + public static Result.Base SdCardNot4BitBusWidthAtUhsIMode => new Result.Base(ModuleSdmmc, 91); + /// Error code: 2024-0092; Inner value: 0xb818 + public static Result.Base SdCardNotSupportSdr104AndSdr50 => new Result.Base(ModuleSdmmc, 92); + /// Error code: 2024-0093; Inner value: 0xba18 + public static Result.Base SdCardCannotSwitchAccessMode => new Result.Base(ModuleSdmmc, 93); + /// Error code: 2024-0094; Inner value: 0xbc18 + public static Result.Base SdCardFailedSwitchAccessMode => new Result.Base(ModuleSdmmc, 94); + /// Error code: 2024-0095; Inner value: 0xbe18 + public static Result.Base SdCardUnacceptableCurrentConsumption => new Result.Base(ModuleSdmmc, 95); + /// Error code: 2024-0096; Inner value: 0xc018 + public static Result.Base SdCardNotReadyToVoltageSwitch => new Result.Base(ModuleSdmmc, 96); + /// Error code: 2024-0097; Inner value: 0xc218 + public static Result.Base SdCardNotCompleteVoltageSwitch => new Result.Base(ModuleSdmmc, 97); - /// Error code: 2024-0128; Range: 128-158; Inner value: 0x10018 - public static Result.Base HostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 128, 158); } - /// Error code: 2024-0129; Inner value: 0x10218 - public static Result.Base InternalClockStableSoftwareTimeout => new Result.Base(ModuleSdmmc, 129); - /// Error code: 2024-0130; Inner value: 0x10418 - public static Result.Base SdHostStandardUnknownAutoCmdError => new Result.Base(ModuleSdmmc, 130); - /// Error code: 2024-0131; Inner value: 0x10618 - public static Result.Base SdHostStandardUnknownError => new Result.Base(ModuleSdmmc, 131); - /// Error code: 2024-0132; Inner value: 0x10818 - public static Result.Base SdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 132); - /// Error code: 2024-0133; Inner value: 0x10a18 - public static Result.Base SdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleSdmmc, 133); - /// Error code: 2024-0134; Inner value: 0x10c18 - public static Result.Base SdHostStandardFailSwitchTo18V => new Result.Base(ModuleSdmmc, 134); - /// Error code: 2024-0135; Inner value: 0x10e18 - public static Result.Base DriveStrengthCalibrationNotCompleted => new Result.Base(ModuleSdmmc, 135); - /// Error code: 2024-0136; Inner value: 0x11018 - public static Result.Base DriveStrengthCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 136); - /// Error code: 2024-0137; Inner value: 0x11218 - public static Result.Base SdmmcCompShortToGnd => new Result.Base(ModuleSdmmc, 137); - /// Error code: 2024-0138; Inner value: 0x11418 - public static Result.Base SdmmcCompOpen => new Result.Base(ModuleSdmmc, 138); + /// Error code: 2024-0128; Range: 128-158; Inner value: 0x10018 + public static Result.Base HostControllerUnexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 128, 158); } + /// Error code: 2024-0129; Inner value: 0x10218 + public static Result.Base InternalClockStableSoftwareTimeout => new Result.Base(ModuleSdmmc, 129); + /// Error code: 2024-0130; Inner value: 0x10418 + public static Result.Base SdHostStandardUnknownAutoCmdError => new Result.Base(ModuleSdmmc, 130); + /// Error code: 2024-0131; Inner value: 0x10618 + public static Result.Base SdHostStandardUnknownError => new Result.Base(ModuleSdmmc, 131); + /// Error code: 2024-0132; Inner value: 0x10818 + public static Result.Base SdmmcDllCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 132); + /// Error code: 2024-0133; Inner value: 0x10a18 + public static Result.Base SdmmcDllApplicationSoftwareTimeout => new Result.Base(ModuleSdmmc, 133); + /// Error code: 2024-0134; Inner value: 0x10c18 + public static Result.Base SdHostStandardFailSwitchTo18V => new Result.Base(ModuleSdmmc, 134); + /// Error code: 2024-0135; Inner value: 0x10e18 + public static Result.Base DriveStrengthCalibrationNotCompleted => new Result.Base(ModuleSdmmc, 135); + /// Error code: 2024-0136; Inner value: 0x11018 + public static Result.Base DriveStrengthCalibrationSoftwareTimeout => new Result.Base(ModuleSdmmc, 136); + /// Error code: 2024-0137; Inner value: 0x11218 + public static Result.Base SdmmcCompShortToGnd => new Result.Base(ModuleSdmmc, 137); + /// Error code: 2024-0138; Inner value: 0x11418 + public static Result.Base SdmmcCompOpen => new Result.Base(ModuleSdmmc, 138); - /// Error code: 2024-0160; Range: 160-190; Inner value: 0x14018 - public static Result.Base InternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 160, 190); } - /// Error code: 2024-0161; Inner value: 0x14218 - public static Result.Base NoWaitedInterrupt => new Result.Base(ModuleSdmmc, 161); - /// Error code: 2024-0162; Inner value: 0x14418 - public static Result.Base WaitInterruptSoftwareTimeout => new Result.Base(ModuleSdmmc, 162); + /// Error code: 2024-0160; Range: 160-190; Inner value: 0x14018 + public static Result.Base InternalError { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleSdmmc, 160, 190); } + /// Error code: 2024-0161; Inner value: 0x14218 + public static Result.Base NoWaitedInterrupt => new Result.Base(ModuleSdmmc, 161); + /// Error code: 2024-0162; Inner value: 0x14418 + public static Result.Base WaitInterruptSoftwareTimeout => new Result.Base(ModuleSdmmc, 162); - /// Error code: 2024-0192; Inner value: 0x18018 - public static Result.Base AbortCommandIssued => new Result.Base(ModuleSdmmc, 192); - /// Error code: 2024-0200; Inner value: 0x19018 - public static Result.Base NotSupported => new Result.Base(ModuleSdmmc, 200); - /// Error code: 2024-0201; Inner value: 0x19218 - public static Result.Base NotImplemented => new Result.Base(ModuleSdmmc, 201); - } + /// Error code: 2024-0192; Inner value: 0x18018 + public static Result.Base AbortCommandIssued => new Result.Base(ModuleSdmmc, 192); + /// Error code: 2024-0200; Inner value: 0x19018 + public static Result.Base NotSupported => new Result.Base(ModuleSdmmc, 200); + /// Error code: 2024-0201; Inner value: 0x19218 + public static Result.Base NotImplemented => new Result.Base(ModuleSdmmc, 201); } diff --git a/src/LibHac/Sdmmc/SdmmcEnums.cs b/src/LibHac/Sdmmc/SdmmcEnums.cs index da184bc7..e7eead68 100644 --- a/src/LibHac/Sdmmc/SdmmcEnums.cs +++ b/src/LibHac/Sdmmc/SdmmcEnums.cs @@ -1,21 +1,20 @@ -namespace LibHac.Sdmmc +namespace LibHac.Sdmmc; + +public enum SpeedMode { - public enum SpeedMode - { - MmcIdentification = 0, - MmcLegacySpeed = 1, - MmcHighSpeed = 2, - MmcHs200 = 3, - MmcHs400 = 4, - SdCardIdentification = 5, - SdCardDefaultSpeed = 6, - SdCardHighSpeed = 7, - SdCardSdr12 = 8, - SdCardSdr25 = 9, - SdCardSdr50 = 10, - SdCardSdr104 = 11, - SdCardDdr50 = 12, - GcAsicFpgaSpeed = 13, - GcAsicSpeed = 14 - } + MmcIdentification = 0, + MmcLegacySpeed = 1, + MmcHighSpeed = 2, + MmcHs200 = 3, + MmcHs400 = 4, + SdCardIdentification = 5, + SdCardDefaultSpeed = 6, + SdCardHighSpeed = 7, + SdCardSdr12 = 8, + SdCardSdr25 = 9, + SdCardSdr50 = 10, + SdCardSdr104 = 11, + SdCardDdr50 = 12, + GcAsicFpgaSpeed = 13, + GcAsicSpeed = 14 } diff --git a/src/LibHac/SdmmcSrv/ISdmmcDeviceManager.cs b/src/LibHac/SdmmcSrv/ISdmmcDeviceManager.cs index 1f6ea629..d1a512d5 100644 --- a/src/LibHac/SdmmcSrv/ISdmmcDeviceManager.cs +++ b/src/LibHac/SdmmcSrv/ISdmmcDeviceManager.cs @@ -1,12 +1,11 @@ using LibHac.Fs; -namespace LibHac.SdmmcSrv +namespace LibHac.SdmmcSrv; + +internal interface ISdmmcDeviceManager { - internal interface ISdmmcDeviceManager - { - Result Lock(out object locker, uint handle); - IStorage GetStorage(); - SdmmcPort GetPortId(); - Result NotifyCloseStorageDevice(uint handle); - } + Result Lock(out object locker, uint handle); + IStorage GetStorage(); + SdmmcPort GetPortId(); + Result NotifyCloseStorageDevice(uint handle); } diff --git a/src/LibHac/SdmmcSrv/SdmmcSrvEnums.cs b/src/LibHac/SdmmcSrv/SdmmcSrvEnums.cs index 530d0444..16d27086 100644 --- a/src/LibHac/SdmmcSrv/SdmmcSrvEnums.cs +++ b/src/LibHac/SdmmcSrv/SdmmcSrvEnums.cs @@ -1,40 +1,39 @@ -namespace LibHac.SdmmcSrv +namespace LibHac.SdmmcSrv; + +public enum SdCardManagerOperationIdValue { - public enum SdCardManagerOperationIdValue - { - GetAndClearErrorInfo = 1, - SuspendControl = 2, - ResumeControl = 3, - SimulateDetectionEventSignaled = 4 - } - - public enum SdCardOperationIdValue - { - GetSpeedMode = 0x1, - GetCid = 0x2, - GetUserAreaNumSectors = 0x3, - GetUserAreaSize = 0x4, - GetProtectedAreaNumSectors = 0x5, - GetProtectedAreaSize = 0x6 - } - - public enum MmcManagerOperationIdValue - { - GetAndClearErrorInfo = 0x1, - SuspendControl = 0x2, - ResumeControl = 0x3, - GetAndClearPatrolReadAllocateBufferCount = 0x4, - GetPatrolCount = 0x5, - SuspendPatrol = 0x6, - ResumePatrol = 0x7 - } - - public enum MmcOperationIdValue - { - GetSpeedMode = 1, - GetCid = 2, - GetPartitionSize = 3, - GetExtendedCsd = 4, - Erase = 5 - } + GetAndClearErrorInfo = 1, + SuspendControl = 2, + ResumeControl = 3, + SimulateDetectionEventSignaled = 4 +} + +public enum SdCardOperationIdValue +{ + GetSpeedMode = 0x1, + GetCid = 0x2, + GetUserAreaNumSectors = 0x3, + GetUserAreaSize = 0x4, + GetProtectedAreaNumSectors = 0x5, + GetProtectedAreaSize = 0x6 +} + +public enum MmcManagerOperationIdValue +{ + GetAndClearErrorInfo = 0x1, + SuspendControl = 0x2, + ResumeControl = 0x3, + GetAndClearPatrolReadAllocateBufferCount = 0x4, + GetPatrolCount = 0x5, + SuspendPatrol = 0x6, + ResumePatrol = 0x7 +} + +public enum MmcOperationIdValue +{ + GetSpeedMode = 1, + GetCid = 2, + GetPartitionSize = 3, + GetExtendedCsd = 4, + Erase = 5 } diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs index 759b8464..f1a42c0a 100644 --- a/src/LibHac/Sf/Buffers.cs +++ b/src/LibHac/Sf/Buffers.cs @@ -3,85 +3,84 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Sf +namespace LibHac.Sf; + +public readonly ref struct InBuffer { - public readonly ref struct InBuffer + private readonly ReadOnlySpan _buffer; + + public int Size => _buffer.Length; + public ReadOnlySpan Buffer => _buffer; + + public InBuffer(ReadOnlySpan buffer) { - private readonly ReadOnlySpan _buffer; - - public int Size => _buffer.Length; - public ReadOnlySpan Buffer => _buffer; - - public InBuffer(ReadOnlySpan buffer) - { - _buffer = buffer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static InBuffer FromSpan(ReadOnlySpan buffer) where T : unmanaged - { - return new InBuffer(MemoryMarshal.Cast(buffer)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static InBuffer FromStruct(in T value) where T : unmanaged - { - return new InBuffer(SpanHelpers.AsReadOnlyByteSpan(in value)); - } + _buffer = buffer; } - public readonly ref struct OutBuffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static InBuffer FromSpan(ReadOnlySpan buffer) where T : unmanaged { - private readonly Span _buffer; - - public int Size => _buffer.Length; - public Span Buffer => _buffer; - - public OutBuffer(Span buffer) - { - _buffer = buffer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OutBuffer FromSpan(Span buffer) where T : unmanaged - { - return new OutBuffer(MemoryMarshal.Cast(buffer)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OutBuffer FromStruct(ref T value) where T : unmanaged - { - return new OutBuffer(SpanHelpers.AsByteSpan(ref value)); - } + return new InBuffer(MemoryMarshal.Cast(buffer)); } - public readonly ref struct InArray where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static InBuffer FromStruct(in T value) where T : unmanaged { - private readonly ReadOnlySpan _array; - - public int Size => _array.Length; - public ReadOnlySpan Array => _array; - - public InArray(ReadOnlySpan array) - { - _array = array; - } - - public ref readonly T this[int i] => ref _array[i]; - } - - public readonly ref struct OutArray where T : unmanaged - { - private readonly Span _array; - - public int Size => _array.Length; - public Span Array => _array; - - public OutArray(Span array) - { - _array = array; - } - - public ref T this[int i] => ref _array[i]; + return new InBuffer(SpanHelpers.AsReadOnlyByteSpan(in value)); } } + +public readonly ref struct OutBuffer +{ + private readonly Span _buffer; + + public int Size => _buffer.Length; + public Span Buffer => _buffer; + + public OutBuffer(Span buffer) + { + _buffer = buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static OutBuffer FromSpan(Span buffer) where T : unmanaged + { + return new OutBuffer(MemoryMarshal.Cast(buffer)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static OutBuffer FromStruct(ref T value) where T : unmanaged + { + return new OutBuffer(SpanHelpers.AsByteSpan(ref value)); + } +} + +public readonly ref struct InArray where T : unmanaged +{ + private readonly ReadOnlySpan _array; + + public int Size => _array.Length; + public ReadOnlySpan Array => _array; + + public InArray(ReadOnlySpan array) + { + _array = array; + } + + public ref readonly T this[int i] => ref _array[i]; +} + +public readonly ref struct OutArray where T : unmanaged +{ + private readonly Span _array; + + public int Size => _array.Length; + public Span Array => _array; + + public OutArray(Span array) + { + _array = array; + } + + public ref T this[int i] => ref _array[i]; +} diff --git a/src/LibHac/Sf/Cmif/ResultCmif.cs b/src/LibHac/Sf/Cmif/ResultCmif.cs index f5b431a0..75a895ea 100644 --- a/src/LibHac/Sf/Cmif/ResultCmif.cs +++ b/src/LibHac/Sf/Cmif/ResultCmif.cs @@ -9,35 +9,34 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Sf.Cmif -{ - public static class ResultCmif - { - public const int ModuleSf = 10; +namespace LibHac.Sf.Cmif; - /// Error code: 2010-0011; Inner value: 0x160a - public static Result.Base CmifProxyAllocationFailed => new Result.Base(ModuleSf, 11); - /// Error code: 2010-0202; Inner value: 0x1940a - public static Result.Base InvalidCmifHeaderSize => new Result.Base(ModuleSf, 202); - /// Error code: 2010-0211; Inner value: 0x1a60a - public static Result.Base InvalidCmifInHeader => new Result.Base(ModuleSf, 211); - /// Error code: 2010-0212; Inner value: 0x1a80a - public static Result.Base InvalidCmifOutHeader => new Result.Base(ModuleSf, 212); - /// Error code: 2010-0221; Inner value: 0x1ba0a - public static Result.Base UnknownMethodId => new Result.Base(ModuleSf, 221); - /// Error code: 2010-0231; Inner value: 0x1ce0a - public static Result.Base InvalidInRawSize => new Result.Base(ModuleSf, 231); - /// Error code: 2010-0232; Inner value: 0x1d00a - public static Result.Base InvalidOutRawSize => new Result.Base(ModuleSf, 232); - /// Error code: 2010-0235; Inner value: 0x1d60a - public static Result.Base InvalidInObjectCount => new Result.Base(ModuleSf, 235); - /// Error code: 2010-0236; Inner value: 0x1d80a - public static Result.Base InvalidOutObjectCount => new Result.Base(ModuleSf, 236); - /// Error code: 2010-0239; Inner value: 0x1de0a - public static Result.Base InvalidInObject => new Result.Base(ModuleSf, 239); - /// Error code: 2010-0261; Inner value: 0x20a0a - public static Result.Base TargetObjectNotFound => new Result.Base(ModuleSf, 261); - /// Error code: 2010-0301; Inner value: 0x25a0a - public static Result.Base OutOfDomainEntry => new Result.Base(ModuleSf, 301); - } +public static class ResultCmif +{ + public const int ModuleSf = 10; + + /// Error code: 2010-0011; Inner value: 0x160a + public static Result.Base CmifProxyAllocationFailed => new Result.Base(ModuleSf, 11); + /// Error code: 2010-0202; Inner value: 0x1940a + public static Result.Base InvalidCmifHeaderSize => new Result.Base(ModuleSf, 202); + /// Error code: 2010-0211; Inner value: 0x1a60a + public static Result.Base InvalidCmifInHeader => new Result.Base(ModuleSf, 211); + /// Error code: 2010-0212; Inner value: 0x1a80a + public static Result.Base InvalidCmifOutHeader => new Result.Base(ModuleSf, 212); + /// Error code: 2010-0221; Inner value: 0x1ba0a + public static Result.Base UnknownMethodId => new Result.Base(ModuleSf, 221); + /// Error code: 2010-0231; Inner value: 0x1ce0a + public static Result.Base InvalidInRawSize => new Result.Base(ModuleSf, 231); + /// Error code: 2010-0232; Inner value: 0x1d00a + public static Result.Base InvalidOutRawSize => new Result.Base(ModuleSf, 232); + /// Error code: 2010-0235; Inner value: 0x1d60a + public static Result.Base InvalidInObjectCount => new Result.Base(ModuleSf, 235); + /// Error code: 2010-0236; Inner value: 0x1d80a + public static Result.Base InvalidOutObjectCount => new Result.Base(ModuleSf, 236); + /// Error code: 2010-0239; Inner value: 0x1de0a + public static Result.Base InvalidInObject => new Result.Base(ModuleSf, 239); + /// Error code: 2010-0261; Inner value: 0x20a0a + public static Result.Base TargetObjectNotFound => new Result.Base(ModuleSf, 261); + /// Error code: 2010-0301; Inner value: 0x25a0a + public static Result.Base OutOfDomainEntry => new Result.Base(ModuleSf, 301); } diff --git a/src/LibHac/Sf/Impl/ResultSfImpl.cs b/src/LibHac/Sf/Impl/ResultSfImpl.cs index f3d84f54..f9609885 100644 --- a/src/LibHac/Sf/Impl/ResultSfImpl.cs +++ b/src/LibHac/Sf/Impl/ResultSfImpl.cs @@ -11,17 +11,16 @@ using System.Runtime.CompilerServices; -namespace LibHac.Sf.Impl -{ - public static class ResultSfImpl - { - public const int ModuleSf = 10; +namespace LibHac.Sf.Impl; - /// Error code: 2010-0800; Range: 800-899 - public static Result.Base.Abstract RequestContextChanged { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 800, 899); } - /// Error code: 2010-0801; Range: 801-809 - public static Result.Base.Abstract RequestInvalidated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 801, 809); } - /// Error code: 2010-0802; Inner value: 0x6440a - public static Result.Base RequestInvalidatedByUser => new Result.Base(ModuleSf, 802); - } +public static class ResultSfImpl +{ + public const int ModuleSf = 10; + + /// Error code: 2010-0800; Range: 800-899 + public static Result.Base.Abstract RequestContextChanged { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 800, 899); } + /// Error code: 2010-0801; Range: 801-809 + public static Result.Base.Abstract RequestInvalidated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 801, 809); } + /// Error code: 2010-0802; Inner value: 0x6440a + public static Result.Base RequestInvalidatedByUser => new Result.Base(ModuleSf, 802); } diff --git a/src/LibHac/Sf/NativeHandle.cs b/src/LibHac/Sf/NativeHandle.cs index 4eda6cf3..621c9938 100644 --- a/src/LibHac/Sf/NativeHandle.cs +++ b/src/LibHac/Sf/NativeHandle.cs @@ -2,30 +2,29 @@ using LibHac.Os; using LibHac.Svc; -namespace LibHac.Sf +namespace LibHac.Sf; + +public class NativeHandle : IDisposable { - public class NativeHandle : IDisposable + private OsState Os { get; } + public Handle Handle { get; private set; } + public bool IsManaged { get; private set; } + + public NativeHandle(OsState os, Handle handle) { - private OsState Os { get; } - public Handle Handle { get; private set; } - public bool IsManaged { get; private set; } + Os = os; + Handle = handle; + } - public NativeHandle(OsState os, Handle handle) - { - Os = os; - Handle = handle; - } + public NativeHandle(OsState os, Handle handle, bool isManaged) + { + Handle = handle; + IsManaged = isManaged; + } - public NativeHandle(OsState os, Handle handle, bool isManaged) - { - Handle = handle; - IsManaged = isManaged; - } - - public void Dispose() - { - if (IsManaged) - Os.CloseNativeHandle(Handle.Object); - } + public void Dispose() + { + if (IsManaged) + Os.CloseNativeHandle(Handle.Object); } } diff --git a/src/LibHac/Sf/ResultSf.cs b/src/LibHac/Sf/ResultSf.cs index e22a4a91..9fe2a7b9 100644 --- a/src/LibHac/Sf/ResultSf.cs +++ b/src/LibHac/Sf/ResultSf.cs @@ -11,23 +11,22 @@ using System.Runtime.CompilerServices; -namespace LibHac.Sf +namespace LibHac.Sf; + +public static class ResultSf { - public static class ResultSf - { - public const int ModuleSf = 10; + public const int ModuleSf = 10; - /// Error code: 2010-0001; Inner value: 0x20a - public static Result.Base NotSupported => new Result.Base(ModuleSf, 1); - /// Error code: 2010-0003; Inner value: 0x60a - public static Result.Base PreconditionViolation => new Result.Base(ModuleSf, 3); + /// Error code: 2010-0001; Inner value: 0x20a + public static Result.Base NotSupported => new Result.Base(ModuleSf, 1); + /// Error code: 2010-0003; Inner value: 0x60a + public static Result.Base PreconditionViolation => new Result.Base(ModuleSf, 3); - /// Error code: 2010-0010; Range: 10-19; Inner value: 0x140a - public static Result.Base MemoryAllocationFailed => new Result.Base(ModuleSf, 10, 19); + /// Error code: 2010-0010; Range: 10-19; Inner value: 0x140a + public static Result.Base MemoryAllocationFailed => new Result.Base(ModuleSf, 10, 19); - /// Error code: 2010-0811; Range: 811-819 - public static Result.Base.Abstract RequestDeferred { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 811, 819); } - /// Error code: 2010-0812; Inner value: 0x6580a - public static Result.Base RequestDeferredByUser => new Result.Base(ModuleSf, 812); - } + /// Error code: 2010-0811; Range: 811-819 + public static Result.Base.Abstract RequestDeferred { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base.Abstract(ModuleSf, 811, 819); } + /// Error code: 2010-0812; Inner value: 0x6580a + public static Result.Base RequestDeferredByUser => new Result.Base(ModuleSf, 812); } diff --git a/src/LibHac/Sm/IServiceObject.cs b/src/LibHac/Sm/IServiceObject.cs index 860946c7..05b294d9 100644 --- a/src/LibHac/Sm/IServiceObject.cs +++ b/src/LibHac/Sm/IServiceObject.cs @@ -1,12 +1,11 @@ using System; using LibHac.Common; -namespace LibHac.Sm +namespace LibHac.Sm; + +// This interface is being used as a stop-gap solution so we can +// have at least some sort of service system for now +public interface IServiceObject : IDisposable { - // This interface is being used as a stop-gap solution so we can - // have at least some sort of service system for now - public interface IServiceObject : IDisposable - { - Result GetServiceObject(ref SharedRef serviceObject); - } + Result GetServiceObject(ref SharedRef serviceObject); } diff --git a/src/LibHac/Sm/ResultSm.cs b/src/LibHac/Sm/ResultSm.cs index c2248033..63551c78 100644 --- a/src/LibHac/Sm/ResultSm.cs +++ b/src/LibHac/Sm/ResultSm.cs @@ -9,29 +9,28 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Sm -{ - public static class ResultSm - { - public const int ModuleSm = 21; +namespace LibHac.Sm; - /// Error code: 2021-0001; Inner value: 0x215 - public static Result.Base OutOfProcesses => new Result.Base(ModuleSm, 1); - /// Error code: 2021-0002; Inner value: 0x415 - public static Result.Base InvalidClient => new Result.Base(ModuleSm, 2); - /// Error code: 2021-0003; Inner value: 0x615 - public static Result.Base OutOfSessions => new Result.Base(ModuleSm, 3); - /// Error code: 2021-0004; Inner value: 0x815 - public static Result.Base AlreadyRegistered => new Result.Base(ModuleSm, 4); - /// Error code: 2021-0005; Inner value: 0xa15 - public static Result.Base OutOfServices => new Result.Base(ModuleSm, 5); - /// Error code: 2021-0006; Inner value: 0xc15 - public static Result.Base InvalidServiceName => new Result.Base(ModuleSm, 6); - /// Error code: 2021-0007; Inner value: 0xe15 - public static Result.Base NotRegistered => new Result.Base(ModuleSm, 7); - /// Error code: 2021-0008; Inner value: 0x1015 - public static Result.Base NotAllowed => new Result.Base(ModuleSm, 8); - /// Error code: 2021-0009; Inner value: 0x1215 - public static Result.Base TooLargeAccessControl => new Result.Base(ModuleSm, 9); - } +public static class ResultSm +{ + public const int ModuleSm = 21; + + /// Error code: 2021-0001; Inner value: 0x215 + public static Result.Base OutOfProcesses => new Result.Base(ModuleSm, 1); + /// Error code: 2021-0002; Inner value: 0x415 + public static Result.Base InvalidClient => new Result.Base(ModuleSm, 2); + /// Error code: 2021-0003; Inner value: 0x615 + public static Result.Base OutOfSessions => new Result.Base(ModuleSm, 3); + /// Error code: 2021-0004; Inner value: 0x815 + public static Result.Base AlreadyRegistered => new Result.Base(ModuleSm, 4); + /// Error code: 2021-0005; Inner value: 0xa15 + public static Result.Base OutOfServices => new Result.Base(ModuleSm, 5); + /// Error code: 2021-0006; Inner value: 0xc15 + public static Result.Base InvalidServiceName => new Result.Base(ModuleSm, 6); + /// Error code: 2021-0007; Inner value: 0xe15 + public static Result.Base NotRegistered => new Result.Base(ModuleSm, 7); + /// Error code: 2021-0008; Inner value: 0x1015 + public static Result.Base NotAllowed => new Result.Base(ModuleSm, 8); + /// Error code: 2021-0009; Inner value: 0x1215 + public static Result.Base TooLargeAccessControl => new Result.Base(ModuleSm, 9); } diff --git a/src/LibHac/Sm/ServiceManager.cs b/src/LibHac/Sm/ServiceManager.cs index 1e4c5b24..69f70b3d 100644 --- a/src/LibHac/Sm/ServiceManager.cs +++ b/src/LibHac/Sm/ServiceManager.cs @@ -4,86 +4,85 @@ using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Svc; -namespace LibHac.Sm +namespace LibHac.Sm; + +// This is basically a makeshift service manager that doesn't do anything +// other than keep service objects for now. It's just here so other stuff +// isn't blocked waiting for something better. +internal class ServiceManager { - // This is basically a makeshift service manager that doesn't do anything - // other than keep service objects for now. It's just here so other stuff - // isn't blocked waiting for something better. - internal class ServiceManager + private Dictionary Services { get; } = new Dictionary(); + + internal Result GetService(ref SharedRef outServiceObject, ServiceName serviceName) { - private Dictionary Services { get; } = new Dictionary(); + Result rc = ValidateServiceName(serviceName); + if (rc.IsFailure()) return rc; - internal Result GetService(ref SharedRef outServiceObject, ServiceName serviceName) + if (!Services.TryGetValue(serviceName, out IServiceObject service)) { - Result rc = ValidateServiceName(serviceName); - if (rc.IsFailure()) return rc; - - if (!Services.TryGetValue(serviceName, out IServiceObject service)) - { - return ResultSvc.NotFound.Log(); - } - - return service.GetServiceObject(ref outServiceObject); + return ResultSvc.NotFound.Log(); } - internal Result RegisterService(IServiceObject service, ServiceName serviceName) + return service.GetServiceObject(ref outServiceObject); + } + + internal Result RegisterService(IServiceObject service, ServiceName serviceName) + { + Result rc = ValidateServiceName(serviceName); + if (rc.IsFailure()) return rc; + + if (!Services.TryAdd(serviceName, service)) { - Result rc = ValidateServiceName(serviceName); - if (rc.IsFailure()) return rc; - - if (!Services.TryAdd(serviceName, service)) - { - return ResultSm.AlreadyRegistered.Log(); - } - - return Result.Success; + return ResultSm.AlreadyRegistered.Log(); } - internal Result UnregisterService(ServiceName serviceName) + return Result.Success; + } + + internal Result UnregisterService(ServiceName serviceName) + { + Result rc = ValidateServiceName(serviceName); + if (rc.IsFailure()) return rc; + + if (!Services.Remove(serviceName, out IServiceObject service)) { - Result rc = ValidateServiceName(serviceName); - if (rc.IsFailure()) return rc; - - if (!Services.Remove(serviceName, out IServiceObject service)) - { - return ResultSm.NotRegistered.Log(); - } - - // ReSharper disable once SuspiciousTypeConversion.Global - if (service is IDisposable disposable) - { - disposable.Dispose(); - } - - return Result.Success; + return ResultSm.NotRegistered.Log(); } - private Result ValidateServiceName(ServiceName name) + // ReSharper disable once SuspiciousTypeConversion.Global + if (service is IDisposable disposable) { - // Service names must be non-empty. - if (name.Name == 0) + disposable.Dispose(); + } + + return Result.Success; + } + + private Result ValidateServiceName(ServiceName name) + { + // Service names must be non-empty. + if (name.Name == 0) + return ResultSm.InvalidServiceName.Log(); + + // Get name length. + int nameLen; + for (nameLen = 1; nameLen < Unsafe.SizeOf(); nameLen++) + { + if (SpanHelpers.AsReadOnlyByteSpan(in name)[nameLen] == 0) + { + break; + } + } + + // Names must be all-zero after they end. + for (; nameLen < Unsafe.SizeOf(); nameLen++) + { + if (SpanHelpers.AsReadOnlyByteSpan(in name)[nameLen] != 0) + { return ResultSm.InvalidServiceName.Log(); - - // Get name length. - int nameLen; - for (nameLen = 1; nameLen < Unsafe.SizeOf(); nameLen++) - { - if (SpanHelpers.AsReadOnlyByteSpan(in name)[nameLen] == 0) - { - break; - } } - - // Names must be all-zero after they end. - for (; nameLen < Unsafe.SizeOf(); nameLen++) - { - if (SpanHelpers.AsReadOnlyByteSpan(in name)[nameLen] != 0) - { - return ResultSm.InvalidServiceName.Log(); - } - } - - return Result.Success; } + + return Result.Success; } } diff --git a/src/LibHac/Sm/ServiceManagerClient.cs b/src/LibHac/Sm/ServiceManagerClient.cs index 45270e3f..bc664ff8 100644 --- a/src/LibHac/Sm/ServiceManagerClient.cs +++ b/src/LibHac/Sm/ServiceManagerClient.cs @@ -1,40 +1,39 @@ using System; using LibHac.Common; -namespace LibHac.Sm +namespace LibHac.Sm; + +public class ServiceManagerClient { - public class ServiceManagerClient + private ServiceManager Server { get; } + + internal ServiceManagerClient(ServiceManager server) { - private ServiceManager Server { get; } + Server = server; + } - internal ServiceManagerClient(ServiceManager server) + public Result GetService(ref SharedRef serviceObject, ReadOnlySpan name) where T : class, IDisposable + { + using var service = new SharedRef(); + + Result rc = Server.GetService(ref service.Ref(), ServiceName.Encode(name)); + if (rc.IsFailure()) return rc.Miss(); + + if (serviceObject.TryCastSet(ref service.Ref())) { - Server = server; + return Result.Success; } - public Result GetService(ref SharedRef serviceObject, ReadOnlySpan name) where T : class, IDisposable - { - using var service = new SharedRef(); + throw new InvalidCastException("The service object is not of the specified type."); + } - Result rc = Server.GetService(ref service.Ref(), ServiceName.Encode(name)); - if (rc.IsFailure()) return rc.Miss(); + public Result RegisterService(IServiceObject serviceObject, ReadOnlySpan name) + { + return Server.RegisterService(serviceObject, ServiceName.Encode(name)); + } - if (serviceObject.TryCastSet(ref service.Ref())) - { - return Result.Success; - } - - throw new InvalidCastException("The service object is not of the specified type."); - } - - public Result RegisterService(IServiceObject serviceObject, ReadOnlySpan name) - { - return Server.RegisterService(serviceObject, ServiceName.Encode(name)); - } - - public Result UnregisterService(ReadOnlySpan name) - { - return Server.UnregisterService(ServiceName.Encode(name)); - } + public Result UnregisterService(ReadOnlySpan name) + { + return Server.UnregisterService(ServiceName.Encode(name)); } } diff --git a/src/LibHac/Sm/ServiceName.cs b/src/LibHac/Sm/ServiceName.cs index 3304d4ff..a5baf17f 100644 --- a/src/LibHac/Sm/ServiceName.cs +++ b/src/LibHac/Sm/ServiceName.cs @@ -3,42 +3,41 @@ using System.Diagnostics; using LibHac.Common; using LibHac.Util; -namespace LibHac.Sm +namespace LibHac.Sm; + +[DebuggerDisplay("{ToString()}")] +public readonly struct ServiceName : IEquatable { - [DebuggerDisplay("{ToString()}")] - public readonly struct ServiceName : IEquatable + private const int MaxLength = 8; + + // The Name should always be assigned in the below Encode method + // ReSharper disable once UnassignedReadonlyField + public readonly ulong Name; + + public static ServiceName Encode(ReadOnlySpan name) { - private const int MaxLength = 8; + var outName = new ServiceName(); + int length = Math.Min(MaxLength, name.Length); - // The Name should always be assigned in the below Encode method - // ReSharper disable once UnassignedReadonlyField - public readonly ulong Name; - - public static ServiceName Encode(ReadOnlySpan name) + for (int i = 0; i < length; i++) { - var outName = new ServiceName(); - int length = Math.Min(MaxLength, name.Length); - - for (int i = 0; i < length; i++) - { - SpanHelpers.AsByteSpan(ref outName)[i] = (byte)name[i]; - } - - return outName; + SpanHelpers.AsByteSpan(ref outName)[i] = (byte)name[i]; } - public override bool Equals(object obj) => obj is ServiceName name && Equals(name); - public bool Equals(ServiceName other) => Name == other.Name; + return outName; + } - public override int GetHashCode() => Name.GetHashCode(); + public override bool Equals(object obj) => obj is ServiceName name && Equals(name); + public bool Equals(ServiceName other) => Name == other.Name; - public static bool operator ==(ServiceName left, ServiceName right) => left.Equals(right); - public static bool operator !=(ServiceName left, ServiceName right) => !(left == right); + public override int GetHashCode() => Name.GetHashCode(); - public override string ToString() - { - ulong name = Name; - return StringUtils.Utf8ZToString(SpanHelpers.AsReadOnlyByteSpan(in name)); - } + public static bool operator ==(ServiceName left, ServiceName right) => left.Equals(right); + public static bool operator !=(ServiceName left, ServiceName right) => !(left == right); + + public override string ToString() + { + ulong name = Name; + return StringUtils.Utf8ZToString(SpanHelpers.AsReadOnlyByteSpan(in name)); } } diff --git a/src/LibHac/Spl/AccessKey.cs b/src/LibHac/Spl/AccessKey.cs index 2a8e0be2..e8cb0772 100644 --- a/src/LibHac/Spl/AccessKey.cs +++ b/src/LibHac/Spl/AccessKey.cs @@ -3,27 +3,26 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; -namespace LibHac.Spl +namespace LibHac.Spl; + +[DebuggerDisplay("{ToString()}")] +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +public struct AccessKey : IEquatable { - [DebuggerDisplay("{ToString()}")] - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - public struct AccessKey : IEquatable + private readonly Key128 Key; + + public ReadOnlySpan Value => SpanHelpers.AsByteSpan(ref this); + + public AccessKey(ReadOnlySpan bytes) { - private readonly Key128 Key; - - public ReadOnlySpan Value => SpanHelpers.AsByteSpan(ref this); - - public AccessKey(ReadOnlySpan bytes) - { - Key = new Key128(bytes); - } - - public override string ToString() => Key.ToString(); - - public override bool Equals(object obj) => obj is AccessKey key && Equals(key); - public bool Equals(AccessKey other) => Key.Equals(other.Key); - public override int GetHashCode() => Key.GetHashCode(); - public static bool operator ==(AccessKey left, AccessKey right) => left.Equals(right); - public static bool operator !=(AccessKey left, AccessKey right) => !(left == right); + Key = new Key128(bytes); } + + public override string ToString() => Key.ToString(); + + public override bool Equals(object obj) => obj is AccessKey key && Equals(key); + public bool Equals(AccessKey other) => Key.Equals(other.Key); + public override int GetHashCode() => Key.GetHashCode(); + public static bool operator ==(AccessKey left, AccessKey right) => left.Equals(right); + public static bool operator !=(AccessKey left, AccessKey right) => !(left == right); } diff --git a/src/LibHac/Spl/ResultSpl.cs b/src/LibHac/Spl/ResultSpl.cs index 558a4349..76872009 100644 --- a/src/LibHac/Spl/ResultSpl.cs +++ b/src/LibHac/Spl/ResultSpl.cs @@ -9,44 +9,43 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Spl +namespace LibHac.Spl; + +public static class ResultSpl { - public static class ResultSpl - { - public const int ModuleSpl = 26; + public const int ModuleSpl = 26; - /// Error code: 2026-0000; Range: 0-99; Inner value: 0x1a - public static Result.Base SecureMonitorError => new Result.Base(ModuleSpl, 0, 99); - /// Error code: 2026-0001; Inner value: 0x21a - public static Result.Base SecureMonitorNotImplemented => new Result.Base(ModuleSpl, 1); - /// Error code: 2026-0002; Inner value: 0x41a - public static Result.Base SecureMonitorInvalidArgument => new Result.Base(ModuleSpl, 2); - /// Error code: 2026-0003; Inner value: 0x61a - public static Result.Base SecureMonitorBusy => new Result.Base(ModuleSpl, 3); - /// Error code: 2026-0004; Inner value: 0x81a - public static Result.Base SecureMonitorNoAsyncOperation => new Result.Base(ModuleSpl, 4); - /// Error code: 2026-0005; Inner value: 0xa1a - public static Result.Base SecureMonitorInvalidAsyncOperation => new Result.Base(ModuleSpl, 5); - /// Error code: 2026-0006; Inner value: 0xc1a - public static Result.Base SecureMonitorNotPermitted => new Result.Base(ModuleSpl, 6); - /// Error code: 2026-0007; Inner value: 0xe1a - public static Result.Base SecureMonitorNotInitialized => new Result.Base(ModuleSpl, 7); + /// Error code: 2026-0000; Range: 0-99; Inner value: 0x1a + public static Result.Base SecureMonitorError => new Result.Base(ModuleSpl, 0, 99); + /// Error code: 2026-0001; Inner value: 0x21a + public static Result.Base SecureMonitorNotImplemented => new Result.Base(ModuleSpl, 1); + /// Error code: 2026-0002; Inner value: 0x41a + public static Result.Base SecureMonitorInvalidArgument => new Result.Base(ModuleSpl, 2); + /// Error code: 2026-0003; Inner value: 0x61a + public static Result.Base SecureMonitorBusy => new Result.Base(ModuleSpl, 3); + /// Error code: 2026-0004; Inner value: 0x81a + public static Result.Base SecureMonitorNoAsyncOperation => new Result.Base(ModuleSpl, 4); + /// Error code: 2026-0005; Inner value: 0xa1a + public static Result.Base SecureMonitorInvalidAsyncOperation => new Result.Base(ModuleSpl, 5); + /// Error code: 2026-0006; Inner value: 0xc1a + public static Result.Base SecureMonitorNotPermitted => new Result.Base(ModuleSpl, 6); + /// Error code: 2026-0007; Inner value: 0xe1a + public static Result.Base SecureMonitorNotInitialized => new Result.Base(ModuleSpl, 7); - /// Error code: 2026-0100; Inner value: 0xc81a - public static Result.Base InvalidSize => new Result.Base(ModuleSpl, 100); - /// Error code: 2026-0101; Inner value: 0xca1a - public static Result.Base UnknownSecureMonitorError => new Result.Base(ModuleSpl, 101); - /// Error code: 2026-0102; Inner value: 0xcc1a - public static Result.Base DecryptionFailed => new Result.Base(ModuleSpl, 102); - /// Error code: 2026-0104; Inner value: 0xd01a - public static Result.Base OutOfKeySlots => new Result.Base(ModuleSpl, 104); - /// Error code: 2026-0105; Inner value: 0xd21a - public static Result.Base InvalidKeySlot => new Result.Base(ModuleSpl, 105); - /// Error code: 2026-0106; Inner value: 0xd41a - public static Result.Base BootReasonAlreadySet => new Result.Base(ModuleSpl, 106); - /// Error code: 2026-0107; Inner value: 0xd61a - public static Result.Base BootReasonNotSet => new Result.Base(ModuleSpl, 107); - /// Error code: 2026-0108; Inner value: 0xd81a - public static Result.Base InvalidArgument => new Result.Base(ModuleSpl, 108); - } + /// Error code: 2026-0100; Inner value: 0xc81a + public static Result.Base InvalidSize => new Result.Base(ModuleSpl, 100); + /// Error code: 2026-0101; Inner value: 0xca1a + public static Result.Base UnknownSecureMonitorError => new Result.Base(ModuleSpl, 101); + /// Error code: 2026-0102; Inner value: 0xcc1a + public static Result.Base DecryptionFailed => new Result.Base(ModuleSpl, 102); + /// Error code: 2026-0104; Inner value: 0xd01a + public static Result.Base OutOfKeySlots => new Result.Base(ModuleSpl, 104); + /// Error code: 2026-0105; Inner value: 0xd21a + public static Result.Base InvalidKeySlot => new Result.Base(ModuleSpl, 105); + /// Error code: 2026-0106; Inner value: 0xd41a + public static Result.Base BootReasonAlreadySet => new Result.Base(ModuleSpl, 106); + /// Error code: 2026-0107; Inner value: 0xd61a + public static Result.Base BootReasonNotSet => new Result.Base(ModuleSpl, 107); + /// Error code: 2026-0108; Inner value: 0xd81a + public static Result.Base InvalidArgument => new Result.Base(ModuleSpl, 108); } diff --git a/src/LibHac/Svc/Handle.cs b/src/LibHac/Svc/Handle.cs index 19d0d1ee..33b051ba 100644 --- a/src/LibHac/Svc/Handle.cs +++ b/src/LibHac/Svc/Handle.cs @@ -1,12 +1,11 @@ -namespace LibHac.Svc -{ - public readonly struct Handle - { - public readonly object Object; +namespace LibHac.Svc; - public Handle(object obj) - { - Object = obj; - } +public readonly struct Handle +{ + public readonly object Object; + + public Handle(object obj) + { + Object = obj; } } diff --git a/src/LibHac/Svc/ResultSvc.cs b/src/LibHac/Svc/ResultSvc.cs index 25806c7b..1f9b3835 100644 --- a/src/LibHac/Svc/ResultSvc.cs +++ b/src/LibHac/Svc/ResultSvc.cs @@ -9,99 +9,98 @@ // code generation portion of the build. //----------------------------------------------------------------------------- -namespace LibHac.Svc -{ - public static class ResultSvc - { - public const int ModuleSvc = 1; +namespace LibHac.Svc; - /// Error code: 2001-0007; Inner value: 0xe01 - public static Result.Base OutOfSessions => new Result.Base(ModuleSvc, 7); - /// Error code: 2001-0014; Inner value: 0x1c01 - public static Result.Base InvalidArgument => new Result.Base(ModuleSvc, 14); - /// Error code: 2001-0033; Inner value: 0x4201 - public static Result.Base NotImplemented => new Result.Base(ModuleSvc, 33); - /// Error code: 2001-0054; Inner value: 0x6c01 - public static Result.Base StopProcessingException => new Result.Base(ModuleSvc, 54); - /// Error code: 2001-0057; Inner value: 0x7201 - public static Result.Base NoSynchronizationObject => new Result.Base(ModuleSvc, 57); - /// Error code: 2001-0059; Inner value: 0x7601 - public static Result.Base TerminationRequested => new Result.Base(ModuleSvc, 59); - /// Error code: 2001-0070; Inner value: 0x8c01 - public static Result.Base NoEvent => new Result.Base(ModuleSvc, 70); - /// Error code: 2001-0101; Inner value: 0xca01 - public static Result.Base InvalidSize => new Result.Base(ModuleSvc, 101); - /// Error code: 2001-0102; Inner value: 0xcc01 - public static Result.Base InvalidAddress => new Result.Base(ModuleSvc, 102); - /// Error code: 2001-0103; Inner value: 0xce01 - public static Result.Base OutOfResource => new Result.Base(ModuleSvc, 103); - /// Error code: 2001-0104; Inner value: 0xd001 - public static Result.Base OutOfMemory => new Result.Base(ModuleSvc, 104); - /// Error code: 2001-0105; Inner value: 0xd201 - public static Result.Base OutOfHandles => new Result.Base(ModuleSvc, 105); - /// Error code: 2001-0106; Inner value: 0xd401 - public static Result.Base InvalidCurrentMemory => new Result.Base(ModuleSvc, 106); - /// Error code: 2001-0108; Inner value: 0xd801 - public static Result.Base InvalidNewMemoryPermission => new Result.Base(ModuleSvc, 108); - /// Error code: 2001-0110; Inner value: 0xdc01 - public static Result.Base InvalidMemoryRegion => new Result.Base(ModuleSvc, 110); - /// Error code: 2001-0112; Inner value: 0xe001 - public static Result.Base InvalidPriority => new Result.Base(ModuleSvc, 112); - /// Error code: 2001-0113; Inner value: 0xe201 - public static Result.Base InvalidCoreId => new Result.Base(ModuleSvc, 113); - /// Error code: 2001-0114; Inner value: 0xe401 - public static Result.Base InvalidHandle => new Result.Base(ModuleSvc, 114); - /// Error code: 2001-0115; Inner value: 0xe601 - public static Result.Base InvalidPointer => new Result.Base(ModuleSvc, 115); - /// Error code: 2001-0116; Inner value: 0xe801 - public static Result.Base InvalidCombination => new Result.Base(ModuleSvc, 116); - /// Error code: 2001-0117; Inner value: 0xea01 - public static Result.Base TimedOut => new Result.Base(ModuleSvc, 117); - /// Error code: 2001-0118; Inner value: 0xec01 - public static Result.Base Cancelled => new Result.Base(ModuleSvc, 118); - /// Error code: 2001-0119; Inner value: 0xee01 - public static Result.Base OutOfRange => new Result.Base(ModuleSvc, 119); - /// Error code: 2001-0120; Inner value: 0xf001 - public static Result.Base InvalidEnumValue => new Result.Base(ModuleSvc, 120); - /// Error code: 2001-0121; Inner value: 0xf201 - public static Result.Base NotFound => new Result.Base(ModuleSvc, 121); - /// Error code: 2001-0122; Inner value: 0xf401 - public static Result.Base Busy => new Result.Base(ModuleSvc, 122); - /// Error code: 2001-0123; Inner value: 0xf601 - public static Result.Base SessionClosed => new Result.Base(ModuleSvc, 123); - /// Error code: 2001-0124; Inner value: 0xf801 - public static Result.Base NotHandled => new Result.Base(ModuleSvc, 124); - /// Error code: 2001-0125; Inner value: 0xfa01 - public static Result.Base InvalidState => new Result.Base(ModuleSvc, 125); - /// Error code: 2001-0126; Inner value: 0xfc01 - public static Result.Base ReservedUsed => new Result.Base(ModuleSvc, 126); - /// Error code: 2001-0127; Inner value: 0xfe01 - public static Result.Base NotSupported => new Result.Base(ModuleSvc, 127); - /// Error code: 2001-0128; Inner value: 0x10001 - public static Result.Base Debug => new Result.Base(ModuleSvc, 128); - /// Error code: 2001-0129; Inner value: 0x10201 - public static Result.Base NoThread => new Result.Base(ModuleSvc, 129); - /// Error code: 2001-0130; Inner value: 0x10401 - public static Result.Base UnknownThread => new Result.Base(ModuleSvc, 130); - /// Error code: 2001-0131; Inner value: 0x10601 - public static Result.Base PortClosed => new Result.Base(ModuleSvc, 131); - /// Error code: 2001-0132; Inner value: 0x10801 - public static Result.Base LimitReached => new Result.Base(ModuleSvc, 132); - /// Error code: 2001-0133; Inner value: 0x10a01 - public static Result.Base InvalidMemoryPool => new Result.Base(ModuleSvc, 133); - /// Error code: 2001-0258; Inner value: 0x20401 - public static Result.Base ReceiveListBroken => new Result.Base(ModuleSvc, 258); - /// Error code: 2001-0259; Inner value: 0x20601 - public static Result.Base OutOfAddressSpace => new Result.Base(ModuleSvc, 259); - /// Error code: 2001-0260; Inner value: 0x20801 - public static Result.Base MessageTooLarge => new Result.Base(ModuleSvc, 260); - /// Error code: 2001-0517; Inner value: 0x40a01 - public static Result.Base InvalidProcessId => new Result.Base(ModuleSvc, 517); - /// Error code: 2001-0518; Inner value: 0x40c01 - public static Result.Base InvalidThreadId => new Result.Base(ModuleSvc, 518); - /// Error code: 2001-0519; Inner value: 0x40e01 - public static Result.Base InvalidId => new Result.Base(ModuleSvc, 519); - /// Error code: 2001-0520; Inner value: 0x41001 - public static Result.Base ProcessTerminated => new Result.Base(ModuleSvc, 520); - } +public static class ResultSvc +{ + public const int ModuleSvc = 1; + + /// Error code: 2001-0007; Inner value: 0xe01 + public static Result.Base OutOfSessions => new Result.Base(ModuleSvc, 7); + /// Error code: 2001-0014; Inner value: 0x1c01 + public static Result.Base InvalidArgument => new Result.Base(ModuleSvc, 14); + /// Error code: 2001-0033; Inner value: 0x4201 + public static Result.Base NotImplemented => new Result.Base(ModuleSvc, 33); + /// Error code: 2001-0054; Inner value: 0x6c01 + public static Result.Base StopProcessingException => new Result.Base(ModuleSvc, 54); + /// Error code: 2001-0057; Inner value: 0x7201 + public static Result.Base NoSynchronizationObject => new Result.Base(ModuleSvc, 57); + /// Error code: 2001-0059; Inner value: 0x7601 + public static Result.Base TerminationRequested => new Result.Base(ModuleSvc, 59); + /// Error code: 2001-0070; Inner value: 0x8c01 + public static Result.Base NoEvent => new Result.Base(ModuleSvc, 70); + /// Error code: 2001-0101; Inner value: 0xca01 + public static Result.Base InvalidSize => new Result.Base(ModuleSvc, 101); + /// Error code: 2001-0102; Inner value: 0xcc01 + public static Result.Base InvalidAddress => new Result.Base(ModuleSvc, 102); + /// Error code: 2001-0103; Inner value: 0xce01 + public static Result.Base OutOfResource => new Result.Base(ModuleSvc, 103); + /// Error code: 2001-0104; Inner value: 0xd001 + public static Result.Base OutOfMemory => new Result.Base(ModuleSvc, 104); + /// Error code: 2001-0105; Inner value: 0xd201 + public static Result.Base OutOfHandles => new Result.Base(ModuleSvc, 105); + /// Error code: 2001-0106; Inner value: 0xd401 + public static Result.Base InvalidCurrentMemory => new Result.Base(ModuleSvc, 106); + /// Error code: 2001-0108; Inner value: 0xd801 + public static Result.Base InvalidNewMemoryPermission => new Result.Base(ModuleSvc, 108); + /// Error code: 2001-0110; Inner value: 0xdc01 + public static Result.Base InvalidMemoryRegion => new Result.Base(ModuleSvc, 110); + /// Error code: 2001-0112; Inner value: 0xe001 + public static Result.Base InvalidPriority => new Result.Base(ModuleSvc, 112); + /// Error code: 2001-0113; Inner value: 0xe201 + public static Result.Base InvalidCoreId => new Result.Base(ModuleSvc, 113); + /// Error code: 2001-0114; Inner value: 0xe401 + public static Result.Base InvalidHandle => new Result.Base(ModuleSvc, 114); + /// Error code: 2001-0115; Inner value: 0xe601 + public static Result.Base InvalidPointer => new Result.Base(ModuleSvc, 115); + /// Error code: 2001-0116; Inner value: 0xe801 + public static Result.Base InvalidCombination => new Result.Base(ModuleSvc, 116); + /// Error code: 2001-0117; Inner value: 0xea01 + public static Result.Base TimedOut => new Result.Base(ModuleSvc, 117); + /// Error code: 2001-0118; Inner value: 0xec01 + public static Result.Base Cancelled => new Result.Base(ModuleSvc, 118); + /// Error code: 2001-0119; Inner value: 0xee01 + public static Result.Base OutOfRange => new Result.Base(ModuleSvc, 119); + /// Error code: 2001-0120; Inner value: 0xf001 + public static Result.Base InvalidEnumValue => new Result.Base(ModuleSvc, 120); + /// Error code: 2001-0121; Inner value: 0xf201 + public static Result.Base NotFound => new Result.Base(ModuleSvc, 121); + /// Error code: 2001-0122; Inner value: 0xf401 + public static Result.Base Busy => new Result.Base(ModuleSvc, 122); + /// Error code: 2001-0123; Inner value: 0xf601 + public static Result.Base SessionClosed => new Result.Base(ModuleSvc, 123); + /// Error code: 2001-0124; Inner value: 0xf801 + public static Result.Base NotHandled => new Result.Base(ModuleSvc, 124); + /// Error code: 2001-0125; Inner value: 0xfa01 + public static Result.Base InvalidState => new Result.Base(ModuleSvc, 125); + /// Error code: 2001-0126; Inner value: 0xfc01 + public static Result.Base ReservedUsed => new Result.Base(ModuleSvc, 126); + /// Error code: 2001-0127; Inner value: 0xfe01 + public static Result.Base NotSupported => new Result.Base(ModuleSvc, 127); + /// Error code: 2001-0128; Inner value: 0x10001 + public static Result.Base Debug => new Result.Base(ModuleSvc, 128); + /// Error code: 2001-0129; Inner value: 0x10201 + public static Result.Base NoThread => new Result.Base(ModuleSvc, 129); + /// Error code: 2001-0130; Inner value: 0x10401 + public static Result.Base UnknownThread => new Result.Base(ModuleSvc, 130); + /// Error code: 2001-0131; Inner value: 0x10601 + public static Result.Base PortClosed => new Result.Base(ModuleSvc, 131); + /// Error code: 2001-0132; Inner value: 0x10801 + public static Result.Base LimitReached => new Result.Base(ModuleSvc, 132); + /// Error code: 2001-0133; Inner value: 0x10a01 + public static Result.Base InvalidMemoryPool => new Result.Base(ModuleSvc, 133); + /// Error code: 2001-0258; Inner value: 0x20401 + public static Result.Base ReceiveListBroken => new Result.Base(ModuleSvc, 258); + /// Error code: 2001-0259; Inner value: 0x20601 + public static Result.Base OutOfAddressSpace => new Result.Base(ModuleSvc, 259); + /// Error code: 2001-0260; Inner value: 0x20801 + public static Result.Base MessageTooLarge => new Result.Base(ModuleSvc, 260); + /// Error code: 2001-0517; Inner value: 0x40a01 + public static Result.Base InvalidProcessId => new Result.Base(ModuleSvc, 517); + /// Error code: 2001-0518; Inner value: 0x40c01 + public static Result.Base InvalidThreadId => new Result.Base(ModuleSvc, 518); + /// Error code: 2001-0519; Inner value: 0x40e01 + public static Result.Base InvalidId => new Result.Base(ModuleSvc, 519); + /// Error code: 2001-0520; Inner value: 0x41001 + public static Result.Base ProcessTerminated => new Result.Base(ModuleSvc, 520); } diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 61143cad..531cd2c7 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -14,411 +14,410 @@ using LibHac.Ncm; using LibHac.Ns; using LibHac.Util; -namespace LibHac +namespace LibHac; + +public class SwitchFs : IDisposable { - public class SwitchFs : IDisposable + public KeySet KeySet { get; } + public IFileSystem ContentFs { get; } + public IFileSystem SaveFs { get; } + + public Dictionary Ncas { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public Dictionary Saves { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public Dictionary Titles { get; } = new Dictionary(); + public Dictionary Applications { get; } = new Dictionary(); + + public SwitchFs(KeySet keySet, IFileSystem contentFileSystem, IFileSystem saveFileSystem) { - public KeySet KeySet { get; } - public IFileSystem ContentFs { get; } - public IFileSystem SaveFs { get; } + KeySet = keySet; + ContentFs = contentFileSystem; + SaveFs = saveFileSystem; - public Dictionary Ncas { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - public Dictionary Saves { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - public Dictionary Titles { get; } = new Dictionary(); - public Dictionary Applications { get; } = new Dictionary(); + OpenAllSaves(); + OpenAllNcas(); + ReadTitles(); + ReadControls(); + CreateApplications(); + } - public SwitchFs(KeySet keySet, IFileSystem contentFileSystem, IFileSystem saveFileSystem) + public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem) + { + var concatFs = new ConcatenationFileSystem(fileSystem); + + using var contentDirPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref contentDirPath.Ref(), "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); + + using var saveDirPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref saveDirPath.Ref(), "/Nintendo/save".ToU8String()).ThrowIfFailure(); + + var contentDirFs = new SubdirectoryFileSystem(concatFs); + contentDirFs.Initialize(in contentDirPath).ThrowIfFailure(); + + AesXtsFileSystem encSaveFs = null; + if (fileSystem.DirectoryExists("/Nintendo/save")) { - KeySet = keySet; - ContentFs = contentFileSystem; - SaveFs = saveFileSystem; + var saveDirFs = new SubdirectoryFileSystem(concatFs); + saveDirFs.Initialize(in saveDirPath).ThrowIfFailure(); - OpenAllSaves(); - OpenAllNcas(); - ReadTitles(); - ReadControls(); - CreateApplications(); + encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000); } - public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem) + var encContentFs = new AesXtsFileSystem(contentDirFs, keySet.SdCardEncryptionKeys[1].DataRo.ToArray(), 0x4000); + + return new SwitchFs(keySet, encContentFs, encSaveFs); + } + + public static SwitchFs OpenNandPartition(KeySet keySet, IAttributeFileSystem fileSystem) + { + var concatFs = new ConcatenationFileSystem(fileSystem); + SubdirectoryFileSystem saveDirFs = null; + SubdirectoryFileSystem contentDirFs; + + if (concatFs.DirectoryExists("/save")) { - var concatFs = new ConcatenationFileSystem(fileSystem); + using var savePath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref savePath.Ref(), "/save".ToU8String()); - using var contentDirPath = new Fs.Path(); - PathFunctions.SetUpFixedPath(ref contentDirPath.Ref(), "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); + saveDirFs = new SubdirectoryFileSystem(concatFs); + saveDirFs.Initialize(in savePath).ThrowIfFailure(); + } - using var saveDirPath = new Fs.Path(); - PathFunctions.SetUpFixedPath(ref saveDirPath.Ref(), "/Nintendo/save".ToU8String()).ThrowIfFailure(); + using var contentsPath = new Fs.Path(); + PathFunctions.SetUpFixedPath(ref contentsPath.Ref(), "/Contents".ToU8String()); - var contentDirFs = new SubdirectoryFileSystem(concatFs); - contentDirFs.Initialize(in contentDirPath).ThrowIfFailure(); + contentDirFs = new SubdirectoryFileSystem(concatFs); + contentDirFs.Initialize(in contentsPath).ThrowIfFailure(); - AesXtsFileSystem encSaveFs = null; - if (fileSystem.DirectoryExists("/Nintendo/save")) + return new SwitchFs(keySet, contentDirFs, saveDirFs); + } + + public static SwitchFs OpenNcaDirectory(KeySet keySet, IFileSystem fileSystem) + { + return new SwitchFs(keySet, fileSystem, null); + } + + private void OpenAllNcas() + { + // Todo: give warning if directories named "*.nca" are found or manually fix the archive bit + IEnumerable files = ContentFs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories) + .Where(x => x.Type == DirectoryEntryType.File); + + foreach (DirectoryEntryEx fileEntry in files) + { + SwitchFsNca nca = null; + try { - var saveDirFs = new SubdirectoryFileSystem(concatFs); - saveDirFs.Initialize(in saveDirPath).ThrowIfFailure(); + using var ncaFile = new UniqueRef(); + ContentFs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000); + nca = new SwitchFsNca(new Nca(KeySet, ncaFile.Release().AsStorage())); + + nca.NcaId = GetNcaFilename(fileEntry.Name, nca); + string extension = nca.Nca.Header.ContentType == NcaContentType.Meta ? ".cnmt.nca" : ".nca"; + nca.Filename = nca.NcaId + extension; } - - var encContentFs = new AesXtsFileSystem(contentDirFs, keySet.SdCardEncryptionKeys[1].DataRo.ToArray(), 0x4000); - - return new SwitchFs(keySet, encContentFs, encSaveFs); - } - - public static SwitchFs OpenNandPartition(KeySet keySet, IAttributeFileSystem fileSystem) - { - var concatFs = new ConcatenationFileSystem(fileSystem); - SubdirectoryFileSystem saveDirFs = null; - SubdirectoryFileSystem contentDirFs; - - if (concatFs.DirectoryExists("/save")) + catch (MissingKeyException ex) { - using var savePath = new Fs.Path(); - PathFunctions.SetUpFixedPath(ref savePath.Ref(), "/save".ToU8String()); - - saveDirFs = new SubdirectoryFileSystem(concatFs); - saveDirFs.Initialize(in savePath).ThrowIfFailure(); - } - - using var contentsPath = new Fs.Path(); - PathFunctions.SetUpFixedPath(ref contentsPath.Ref(), "/Contents".ToU8String()); - - contentDirFs = new SubdirectoryFileSystem(concatFs); - contentDirFs.Initialize(in contentsPath).ThrowIfFailure(); - - return new SwitchFs(keySet, contentDirFs, saveDirFs); - } - - public static SwitchFs OpenNcaDirectory(KeySet keySet, IFileSystem fileSystem) - { - return new SwitchFs(keySet, fileSystem, null); - } - - private void OpenAllNcas() - { - // Todo: give warning if directories named "*.nca" are found or manually fix the archive bit - IEnumerable files = ContentFs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories) - .Where(x => x.Type == DirectoryEntryType.File); - - foreach (DirectoryEntryEx fileEntry in files) - { - SwitchFsNca nca = null; - try + if (ex.Name == null) + { Console.WriteLine($"{ex.Message} File:\n{fileEntry}"); } + else { - using var ncaFile = new UniqueRef(); - ContentFs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - nca = new SwitchFsNca(new Nca(KeySet, ncaFile.Release().AsStorage())); - - nca.NcaId = GetNcaFilename(fileEntry.Name, nca); - string extension = nca.Nca.Header.ContentType == NcaContentType.Meta ? ".cnmt.nca" : ".nca"; - nca.Filename = nca.NcaId + extension; - } - catch (MissingKeyException ex) - { - if (ex.Name == null) - { Console.WriteLine($"{ex.Message} File:\n{fileEntry}"); } - else - { - string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name; - Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {fileEntry}"); - } - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message} File: {fileEntry.FullPath}"); - } - - if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca); - } - } - - private void OpenAllSaves() - { - if (SaveFs == null) return; - - foreach (DirectoryEntryEx fileEntry in SaveFs.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) - { - SaveDataFileSystem save = null; - string saveName = System.IO.Path.GetFileNameWithoutExtension(fileEntry.Name); - - try - { - using var file = new UniqueRef(); - SaveFs.OpenFile(ref file.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - save = new SaveDataFileSystem(KeySet, file.Release().AsStorage(), IntegrityCheckLevel.None, true); - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message} File: {fileEntry.FullPath}"); - } - - if (save != null && saveName != null) - { - Saves[saveName] = save; + string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name; + Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {fileEntry}"); } } - } - - private void ReadTitles() - { - foreach (SwitchFsNca nca in Ncas.Values.Where(x => x.Nca.Header.ContentType == NcaContentType.Meta)) + catch (Exception ex) { - try - { - var title = new Title(); - - IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; - - using var file = new UniqueRef(); - fs.OpenFile(ref file.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - var metadata = new Cnmt(file.Release().AsStream()); - title.Id = metadata.TitleId; - title.Version = metadata.TitleVersion; - title.Metadata = metadata; - title.MetaNca = nca; - title.Ncas.Add(nca); - - foreach (CnmtContentEntry content in metadata.ContentEntries) - { - string ncaId = content.NcaId.ToHexString(); - - if (Ncas.TryGetValue(ncaId, out SwitchFsNca contentNca)) - { - title.Ncas.Add(contentNca); - } - - switch (content.Type) - { - case Ncm.ContentType.Program: - case Ncm.ContentType.Data: - title.MainNca = contentNca; - break; - case Ncm.ContentType.Control: - title.ControlNca = contentNca; - break; - } - } - - Titles[title.Id] = title; - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message} File: {nca.Filename}"); - } - } - } - - private void ReadControls() - { - foreach (Title title in Titles.Values.Where(x => x.ControlNca != null)) - { - IFileSystem romfs = title.ControlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - using (var control = new UniqueRef()) - { - romfs.OpenFile(ref control.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - control.Get.Read(out _, 0, title.Control.ByteSpan).ThrowIfFailure(); - } - - foreach (ref ApplicationControlTitle desc in title.Control.Value.Titles) - { - if (!desc.Name.IsEmpty()) - { - title.Name = desc.Name.ToString(); - break; - } - } - } - } - - private void CreateApplications() - { - foreach (Title title in Titles.Values.Where(x => x.Metadata.Type >= ContentMetaType.Application)) - { - Cnmt meta = title.Metadata; - ulong appId = meta.ApplicationTitleId; - - if (!Applications.TryGetValue(appId, out Application app)) - { - app = new Application(); - Applications.Add(appId, app); - } - - app.AddTitle(title); + Console.WriteLine($"{ex.Message} File: {fileEntry.FullPath}"); } - foreach (Application app in Applications.Values) - { - SwitchFsNca main = app.Main?.MainNca; - SwitchFsNca patch = app.Patch?.MainNca; - - if (main != null && patch != null) - { - patch.BaseNca = main.Nca; - } - } - } - - private string GetNcaFilename(string name, SwitchFsNca nca) - { - if (nca.Nca.Header.ContentType != NcaContentType.Meta || !name.EndsWith(".cnmt.nca")) - { - return System.IO.Path.GetFileNameWithoutExtension(name); - } - - return name.Substring(0, name.Length - ".cnmt.nca".Length); - } - - private void DisposeNcas() - { - //foreach (SwitchFsNca nca in Ncas.Values) - //{ - // nca.Dispose(); - //} - Ncas.Clear(); - Titles.Clear(); - } - - public void Dispose() - { - DisposeNcas(); + if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca); } } - public class SwitchFsNca + private void OpenAllSaves() { - public Nca Nca { get; set; } - public Nca BaseNca { get; set; } - public string NcaId { get; set; } - public string Filename { get; set; } + if (SaveFs == null) return; - public SwitchFsNca(Nca nca) + foreach (DirectoryEntryEx fileEntry in SaveFs.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) { - Nca = nca; - } + SaveDataFileSystem save = null; + string saveName = System.IO.Path.GetFileNameWithoutExtension(fileEntry.Name); - public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel) - { - if (BaseNca != null) return BaseNca.OpenStorageWithPatch(Nca, index, integrityCheckLevel); - - return Nca.OpenStorage(index, integrityCheckLevel); - } - - public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) - { - if (BaseNca != null) return BaseNca.OpenFileSystemWithPatch(Nca, index, integrityCheckLevel); - - return Nca.OpenFileSystem(index, integrityCheckLevel); - } - - public IStorage OpenStorage(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenStorage(Nca.GetSectionIndexFromType(type, Nca.Header.ContentType), integrityCheckLevel); - } - - public IFileSystem OpenFileSystem(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) - { - return OpenFileSystem(Nca.GetSectionIndexFromType(type, Nca.Header.ContentType), integrityCheckLevel); - } - - public Validity VerifyNca(IProgressReport logger = null, bool quiet = false) - { - if (BaseNca != null) + try { - return BaseNca.VerifyNca(Nca, logger, quiet); + using var file = new UniqueRef(); + SaveFs.OpenFile(ref file.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + save = new SaveDataFileSystem(KeySet, file.Release().AsStorage(), IntegrityCheckLevel.None, true); } - else + catch (Exception ex) { - return Nca.VerifyNca(logger, quiet); + Console.WriteLine($"{ex.Message} File: {fileEntry.FullPath}"); + } + + if (save != null && saveName != null) + { + Saves[saveName] = save; } } } - [DebuggerDisplay("{" + nameof(Name) + "}")] - public class Title + private void ReadTitles() { - public ulong Id { get; internal set; } - public TitleVersion Version { get; internal set; } - public List Ncas { get; } = new List(); - public Cnmt Metadata { get; internal set; } - - public string Name { get; internal set; } - public BlitStruct Control { get; } = new BlitStruct(1); - public SwitchFsNca MetaNca { get; internal set; } - public SwitchFsNca MainNca { get; internal set; } - public SwitchFsNca ControlNca { get; internal set; } - - public long GetSize() + foreach (SwitchFsNca nca in Ncas.Values.Where(x => x.Nca.Header.ContentType == NcaContentType.Meta)) { - return Ncas.Sum(x => x.Nca.Header.NcaSize); + try + { + var title = new Title(); + + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var file = new UniqueRef(); + fs.OpenFile(ref file.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + var metadata = new Cnmt(file.Release().AsStream()); + title.Id = metadata.TitleId; + title.Version = metadata.TitleVersion; + title.Metadata = metadata; + title.MetaNca = nca; + title.Ncas.Add(nca); + + foreach (CnmtContentEntry content in metadata.ContentEntries) + { + string ncaId = content.NcaId.ToHexString(); + + if (Ncas.TryGetValue(ncaId, out SwitchFsNca contentNca)) + { + title.Ncas.Add(contentNca); + } + + switch (content.Type) + { + case Ncm.ContentType.Program: + case Ncm.ContentType.Data: + title.MainNca = contentNca; + break; + case Ncm.ContentType.Control: + title.ControlNca = contentNca; + break; + } + } + + Titles[title.Id] = title; + } + catch (Exception ex) + { + Console.WriteLine($"{ex.Message} File: {nca.Filename}"); + } } } - public class Application + private void ReadControls() { - public Title Main { get; private set; } - public Title Patch { get; private set; } - public List AddOnContent { get; } = new List<Title>(); - - public ulong TitleId { get; private set; } - public TitleVersion Version { get; private set; } - public BlitStruct<ApplicationControlProperty> Nacp { get; private set; } = new BlitStruct<ApplicationControlProperty>(1); - - public string Name { get; private set; } - public string DisplayVersion { get; private set; } - - public void AddTitle(Title title) + foreach (Title title in Titles.Values.Where(x => x.ControlNca != null)) { - if (TitleId != 0 && title.Metadata.ApplicationTitleId != TitleId) - throw new InvalidDataException("Title IDs do not match"); - TitleId = title.Metadata.ApplicationTitleId; + IFileSystem romfs = title.ControlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - switch (title.Metadata.Type) + using (var control = new UniqueRef<IFile>()) { - case ContentMetaType.Application: - Main = title; - break; - case ContentMetaType.Patch: - Patch = title; - break; - case ContentMetaType.AddOnContent: - AddOnContent.Add(title); - break; - case ContentMetaType.Delta: - break; + romfs.OpenFile(ref control.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + control.Get.Read(out _, 0, title.Control.ByteSpan).ThrowIfFailure(); } - UpdateInfo(); + foreach (ref ApplicationControlTitle desc in title.Control.Value.Titles) + { + if (!desc.Name.IsEmpty()) + { + title.Name = desc.Name.ToString(); + break; + } + } + } + } + + private void CreateApplications() + { + foreach (Title title in Titles.Values.Where(x => x.Metadata.Type >= ContentMetaType.Application)) + { + Cnmt meta = title.Metadata; + ulong appId = meta.ApplicationTitleId; + + if (!Applications.TryGetValue(appId, out Application app)) + { + app = new Application(); + Applications.Add(appId, app); + } + + app.AddTitle(title); } - private void UpdateInfo() + foreach (Application app in Applications.Values) { - if (Patch != null) + SwitchFsNca main = app.Main?.MainNca; + SwitchFsNca patch = app.Patch?.MainNca; + + if (main != null && patch != null) { - Name = Patch.Name; - Version = Patch.Version; - DisplayVersion = Patch.Control.Value.DisplayVersion.ToString(); - Nacp = Patch.Control; - } - else if (Main != null) - { - Name = Main.Name; - Version = Main.Version; - DisplayVersion = Main.Control.Value.DisplayVersion.ToString(); - Nacp = Main.Control; - } - else - { - Name = ""; - DisplayVersion = ""; + patch.BaseNca = main.Nca; } } } + + private string GetNcaFilename(string name, SwitchFsNca nca) + { + if (nca.Nca.Header.ContentType != NcaContentType.Meta || !name.EndsWith(".cnmt.nca")) + { + return System.IO.Path.GetFileNameWithoutExtension(name); + } + + return name.Substring(0, name.Length - ".cnmt.nca".Length); + } + + private void DisposeNcas() + { + //foreach (SwitchFsNca nca in Ncas.Values) + //{ + // nca.Dispose(); + //} + Ncas.Clear(); + Titles.Clear(); + } + + public void Dispose() + { + DisposeNcas(); + } +} + +public class SwitchFsNca +{ + public Nca Nca { get; set; } + public Nca BaseNca { get; set; } + public string NcaId { get; set; } + public string Filename { get; set; } + + public SwitchFsNca(Nca nca) + { + Nca = nca; + } + + public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel) + { + if (BaseNca != null) return BaseNca.OpenStorageWithPatch(Nca, index, integrityCheckLevel); + + return Nca.OpenStorage(index, integrityCheckLevel); + } + + public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel) + { + if (BaseNca != null) return BaseNca.OpenFileSystemWithPatch(Nca, index, integrityCheckLevel); + + return Nca.OpenFileSystem(index, integrityCheckLevel); + } + + public IStorage OpenStorage(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenStorage(Nca.GetSectionIndexFromType(type, Nca.Header.ContentType), integrityCheckLevel); + } + + public IFileSystem OpenFileSystem(NcaSectionType type, IntegrityCheckLevel integrityCheckLevel) + { + return OpenFileSystem(Nca.GetSectionIndexFromType(type, Nca.Header.ContentType), integrityCheckLevel); + } + + public Validity VerifyNca(IProgressReport logger = null, bool quiet = false) + { + if (BaseNca != null) + { + return BaseNca.VerifyNca(Nca, logger, quiet); + } + else + { + return Nca.VerifyNca(logger, quiet); + } + } +} + +[DebuggerDisplay("{" + nameof(Name) + "}")] +public class Title +{ + public ulong Id { get; internal set; } + public TitleVersion Version { get; internal set; } + public List<SwitchFsNca> Ncas { get; } = new List<SwitchFsNca>(); + public Cnmt Metadata { get; internal set; } + + public string Name { get; internal set; } + public BlitStruct<ApplicationControlProperty> Control { get; } = new BlitStruct<ApplicationControlProperty>(1); + public SwitchFsNca MetaNca { get; internal set; } + public SwitchFsNca MainNca { get; internal set; } + public SwitchFsNca ControlNca { get; internal set; } + + public long GetSize() + { + return Ncas.Sum(x => x.Nca.Header.NcaSize); + } +} + +public class Application +{ + public Title Main { get; private set; } + public Title Patch { get; private set; } + public List<Title> AddOnContent { get; } = new List<Title>(); + + public ulong TitleId { get; private set; } + public TitleVersion Version { get; private set; } + public BlitStruct<ApplicationControlProperty> Nacp { get; private set; } = new BlitStruct<ApplicationControlProperty>(1); + + public string Name { get; private set; } + public string DisplayVersion { get; private set; } + + public void AddTitle(Title title) + { + if (TitleId != 0 && title.Metadata.ApplicationTitleId != TitleId) + throw new InvalidDataException("Title IDs do not match"); + TitleId = title.Metadata.ApplicationTitleId; + + switch (title.Metadata.Type) + { + case ContentMetaType.Application: + Main = title; + break; + case ContentMetaType.Patch: + Patch = title; + break; + case ContentMetaType.AddOnContent: + AddOnContent.Add(title); + break; + case ContentMetaType.Delta: + break; + } + + UpdateInfo(); + } + + private void UpdateInfo() + { + if (Patch != null) + { + Name = Patch.Name; + Version = Patch.Version; + DisplayVersion = Patch.Control.Value.DisplayVersion.ToString(); + Nacp = Patch.Control; + } + else if (Main != null) + { + Name = Main.Name; + Version = Main.Version; + DisplayVersion = Main.Control.Value.DisplayVersion.ToString(); + Nacp = Main.Control; + } + else + { + Name = ""; + DisplayVersion = ""; + } + } } diff --git a/src/LibHac/ThrowHelper.cs b/src/LibHac/ThrowHelper.cs index 2eb6773d..b6e4524c 100644 --- a/src/LibHac/ThrowHelper.cs +++ b/src/LibHac/ThrowHelper.cs @@ -1,23 +1,22 @@ using System; -namespace LibHac +namespace LibHac; + +internal static class ThrowHelper { - internal static class ThrowHelper + public static void ThrowResult(Result result) => throw new HorizonResultException(result); + + public static void ThrowResult(Result result, Exception innerException) => + throw new HorizonResultException(result, string.Empty, innerException); + + public static void ThrowResult(Result result, string message) => + throw new HorizonResultException(result, message); + + public static void ThrowResult(Result result, string message, Exception innerException) => + throw new HorizonResultException(result, message, innerException); + + internal static void ThrowArgumentOutOfRangeException() { - public static void ThrowResult(Result result) => throw new HorizonResultException(result); - - public static void ThrowResult(Result result, Exception innerException) => - throw new HorizonResultException(result, string.Empty, innerException); - - public static void ThrowResult(Result result, string message) => - throw new HorizonResultException(result, message); - - public static void ThrowResult(Result result, string message, Exception innerException) => - throw new HorizonResultException(result, message, innerException); - - internal static void ThrowArgumentOutOfRangeException() - { - throw new ArgumentOutOfRangeException(); - } + throw new ArgumentOutOfRangeException(); } } diff --git a/src/LibHac/Ticket.cs b/src/LibHac/Ticket.cs index 4ada57bf..1b45143c 100644 --- a/src/LibHac/Ticket.cs +++ b/src/LibHac/Ticket.cs @@ -3,193 +3,192 @@ using System.IO; using LibHac.Common.Keys; using LibHac.Util; -namespace LibHac +namespace LibHac; + +public class Ticket { - public class Ticket + public TicketSigType SignatureType { get; set; } + public byte[] Signature { get; set; } + public string Issuer { get; set; } + public byte[] TitleKeyBlock { get; set; } + public byte FormatVersion { get; set; } + public TitleKeyType TitleKeyType { get; set; } + public LicenseType LicenseType { get; set; } + public ushort TicketVersion { get; set; } + public byte CryptoType { get; set; } + public PropertyFlags PropertyMask { get; set; } + public ulong TicketId { get; set; } + public ulong DeviceId { get; set; } + public byte[] RightsId { get; set; } + public uint AccountId { get; set; } + public int SectTotalSize { get; set; } + public int SectHeaderOffset { get; set; } + public short SectNum { get; set; } + public short SectEntrySize { get; set; } + + public byte[] File { get; } + + internal static readonly byte[] LabelHash = { - public TicketSigType SignatureType { get; set; } - public byte[] Signature { get; set; } - public string Issuer { get; set; } - public byte[] TitleKeyBlock { get; set; } - public byte FormatVersion { get; set; } - public TitleKeyType TitleKeyType { get; set; } - public LicenseType LicenseType { get; set; } - public ushort TicketVersion { get; set; } - public byte CryptoType { get; set; } - public PropertyFlags PropertyMask { get; set; } - public ulong TicketId { get; set; } - public ulong DeviceId { get; set; } - public byte[] RightsId { get; set; } - public uint AccountId { get; set; } - public int SectTotalSize { get; set; } - public int SectHeaderOffset { get; set; } - public short SectNum { get; set; } - public short SectEntrySize { get; set; } + 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, + 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 + }; - public byte[] File { get; } + public Ticket() { } - internal static readonly byte[] LabelHash = + public Ticket(Stream stream) : this(new BinaryReader(stream)) { } + + public Ticket(BinaryReader reader) + { + long fileStart = reader.BaseStream.Position; + SignatureType = (TicketSigType)reader.ReadUInt32(); + + switch (SignatureType) { - 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, - 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 - }; - - public Ticket() { } - - public Ticket(Stream stream) : this(new BinaryReader(stream)) { } - - public Ticket(BinaryReader reader) - { - long fileStart = reader.BaseStream.Position; - SignatureType = (TicketSigType)reader.ReadUInt32(); - - switch (SignatureType) - { - case TicketSigType.Rsa4096Sha1: - case TicketSigType.Rsa4096Sha256: - Signature = reader.ReadBytes(0x200); - reader.BaseStream.Position += 0x3c; - break; - case TicketSigType.Rsa2048Sha1: - case TicketSigType.Rsa2048Sha256: - Signature = reader.ReadBytes(0x100); - reader.BaseStream.Position += 0x3c; - break; - case TicketSigType.EcdsaSha1: - case TicketSigType.EcdsaSha256: - Signature = reader.ReadBytes(0x3c); - reader.BaseStream.Position += 0x40; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - long dataStart = reader.BaseStream.Position; - - Issuer = reader.ReadUtf8Z(0x40); - reader.BaseStream.Position = dataStart + 0x40; - TitleKeyBlock = reader.ReadBytes(0x100); - FormatVersion = reader.ReadByte(); - TitleKeyType = (TitleKeyType)reader.ReadByte(); - TicketVersion = reader.ReadUInt16(); - LicenseType = (LicenseType)reader.ReadByte(); - CryptoType = reader.ReadByte(); - PropertyMask = (PropertyFlags)reader.ReadUInt32(); - reader.BaseStream.Position = dataStart + 0x150; - TicketId = reader.ReadUInt64(); - DeviceId = reader.ReadUInt64(); - RightsId = reader.ReadBytes(0x10); - AccountId = reader.ReadUInt32(); - SectTotalSize = reader.ReadInt32(); - SectHeaderOffset = reader.ReadInt32(); - SectNum = reader.ReadInt16(); - SectEntrySize = reader.ReadInt16(); - - reader.BaseStream.Position = fileStart; - File = reader.ReadBytes(SectHeaderOffset); + case TicketSigType.Rsa4096Sha1: + case TicketSigType.Rsa4096Sha256: + Signature = reader.ReadBytes(0x200); + reader.BaseStream.Position += 0x3c; + break; + case TicketSigType.Rsa2048Sha1: + case TicketSigType.Rsa2048Sha256: + Signature = reader.ReadBytes(0x100); + reader.BaseStream.Position += 0x3c; + break; + case TicketSigType.EcdsaSha1: + case TicketSigType.EcdsaSha256: + Signature = reader.ReadBytes(0x3c); + reader.BaseStream.Position += 0x40; + break; + default: + throw new ArgumentOutOfRangeException(); } - public byte[] GetBytes() + long dataStart = reader.BaseStream.Position; + + Issuer = reader.ReadUtf8Z(0x40); + reader.BaseStream.Position = dataStart + 0x40; + TitleKeyBlock = reader.ReadBytes(0x100); + FormatVersion = reader.ReadByte(); + TitleKeyType = (TitleKeyType)reader.ReadByte(); + TicketVersion = reader.ReadUInt16(); + LicenseType = (LicenseType)reader.ReadByte(); + CryptoType = reader.ReadByte(); + PropertyMask = (PropertyFlags)reader.ReadUInt32(); + reader.BaseStream.Position = dataStart + 0x150; + TicketId = reader.ReadUInt64(); + DeviceId = reader.ReadUInt64(); + RightsId = reader.ReadBytes(0x10); + AccountId = reader.ReadUInt32(); + SectTotalSize = reader.ReadInt32(); + SectHeaderOffset = reader.ReadInt32(); + SectNum = reader.ReadInt16(); + SectEntrySize = reader.ReadInt16(); + + reader.BaseStream.Position = fileStart; + File = reader.ReadBytes(SectHeaderOffset); + } + + public byte[] GetBytes() + { + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + int sigLength; + + switch (SignatureType) { - var stream = new MemoryStream(); - var writer = new BinaryWriter(stream); - int sigLength; - - switch (SignatureType) - { - case TicketSigType.Rsa4096Sha1: - case TicketSigType.Rsa4096Sha256: - sigLength = 0x200; - break; - case TicketSigType.Rsa2048Sha1: - case TicketSigType.Rsa2048Sha256: - sigLength = 0x100; - break; - case TicketSigType.EcdsaSha1: - case TicketSigType.EcdsaSha256: - sigLength = 0x3c; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - long bodyStart = Alignment.AlignUp(4 + sigLength, 0x40); - - writer.Write((int)SignatureType); - - if (Signature?.Length == sigLength) - { - writer.Write(Signature); - } - - stream.Position = bodyStart; - if (Issuer != null) writer.WriteUTF8(Issuer); - stream.Position = bodyStart + 0x40; - if (TitleKeyBlock?.Length <= 0x100) writer.Write(TitleKeyBlock); - stream.Position = bodyStart + 0x140; - writer.Write(FormatVersion); - writer.Write((byte)TitleKeyType); - writer.Write(TicketVersion); - writer.Write((byte)LicenseType); - writer.Write(CryptoType); - writer.Write((uint)PropertyMask); - stream.Position = bodyStart + 0x150; - writer.Write(TicketId); - writer.Write(DeviceId); - if (RightsId?.Length <= 0x10) writer.Write(RightsId); - writer.Write(AccountId); - writer.Write(SectTotalSize); - writer.Write(SectHeaderOffset); - writer.Write(SectNum); - writer.Write(SectEntrySize); - - return stream.ToArray(); + case TicketSigType.Rsa4096Sha1: + case TicketSigType.Rsa4096Sha256: + sigLength = 0x200; + break; + case TicketSigType.Rsa2048Sha1: + case TicketSigType.Rsa2048Sha256: + sigLength = 0x100; + break; + case TicketSigType.EcdsaSha1: + case TicketSigType.EcdsaSha256: + sigLength = 0x3c; + break; + default: + throw new ArgumentOutOfRangeException(); } - public byte[] GetTitleKey(KeySet keySet) + long bodyStart = Alignment.AlignUp(4 + sigLength, 0x40); + + writer.Write((int)SignatureType); + + if (Signature?.Length == sigLength) { - if (TitleKeyType == TitleKeyType.Common) - { - byte[] commonKey = new byte[0x10]; - Array.Copy(TitleKeyBlock, commonKey, commonKey.Length); - return commonKey; - } - - return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keySet.ETicketExtKeyRsa); + writer.Write(Signature); } + + stream.Position = bodyStart; + if (Issuer != null) writer.WriteUTF8(Issuer); + stream.Position = bodyStart + 0x40; + if (TitleKeyBlock?.Length <= 0x100) writer.Write(TitleKeyBlock); + stream.Position = bodyStart + 0x140; + writer.Write(FormatVersion); + writer.Write((byte)TitleKeyType); + writer.Write(TicketVersion); + writer.Write((byte)LicenseType); + writer.Write(CryptoType); + writer.Write((uint)PropertyMask); + stream.Position = bodyStart + 0x150; + writer.Write(TicketId); + writer.Write(DeviceId); + if (RightsId?.Length <= 0x10) writer.Write(RightsId); + writer.Write(AccountId); + writer.Write(SectTotalSize); + writer.Write(SectHeaderOffset); + writer.Write(SectNum); + writer.Write(SectEntrySize); + + return stream.ToArray(); } - public enum TicketSigType + public byte[] GetTitleKey(KeySet keySet) { - Rsa4096Sha1 = 0x10000, - Rsa2048Sha1, - EcdsaSha1, - Rsa4096Sha256, - Rsa2048Sha256, - EcdsaSha256 - } + if (TitleKeyType == TitleKeyType.Common) + { + byte[] commonKey = new byte[0x10]; + Array.Copy(TitleKeyBlock, commonKey, commonKey.Length); + return commonKey; + } - public enum TitleKeyType - { - Common, - Personalized - } - - public enum LicenseType - { - Permanent, - Demo, - Trial, - Rental, - Subscription, - Service - } - - [Flags] - public enum PropertyFlags - { - PreInstall = 1 << 0, - SharedTitle = 1 << 1, - AllowAllContent = 1 << 2 + return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keySet.ETicketExtKeyRsa); } } + +public enum TicketSigType +{ + Rsa4096Sha1 = 0x10000, + Rsa2048Sha1, + EcdsaSha1, + Rsa4096Sha256, + Rsa2048Sha256, + EcdsaSha256 +} + +public enum TitleKeyType +{ + Common, + Personalized +} + +public enum LicenseType +{ + Permanent, + Demo, + Trial, + Rental, + Subscription, + Service +} + +[Flags] +public enum PropertyFlags +{ + PreInstall = 1 << 0, + SharedTitle = 1 << 1, + AllowAllContent = 1 << 2 +} diff --git a/src/LibHac/Time/PosixTime.cs b/src/LibHac/Time/PosixTime.cs index 68481948..b50fed8a 100644 --- a/src/LibHac/Time/PosixTime.cs +++ b/src/LibHac/Time/PosixTime.cs @@ -1,36 +1,35 @@ using System; -namespace LibHac.Time +namespace LibHac.Time; + +public readonly struct PosixTime : IComparable<PosixTime>, IEquatable<PosixTime> { - public readonly struct PosixTime : IComparable<PosixTime>, IEquatable<PosixTime> + public readonly long Value; + + public PosixTime(long value) { - public readonly long Value; - - public PosixTime(long value) - { - Value = value; - } - - public static PosixTime operator +(PosixTime left, TimeSpan right) => - new PosixTime(left.Value + right.GetSeconds()); - - public static PosixTime operator -(PosixTime left, TimeSpan right) => - new PosixTime(left.Value - right.GetSeconds()); - - public static TimeSpan operator -(PosixTime left, PosixTime right) => - TimeSpan.FromSeconds(left.Value - right.Value); - - public static bool operator ==(PosixTime left, PosixTime right) => left.Value == right.Value; - public static bool operator !=(PosixTime left, PosixTime right) => left.Value != right.Value; - public static bool operator <=(PosixTime left, PosixTime right) => left.Value <= right.Value; - public static bool operator >=(PosixTime left, PosixTime right) => left.Value >= right.Value; - public static bool operator <(PosixTime left, PosixTime right) => left.Value < right.Value; - public static bool operator >(PosixTime left, PosixTime right) => left.Value > right.Value; - - public bool Equals(PosixTime other) => Value == other.Value; - public int CompareTo(PosixTime other) => Value.CompareTo(other.Value); - - public override bool Equals(object obj) => obj is PosixTime time && Equals(time); - public override int GetHashCode() => Value.GetHashCode(); + Value = value; } -} \ No newline at end of file + + public static PosixTime operator +(PosixTime left, TimeSpan right) => + new PosixTime(left.Value + right.GetSeconds()); + + public static PosixTime operator -(PosixTime left, TimeSpan right) => + new PosixTime(left.Value - right.GetSeconds()); + + public static TimeSpan operator -(PosixTime left, PosixTime right) => + TimeSpan.FromSeconds(left.Value - right.Value); + + public static bool operator ==(PosixTime left, PosixTime right) => left.Value == right.Value; + public static bool operator !=(PosixTime left, PosixTime right) => left.Value != right.Value; + public static bool operator <=(PosixTime left, PosixTime right) => left.Value <= right.Value; + public static bool operator >=(PosixTime left, PosixTime right) => left.Value >= right.Value; + public static bool operator <(PosixTime left, PosixTime right) => left.Value < right.Value; + public static bool operator >(PosixTime left, PosixTime right) => left.Value > right.Value; + + public bool Equals(PosixTime other) => Value == other.Value; + public int CompareTo(PosixTime other) => Value.CompareTo(other.Value); + + public override bool Equals(object obj) => obj is PosixTime time && Equals(time); + public override int GetHashCode() => Value.GetHashCode(); +} diff --git a/src/LibHac/TimeSpan.cs b/src/LibHac/TimeSpan.cs index 99cd0c57..9374c043 100644 --- a/src/LibHac/TimeSpan.cs +++ b/src/LibHac/TimeSpan.cs @@ -1,85 +1,84 @@ using System; -namespace LibHac +namespace LibHac; + +public readonly struct TimeSpan : IEquatable<TimeSpan>, IComparable<TimeSpan> { - public readonly struct TimeSpan : IEquatable<TimeSpan>, IComparable<TimeSpan> - { - private readonly TimeSpanType _ts; + private readonly TimeSpanType _ts; - public TimeSpan(nint _) => _ts = new TimeSpanType(); - public TimeSpan(TimeSpanType ts) => _ts = ts; + public TimeSpan(nint _) => _ts = new TimeSpanType(); + public TimeSpan(TimeSpanType ts) => _ts = ts; - public static TimeSpan FromNanoSeconds(long nanoSeconds) => new(TimeSpanType.FromNanoSeconds(nanoSeconds)); - public static TimeSpan FromMicroSeconds(long microSeconds) => new(TimeSpanType.FromMicroSeconds(microSeconds)); - public static TimeSpan FromMilliSeconds(long milliSeconds) => new(TimeSpanType.FromMilliSeconds(milliSeconds)); - public static TimeSpan FromSeconds(long seconds) => new(TimeSpanType.FromSeconds(seconds)); - public static TimeSpan FromMinutes(long minutes) => new(TimeSpanType.FromMinutes(minutes)); - public static TimeSpan FromHours(long hours) => new(TimeSpanType.FromHours(hours)); - public static TimeSpan FromDays(long days) => new(TimeSpanType.FromDays(days)); + public static TimeSpan FromNanoSeconds(long nanoSeconds) => new(TimeSpanType.FromNanoSeconds(nanoSeconds)); + public static TimeSpan FromMicroSeconds(long microSeconds) => new(TimeSpanType.FromMicroSeconds(microSeconds)); + public static TimeSpan FromMilliSeconds(long milliSeconds) => new(TimeSpanType.FromMilliSeconds(milliSeconds)); + public static TimeSpan FromSeconds(long seconds) => new(TimeSpanType.FromSeconds(seconds)); + public static TimeSpan FromMinutes(long minutes) => new(TimeSpanType.FromMinutes(minutes)); + public static TimeSpan FromHours(long hours) => new(TimeSpanType.FromHours(hours)); + public static TimeSpan FromDays(long days) => new(TimeSpanType.FromDays(days)); - public long GetNanoSeconds() => _ts.GetNanoSeconds(); - public long GetMicroSeconds() => _ts.GetMicroSeconds(); - public long GetMilliSeconds() => _ts.GetMilliSeconds(); - public long GetSeconds() => _ts.GetSeconds(); - public long GetMinutes() => _ts.GetMinutes(); - public long GetHours() => _ts.GetHours(); - public long GetDays() => _ts.GetDays(); + public long GetNanoSeconds() => _ts.GetNanoSeconds(); + public long GetMicroSeconds() => _ts.GetMicroSeconds(); + public long GetMilliSeconds() => _ts.GetMilliSeconds(); + public long GetSeconds() => _ts.GetSeconds(); + public long GetMinutes() => _ts.GetMinutes(); + public long GetHours() => _ts.GetHours(); + public long GetDays() => _ts.GetDays(); - public static bool operator ==(TimeSpan left, TimeSpan right) => left.Equals(right); - public static bool operator !=(TimeSpan left, TimeSpan right) => !(left == right); - public static bool operator <(TimeSpan left, TimeSpan right) => left._ts < right._ts; - public static bool operator >(TimeSpan left, TimeSpan right) => left._ts > right._ts; - public static bool operator <=(TimeSpan left, TimeSpan right) => left._ts <= right._ts; - public static bool operator >=(TimeSpan left, TimeSpan right) => left._ts >= right._ts; + public static bool operator ==(TimeSpan left, TimeSpan right) => left.Equals(right); + public static bool operator !=(TimeSpan left, TimeSpan right) => !(left == right); + public static bool operator <(TimeSpan left, TimeSpan right) => left._ts < right._ts; + public static bool operator >(TimeSpan left, TimeSpan right) => left._ts > right._ts; + public static bool operator <=(TimeSpan left, TimeSpan right) => left._ts <= right._ts; + public static bool operator >=(TimeSpan left, TimeSpan right) => left._ts >= right._ts; - public static TimeSpan operator +(TimeSpan left, TimeSpan right) => new(left._ts + right._ts); - public static TimeSpan operator -(TimeSpan left, TimeSpan right) => new(left._ts - right._ts); + public static TimeSpan operator +(TimeSpan left, TimeSpan right) => new(left._ts + right._ts); + public static TimeSpan operator -(TimeSpan left, TimeSpan right) => new(left._ts - right._ts); - public static implicit operator TimeSpanType(TimeSpan ts) => ts._ts; + public static implicit operator TimeSpanType(TimeSpan ts) => ts._ts; - public override bool Equals(object obj) => obj is TimeSpan ts && Equals(ts); - public bool Equals(TimeSpan other) => _ts == other._ts; - public override int GetHashCode() => _ts.GetHashCode(); - public int CompareTo(TimeSpan other) => _ts.CompareTo(other._ts); - public override string ToString() => _ts.ToString(); - } - - public readonly struct TimeSpanType : IEquatable<TimeSpanType>, IComparable<TimeSpanType> - { - private readonly long _nanoSeconds; - - private TimeSpanType(long nanoSeconds) => _nanoSeconds = nanoSeconds; - - public static TimeSpanType FromNanoSeconds(long nanoSeconds) => new(nanoSeconds); - public static TimeSpanType FromMicroSeconds(long microSeconds) => new(microSeconds * 1000L); - public static TimeSpanType FromMilliSeconds(long milliSeconds) => new(milliSeconds * (1000L * 1000)); - public static TimeSpanType FromSeconds(long seconds) => new(seconds * (1000L * 1000 * 1000)); - public static TimeSpanType FromMinutes(long minutes) => new(minutes * (1000L * 1000 * 1000 * 60)); - public static TimeSpanType FromHours(long hours) => new(hours * (1000L * 1000 * 1000 * 60 * 60)); - public static TimeSpanType FromDays(long days) => new(days * (1000L * 1000 * 1000 * 60 * 60 * 24)); - - public long GetNanoSeconds() => _nanoSeconds; - public long GetMicroSeconds() => _nanoSeconds / 1000; - public long GetMilliSeconds() => _nanoSeconds / (1000L * 1000); - public long GetSeconds() => _nanoSeconds / (1000L * 1000 * 1000); - public long GetMinutes() => _nanoSeconds / (1000L * 1000 * 1000 * 60); - public long GetHours() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60); - public long GetDays() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60 * 24); - - public static bool operator ==(TimeSpanType left, TimeSpanType right) => left._nanoSeconds == right._nanoSeconds; - public static bool operator !=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds != right._nanoSeconds; - public static bool operator <(TimeSpanType left, TimeSpanType right) => left._nanoSeconds < right._nanoSeconds; - public static bool operator >(TimeSpanType left, TimeSpanType right) => left._nanoSeconds > right._nanoSeconds; - public static bool operator <=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds <= right._nanoSeconds; - public static bool operator >=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds >= right._nanoSeconds; - - public static TimeSpanType operator +(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds + right._nanoSeconds); - public static TimeSpanType operator -(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds - right._nanoSeconds); - - public override bool Equals(object obj) => obj is TimeSpanType ts && Equals(ts); - public bool Equals(TimeSpanType other) => _nanoSeconds == other._nanoSeconds; - public override int GetHashCode() => (int)_nanoSeconds; - public int CompareTo(TimeSpanType other) => _nanoSeconds.CompareTo(other._nanoSeconds); - public override string ToString() => _nanoSeconds.ToString(); - } + public override bool Equals(object obj) => obj is TimeSpan ts && Equals(ts); + public bool Equals(TimeSpan other) => _ts == other._ts; + public override int GetHashCode() => _ts.GetHashCode(); + public int CompareTo(TimeSpan other) => _ts.CompareTo(other._ts); + public override string ToString() => _ts.ToString(); +} + +public readonly struct TimeSpanType : IEquatable<TimeSpanType>, IComparable<TimeSpanType> +{ + private readonly long _nanoSeconds; + + private TimeSpanType(long nanoSeconds) => _nanoSeconds = nanoSeconds; + + public static TimeSpanType FromNanoSeconds(long nanoSeconds) => new(nanoSeconds); + public static TimeSpanType FromMicroSeconds(long microSeconds) => new(microSeconds * 1000L); + public static TimeSpanType FromMilliSeconds(long milliSeconds) => new(milliSeconds * (1000L * 1000)); + public static TimeSpanType FromSeconds(long seconds) => new(seconds * (1000L * 1000 * 1000)); + public static TimeSpanType FromMinutes(long minutes) => new(minutes * (1000L * 1000 * 1000 * 60)); + public static TimeSpanType FromHours(long hours) => new(hours * (1000L * 1000 * 1000 * 60 * 60)); + public static TimeSpanType FromDays(long days) => new(days * (1000L * 1000 * 1000 * 60 * 60 * 24)); + + public long GetNanoSeconds() => _nanoSeconds; + public long GetMicroSeconds() => _nanoSeconds / 1000; + public long GetMilliSeconds() => _nanoSeconds / (1000L * 1000); + public long GetSeconds() => _nanoSeconds / (1000L * 1000 * 1000); + public long GetMinutes() => _nanoSeconds / (1000L * 1000 * 1000 * 60); + public long GetHours() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60); + public long GetDays() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60 * 24); + + public static bool operator ==(TimeSpanType left, TimeSpanType right) => left._nanoSeconds == right._nanoSeconds; + public static bool operator !=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds != right._nanoSeconds; + public static bool operator <(TimeSpanType left, TimeSpanType right) => left._nanoSeconds < right._nanoSeconds; + public static bool operator >(TimeSpanType left, TimeSpanType right) => left._nanoSeconds > right._nanoSeconds; + public static bool operator <=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds <= right._nanoSeconds; + public static bool operator >=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds >= right._nanoSeconds; + + public static TimeSpanType operator +(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds + right._nanoSeconds); + public static TimeSpanType operator -(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds - right._nanoSeconds); + + public override bool Equals(object obj) => obj is TimeSpanType ts && Equals(ts); + public bool Equals(TimeSpanType other) => _nanoSeconds == other._nanoSeconds; + public override int GetHashCode() => (int)_nanoSeconds; + public int CompareTo(TimeSpanType other) => _nanoSeconds.CompareTo(other._nanoSeconds); + public override string ToString() => _nanoSeconds.ToString(); } diff --git a/src/LibHac/Util/Alignment.cs b/src/LibHac/Util/Alignment.cs index 19cf41c8..2096f76e 100644 --- a/src/LibHac/Util/Alignment.cs +++ b/src/LibHac/Util/Alignment.cs @@ -3,62 +3,61 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Diag; -namespace LibHac.Util +namespace LibHac.Util; + +public static class Alignment { - public static class Alignment + // The alignment functions in this class come from C++ templates that always cast to unsigned types + + public static ulong AlignUpPow2(ulong value, uint alignment) { - // The alignment functions in this class come from C++ templates that always cast to unsigned types + Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); - public static ulong AlignUpPow2(ulong value, uint alignment) - { - Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); - - ulong invMask = alignment - 1; - return ((value + invMask) & ~invMask); - } - - public static ulong AlignDownPow2(ulong value, uint alignment) - { - Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); - - ulong invMask = alignment - 1; - return (value & ~invMask); - } - - public static bool IsAlignedPow2(ulong value, uint alignment) - { - Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); - - ulong invMask = alignment - 1; - return (value & invMask) == 0; - } - - public static bool IsAlignedPow2<T>(ReadOnlySpan<T> buffer, uint alignment) - { - return IsAlignedPow2(ref MemoryMarshal.GetReference(buffer), alignment); - } - - public static unsafe bool IsAlignedPow2<T>(ref T pointer, uint alignment) - { - return IsAlignedPow2((ulong)Unsafe.AsPointer(ref pointer), alignment); - } - - public static int AlignUpPow2(int value, uint alignment) => (int)AlignUpPow2((ulong)value, alignment); - public static long AlignUpPow2(long value, uint alignment) => (long)AlignUpPow2((ulong)value, alignment); - public static int AlignDownPow2(int value, uint alignment) => (int)AlignDownPow2((ulong)value, alignment); - public static long AlignDownPow2(long value, uint alignment) => (long)AlignDownPow2((ulong)value, alignment); - public static bool IsAlignedPow2(int value, uint alignment) => IsAlignedPow2((ulong)value, alignment); - public static bool IsAlignedPow2(long value, uint alignment) => IsAlignedPow2((ulong)value, alignment); - - public static ulong AlignUp(ulong value, uint alignment) => AlignDown(value + alignment - 1, alignment); - public static ulong AlignDown(ulong value, uint alignment) => value - value % alignment; - public static bool IsAligned(ulong value, uint alignment) => value % alignment == 0; - - public static int AlignUp(int value, uint alignment) => (int)AlignUp((ulong)value, alignment); - public static long AlignUp(long value, uint alignment) => (long)AlignUp((ulong)value, alignment); - public static int AlignDown(int value, uint alignment) => (int)AlignDown((ulong)value, alignment); - public static long AlignDown(long value, uint alignment) => (long)AlignDown((ulong)value, alignment); - public static bool IsAligned(int value, uint alignment) => IsAligned((ulong)value, alignment); - public static bool IsAligned(long value, uint alignment) => IsAligned((ulong)value, alignment); + ulong invMask = alignment - 1; + return ((value + invMask) & ~invMask); } + + public static ulong AlignDownPow2(ulong value, uint alignment) + { + Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); + + ulong invMask = alignment - 1; + return (value & ~invMask); + } + + public static bool IsAlignedPow2(ulong value, uint alignment) + { + Assert.SdkRequires(BitUtil.IsPowerOfTwo(alignment)); + + ulong invMask = alignment - 1; + return (value & invMask) == 0; + } + + public static bool IsAlignedPow2<T>(ReadOnlySpan<T> buffer, uint alignment) + { + return IsAlignedPow2(ref MemoryMarshal.GetReference(buffer), alignment); + } + + public static unsafe bool IsAlignedPow2<T>(ref T pointer, uint alignment) + { + return IsAlignedPow2((ulong)Unsafe.AsPointer(ref pointer), alignment); + } + + public static int AlignUpPow2(int value, uint alignment) => (int)AlignUpPow2((ulong)value, alignment); + public static long AlignUpPow2(long value, uint alignment) => (long)AlignUpPow2((ulong)value, alignment); + public static int AlignDownPow2(int value, uint alignment) => (int)AlignDownPow2((ulong)value, alignment); + public static long AlignDownPow2(long value, uint alignment) => (long)AlignDownPow2((ulong)value, alignment); + public static bool IsAlignedPow2(int value, uint alignment) => IsAlignedPow2((ulong)value, alignment); + public static bool IsAlignedPow2(long value, uint alignment) => IsAlignedPow2((ulong)value, alignment); + + public static ulong AlignUp(ulong value, uint alignment) => AlignDown(value + alignment - 1, alignment); + public static ulong AlignDown(ulong value, uint alignment) => value - value % alignment; + public static bool IsAligned(ulong value, uint alignment) => value % alignment == 0; + + public static int AlignUp(int value, uint alignment) => (int)AlignUp((ulong)value, alignment); + public static long AlignUp(long value, uint alignment) => (long)AlignUp((ulong)value, alignment); + public static int AlignDown(int value, uint alignment) => (int)AlignDown((ulong)value, alignment); + public static long AlignDown(long value, uint alignment) => (long)AlignDown((ulong)value, alignment); + public static bool IsAligned(int value, uint alignment) => IsAligned((ulong)value, alignment); + public static bool IsAligned(long value, uint alignment) => IsAligned((ulong)value, alignment); } diff --git a/src/LibHac/Util/BitUtil.cs b/src/LibHac/Util/BitUtil.cs index 0af9f02f..002af32d 100644 --- a/src/LibHac/Util/BitUtil.cs +++ b/src/LibHac/Util/BitUtil.cs @@ -1,46 +1,45 @@ using System.Runtime.CompilerServices; -namespace LibHac.Util +namespace LibHac.Util; + +public static class BitUtil { - public static class BitUtil + public static bool IsPowerOfTwo(int value) { - public static bool IsPowerOfTwo(int value) - { - return value > 0 && ResetLeastSignificantOneBit(value) == 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPowerOfTwo(long value) - { - return value > 0 && ResetLeastSignificantOneBit(value) == 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPowerOfTwo(ulong value) - { - return value > 0 && ResetLeastSignificantOneBit(value) == 0; - } - - private static int ResetLeastSignificantOneBit(int value) - { - return value & (value - 1); - } - - private static long ResetLeastSignificantOneBit(long value) - { - return value & (value - 1); - } - - private static ulong ResetLeastSignificantOneBit(ulong value) - { - return value & (value - 1); - } - - // DivideUp comes from a C++ template that always casts to unsigned types - public static uint DivideUp(uint value, uint divisor) => (value + divisor - 1) / divisor; - public static ulong DivideUp(ulong value, ulong divisor) => (value + divisor - 1) / divisor; - - public static int DivideUp(int value, int divisor) => (int)DivideUp((uint)value, (uint)divisor); - public static long DivideUp(long value, long divisor) => (long)DivideUp((ulong)value, (ulong)divisor); + return value > 0 && ResetLeastSignificantOneBit(value) == 0; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPowerOfTwo(long value) + { + return value > 0 && ResetLeastSignificantOneBit(value) == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPowerOfTwo(ulong value) + { + return value > 0 && ResetLeastSignificantOneBit(value) == 0; + } + + private static int ResetLeastSignificantOneBit(int value) + { + return value & (value - 1); + } + + private static long ResetLeastSignificantOneBit(long value) + { + return value & (value - 1); + } + + private static ulong ResetLeastSignificantOneBit(ulong value) + { + return value & (value - 1); + } + + // DivideUp comes from a C++ template that always casts to unsigned types + public static uint DivideUp(uint value, uint divisor) => (value + divisor - 1) / divisor; + public static ulong DivideUp(ulong value, ulong divisor) => (value + divisor - 1) / divisor; + + public static int DivideUp(int value, int divisor) => (int)DivideUp((uint)value, (uint)divisor); + public static long DivideUp(long value, long divisor) => (long)DivideUp((ulong)value, (ulong)divisor); } diff --git a/src/LibHac/Util/CharacterEncoding.cs b/src/LibHac/Util/CharacterEncoding.cs index 49f40d41..72aa08dc 100644 --- a/src/LibHac/Util/CharacterEncoding.cs +++ b/src/LibHac/Util/CharacterEncoding.cs @@ -3,1109 +3,1108 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; -namespace LibHac.Util +namespace LibHac.Util; + +public enum CharacterEncodingResult { - public enum CharacterEncodingResult + Success = 0, + InsufficientLength = 1, + InvalidFormat = 2 +} + +public static class CharacterEncoding +{ + private static ReadOnlySpan<sbyte> Utf8NBytesInnerTable => new sbyte[] { - Success = 0, - InsufficientLength = 1, - InvalidFormat = 2 - } + -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 + }; - public static class CharacterEncoding + private static ReadOnlySpan<sbyte> Utf8NBytesTable => Utf8NBytesInnerTable.Slice(1); + + private static CharacterEncodingResult ConvertStringUtf8ToUtf16Impl(out int codeUnitsWritten, + out int codeUnitsRead, Span<ushort> destination, ReadOnlySpan<byte> source) { - private static ReadOnlySpan<sbyte> Utf8NBytesInnerTable => new sbyte[] + if (source.Length == 0) { - -1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 - }; - - private static ReadOnlySpan<sbyte> Utf8NBytesTable => Utf8NBytesInnerTable.Slice(1); - - private static CharacterEncodingResult ConvertStringUtf8ToUtf16Impl(out int codeUnitsWritten, - out int codeUnitsRead, Span<ushort> destination, ReadOnlySpan<byte> source) - { - if (source.Length == 0) - { - codeUnitsWritten = 0; - codeUnitsRead = 0; - return CharacterEncodingResult.Success; - } - - ReadOnlySpan<byte> src = source; - Span<ushort> dst = destination; - - while (src.Length > 0) - { - int codePointBytes = Utf8NBytesTable[src[0]]; - - if (src.Length < codePointBytes) - goto ReturnInvalidFormat; - - if (dst.Length == 0) - goto ReturnInsufficientLength; - - uint codePoint; - - switch (codePointBytes) - { - case 1: - dst[0] = src[0]; - src = src.Slice(1); - dst = dst.Slice(1); - break; - - case 2: - // Check if the encoding is overlong - if ((src[0] & 0x1E) == 0) - goto ReturnInvalidFormat; - - if ((src[1] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 0x1Fu) << 6) | - ((src[1] & 0x3Fu) << 0); - - dst[0] = (ushort)codePoint; - src = src.Slice(2); - dst = dst.Slice(1); - break; - - case 3: - if ((src[1] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - if ((src[2] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 0xFu) << 12) | - ((src[1] & 0x3Fu) << 6) | - ((src[2] & 0x3Fu) << 0); - - // Check if the encoding is overlong - if ((codePoint & 0xF800) == 0) - goto ReturnInvalidFormat; - - // Check if the code point is in the range reserved for UTF-16 surrogates - if ((codePoint & 0xF800) == 0xD800) - goto ReturnInvalidFormat; - - dst[0] = (ushort)codePoint; - src = src.Slice(3); - dst = dst.Slice(1); - break; - - case 4: - if ((src[1] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - if ((src[2] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - if ((src[3] & 0xC0) != 0x80) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 7u) << 18) | - ((src[1] & 0x3Fu) << 12) | - ((src[2] & 0x3Fu) << 6) | - ((src[3] & 0x3Fu) << 0); - - // Check if the code point is outside the range of valid code points - if (codePoint < 0x10000 || codePoint >= 0x110000) - goto ReturnInvalidFormat; - - // Make sure we have enough space left in the destination - if (dst.Length == 1) - goto ReturnInsufficientLength; - - ushort highSurrogate = (ushort)((codePoint - 0x10000) / 0x400 + 0xD800); - ushort lowSurrogate = (ushort)((codePoint - 0x10000) % 0x400 + 0xDC00); - - dst[0] = highSurrogate; - dst[1] = lowSurrogate; - src = src.Slice(4); - dst = dst.Slice(2); - break; - - default: - goto ReturnInvalidFormat; - } - } - - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; + codeUnitsWritten = 0; + codeUnitsRead = 0; return CharacterEncodingResult.Success; - - ReturnInvalidFormat: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InvalidFormat; - - ReturnInsufficientLength: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InsufficientLength; } - private static CharacterEncodingResult ConvertStringUtf16ToUtf8Impl(out int codeUnitsWritten, - out int codeUnitsRead, Span<byte> destination, ReadOnlySpan<ushort> source) + ReadOnlySpan<byte> src = source; + Span<ushort> dst = destination; + + while (src.Length > 0) { - if (source.Length == 0) + int codePointBytes = Utf8NBytesTable[src[0]]; + + if (src.Length < codePointBytes) + goto ReturnInvalidFormat; + + if (dst.Length == 0) + goto ReturnInsufficientLength; + + uint codePoint; + + switch (codePointBytes) { - codeUnitsWritten = 0; - codeUnitsRead = 0; - return CharacterEncodingResult.Success; - } - - ReadOnlySpan<ushort> src = source; - Span<byte> dst = destination; - - while (src.Length > 0) - { - ushort codeUnit1 = src[0]; - - if (codeUnit1 < 0x80) - { - if (dst.Length < 1) - goto ReturnInsufficientLength; - - dst[0] = (byte)codeUnit1; + case 1: + dst[0] = src[0]; src = src.Slice(1); dst = dst.Slice(1); - } - else if ((codeUnit1 & 0xF800) == 0) - { - if (dst.Length < 2) - goto ReturnInsufficientLength; - - dst[0] = (byte)(0xC0 | (codeUnit1 >> 6) & 0x1F); - dst[1] = (byte)(0x80 | codeUnit1 & 0x3F); - src = src.Slice(1); - dst = dst.Slice(2); - } - else if (codeUnit1 < 0xD800 || codeUnit1 >= 0xE000) - { - if (dst.Length < 3) - goto ReturnInsufficientLength; - - dst[0] = (byte)(0xE0 | (codeUnit1 >> 12) & 0xF); - dst[1] = (byte)(0x80 | (codeUnit1 >> 6) & 0x3F); - dst[2] = (byte)(0x80 | codeUnit1 & 0x3F); - src = src.Slice(1); - dst = dst.Slice(3); - } - else - { - uint utf32; - - if (source.Length == 1) - { - // If the code unit is a high surrogate - if ((codeUnit1 & 0xF800) == 0xD800 && (codeUnit1 & 0x400) == 0) - { - if (dst.Length < 1) - goto ReturnInsufficientLength; - - // We have the first half of a surrogate pair. Get the code point as if the low surrogate - // were 0xDC00, effectively ignoring it. The first byte of the UTF-8-encoded code point does not - // ever depend on the low surrogate, so we can write what the first byte would be. - // The second byte doesn't ever depend on the low surrogate either, so I don't know why Nintendo - // doesn't write that one too. I'll admit I'm not even sure why they write the first byte. This - // reasoning is simply my best guess. - const int codeUnit2 = 0xDC00; - utf32 = ((codeUnit1 - 0xD800u) << 10) + codeUnit2 + 0x2400; - - dst[0] = (byte)(0xF0 | (utf32 >> 18)); - dst = dst.Slice(1); - } + break; + case 2: + // Check if the encoding is overlong + if ((src[0] & 0x1E) == 0) goto ReturnInvalidFormat; - } - - int codeUnitsUsed = ConvertCharacterUtf16ToUtf32(out utf32, codeUnit1, src[1]); - - if (codeUnitsUsed < 0) - { - if (codeUnitsUsed == -2 && dst.Length > 0) - { - // We have an unpaired surrogate. Output the first UTF-8 code unit of the code point - // ConvertCharacterUtf16ToUtf32 gave us. Nintendo's reason for doing this is unclear. - dst[0] = (byte)(0xF0 | (utf32 >> 18)); - dst = dst.Slice(1); - } + if ((src[1] & 0xC0) != 0x80) goto ReturnInvalidFormat; - } - if (dst.Length < 4) - goto ReturnInsufficientLength; + codePoint = ((src[0] & 0x1Fu) << 6) | + ((src[1] & 0x3Fu) << 0); - dst[0] = (byte)(0xF0 | (utf32 >> 18)); - dst[1] = (byte)(0x80 | (utf32 >> 12) & 0x3F); - dst[2] = (byte)(0x80 | (utf32 >> 6) & 0x3F); - dst[3] = (byte)(0x80 | (utf32 >> 0) & 0x3F); + dst[0] = (ushort)codePoint; src = src.Slice(2); - dst = dst.Slice(4); - } - } - - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.Success; - - ReturnInvalidFormat: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InvalidFormat; - - ReturnInsufficientLength: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InsufficientLength; - } - - private static CharacterEncodingResult ConvertStringUtf8ToUtf32Impl(out int codeUnitsWritten, - out int codeUnitsRead, Span<uint> destination, ReadOnlySpan<byte> source) - { - if (source.Length == 0) - { - codeUnitsWritten = 0; - codeUnitsRead = 0; - return CharacterEncodingResult.Success; - } - - ReadOnlySpan<byte> src = source; - Span<uint> dst = destination; - - while (src.Length > 0) - { - int codePointBytes = Utf8NBytesTable[src[0]]; - - if (src.Length < codePointBytes) - goto ReturnInvalidFormat; - - if (dst.Length == 0) - goto ReturnInsufficientLength; - - uint codePoint; - - switch (codePointBytes) - { - case 1: - dst[0] = src[0]; - src = src.Slice(1); - dst = dst.Slice(1); - break; - - case 2: - // Check if the encoding is overlong - if ((src[0] & 0x1E) == 0) - goto ReturnInvalidFormat; - - if (Utf8NBytesTable[src[1]] != 0) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 0x1Fu) << 6) | - ((src[1] & 0x3Fu) << 0); - - dst[0] = codePoint; - src = src.Slice(2); - dst = dst.Slice(1); - break; - - case 3: - if (Utf8NBytesTable[src[1]] != 0) - goto ReturnInvalidFormat; - - if (Utf8NBytesTable[src[2]] != 0) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 0xFu) << 12) | - ((src[1] & 0x3Fu) << 6) | - ((src[2] & 0x3Fu) << 0); - - // Check if the encoding is overlong - if ((codePoint & 0xF800) == 0) - goto ReturnInvalidFormat; - - // Check if the code point is in the range reserved for UTF-16 surrogates - if ((codePoint & 0xF800) == 0xD800) - goto ReturnInvalidFormat; - - dst[0] = codePoint; - src = src.Slice(3); - dst = dst.Slice(1); - break; - - case 4: - if (Utf8NBytesTable[src[1]] != 0) - goto ReturnInvalidFormat; - - if (Utf8NBytesTable[src[2]] != 0) - goto ReturnInvalidFormat; - - if (Utf8NBytesTable[src[3]] != 0) - goto ReturnInvalidFormat; - - codePoint = ((src[0] & 7u) << 18) | - ((src[1] & 0x3Fu) << 12) | - ((src[2] & 0x3Fu) << 6) | - ((src[3] & 0x3Fu) << 0); - - // Check if the code point is outside the range of valid code points - if (codePoint < 0x10000 || codePoint >= 0x110000) - goto ReturnInvalidFormat; - - dst[0] = codePoint; - src = src.Slice(4); - dst = dst.Slice(1); - break; - - default: - goto ReturnInvalidFormat; - } - } - - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.Success; - - ReturnInvalidFormat: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InvalidFormat; - - ReturnInsufficientLength: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InsufficientLength; - } - - private static CharacterEncodingResult ConvertStringUtf32ToUtf8Impl(out int codeUnitsWritten, - out int codeUnitsRead, Span<byte> destination, ReadOnlySpan<uint> source) - { - if (source.Length == 0) - { - codeUnitsWritten = 0; - codeUnitsRead = 0; - return CharacterEncodingResult.Success; - } - - ReadOnlySpan<uint> src = source; - Span<byte> dst = destination; - - while ((uint)src.Length > 0) - { - uint codePoint = src[0]; - - if (codePoint < 0x80) - { - if (dst.Length < 1) - goto ReturnInsufficientLength; - - dst[0] = (byte)codePoint; dst = dst.Slice(1); - } - else if (codePoint < 0x800) - { - if (dst.Length < 2) - goto ReturnInsufficientLength; + break; - dst[0] = (byte)(0xC0 | codePoint >> 6); - dst[1] = (byte)(0x80 | codePoint & 0x3F); - dst = dst.Slice(2); - } - else if (codePoint < 0x10000) - { - if (codePoint >= 0xD800 && codePoint <= 0xDFFF) + case 3: + if ((src[1] & 0xC0) != 0x80) goto ReturnInvalidFormat; - if (dst.Length < 3) - goto ReturnInsufficientLength; + if ((src[2] & 0xC0) != 0x80) + goto ReturnInvalidFormat; - dst[0] = (byte)(0xE0 | (codePoint >> 12) & 0xF); - dst[1] = (byte)(0x80 | (codePoint >> 6) & 0x3F); - dst[2] = (byte)(0x80 | (codePoint >> 0) & 0x3F); - dst = dst.Slice(3); - } - else if (codePoint < 0x110000) - { - if (dst.Length < 4) - goto ReturnInsufficientLength; + codePoint = ((src[0] & 0xFu) << 12) | + ((src[1] & 0x3Fu) << 6) | + ((src[2] & 0x3Fu) << 0); - dst[0] = (byte)(0xF0 | codePoint >> 18); - dst[1] = (byte)(0x80 | (codePoint >> 12) & 0x3F); - dst[2] = (byte)(0x80 | (codePoint >> 6) & 0x3F); - dst[3] = (byte)(0x80 | (codePoint >> 0) & 0x3F); - dst = dst.Slice(4); - } - else - { - goto ReturnInvalidFormat; - } + // Check if the encoding is overlong + if ((codePoint & 0xF800) == 0) + goto ReturnInvalidFormat; - src = src.Slice(1); - } + // Check if the code point is in the range reserved for UTF-16 surrogates + if ((codePoint & 0xF800) == 0xD800) + goto ReturnInvalidFormat; - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.Success; - - ReturnInvalidFormat: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InvalidFormat; - - ReturnInsufficientLength: - codeUnitsWritten = destination.Length - dst.Length; - codeUnitsRead = source.Length - src.Length; - return CharacterEncodingResult.InsufficientLength; - } - - private static int ConvertCharacterUtf16ToUtf32(out uint outUtf32, ushort codeUnit1, ushort codeUnit2) - { - UnsafeHelpers.SkipParamInit(out outUtf32); - - // If the first code unit isn't a surrogate, simply copy it to the output - if ((codeUnit1 & 0xF800) != 0xD800) - { - outUtf32 = codeUnit1; - return 1; - } - - // Make sure the high surrogate isn't in the range of low surrogate values - if ((codeUnit1 & 0x400) != 0) - return -1; - - // We still output a code point value if we have an unpaired high surrogate. - // Nintendo's reason for doing this is unclear. - outUtf32 = ((codeUnit1 - 0xD800u) << 10) + codeUnit2 + 0x2400; - - // Make sure the low surrogate is in the range of low surrogate values - if ((codeUnit2 & 0xFC00) != 0xDC00) - return -2; - - return 2; - } - - private static int GetLengthOfUtf16(ReadOnlySpan<ushort> source) - { - for (int i = 0; i < source.Length; i++) - { - if (source[i] == 0) - return i; - } - - return source.Length; - } - - private static int GetLengthOfUtf32(ReadOnlySpan<uint> source) - { - for (int i = 0; i < source.Length; i++) - { - if (source[i] == 0) - return i; - } - - return source.Length; - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<ushort> destination, - ReadOnlySpan<byte> source, int sourceLength) - { - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - return ConvertStringUtf8ToUtf16Impl(out _, out _, destination, source.Slice(0, sourceLength)); - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<char> destination, - ReadOnlySpan<byte> source, int sourceLength) - { - return ConvertStringUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source, sourceLength); - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<ushort> destination, - ReadOnlySpan<byte> source) - { - int length = StringUtils.GetLength(source); - - Assert.SdkAssert(0 <= length); - - CharacterEncodingResult result = ConvertStringUtf8ToUtf16Impl(out int writtenCount, out _, - destination.Slice(0, destination.Length - 1), source.Slice(0, length)); - - if (result == CharacterEncodingResult.Success) - destination[writtenCount] = 0; - - return result; - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<char> destination, - ReadOnlySpan<byte> source) - { - return ConvertStringUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source); - } - - public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<ushort> source, int sourceLength) - { - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - return ConvertStringUtf16ToUtf8Impl(out _, out _, destination, source.Slice(0, sourceLength)); - } - - public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<char> source, int sourceLength) - { - return ConvertStringUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source), sourceLength); - } - - public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<ushort> source) - { - int length = GetLengthOfUtf16(source); - - Assert.SdkAssert(0 <= length); - - CharacterEncodingResult result = ConvertStringUtf16ToUtf8Impl(out int writtenCount, out _, - destination.Slice(0, destination.Length - 1), source.Slice(0, length)); - - if (result == CharacterEncodingResult.Success) - destination[writtenCount] = 0; - - return result; - } - - public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<char> source) - { - return ConvertStringUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source)); - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf16Native(out int length, - ReadOnlySpan<byte> source, int sourceLength) - { - UnsafeHelpers.SkipParamInit(out length); - Span<ushort> buffer = stackalloc ushort[0x20]; - - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - int totalLength = 0; - source = source.Slice(0, sourceLength); - - while (source.Length > 0) - { - CharacterEncodingResult result = - ConvertStringUtf8ToUtf16Impl(out int writtenCount, out int readCount, buffer, source); - - if (result == CharacterEncodingResult.InvalidFormat) - return CharacterEncodingResult.InvalidFormat; - - totalLength += writtenCount; - source = source.Slice(readCount); - } - - Assert.SdkAssert(0 <= totalLength); - - length = totalLength; - return CharacterEncodingResult.Success; - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf16Native(out int length, - ReadOnlySpan<byte> source) - { - int sourceLength = StringUtils.GetLength(source); - - Assert.SdkAssert(0 <= sourceLength); - - return GetLengthOfConvertedStringUtf8ToUtf16Native(out length, source, sourceLength); - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, - ReadOnlySpan<ushort> source, int sourceLength) - { - UnsafeHelpers.SkipParamInit(out length); - Span<byte> buffer = stackalloc byte[0x20]; - - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - int totalLength = 0; - source = source.Slice(0, sourceLength); - - while (source.Length > 0) - { - CharacterEncodingResult result = - ConvertStringUtf16ToUtf8Impl(out int writtenCount, out int readCount, buffer, source); - - if (result == CharacterEncodingResult.InvalidFormat) - return CharacterEncodingResult.InvalidFormat; - - totalLength += writtenCount; - source = source.Slice(readCount); - } - - Assert.SdkAssert(0 <= totalLength); - - length = totalLength; - return CharacterEncodingResult.Success; - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, - ReadOnlySpan<char> source, int sourceLength) - { - return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, MemoryMarshal.Cast<char, ushort>(source), - sourceLength); - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, - ReadOnlySpan<ushort> source) - { - int sourceLength = GetLengthOfUtf16(source); - - Assert.SdkAssert(0 <= sourceLength); - - return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, source, sourceLength); - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, - ReadOnlySpan<char> source) - { - return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, MemoryMarshal.Cast<char, ushort>(source)); - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf32(Span<uint> destination, - ReadOnlySpan<byte> source, int sourceLength) - { - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - return ConvertStringUtf8ToUtf32Impl(out _, out _, destination, source.Slice(0, sourceLength)); - } - - public static CharacterEncodingResult ConvertStringUtf8ToUtf32(Span<uint> destination, - ReadOnlySpan<byte> source) - { - int sourceLength = StringUtils.GetLength(source); - - Assert.SdkAssert(0 <= sourceLength); - - CharacterEncodingResult result = ConvertStringUtf8ToUtf32Impl(out int writtenCount, out _, - destination.Slice(0, destination.Length - 1), source.Slice(0, sourceLength)); - - if (result == CharacterEncodingResult.Success) - destination[writtenCount] = 0; - - return result; - } - - public static CharacterEncodingResult ConvertStringUtf32ToUtf8(Span<byte> destination, - ReadOnlySpan<uint> source, int sourceLength) - { - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - return ConvertStringUtf32ToUtf8Impl(out _, out _, destination, source.Slice(0, sourceLength)); - } - - public static CharacterEncodingResult ConvertStringUtf32ToUtf8(Span<byte> destination, - ReadOnlySpan<uint> source) - { - int sourceLength = GetLengthOfUtf32(source); - - Assert.SdkAssert(0 <= sourceLength); - - CharacterEncodingResult result = ConvertStringUtf32ToUtf8Impl(out int writtenCount, out _, - destination.Slice(0, destination.Length - 1), source.Slice(0, sourceLength)); - - if (result == CharacterEncodingResult.Success) - destination[writtenCount] = 0; - - return result; - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf32(out int length, - ReadOnlySpan<byte> source, int sourceLength) - { - UnsafeHelpers.SkipParamInit(out length); - Span<uint> buffer = stackalloc uint[0x20]; - - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - int totalLength = 0; - source = source.Slice(0, sourceLength); - - while (source.Length > 0) - { - CharacterEncodingResult result = - ConvertStringUtf8ToUtf32Impl(out int writtenCount, out int readCount, buffer, source); - - if (result == CharacterEncodingResult.InvalidFormat) - return CharacterEncodingResult.InvalidFormat; - - totalLength += writtenCount; - source = source.Slice(readCount); - } - - Assert.SdkAssert(0 <= totalLength); - - length = totalLength; - return CharacterEncodingResult.Success; - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf32(out int length, - ReadOnlySpan<byte> source) - { - int sourceLength = StringUtils.GetLength(source); - - Assert.SdkAssert(0 <= sourceLength); - - return GetLengthOfConvertedStringUtf8ToUtf32(out length, source, sourceLength); - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf32ToUtf8(out int length, - ReadOnlySpan<uint> source, int sourceLength) - { - UnsafeHelpers.SkipParamInit(out length); - Span<byte> buffer = stackalloc byte[0x20]; - - Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); - Assert.SdkRequires(sourceLength <= source.Length); - - int totalLength = 0; - source = source.Slice(0, sourceLength); - - while (source.Length > 0) - { - CharacterEncodingResult result = - ConvertStringUtf32ToUtf8Impl(out int writtenCount, out int readCount, buffer, source); - - if (result == CharacterEncodingResult.InvalidFormat) - return CharacterEncodingResult.InvalidFormat; - - totalLength += writtenCount; - source = source.Slice(readCount); - } - - Assert.SdkAssert(0 <= totalLength); - - length = totalLength; - return CharacterEncodingResult.Success; - } - - public static CharacterEncodingResult GetLengthOfConvertedStringUtf32ToUtf8(out int length, - ReadOnlySpan<uint> source) - { - int sourceLength = GetLengthOfUtf32(source); - - Assert.SdkAssert(0 <= sourceLength); - - return GetLengthOfConvertedStringUtf32ToUtf8(out length, source, sourceLength); - } - - public static CharacterEncodingResult ConvertCharacterUtf8ToUtf16Native(Span<ushort> destination, - ReadOnlySpan<byte> source) - { - if (destination.Length < 2) - return CharacterEncodingResult.InsufficientLength; - - if (source.Length < 1) - return CharacterEncodingResult.InvalidFormat; - - Span<byte> bufferSrc = stackalloc byte[5]; - Span<ushort> bufferDst = stackalloc ushort[3]; - - bufferSrc[0] = source[0]; - bufferSrc[1] = 0; - bufferSrc[2] = 0; - bufferSrc[3] = 0; - bufferSrc[4] = 0; - - // Read more code units if needed - if (source[0] >= 0xC2 && source[0] < 0xE0) - { - if (source.Length < 2) - return CharacterEncodingResult.InvalidFormat; - - bufferSrc[1] = source[1]; - } - else if (source[0] >= 0xE0 && source[0] < 0xF0) - { - if (source.Length < 3) - return CharacterEncodingResult.InvalidFormat; - - bufferSrc[1] = source[1]; - bufferSrc[2] = source[2]; - - } - else if (source[0] >= 0xF0 && source[0] < 0xF8) - { - if (source.Length < 4) - return CharacterEncodingResult.InvalidFormat; - - bufferSrc[1] = source[1]; - bufferSrc[2] = source[2]; - bufferSrc[3] = source[3]; - } - - bufferDst.Clear(); - - CharacterEncodingResult result = ConvertStringUtf8ToUtf16Native(bufferDst, bufferSrc); - destination[0] = bufferDst[0]; - destination[1] = bufferDst[1]; - - return result; - } - - public static CharacterEncodingResult ConvertCharacterUtf8ToUtf16Native(Span<char> destination, - ReadOnlySpan<byte> source) - { - return ConvertCharacterUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source); - } - - public static CharacterEncodingResult ConvertCharacterUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<ushort> source) - { - if (destination.Length < 4) - return CharacterEncodingResult.InsufficientLength; - - if (source.Length < 1) - return CharacterEncodingResult.InvalidFormat; - - Span<ushort> bufferSrc = stackalloc ushort[3]; - Span<byte> bufferDst = stackalloc byte[5]; - - bufferSrc[0] = source[0]; - bufferSrc[1] = 0; - bufferSrc[2] = 0; - - // Read more code units if needed - if (source[0] >= 0xD800 && source[0] < 0xE000) - { - if (source.Length < 2) - return CharacterEncodingResult.InvalidFormat; - - bufferSrc[1] = source[1]; - } - - bufferDst.Clear(); - - CharacterEncodingResult result = ConvertStringUtf16NativeToUtf8(bufferDst, bufferSrc); - destination[0] = bufferDst[0]; - destination[1] = bufferDst[1]; - destination[2] = bufferDst[2]; - destination[3] = bufferDst[3]; - - return result; - } - - public static CharacterEncodingResult ConvertCharacterUtf16NativeToUtf8(Span<byte> destination, - ReadOnlySpan<char> source) - { - return ConvertCharacterUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source)); - } - - public static CharacterEncodingResult ConvertCharacterUtf8ToUtf32(out uint destination, - ReadOnlySpan<byte> source) - { - UnsafeHelpers.SkipParamInit(out destination); - - if (source.Length < 1) - return CharacterEncodingResult.InvalidFormat; - - switch (Utf8NBytesTable[source[0]]) - { - case 1: - destination = source[0]; - return CharacterEncodingResult.Success; - - case 2: - if (source.Length < 2) break; - if ((source[0] & 0x1E) == 0) break; - if (Utf8NBytesTable[source[1]] != 0) break; - - destination = ((source[0] & 0x1Fu) << 6) | ((source[1] & 0x3Fu) << 0); - return CharacterEncodingResult.Success; - - case 3: - if (source.Length < 3) break; - if (Utf8NBytesTable[source[1]] != 0 || Utf8NBytesTable[source[2]] != 0) break; - - uint codePoint3 = ((source[0] & 0xFu) << 12) | ((source[1] & 0x3Fu) << 6) | ((source[2] & 0x3Fu) << 0); - - if ((codePoint3 & 0xF800) == 0 || (codePoint3 & 0xF800) == 0xD800) - break; - - destination = codePoint3; - return CharacterEncodingResult.Success; - - case 4: - if (source.Length < 4) break; - if (Utf8NBytesTable[source[1]] != 0 || Utf8NBytesTable[source[2]] != 0 || Utf8NBytesTable[source[3]] != 0) break; - - uint codePoint4 = ((source[0] & 7u) << 18) | ((source[1] & 0x3Fu) << 12) | ((source[2] & 0x3Fu) << 6) | ((source[3] & 0x3Fu) << 0); - - if (codePoint4 < 0x10000 || codePoint4 >= 0x110000) - break; - - destination = codePoint4; - return CharacterEncodingResult.Success; - } - - return CharacterEncodingResult.InvalidFormat; - } - - public static CharacterEncodingResult ConvertCharacterUtf32ToUtf8(Span<byte> destination, uint source) - { - if (destination.Length < 4) - return CharacterEncodingResult.InsufficientLength; - - destination[0] = 0; - destination[1] = 0; - destination[2] = 0; - destination[3] = 0; - - if (source < 0x80) - { - destination[0] = (byte)source; - } - else if (source < 0x800) - { - destination[0] = (byte)(0xC0 | source >> 6); - destination[1] = (byte)(0x80 | (source & 0x3F)); - } - else if (source < 0x10000) - { - if (source >= 0xD800 && source <= 0xDFFF) - return CharacterEncodingResult.InvalidFormat; - - destination[0] = (byte)(0xE0 | (source >> 12) & 0xF); - destination[1] = (byte)(0x80 | (source >> 6) & 0x3F); - destination[2] = (byte)(0x80 | (source >> 0) & 0x3F); - - } - else if (source < 0x110000) - { - destination[0] = (byte)(0xF0 | (source >> 18)); - destination[1] = (byte)(0x80 | (source >> 12) & 0x3F); - destination[2] = (byte)(0x80 | (source >> 6) & 0x3F); - destination[3] = (byte)(0x80 | (source >> 0) & 0x3F); - } - else - { - return CharacterEncodingResult.InvalidFormat; - } - - return CharacterEncodingResult.Success; - } - - public static CharacterEncodingResult PickOutCharacterFromUtf8String(Span<byte> destinationChar, - ref ReadOnlySpan<byte> source) - { - Assert.SdkRequires(destinationChar.Length >= 4); - Assert.SdkRequires(source.Length >= 1); - Assert.SdkRequires(source[0] != 0); - - ReadOnlySpan<byte> str = source; - - if (destinationChar.Length < 4) - return CharacterEncodingResult.InsufficientLength; - - if (str.Length < 1) - return CharacterEncodingResult.InvalidFormat; - - destinationChar[0] = 0; - destinationChar[1] = 0; - destinationChar[2] = 0; - destinationChar[3] = 0; - - uint codePoint = str[0]; - - switch (Utf8NBytesTable[(int)codePoint]) - { - case 1: - destinationChar[0] = str[0]; - source = str.Slice(1); - break; - - case 2: - if (str.Length < 2) - return CharacterEncodingResult.InvalidFormat; - - if ((str[0] & 0x1E) == 0 || Utf8NBytesTable[str[1]] != 0) - return CharacterEncodingResult.InvalidFormat; - - destinationChar[0] = str[0]; - destinationChar[1] = str[1]; - source = str.Slice(2); - break; - - case 3: - if (str.Length < 3) - return CharacterEncodingResult.InvalidFormat; - - if (Utf8NBytesTable[str[1]] != 0 || Utf8NBytesTable[str[2]] != 0) - return CharacterEncodingResult.InvalidFormat; - - codePoint = ((str[0] & 0xFu) << 12) | - ((str[1] & 0x3Fu) << 6) | - ((str[2] & 0x3Fu) << 0); - - if ((codePoint & 0xF800) == 0 || (codePoint & 0xF800) == 0xD800) - return CharacterEncodingResult.InvalidFormat; - - destinationChar[0] = str[0]; - destinationChar[1] = str[1]; - destinationChar[2] = str[2]; - source = str.Slice(3); + dst[0] = (ushort)codePoint; + src = src.Slice(3); + dst = dst.Slice(1); break; case 4: - if (str.Length < 4) - return CharacterEncodingResult.InvalidFormat; + if ((src[1] & 0xC0) != 0x80) + goto ReturnInvalidFormat; - if (Utf8NBytesTable[str[1]] != 0 || Utf8NBytesTable[str[2]] != 0 || Utf8NBytesTable[str[3]] != 0) - return CharacterEncodingResult.InvalidFormat; + if ((src[2] & 0xC0) != 0x80) + goto ReturnInvalidFormat; - codePoint = ((str[0] & 7u) << 18) | - ((str[1] & 0x3Fu) << 12) | - ((str[2] & 0x3Fu) << 6) | - ((str[3] & 0x3Fu) << 0); + if ((src[3] & 0xC0) != 0x80) + goto ReturnInvalidFormat; + codePoint = ((src[0] & 7u) << 18) | + ((src[1] & 0x3Fu) << 12) | + ((src[2] & 0x3Fu) << 6) | + ((src[3] & 0x3Fu) << 0); + + // Check if the code point is outside the range of valid code points if (codePoint < 0x10000 || codePoint >= 0x110000) - return CharacterEncodingResult.InvalidFormat; + goto ReturnInvalidFormat; - destinationChar[0] = str[0]; - destinationChar[1] = str[1]; - destinationChar[2] = str[2]; - destinationChar[3] = str[3]; - source = str.Slice(4); + // Make sure we have enough space left in the destination + if (dst.Length == 1) + goto ReturnInsufficientLength; + + ushort highSurrogate = (ushort)((codePoint - 0x10000) / 0x400 + 0xD800); + ushort lowSurrogate = (ushort)((codePoint - 0x10000) % 0x400 + 0xDC00); + + dst[0] = highSurrogate; + dst[1] = lowSurrogate; + src = src.Slice(4); + dst = dst.Slice(2); break; default: - return CharacterEncodingResult.InvalidFormat; + goto ReturnInvalidFormat; } + } + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.Success; + + ReturnInvalidFormat: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InvalidFormat; + + ReturnInsufficientLength: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InsufficientLength; + } + + private static CharacterEncodingResult ConvertStringUtf16ToUtf8Impl(out int codeUnitsWritten, + out int codeUnitsRead, Span<byte> destination, ReadOnlySpan<ushort> source) + { + if (source.Length == 0) + { + codeUnitsWritten = 0; + codeUnitsRead = 0; return CharacterEncodingResult.Success; } + + ReadOnlySpan<ushort> src = source; + Span<byte> dst = destination; + + while (src.Length > 0) + { + ushort codeUnit1 = src[0]; + + if (codeUnit1 < 0x80) + { + if (dst.Length < 1) + goto ReturnInsufficientLength; + + dst[0] = (byte)codeUnit1; + src = src.Slice(1); + dst = dst.Slice(1); + } + else if ((codeUnit1 & 0xF800) == 0) + { + if (dst.Length < 2) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xC0 | (codeUnit1 >> 6) & 0x1F); + dst[1] = (byte)(0x80 | codeUnit1 & 0x3F); + src = src.Slice(1); + dst = dst.Slice(2); + } + else if (codeUnit1 < 0xD800 || codeUnit1 >= 0xE000) + { + if (dst.Length < 3) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xE0 | (codeUnit1 >> 12) & 0xF); + dst[1] = (byte)(0x80 | (codeUnit1 >> 6) & 0x3F); + dst[2] = (byte)(0x80 | codeUnit1 & 0x3F); + src = src.Slice(1); + dst = dst.Slice(3); + } + else + { + uint utf32; + + if (source.Length == 1) + { + // If the code unit is a high surrogate + if ((codeUnit1 & 0xF800) == 0xD800 && (codeUnit1 & 0x400) == 0) + { + if (dst.Length < 1) + goto ReturnInsufficientLength; + + // We have the first half of a surrogate pair. Get the code point as if the low surrogate + // were 0xDC00, effectively ignoring it. The first byte of the UTF-8-encoded code point does not + // ever depend on the low surrogate, so we can write what the first byte would be. + // The second byte doesn't ever depend on the low surrogate either, so I don't know why Nintendo + // doesn't write that one too. I'll admit I'm not even sure why they write the first byte. This + // reasoning is simply my best guess. + const int codeUnit2 = 0xDC00; + utf32 = ((codeUnit1 - 0xD800u) << 10) + codeUnit2 + 0x2400; + + dst[0] = (byte)(0xF0 | (utf32 >> 18)); + dst = dst.Slice(1); + } + + goto ReturnInvalidFormat; + } + + int codeUnitsUsed = ConvertCharacterUtf16ToUtf32(out utf32, codeUnit1, src[1]); + + if (codeUnitsUsed < 0) + { + if (codeUnitsUsed == -2 && dst.Length > 0) + { + // We have an unpaired surrogate. Output the first UTF-8 code unit of the code point + // ConvertCharacterUtf16ToUtf32 gave us. Nintendo's reason for doing this is unclear. + dst[0] = (byte)(0xF0 | (utf32 >> 18)); + dst = dst.Slice(1); + } + + goto ReturnInvalidFormat; + } + + if (dst.Length < 4) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xF0 | (utf32 >> 18)); + dst[1] = (byte)(0x80 | (utf32 >> 12) & 0x3F); + dst[2] = (byte)(0x80 | (utf32 >> 6) & 0x3F); + dst[3] = (byte)(0x80 | (utf32 >> 0) & 0x3F); + src = src.Slice(2); + dst = dst.Slice(4); + } + } + + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.Success; + + ReturnInvalidFormat: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InvalidFormat; + + ReturnInsufficientLength: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InsufficientLength; + } + + private static CharacterEncodingResult ConvertStringUtf8ToUtf32Impl(out int codeUnitsWritten, + out int codeUnitsRead, Span<uint> destination, ReadOnlySpan<byte> source) + { + if (source.Length == 0) + { + codeUnitsWritten = 0; + codeUnitsRead = 0; + return CharacterEncodingResult.Success; + } + + ReadOnlySpan<byte> src = source; + Span<uint> dst = destination; + + while (src.Length > 0) + { + int codePointBytes = Utf8NBytesTable[src[0]]; + + if (src.Length < codePointBytes) + goto ReturnInvalidFormat; + + if (dst.Length == 0) + goto ReturnInsufficientLength; + + uint codePoint; + + switch (codePointBytes) + { + case 1: + dst[0] = src[0]; + src = src.Slice(1); + dst = dst.Slice(1); + break; + + case 2: + // Check if the encoding is overlong + if ((src[0] & 0x1E) == 0) + goto ReturnInvalidFormat; + + if (Utf8NBytesTable[src[1]] != 0) + goto ReturnInvalidFormat; + + codePoint = ((src[0] & 0x1Fu) << 6) | + ((src[1] & 0x3Fu) << 0); + + dst[0] = codePoint; + src = src.Slice(2); + dst = dst.Slice(1); + break; + + case 3: + if (Utf8NBytesTable[src[1]] != 0) + goto ReturnInvalidFormat; + + if (Utf8NBytesTable[src[2]] != 0) + goto ReturnInvalidFormat; + + codePoint = ((src[0] & 0xFu) << 12) | + ((src[1] & 0x3Fu) << 6) | + ((src[2] & 0x3Fu) << 0); + + // Check if the encoding is overlong + if ((codePoint & 0xF800) == 0) + goto ReturnInvalidFormat; + + // Check if the code point is in the range reserved for UTF-16 surrogates + if ((codePoint & 0xF800) == 0xD800) + goto ReturnInvalidFormat; + + dst[0] = codePoint; + src = src.Slice(3); + dst = dst.Slice(1); + break; + + case 4: + if (Utf8NBytesTable[src[1]] != 0) + goto ReturnInvalidFormat; + + if (Utf8NBytesTable[src[2]] != 0) + goto ReturnInvalidFormat; + + if (Utf8NBytesTable[src[3]] != 0) + goto ReturnInvalidFormat; + + codePoint = ((src[0] & 7u) << 18) | + ((src[1] & 0x3Fu) << 12) | + ((src[2] & 0x3Fu) << 6) | + ((src[3] & 0x3Fu) << 0); + + // Check if the code point is outside the range of valid code points + if (codePoint < 0x10000 || codePoint >= 0x110000) + goto ReturnInvalidFormat; + + dst[0] = codePoint; + src = src.Slice(4); + dst = dst.Slice(1); + break; + + default: + goto ReturnInvalidFormat; + } + } + + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.Success; + + ReturnInvalidFormat: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InvalidFormat; + + ReturnInsufficientLength: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InsufficientLength; + } + + private static CharacterEncodingResult ConvertStringUtf32ToUtf8Impl(out int codeUnitsWritten, + out int codeUnitsRead, Span<byte> destination, ReadOnlySpan<uint> source) + { + if (source.Length == 0) + { + codeUnitsWritten = 0; + codeUnitsRead = 0; + return CharacterEncodingResult.Success; + } + + ReadOnlySpan<uint> src = source; + Span<byte> dst = destination; + + while ((uint)src.Length > 0) + { + uint codePoint = src[0]; + + if (codePoint < 0x80) + { + if (dst.Length < 1) + goto ReturnInsufficientLength; + + dst[0] = (byte)codePoint; + dst = dst.Slice(1); + } + else if (codePoint < 0x800) + { + if (dst.Length < 2) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xC0 | codePoint >> 6); + dst[1] = (byte)(0x80 | codePoint & 0x3F); + dst = dst.Slice(2); + } + else if (codePoint < 0x10000) + { + if (codePoint >= 0xD800 && codePoint <= 0xDFFF) + goto ReturnInvalidFormat; + + if (dst.Length < 3) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xE0 | (codePoint >> 12) & 0xF); + dst[1] = (byte)(0x80 | (codePoint >> 6) & 0x3F); + dst[2] = (byte)(0x80 | (codePoint >> 0) & 0x3F); + dst = dst.Slice(3); + } + else if (codePoint < 0x110000) + { + if (dst.Length < 4) + goto ReturnInsufficientLength; + + dst[0] = (byte)(0xF0 | codePoint >> 18); + dst[1] = (byte)(0x80 | (codePoint >> 12) & 0x3F); + dst[2] = (byte)(0x80 | (codePoint >> 6) & 0x3F); + dst[3] = (byte)(0x80 | (codePoint >> 0) & 0x3F); + dst = dst.Slice(4); + } + else + { + goto ReturnInvalidFormat; + } + + src = src.Slice(1); + } + + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.Success; + + ReturnInvalidFormat: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InvalidFormat; + + ReturnInsufficientLength: + codeUnitsWritten = destination.Length - dst.Length; + codeUnitsRead = source.Length - src.Length; + return CharacterEncodingResult.InsufficientLength; + } + + private static int ConvertCharacterUtf16ToUtf32(out uint outUtf32, ushort codeUnit1, ushort codeUnit2) + { + UnsafeHelpers.SkipParamInit(out outUtf32); + + // If the first code unit isn't a surrogate, simply copy it to the output + if ((codeUnit1 & 0xF800) != 0xD800) + { + outUtf32 = codeUnit1; + return 1; + } + + // Make sure the high surrogate isn't in the range of low surrogate values + if ((codeUnit1 & 0x400) != 0) + return -1; + + // We still output a code point value if we have an unpaired high surrogate. + // Nintendo's reason for doing this is unclear. + outUtf32 = ((codeUnit1 - 0xD800u) << 10) + codeUnit2 + 0x2400; + + // Make sure the low surrogate is in the range of low surrogate values + if ((codeUnit2 & 0xFC00) != 0xDC00) + return -2; + + return 2; + } + + private static int GetLengthOfUtf16(ReadOnlySpan<ushort> source) + { + for (int i = 0; i < source.Length; i++) + { + if (source[i] == 0) + return i; + } + + return source.Length; + } + + private static int GetLengthOfUtf32(ReadOnlySpan<uint> source) + { + for (int i = 0; i < source.Length; i++) + { + if (source[i] == 0) + return i; + } + + return source.Length; + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<ushort> destination, + ReadOnlySpan<byte> source, int sourceLength) + { + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + return ConvertStringUtf8ToUtf16Impl(out _, out _, destination, source.Slice(0, sourceLength)); + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<char> destination, + ReadOnlySpan<byte> source, int sourceLength) + { + return ConvertStringUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source, sourceLength); + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<ushort> destination, + ReadOnlySpan<byte> source) + { + int length = StringUtils.GetLength(source); + + Assert.SdkAssert(0 <= length); + + CharacterEncodingResult result = ConvertStringUtf8ToUtf16Impl(out int writtenCount, out _, + destination.Slice(0, destination.Length - 1), source.Slice(0, length)); + + if (result == CharacterEncodingResult.Success) + destination[writtenCount] = 0; + + return result; + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf16Native(Span<char> destination, + ReadOnlySpan<byte> source) + { + return ConvertStringUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source); + } + + public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<ushort> source, int sourceLength) + { + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + return ConvertStringUtf16ToUtf8Impl(out _, out _, destination, source.Slice(0, sourceLength)); + } + + public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<char> source, int sourceLength) + { + return ConvertStringUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source), sourceLength); + } + + public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<ushort> source) + { + int length = GetLengthOfUtf16(source); + + Assert.SdkAssert(0 <= length); + + CharacterEncodingResult result = ConvertStringUtf16ToUtf8Impl(out int writtenCount, out _, + destination.Slice(0, destination.Length - 1), source.Slice(0, length)); + + if (result == CharacterEncodingResult.Success) + destination[writtenCount] = 0; + + return result; + } + + public static CharacterEncodingResult ConvertStringUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<char> source) + { + return ConvertStringUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source)); + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf16Native(out int length, + ReadOnlySpan<byte> source, int sourceLength) + { + UnsafeHelpers.SkipParamInit(out length); + Span<ushort> buffer = stackalloc ushort[0x20]; + + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + int totalLength = 0; + source = source.Slice(0, sourceLength); + + while (source.Length > 0) + { + CharacterEncodingResult result = + ConvertStringUtf8ToUtf16Impl(out int writtenCount, out int readCount, buffer, source); + + if (result == CharacterEncodingResult.InvalidFormat) + return CharacterEncodingResult.InvalidFormat; + + totalLength += writtenCount; + source = source.Slice(readCount); + } + + Assert.SdkAssert(0 <= totalLength); + + length = totalLength; + return CharacterEncodingResult.Success; + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf16Native(out int length, + ReadOnlySpan<byte> source) + { + int sourceLength = StringUtils.GetLength(source); + + Assert.SdkAssert(0 <= sourceLength); + + return GetLengthOfConvertedStringUtf8ToUtf16Native(out length, source, sourceLength); + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, + ReadOnlySpan<ushort> source, int sourceLength) + { + UnsafeHelpers.SkipParamInit(out length); + Span<byte> buffer = stackalloc byte[0x20]; + + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + int totalLength = 0; + source = source.Slice(0, sourceLength); + + while (source.Length > 0) + { + CharacterEncodingResult result = + ConvertStringUtf16ToUtf8Impl(out int writtenCount, out int readCount, buffer, source); + + if (result == CharacterEncodingResult.InvalidFormat) + return CharacterEncodingResult.InvalidFormat; + + totalLength += writtenCount; + source = source.Slice(readCount); + } + + Assert.SdkAssert(0 <= totalLength); + + length = totalLength; + return CharacterEncodingResult.Success; + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, + ReadOnlySpan<char> source, int sourceLength) + { + return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, MemoryMarshal.Cast<char, ushort>(source), + sourceLength); + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, + ReadOnlySpan<ushort> source) + { + int sourceLength = GetLengthOfUtf16(source); + + Assert.SdkAssert(0 <= sourceLength); + + return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, source, sourceLength); + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf16NativeToUtf8(out int length, + ReadOnlySpan<char> source) + { + return GetLengthOfConvertedStringUtf16NativeToUtf8(out length, MemoryMarshal.Cast<char, ushort>(source)); + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf32(Span<uint> destination, + ReadOnlySpan<byte> source, int sourceLength) + { + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + return ConvertStringUtf8ToUtf32Impl(out _, out _, destination, source.Slice(0, sourceLength)); + } + + public static CharacterEncodingResult ConvertStringUtf8ToUtf32(Span<uint> destination, + ReadOnlySpan<byte> source) + { + int sourceLength = StringUtils.GetLength(source); + + Assert.SdkAssert(0 <= sourceLength); + + CharacterEncodingResult result = ConvertStringUtf8ToUtf32Impl(out int writtenCount, out _, + destination.Slice(0, destination.Length - 1), source.Slice(0, sourceLength)); + + if (result == CharacterEncodingResult.Success) + destination[writtenCount] = 0; + + return result; + } + + public static CharacterEncodingResult ConvertStringUtf32ToUtf8(Span<byte> destination, + ReadOnlySpan<uint> source, int sourceLength) + { + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + return ConvertStringUtf32ToUtf8Impl(out _, out _, destination, source.Slice(0, sourceLength)); + } + + public static CharacterEncodingResult ConvertStringUtf32ToUtf8(Span<byte> destination, + ReadOnlySpan<uint> source) + { + int sourceLength = GetLengthOfUtf32(source); + + Assert.SdkAssert(0 <= sourceLength); + + CharacterEncodingResult result = ConvertStringUtf32ToUtf8Impl(out int writtenCount, out _, + destination.Slice(0, destination.Length - 1), source.Slice(0, sourceLength)); + + if (result == CharacterEncodingResult.Success) + destination[writtenCount] = 0; + + return result; + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf32(out int length, + ReadOnlySpan<byte> source, int sourceLength) + { + UnsafeHelpers.SkipParamInit(out length); + Span<uint> buffer = stackalloc uint[0x20]; + + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + int totalLength = 0; + source = source.Slice(0, sourceLength); + + while (source.Length > 0) + { + CharacterEncodingResult result = + ConvertStringUtf8ToUtf32Impl(out int writtenCount, out int readCount, buffer, source); + + if (result == CharacterEncodingResult.InvalidFormat) + return CharacterEncodingResult.InvalidFormat; + + totalLength += writtenCount; + source = source.Slice(readCount); + } + + Assert.SdkAssert(0 <= totalLength); + + length = totalLength; + return CharacterEncodingResult.Success; + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf8ToUtf32(out int length, + ReadOnlySpan<byte> source) + { + int sourceLength = StringUtils.GetLength(source); + + Assert.SdkAssert(0 <= sourceLength); + + return GetLengthOfConvertedStringUtf8ToUtf32(out length, source, sourceLength); + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf32ToUtf8(out int length, + ReadOnlySpan<uint> source, int sourceLength) + { + UnsafeHelpers.SkipParamInit(out length); + Span<byte> buffer = stackalloc byte[0x20]; + + Assert.SdkRequires(0 <= sourceLength, $"{nameof(sourceLength)} must not be negative."); + Assert.SdkRequires(sourceLength <= source.Length); + + int totalLength = 0; + source = source.Slice(0, sourceLength); + + while (source.Length > 0) + { + CharacterEncodingResult result = + ConvertStringUtf32ToUtf8Impl(out int writtenCount, out int readCount, buffer, source); + + if (result == CharacterEncodingResult.InvalidFormat) + return CharacterEncodingResult.InvalidFormat; + + totalLength += writtenCount; + source = source.Slice(readCount); + } + + Assert.SdkAssert(0 <= totalLength); + + length = totalLength; + return CharacterEncodingResult.Success; + } + + public static CharacterEncodingResult GetLengthOfConvertedStringUtf32ToUtf8(out int length, + ReadOnlySpan<uint> source) + { + int sourceLength = GetLengthOfUtf32(source); + + Assert.SdkAssert(0 <= sourceLength); + + return GetLengthOfConvertedStringUtf32ToUtf8(out length, source, sourceLength); + } + + public static CharacterEncodingResult ConvertCharacterUtf8ToUtf16Native(Span<ushort> destination, + ReadOnlySpan<byte> source) + { + if (destination.Length < 2) + return CharacterEncodingResult.InsufficientLength; + + if (source.Length < 1) + return CharacterEncodingResult.InvalidFormat; + + Span<byte> bufferSrc = stackalloc byte[5]; + Span<ushort> bufferDst = stackalloc ushort[3]; + + bufferSrc[0] = source[0]; + bufferSrc[1] = 0; + bufferSrc[2] = 0; + bufferSrc[3] = 0; + bufferSrc[4] = 0; + + // Read more code units if needed + if (source[0] >= 0xC2 && source[0] < 0xE0) + { + if (source.Length < 2) + return CharacterEncodingResult.InvalidFormat; + + bufferSrc[1] = source[1]; + } + else if (source[0] >= 0xE0 && source[0] < 0xF0) + { + if (source.Length < 3) + return CharacterEncodingResult.InvalidFormat; + + bufferSrc[1] = source[1]; + bufferSrc[2] = source[2]; + + } + else if (source[0] >= 0xF0 && source[0] < 0xF8) + { + if (source.Length < 4) + return CharacterEncodingResult.InvalidFormat; + + bufferSrc[1] = source[1]; + bufferSrc[2] = source[2]; + bufferSrc[3] = source[3]; + } + + bufferDst.Clear(); + + CharacterEncodingResult result = ConvertStringUtf8ToUtf16Native(bufferDst, bufferSrc); + destination[0] = bufferDst[0]; + destination[1] = bufferDst[1]; + + return result; + } + + public static CharacterEncodingResult ConvertCharacterUtf8ToUtf16Native(Span<char> destination, + ReadOnlySpan<byte> source) + { + return ConvertCharacterUtf8ToUtf16Native(MemoryMarshal.Cast<char, ushort>(destination), source); + } + + public static CharacterEncodingResult ConvertCharacterUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<ushort> source) + { + if (destination.Length < 4) + return CharacterEncodingResult.InsufficientLength; + + if (source.Length < 1) + return CharacterEncodingResult.InvalidFormat; + + Span<ushort> bufferSrc = stackalloc ushort[3]; + Span<byte> bufferDst = stackalloc byte[5]; + + bufferSrc[0] = source[0]; + bufferSrc[1] = 0; + bufferSrc[2] = 0; + + // Read more code units if needed + if (source[0] >= 0xD800 && source[0] < 0xE000) + { + if (source.Length < 2) + return CharacterEncodingResult.InvalidFormat; + + bufferSrc[1] = source[1]; + } + + bufferDst.Clear(); + + CharacterEncodingResult result = ConvertStringUtf16NativeToUtf8(bufferDst, bufferSrc); + destination[0] = bufferDst[0]; + destination[1] = bufferDst[1]; + destination[2] = bufferDst[2]; + destination[3] = bufferDst[3]; + + return result; + } + + public static CharacterEncodingResult ConvertCharacterUtf16NativeToUtf8(Span<byte> destination, + ReadOnlySpan<char> source) + { + return ConvertCharacterUtf16NativeToUtf8(destination, MemoryMarshal.Cast<char, ushort>(source)); + } + + public static CharacterEncodingResult ConvertCharacterUtf8ToUtf32(out uint destination, + ReadOnlySpan<byte> source) + { + UnsafeHelpers.SkipParamInit(out destination); + + if (source.Length < 1) + return CharacterEncodingResult.InvalidFormat; + + switch (Utf8NBytesTable[source[0]]) + { + case 1: + destination = source[0]; + return CharacterEncodingResult.Success; + + case 2: + if (source.Length < 2) break; + if ((source[0] & 0x1E) == 0) break; + if (Utf8NBytesTable[source[1]] != 0) break; + + destination = ((source[0] & 0x1Fu) << 6) | ((source[1] & 0x3Fu) << 0); + return CharacterEncodingResult.Success; + + case 3: + if (source.Length < 3) break; + if (Utf8NBytesTable[source[1]] != 0 || Utf8NBytesTable[source[2]] != 0) break; + + uint codePoint3 = ((source[0] & 0xFu) << 12) | ((source[1] & 0x3Fu) << 6) | ((source[2] & 0x3Fu) << 0); + + if ((codePoint3 & 0xF800) == 0 || (codePoint3 & 0xF800) == 0xD800) + break; + + destination = codePoint3; + return CharacterEncodingResult.Success; + + case 4: + if (source.Length < 4) break; + if (Utf8NBytesTable[source[1]] != 0 || Utf8NBytesTable[source[2]] != 0 || Utf8NBytesTable[source[3]] != 0) break; + + uint codePoint4 = ((source[0] & 7u) << 18) | ((source[1] & 0x3Fu) << 12) | ((source[2] & 0x3Fu) << 6) | ((source[3] & 0x3Fu) << 0); + + if (codePoint4 < 0x10000 || codePoint4 >= 0x110000) + break; + + destination = codePoint4; + return CharacterEncodingResult.Success; + } + + return CharacterEncodingResult.InvalidFormat; + } + + public static CharacterEncodingResult ConvertCharacterUtf32ToUtf8(Span<byte> destination, uint source) + { + if (destination.Length < 4) + return CharacterEncodingResult.InsufficientLength; + + destination[0] = 0; + destination[1] = 0; + destination[2] = 0; + destination[3] = 0; + + if (source < 0x80) + { + destination[0] = (byte)source; + } + else if (source < 0x800) + { + destination[0] = (byte)(0xC0 | source >> 6); + destination[1] = (byte)(0x80 | (source & 0x3F)); + } + else if (source < 0x10000) + { + if (source >= 0xD800 && source <= 0xDFFF) + return CharacterEncodingResult.InvalidFormat; + + destination[0] = (byte)(0xE0 | (source >> 12) & 0xF); + destination[1] = (byte)(0x80 | (source >> 6) & 0x3F); + destination[2] = (byte)(0x80 | (source >> 0) & 0x3F); + + } + else if (source < 0x110000) + { + destination[0] = (byte)(0xF0 | (source >> 18)); + destination[1] = (byte)(0x80 | (source >> 12) & 0x3F); + destination[2] = (byte)(0x80 | (source >> 6) & 0x3F); + destination[3] = (byte)(0x80 | (source >> 0) & 0x3F); + } + else + { + return CharacterEncodingResult.InvalidFormat; + } + + return CharacterEncodingResult.Success; + } + + public static CharacterEncodingResult PickOutCharacterFromUtf8String(Span<byte> destinationChar, + ref ReadOnlySpan<byte> source) + { + Assert.SdkRequires(destinationChar.Length >= 4); + Assert.SdkRequires(source.Length >= 1); + Assert.SdkRequires(source[0] != 0); + + ReadOnlySpan<byte> str = source; + + if (destinationChar.Length < 4) + return CharacterEncodingResult.InsufficientLength; + + if (str.Length < 1) + return CharacterEncodingResult.InvalidFormat; + + destinationChar[0] = 0; + destinationChar[1] = 0; + destinationChar[2] = 0; + destinationChar[3] = 0; + + uint codePoint = str[0]; + + switch (Utf8NBytesTable[(int)codePoint]) + { + case 1: + destinationChar[0] = str[0]; + source = str.Slice(1); + break; + + case 2: + if (str.Length < 2) + return CharacterEncodingResult.InvalidFormat; + + if ((str[0] & 0x1E) == 0 || Utf8NBytesTable[str[1]] != 0) + return CharacterEncodingResult.InvalidFormat; + + destinationChar[0] = str[0]; + destinationChar[1] = str[1]; + source = str.Slice(2); + break; + + case 3: + if (str.Length < 3) + return CharacterEncodingResult.InvalidFormat; + + if (Utf8NBytesTable[str[1]] != 0 || Utf8NBytesTable[str[2]] != 0) + return CharacterEncodingResult.InvalidFormat; + + codePoint = ((str[0] & 0xFu) << 12) | + ((str[1] & 0x3Fu) << 6) | + ((str[2] & 0x3Fu) << 0); + + if ((codePoint & 0xF800) == 0 || (codePoint & 0xF800) == 0xD800) + return CharacterEncodingResult.InvalidFormat; + + destinationChar[0] = str[0]; + destinationChar[1] = str[1]; + destinationChar[2] = str[2]; + source = str.Slice(3); + break; + + case 4: + if (str.Length < 4) + return CharacterEncodingResult.InvalidFormat; + + if (Utf8NBytesTable[str[1]] != 0 || Utf8NBytesTable[str[2]] != 0 || Utf8NBytesTable[str[3]] != 0) + return CharacterEncodingResult.InvalidFormat; + + codePoint = ((str[0] & 7u) << 18) | + ((str[1] & 0x3Fu) << 12) | + ((str[2] & 0x3Fu) << 6) | + ((str[3] & 0x3Fu) << 0); + + if (codePoint < 0x10000 || codePoint >= 0x110000) + return CharacterEncodingResult.InvalidFormat; + + destinationChar[0] = str[0]; + destinationChar[1] = str[1]; + destinationChar[2] = str[2]; + destinationChar[3] = str[3]; + source = str.Slice(4); + break; + + default: + return CharacterEncodingResult.InvalidFormat; + } + + return CharacterEncodingResult.Success; } } diff --git a/src/LibHac/Util/Impl/HexConverter.cs b/src/LibHac/Util/Impl/HexConverter.cs index 415e2eda..e7bd9471 100644 --- a/src/LibHac/Util/Impl/HexConverter.cs +++ b/src/LibHac/Util/Impl/HexConverter.cs @@ -5,229 +5,228 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace LibHac.Util.Impl +namespace LibHac.Util.Impl; + +internal static class HexConverter { - internal static class HexConverter + public enum Casing : uint { - public enum Casing : uint - { - // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. - Upper = 0, + // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. + Upper = 0, - // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. - // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) - // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, - // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) - // don't have the 0x20 bit set, so ORing them maps to - // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. - Lower = 0x2020U - } - - // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], - // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then - // subtract this integer from a constant minuend as shown below. - // - // [ 1000 1001 1000 1001 ] - // - [ 0000 HHHH 0000 LLLL ] - // ========================= - // [ *YYY **** *ZZZ **** ] - // - // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. - // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. - // (We don't care about the value of asterisked bits.) - // - // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). - // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). - // => hex := nibble + 55. - // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. - // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. - - // The commented out code below is code that directly implements the logic described above. - - // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); - // uint difference = 0x8989U - packedOriginalValues; - // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values - // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; - - // The code below is equivalent to the commented out code above but has been tweaked - // to allow codegen to make some extra optimizations. - - // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. - // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. - - // Finally, write to the output buffer starting with the *highest* index so that codegen can - // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) - - // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is - // writing to a span of known length (or the caller has already checked the bounds of the - // furthest access). - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToBytesBuffer(byte value, Span<byte> buffer, int startingIndex = 0, Casing casing = Casing.Upper) - { - uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; - uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; - - buffer[startingIndex + 1] = (byte)packedResult; - buffer[startingIndex] = (byte)(packedResult >> 8); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper) - { - uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; - uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; - - buffer[startingIndex + 1] = (char)(packedResult & 0xFF); - buffer[startingIndex] = (char)(packedResult >> 8); - } - - public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper) - { - Debug.Assert(chars.Length >= bytes.Length * 2); - - for (int pos = 0; pos < bytes.Length; ++pos) - { - ToCharsBuffer(bytes[pos], chars, pos * 2, casing); - } - } - - public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper) - { - fixed (byte* bytesPtr = bytes) - { - // Todo: Make lambda static in C# 9 - return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) => - { - var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length); - EncodeToUtf16(ros, chars, args.casing); - }); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static char ToCharUpper(int value) - { - value &= 0xF; - value += '0'; - - if (value > '9') - { - value += ('A' - ('9' + 1)); - } - - return (char)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static char ToCharLower(int value) - { - value &= 0xF; - value += '0'; - - if (value > '9') - { - value += ('a' - ('9' + 1)); - } - - return (char)value; - } - - public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes) - { - return TryDecodeFromUtf16(chars, bytes, out _); - } - - public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed) - { - Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); - Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); - - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; - while (j < bytes.Length) - { - byteLo = FromChar(chars[i + 1]); - byteHi = FromChar(chars[i]); - - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - break; - - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; - } - - if (byteLo == 0xFF) - i++; - - charsProcessed = i; - return (byteLo | byteHi) != 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromChar(int c) - { - return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromUpperChar(int c) - { - return c > 71 ? 0xFF : CharToHexLookup[c]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FromLowerChar(int c) - { - if ((uint)(c - '0') <= '9' - '0') - return c - '0'; - - if ((uint)(c - 'a') <= 'f' - 'a') - return c - 'a' + 10; - - return 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexChar(int c) - { - return FromChar(c) != 0xFF; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexUpperChar(int c) - { - return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsHexLowerChar(int c) - { - return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); - } - - /// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary> - public static ReadOnlySpan<byte> CharToHexLookup => new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 - }; + // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. + // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) + // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, + // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) + // don't have the 0x20 bit set, so ORing them maps to + // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. + Lower = 0x2020U } -} \ No newline at end of file + + // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], + // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then + // subtract this integer from a constant minuend as shown below. + // + // [ 1000 1001 1000 1001 ] + // - [ 0000 HHHH 0000 LLLL ] + // ========================= + // [ *YYY **** *ZZZ **** ] + // + // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. + // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. + // (We don't care about the value of asterisked bits.) + // + // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). + // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). + // => hex := nibble + 55. + // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. + // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. + + // The commented out code below is code that directly implements the logic described above. + + // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); + // uint difference = 0x8989U - packedOriginalValues; + // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values + // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; + + // The code below is equivalent to the commented out code above but has been tweaked + // to allow codegen to make some extra optimizations. + + // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. + // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. + + // Finally, write to the output buffer starting with the *highest* index so that codegen can + // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) + + // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is + // writing to a span of known length (or the caller has already checked the bounds of the + // furthest access). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToBytesBuffer(byte value, Span<byte> buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (byte)packedResult; + buffer[startingIndex] = (byte)(packedResult >> 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (char)(packedResult & 0xFF); + buffer[startingIndex] = (char)(packedResult >> 8); + } + + public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper) + { + Debug.Assert(chars.Length >= bytes.Length * 2); + + for (int pos = 0; pos < bytes.Length; ++pos) + { + ToCharsBuffer(bytes[pos], chars, pos * 2, casing); + } + } + + public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper) + { + fixed (byte* bytesPtr = bytes) + { + // Todo: Make lambda static in C# 9 + return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) => + { + var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length); + EncodeToUtf16(ros, chars, args.casing); + }); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharUpper(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('A' - ('9' + 1)); + } + + return (char)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharLower(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('a' - ('9' + 1)); + } + + return (char)value; + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes) + { + return TryDecodeFromUtf16(chars, bytes, out _); + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed) + { + Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); + Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); + + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. + if ((byteLo | byteHi) == 0xFF) + break; + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + i++; + + charsProcessed = i; + return (byteLo | byteHi) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromChar(int c) + { + return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromUpperChar(int c) + { + return c > 71 ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromLowerChar(int c) + { + if ((uint)(c - '0') <= '9' - '0') + return c - '0'; + + if ((uint)(c - 'a') <= 'f' - 'a') + return c - 'a' + 10; + + return 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexChar(int c) + { + return FromChar(c) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexUpperChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexLowerChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); + } + + /// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary> + public static ReadOnlySpan<byte> CharToHexLookup => new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + }; +} diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs index cac1eda0..f9d8ac7d 100644 --- a/src/LibHac/Util/Optional.cs +++ b/src/LibHac/Util/Optional.cs @@ -2,50 +2,49 @@ using LibHac.Common; using LibHac.Diag; -namespace LibHac.Util +namespace LibHac.Util; + +public struct Optional<T> { - public struct Optional<T> + private bool _hasValue; + private T _value; + + public readonly bool HasValue => _hasValue; + public ref T Value { - private bool _hasValue; - private T _value; - - public readonly bool HasValue => _hasValue; - public ref T Value + get { - get - { - Assert.SdkRequires(_hasValue); - // It's beautiful, working around C# rules - return ref MemoryMarshal.GetReference(SpanHelpers.CreateSpan(ref _value, 1)); - } - } - public readonly ref readonly T ValueRo - { - get - { - Assert.SdkRequires(_hasValue); - return ref MemoryMarshal.GetReference(SpanHelpers.CreateReadOnlySpan(in _value, 1)); - } - } - - public Optional(in T value) - { - _value = value; - _hasValue = true; - } - - public static implicit operator Optional<T>(in T value) => new Optional<T>(in value); - - public void Set(in T value) - { - _value = value; - _hasValue = true; - } - - public void Clear() - { - _hasValue = false; - _value = default; + Assert.SdkRequires(_hasValue); + // It's beautiful, working around C# rules + return ref MemoryMarshal.GetReference(SpanHelpers.CreateSpan(ref _value, 1)); } } + public readonly ref readonly T ValueRo + { + get + { + Assert.SdkRequires(_hasValue); + return ref MemoryMarshal.GetReference(SpanHelpers.CreateReadOnlySpan(in _value, 1)); + } + } + + public Optional(in T value) + { + _value = value; + _hasValue = true; + } + + public static implicit operator Optional<T>(in T value) => new Optional<T>(in value); + + public void Set(in T value) + { + _value = value; + _hasValue = true; + } + + public void Clear() + { + _hasValue = false; + _value = default; + } } diff --git a/src/LibHac/Util/Overlap.cs b/src/LibHac/Util/Overlap.cs index 1b05091d..6c10c095 100644 --- a/src/LibHac/Util/Overlap.cs +++ b/src/LibHac/Util/Overlap.cs @@ -1,20 +1,19 @@ -namespace LibHac.Util +namespace LibHac.Util; + +public static class Overlap { - public static class Overlap + public static bool HasOverlap(ulong start0, ulong size0, ulong start1, ulong size1) { - public static bool HasOverlap(ulong start0, ulong size0, ulong start1, ulong size1) + if (start0 <= start1) { - if (start0 <= start1) - { - return start1 < start0 + size0; - } - - return start0 < start1 + size1; + return start1 < start0 + size0; } - public static bool Contains(ulong start, ulong size, ulong value) - { - return start <= value && value < start + size; - } + return start0 < start1 + size1; + } + + public static bool Contains(ulong start, ulong size, ulong value) + { + return start <= value && value < start + size; } } diff --git a/src/LibHac/Util/StringUtils.cs b/src/LibHac/Util/StringUtils.cs index 634cc511..e3f561b7 100644 --- a/src/LibHac/Util/StringUtils.cs +++ b/src/LibHac/Util/StringUtils.cs @@ -2,319 +2,318 @@ using System.Text; using LibHac.Util.Impl; -namespace LibHac.Util +namespace LibHac.Util; + +public static class StringUtils { - public static class StringUtils + public static int Copy(Span<byte> dest, ReadOnlySpan<byte> source) { - public static int Copy(Span<byte> dest, ReadOnlySpan<byte> source) - { - int maxLen = Math.Min(dest.Length, source.Length); + int maxLen = Math.Min(dest.Length, source.Length); - int i; - for (i = 0; i < maxLen && source[i] != 0; i++) + int i; + for (i = 0; i < maxLen && source[i] != 0; i++) + dest[i] = source[i]; + + if (i < dest.Length) + { + dest[i] = 0; + } + + return i; + } + + public static int Copy(Span<byte> dest, ReadOnlySpan<byte> source, int maxLen) + { + int maxLenLocal = Math.Min(Math.Min(dest.Length, source.Length), maxLen); + + int i; + for (i = 0; i < maxLenLocal && source[i] != 0; i++) + dest[i] = source[i]; + + if (i < dest.Length) + { + dest[i] = 0; + } + + return i; + } + + public static int Strlcpy(Span<byte> dest, ReadOnlySpan<byte> source, int maxLen) + { + int maxLenLocal = Math.Min(Math.Min(dest.Length, source.Length), maxLen); + + int i = 0; + if (maxLenLocal > 0) + { + for (; i < maxLenLocal - 1 && source[i] != 0; i++) + { dest[i] = source[i]; - - if (i < dest.Length) - { - dest[i] = 0; } - return i; + dest[i] = 0; } - public static int Copy(Span<byte> dest, ReadOnlySpan<byte> source, int maxLen) + while (i < source.Length && source[i] != 0) { - int maxLenLocal = Math.Min(Math.Min(dest.Length, source.Length), maxLen); - - int i; - for (i = 0; i < maxLenLocal && source[i] != 0; i++) - dest[i] = source[i]; - - if (i < dest.Length) - { - dest[i] = 0; - } - - return i; + i++; } - public static int Strlcpy(Span<byte> dest, ReadOnlySpan<byte> source, int maxLen) + return i; + } + + public static int GetLength(ReadOnlySpan<byte> s) + { + int i = 0; + + while (i < s.Length && s[i] != 0) { - int maxLenLocal = Math.Min(Math.Min(dest.Length, source.Length), maxLen); - - int i = 0; - if (maxLenLocal > 0) - { - for (; i < maxLenLocal - 1 && source[i] != 0; i++) - { - dest[i] = source[i]; - } - - dest[i] = 0; - } - - while (i < source.Length && source[i] != 0) - { - i++; - } - - return i; + i++; } - public static int GetLength(ReadOnlySpan<byte> s) + return i; + } + + public static int GetLength(ReadOnlySpan<byte> s, int maxLen) + { + int i = 0; + + while (i < maxLen && i < s.Length && s[i] != 0) { - int i = 0; - - while (i < s.Length && s[i] != 0) - { - i++; - } - - return i; + i++; } - public static int GetLength(ReadOnlySpan<byte> s, int maxLen) + return i; + } + + public static int Compare(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2) + { + int i = 0; + + while (true) { - int i = 0; + int c1 = ((uint)i < (uint)s1.Length ? s1[i] : 0); + int c2 = ((uint)i < (uint)s2.Length ? s2[i] : 0); - while (i < maxLen && i < s.Length && s[i] != 0) - { - i++; - } + if (c1 != c2) + return c1 - c2; - return i; - } - - public static int Compare(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2) - { - int i = 0; - - while (true) - { - int c1 = ((uint)i < (uint)s1.Length ? s1[i] : 0); - int c2 = ((uint)i < (uint)s2.Length ? s2[i] : 0); - - if (c1 != c2) - return c1 - c2; - - if (c1 == 0) - return 0; - - i++; - } - } - - public static int Compare(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2, int maxLen) - { - for (int i = 0; i < maxLen; i++) - { - int c1 = ((uint)i < (uint)s1.Length ? s1[i] : 0); - int c2 = ((uint)i < (uint)s2.Length ? s2[i] : 0); - - if (c1 != c2) - return c1 - c2; - - if (c1 == 0) - return 0; - } - - return 0; - } - - public static int CompareCaseInsensitive(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2) - { - int i = 0; - - while (true) - { - int c1 = ((uint)i < (uint)s1.Length ? ToLowerAsciiInvariant(s1[i]) : 0); - int c2 = ((uint)i < (uint)s2.Length ? ToLowerAsciiInvariant(s2[i]) : 0); - - if (c1 != c2) - return c1 - c2; - - if (c1 == 0) - return 0; - - i++; - } - } - - public static int CompareCaseInsensitive(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2, int maxLen) - { - for (int i = 0; i < maxLen; i++) - { - int c1 = ((uint)i < (uint)s1.Length ? ToLowerAsciiInvariant(s1[i]) : 0); - int c2 = ((uint)i < (uint)s2.Length ? ToLowerAsciiInvariant(s2[i]) : 0); - - if (c1 != c2) - return c1 - c2; - - if (c1 == 0) - return 0; - } - - return 0; - } - - private static byte ToLowerAsciiInvariant(byte c) - { - if ((uint)(c - 'A') <= 'Z' - 'A') - { - c = (byte)(c | 0x20); - } - return c; - } - - /// <summary> - /// Concatenates 2 byte strings. - /// </summary> - /// <param name="dest"></param> - /// <param name="source"></param> - /// <returns>The length of the resulting string.</returns> - /// <remarks>This function appends the source string to the end of the null-terminated destination string. - /// If the destination buffer is not large enough to contain the resulting string, - /// bytes from the source string will be appended to the destination string util the buffer is full. - /// If the length of the final string is the same length of the destination buffer, - /// no null terminating byte will be written to the end of the string.</remarks> - public static int Concat(Span<byte> dest, ReadOnlySpan<byte> source) - { - return Concat(dest, source, GetLength(dest)); - } - - public static int Concat(Span<byte> dest, ReadOnlySpan<byte> source, int destLength) - { - int iDest = destLength; - - for (int i = 0; iDest < dest.Length && i < source.Length && source[i] != 0; i++, iDest++) - { - dest[iDest] = source[i]; - } - - if (iDest < dest.Length) - { - dest[iDest] = 0; - } - - return iDest; - } - - public static int Find(ReadOnlySpan<byte> haystack, ReadOnlySpan<byte> needle) - { - if (needle.Length == 0) + if (c1 == 0) return 0; - for (int i = 0; i <= haystack.Length - needle.Length; i++) - { - int j; - for (j = 0; j < needle.Length && haystack[i + j] == needle[j]; j++) { } + i++; + } + } - if (j == needle.Length) - return i; - } + public static int Compare(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2, int maxLen) + { + for (int i = 0; i < maxLen; i++) + { + int c1 = ((uint)i < (uint)s1.Length ? s1[i] : 0); + int c2 = ((uint)i < (uint)s2.Length ? s2[i] : 0); - return -1; + if (c1 != c2) + return c1 - c2; + + if (c1 == 0) + return 0; } - public static ReadOnlySpan<byte> StringToUtf8(string value) + return 0; + } + + public static int CompareCaseInsensitive(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2) + { + int i = 0; + + while (true) { - return Encoding.UTF8.GetBytes(value).AsSpan(); + int c1 = ((uint)i < (uint)s1.Length ? ToLowerAsciiInvariant(s1[i]) : 0); + int c2 = ((uint)i < (uint)s2.Length ? ToLowerAsciiInvariant(s2[i]) : 0); + + if (c1 != c2) + return c1 - c2; + + if (c1 == 0) + return 0; + + i++; + } + } + + public static int CompareCaseInsensitive(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2, int maxLen) + { + for (int i = 0; i < maxLen; i++) + { + int c1 = ((uint)i < (uint)s1.Length ? ToLowerAsciiInvariant(s1[i]) : 0); + int c2 = ((uint)i < (uint)s2.Length ? ToLowerAsciiInvariant(s2[i]) : 0); + + if (c1 != c2) + return c1 - c2; + + if (c1 == 0) + return 0; } - public static string Utf8ToString(ReadOnlySpan<byte> value) + return 0; + } + + private static byte ToLowerAsciiInvariant(byte c) + { + if ((uint)(c - 'A') <= 'Z' - 'A') { - return Encoding.UTF8.GetString(value); + c = (byte)(c | 0x20); + } + return c; + } + + /// <summary> + /// Concatenates 2 byte strings. + /// </summary> + /// <param name="dest"></param> + /// <param name="source"></param> + /// <returns>The length of the resulting string.</returns> + /// <remarks>This function appends the source string to the end of the null-terminated destination string. + /// If the destination buffer is not large enough to contain the resulting string, + /// bytes from the source string will be appended to the destination string util the buffer is full. + /// If the length of the final string is the same length of the destination buffer, + /// no null terminating byte will be written to the end of the string.</remarks> + public static int Concat(Span<byte> dest, ReadOnlySpan<byte> source) + { + return Concat(dest, source, GetLength(dest)); + } + + public static int Concat(Span<byte> dest, ReadOnlySpan<byte> source, int destLength) + { + int iDest = destLength; + + for (int i = 0; iDest < dest.Length && i < source.Length && source[i] != 0; i++, iDest++) + { + dest[iDest] = source[i]; } - public static string NullTerminatedUtf8ToString(ReadOnlySpan<byte> value) + if (iDest < dest.Length) { - int length = GetLength(value); - - return Encoding.UTF8.GetString(value.Slice(0, length)); + dest[iDest] = 0; } - public static string Utf8ZToString(ReadOnlySpan<byte> value) + return iDest; + } + + public static int Find(ReadOnlySpan<byte> haystack, ReadOnlySpan<byte> needle) + { + if (needle.Length == 0) + return 0; + + for (int i = 0; i <= haystack.Length - needle.Length; i++) { - return Utf8ToString(value.Slice(0, GetLength(value))); + int j; + for (j = 0; j < needle.Length && haystack[i + j] == needle[j]; j++) { } + + if (j == needle.Length) + return i; } - public static bool IsAlpha(byte c) - { - return (c | 0x20u) - (byte)'a' <= 'z' - 'a'; - } + return -1; + } - public static bool IsDigit(byte c) - { - return (uint)(c - (byte)'0') <= 9; - } + public static ReadOnlySpan<byte> StringToUtf8(string value) + { + return Encoding.UTF8.GetBytes(value).AsSpan(); + } - public static bool IsHexDigit(byte c) - { - return HexConverter.CharToHexLookup[c] != 0xFF; - } + public static string Utf8ToString(ReadOnlySpan<byte> value) + { + return Encoding.UTF8.GetString(value); + } - public static bool TryFromHexString(ReadOnlySpan<char> chars, Span<byte> outputBytes) - { - if ((uint)chars.Length % 2 != 0) - return false; + public static string NullTerminatedUtf8ToString(ReadOnlySpan<byte> value) + { + int length = GetLength(value); - uint bytesLength = (uint)chars.Length / 2; + return Encoding.UTF8.GetString(value.Slice(0, length)); + } - if ((uint)outputBytes.Length >= bytesLength) - { - Span<byte> bytes = outputBytes.Slice(0, (int)bytesLength); - return HexConverter.TryDecodeFromUtf16(chars, bytes); - } + public static string Utf8ZToString(ReadOnlySpan<byte> value) + { + return Utf8ToString(value.Slice(0, GetLength(value))); + } + public static bool IsAlpha(byte c) + { + return (c | 0x20u) - (byte)'a' <= 'z' - 'a'; + } + + public static bool IsDigit(byte c) + { + return (uint)(c - (byte)'0') <= 9; + } + + public static bool IsHexDigit(byte c) + { + return HexConverter.CharToHexLookup[c] != 0xFF; + } + + public static bool TryFromHexString(ReadOnlySpan<char> chars, Span<byte> outputBytes) + { + if ((uint)chars.Length % 2 != 0) return false; + + uint bytesLength = (uint)chars.Length / 2; + + if ((uint)outputBytes.Length >= bytesLength) + { + Span<byte> bytes = outputBytes.Slice(0, (int)bytesLength); + return HexConverter.TryDecodeFromUtf16(chars, bytes); } - public static byte[] ToBytes(this string input) + return false; + } + + public static byte[] ToBytes(this string input) + { + return FromHexString(input); + } + + public static byte[] FromHexString(string input) + { + if ((uint)input.Length % 2 != 0) + throw new FormatException("Hex input must be a multiple of 2."); + + byte[] result = new byte[input.Length >> 1]; + + if (!HexConverter.TryDecodeFromUtf16(input, result)) { - return FromHexString(input); + throw new FormatException("Hex input contains invalid characters."); } - public static byte[] FromHexString(string input) + return result; + } + + public static void FromHexString(ReadOnlySpan<char> chars, Span<byte> outputBytes) + { + if ((uint)chars.Length % 2 != 0) + throw new FormatException("Hex input must be a multiple of 2."); + + uint bytesLength = (uint)chars.Length / 2; + + if ((uint)outputBytes.Length >= bytesLength) { - if ((uint)input.Length % 2 != 0) - throw new FormatException("Hex input must be a multiple of 2."); + Span<byte> bytes = outputBytes.Slice(0, (int)bytesLength); - byte[] result = new byte[input.Length >> 1]; - - if (!HexConverter.TryDecodeFromUtf16(input, result)) + if (!HexConverter.TryDecodeFromUtf16(chars, bytes)) { throw new FormatException("Hex input contains invalid characters."); } - - return result; } - public static void FromHexString(ReadOnlySpan<char> chars, Span<byte> outputBytes) - { - if ((uint)chars.Length % 2 != 0) - throw new FormatException("Hex input must be a multiple of 2."); + throw new ArgumentException("Buffer is not large enough to fit the input hex string.", nameof(outputBytes)); + } - uint bytesLength = (uint)chars.Length / 2; + public static string ToHexString(this byte[] bytes) => ToHexString((ReadOnlySpan<byte>)bytes); + public static string ToHexString(this Span<byte> bytes) => ToHexString((ReadOnlySpan<byte>)bytes); - if ((uint)outputBytes.Length >= bytesLength) - { - Span<byte> bytes = outputBytes.Slice(0, (int)bytesLength); - - if (!HexConverter.TryDecodeFromUtf16(chars, bytes)) - { - throw new FormatException("Hex input contains invalid characters."); - } - } - - throw new ArgumentException("Buffer is not large enough to fit the input hex string.", nameof(outputBytes)); - } - - public static string ToHexString(this byte[] bytes) => ToHexString((ReadOnlySpan<byte>)bytes); - public static string ToHexString(this Span<byte> bytes) => ToHexString((ReadOnlySpan<byte>)bytes); - - public static string ToHexString(this ReadOnlySpan<byte> bytes) - { - return HexConverter.ToString(bytes); - } + public static string ToHexString(this ReadOnlySpan<byte> bytes) + { + return HexConverter.ToString(bytes); } } diff --git a/src/LibHac/Util/UniqueLock.cs b/src/LibHac/Util/UniqueLock.cs index ed4f68b9..8820fb1e 100644 --- a/src/LibHac/Util/UniqueLock.cs +++ b/src/LibHac/Util/UniqueLock.cs @@ -1,71 +1,70 @@ using System; using System.Threading; -namespace LibHac.Util +namespace LibHac.Util; + +/// <summary> +/// Represents a lock that may be passed between functions or objects. +/// </summary> +/// <remarks>This struct must never be copied. It must always be passed by +/// reference or moved via the move constructor.</remarks> +public struct UniqueLock : IDisposable { + private object _lockObject; + private bool _isLocked; + /// <summary> - /// Represents a lock that may be passed between functions or objects. + /// Creates a new <see cref="UniqueLock"/> from the provided object and acquires the lock. /// </summary> - /// <remarks>This struct must never be copied. It must always be passed by - /// reference or moved via the move constructor.</remarks> - public struct UniqueLock : IDisposable + /// <param name="lockObject">The object to lock.</param> + public UniqueLock(object lockObject) { - private object _lockObject; - private bool _isLocked; + _lockObject = lockObject; + _isLocked = false; - /// <summary> - /// Creates a new <see cref="UniqueLock"/> from the provided object and acquires the lock. - /// </summary> - /// <param name="lockObject">The object to lock.</param> - public UniqueLock(object lockObject) + Lock(); + } + + public UniqueLock(ref UniqueLock other) + { + _lockObject = other._lockObject; + _isLocked = other._isLocked; + + other._isLocked = false; + other._lockObject = null; + } + + public bool IsLocked => _isLocked; + + public void Lock() + { + if (_isLocked) { - _lockObject = lockObject; - _isLocked = false; - - Lock(); + throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked."); } - public UniqueLock(ref UniqueLock other) - { - _lockObject = other._lockObject; - _isLocked = other._isLocked; + Monitor.Enter(_lockObject, ref _isLocked); + } - other._isLocked = false; - other._lockObject = null; + public void Unlock() + { + if (!_isLocked) + { + throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked."); } - public bool IsLocked => _isLocked; + Monitor.Exit(_lockObject); + _isLocked = false; + } - public void Lock() + public void Dispose() + { + if (_isLocked) { - if (_isLocked) - { - throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked."); - } - - Monitor.Enter(_lockObject, ref _isLocked); - } - - public void Unlock() - { - if (!_isLocked) - { - throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked."); - } - Monitor.Exit(_lockObject); + _isLocked = false; - } - - public void Dispose() - { - if (_isLocked) - { - Monitor.Exit(_lockObject); - - _isLocked = false; - _lockObject = null; - } + _lockObject = null; } } } diff --git a/src/LibHac/Utilities.cs b/src/LibHac/Utilities.cs index a358671c..1f3eeb15 100644 --- a/src/LibHac/Utilities.cs +++ b/src/LibHac/Utilities.cs @@ -4,300 +4,299 @@ using System.Numerics; using System.Runtime.InteropServices; using System.Text; -namespace LibHac +namespace LibHac; + +public static class Utilities { - public static class Utilities + private const int MediaSize = 0x200; + + public static bool ArraysEqual<T>(T[] a1, T[] a2) { - private const int MediaSize = 0x200; + if (a1 == null || a2 == null) return false; + if (a1 == a2) return true; + if (a1.Length != a2.Length) return false; - public static bool ArraysEqual<T>(T[] a1, T[] a2) + for (int i = 0; i < a1.Length; i++) { - if (a1 == null || a2 == null) return false; - if (a1 == a2) return true; - if (a1.Length != a2.Length) return false; - - for (int i = 0; i < a1.Length; i++) + if (!a1[i].Equals(a2[i])) { - if (!a1[i].Equals(a2[i])) - { - return false; - } - } - - return true; - } - - public static bool SpansEqual<T>(Span<T> a1, Span<T> a2) where T : IEquatable<T> - { - return a1.SequenceEqual(a2); - } - - public static bool SpansEqual<T>(ReadOnlySpan<T> a1, ReadOnlySpan<T> a2) where T : IEquatable<T> - { - return a1.SequenceEqual(a2); - } - - public static bool IsZeros(this byte[] array) => ((ReadOnlySpan<byte>)array).IsZeros(); - public static bool IsZeros(this Span<byte> span) => ((ReadOnlySpan<byte>)span).IsZeros(); - - public static bool IsZeros(this ReadOnlySpan<byte> span) - { - for (int i = 0; i < span.Length; i++) - { - if (span[i] != 0) - { - return false; - } - } - - return true; - } - - public static void XorArrays(Span<byte> transformData, ReadOnlySpan<byte> xorData) - { - int sisdStart = 0; - if (Vector.IsHardwareAccelerated) - { - Span<Vector<byte>> dataVec = MemoryMarshal.Cast<byte, Vector<byte>>(transformData); - ReadOnlySpan<Vector<byte>> xorVec = MemoryMarshal.Cast<byte, Vector<byte>>(xorData); - sisdStart = dataVec.Length * Vector<byte>.Count; - - for (int i = 0; i < dataVec.Length; i++) - { - dataVec[i] ^= xorVec[i]; - } - } - - for (int i = sisdStart; i < transformData.Length; i++) - { - transformData[i] ^= xorData[i]; + return false; } } - public static void XorArrays(Span<byte> output, ReadOnlySpan<byte> input1, ReadOnlySpan<byte> input2) + return true; + } + + public static bool SpansEqual<T>(Span<T> a1, Span<T> a2) where T : IEquatable<T> + { + return a1.SequenceEqual(a2); + } + + public static bool SpansEqual<T>(ReadOnlySpan<T> a1, ReadOnlySpan<T> a2) where T : IEquatable<T> + { + return a1.SequenceEqual(a2); + } + + public static bool IsZeros(this byte[] array) => ((ReadOnlySpan<byte>)array).IsZeros(); + public static bool IsZeros(this Span<byte> span) => ((ReadOnlySpan<byte>)span).IsZeros(); + + public static bool IsZeros(this ReadOnlySpan<byte> span) + { + for (int i = 0; i < span.Length; i++) { - int length = Math.Min(input1.Length, input2.Length); - - int sisdStart = 0; - if (Vector.IsHardwareAccelerated) + if (span[i] != 0) { - int lengthVec = length / Vector<byte>.Count; - - Span<Vector<byte>> outputVec = MemoryMarshal.Cast<byte, Vector<byte>>(output); - ReadOnlySpan<Vector<byte>> input1Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input1); - ReadOnlySpan<Vector<byte>> input2Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input2); - - sisdStart = lengthVec * Vector<byte>.Count; - - for (int i = 0; i < lengthVec; i++) - { - outputVec[i] = input1Vec[i] ^ input2Vec[i]; - } - } - - for (int i = sisdStart; i < length; i++) - { - output[i] = (byte)(input1[i] ^ input2[i]); + return false; } } - public static void CopyStream(this Stream input, Stream output, long length, IProgressReport progress = null) - { - const int bufferSize = 0x8000; - long remaining = length; - byte[] buffer = new byte[bufferSize]; - progress?.SetTotal(length); + return true; + } - int read; - while ((read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, remaining))) > 0) + public static void XorArrays(Span<byte> transformData, ReadOnlySpan<byte> xorData) + { + int sisdStart = 0; + if (Vector.IsHardwareAccelerated) + { + Span<Vector<byte>> dataVec = MemoryMarshal.Cast<byte, Vector<byte>>(transformData); + ReadOnlySpan<Vector<byte>> xorVec = MemoryMarshal.Cast<byte, Vector<byte>>(xorData); + sisdStart = dataVec.Length * Vector<byte>.Count; + + for (int i = 0; i < dataVec.Length; i++) { - output.Write(buffer, 0, read); - remaining -= read; - progress?.ReportAdd(read); + dataVec[i] ^= xorVec[i]; } } - public static void WriteAllBytes(this Stream input, string filename, IProgressReport progress = null) + for (int i = sisdStart; i < transformData.Length; i++) { - input.Position = 0; + transformData[i] ^= xorData[i]; + } + } - using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write)) + public static void XorArrays(Span<byte> output, ReadOnlySpan<byte> input1, ReadOnlySpan<byte> input2) + { + int length = Math.Min(input1.Length, input2.Length); + + int sisdStart = 0; + if (Vector.IsHardwareAccelerated) + { + int lengthVec = length / Vector<byte>.Count; + + Span<Vector<byte>> outputVec = MemoryMarshal.Cast<byte, Vector<byte>>(output); + ReadOnlySpan<Vector<byte>> input1Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input1); + ReadOnlySpan<Vector<byte>> input2Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input2); + + sisdStart = lengthVec * Vector<byte>.Count; + + for (int i = 0; i < lengthVec; i++) { - input.CopyStream(outFile, input.Length, progress); + outputVec[i] = input1Vec[i] ^ input2Vec[i]; } } - public static string ReadAsciiZ(this BinaryReader reader, int maxLength = Int32.MaxValue) + for (int i = sisdStart; i < length; i++) { - long start = reader.BaseStream.Position; - int size = 0; + output[i] = (byte)(input1[i] ^ input2[i]); + } + } - // Read until we hit the end of the stream (-1) or a zero - while (reader.BaseStream.ReadByte() - 1 > 0 && size < maxLength) - { - size++; - } + public static void CopyStream(this Stream input, Stream output, long length, IProgressReport progress = null) + { + const int bufferSize = 0x8000; + long remaining = length; + byte[] buffer = new byte[bufferSize]; + progress?.SetTotal(length); - reader.BaseStream.Position = start; - string text = reader.ReadAscii(size); - reader.BaseStream.Position++; // Skip the null byte - return text; + int read; + while ((read = input.Read(buffer, 0, (int)Math.Min(buffer.Length, remaining))) > 0) + { + output.Write(buffer, 0, read); + remaining -= read; + progress?.ReportAdd(read); + } + } + + public static void WriteAllBytes(this Stream input, string filename, IProgressReport progress = null) + { + input.Position = 0; + + using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write)) + { + input.CopyStream(outFile, input.Length, progress); + } + } + + public static string ReadAsciiZ(this BinaryReader reader, int maxLength = Int32.MaxValue) + { + long start = reader.BaseStream.Position; + int size = 0; + + // Read until we hit the end of the stream (-1) or a zero + while (reader.BaseStream.ReadByte() - 1 > 0 && size < maxLength) + { + size++; } - public static string ReadUtf8Z(this BinaryReader reader, int maxLength = int.MaxValue) + reader.BaseStream.Position = start; + string text = reader.ReadAscii(size); + reader.BaseStream.Position++; // Skip the null byte + return text; + } + + public static string ReadUtf8Z(this BinaryReader reader, int maxLength = int.MaxValue) + { + long start = reader.BaseStream.Position; + int size = 0; + + // Read until we hit the end of the stream (-1) or a zero + while (reader.BaseStream.ReadByte() - 1 > 0 && size < maxLength) { - long start = reader.BaseStream.Position; - int size = 0; - - // Read until we hit the end of the stream (-1) or a zero - while (reader.BaseStream.ReadByte() - 1 > 0 && size < maxLength) - { - size++; - } - - reader.BaseStream.Position = start; - string text = reader.ReadUtf8(size); - reader.BaseStream.Position++; // Skip the null byte - return text; + size++; } - public static void WriteUTF8(this BinaryWriter writer, string value) - { - byte[] text = Encoding.UTF8.GetBytes(value); - writer.Write(text); - } + reader.BaseStream.Position = start; + string text = reader.ReadUtf8(size); + reader.BaseStream.Position++; // Skip the null byte + return text; + } - public static void WriteUTF8Z(this BinaryWriter writer, string value) - { - writer.WriteUTF8(value); - writer.Write((byte)0); - } + public static void WriteUTF8(this BinaryWriter writer, string value) + { + byte[] text = Encoding.UTF8.GetBytes(value); + writer.Write(text); + } - public static string ReadAscii(this BinaryReader reader, int size) - { - return Encoding.ASCII.GetString(reader.ReadBytes(size), 0, size); - } + public static void WriteUTF8Z(this BinaryWriter writer, string value) + { + writer.WriteUTF8(value); + writer.Write((byte)0); + } - public static string ReadUtf8(this BinaryReader reader, int size) - { - return Encoding.UTF8.GetString(reader.ReadBytes(size), 0, size); - } + public static string ReadAscii(this BinaryReader reader, int size) + { + return Encoding.ASCII.GetString(reader.ReadBytes(size), 0, size); + } - public static long MediaToReal(long media) - { - return MediaSize * media; - } + public static string ReadUtf8(this BinaryReader reader, int size) + { + return Encoding.UTF8.GetString(reader.ReadBytes(size), 0, size); + } - // https://stackoverflow.com/a/11124118 - public static string GetBytesReadable(long bytes) + public static long MediaToReal(long media) + { + return MediaSize * media; + } + + // https://stackoverflow.com/a/11124118 + public static string GetBytesReadable(long bytes) + { + // Get absolute value + long absBytes = bytes < 0 ? -bytes : bytes; + // Determine the suffix and readable value + string suffix; + double readable; + if (absBytes >= 0x1000000000000000) // Exabyte { - // Get absolute value - long absBytes = bytes < 0 ? -bytes : bytes; - // Determine the suffix and readable value - string suffix; - double readable; - if (absBytes >= 0x1000000000000000) // Exabyte + suffix = "EB"; + readable = bytes >> 50; + } + else if (absBytes >= 0x4000000000000) // Petabyte + { + suffix = "PB"; + readable = bytes >> 40; + } + else if (absBytes >= 0x10000000000) // Terabyte + { + suffix = "TB"; + readable = bytes >> 30; + } + else if (absBytes >= 0x40000000) // Gigabyte + { + suffix = "GB"; + readable = bytes >> 20; + } + else if (absBytes >= 0x100000) // Megabyte + { + suffix = "MB"; + readable = bytes >> 10; + } + else if (absBytes >= 0x400) // Kilobyte + { + suffix = "KB"; + readable = bytes; + } + else + { + return bytes.ToString("0 B"); // Byte + } + // Divide by 1024 to get fractional value + readable = readable / 1024; + // Return formatted number with suffix + return readable.ToString("0.### ") + suffix; + } + + public static void IncrementByteArray(byte[] array) + { + for (int i = array.Length - 1; i >= 0; i--) + { + if (++array[i] != 0) + break; + } + } + + public static void MemDump(this StringBuilder sb, string prefix, byte[] data) + { + int max = 32; + int remaining = data.Length; + bool first = true; + int offset = 0; + + while (remaining > 0) + { + max = Math.Min(max, remaining); + + if (first) { - suffix = "EB"; - readable = bytes >> 50; - } - else if (absBytes >= 0x4000000000000) // Petabyte - { - suffix = "PB"; - readable = bytes >> 40; - } - else if (absBytes >= 0x10000000000) // Terabyte - { - suffix = "TB"; - readable = bytes >> 30; - } - else if (absBytes >= 0x40000000) // Gigabyte - { - suffix = "GB"; - readable = bytes >> 20; - } - else if (absBytes >= 0x100000) // Megabyte - { - suffix = "MB"; - readable = bytes >> 10; - } - else if (absBytes >= 0x400) // Kilobyte - { - suffix = "KB"; - readable = bytes; + sb.Append(prefix); + first = false; } else { - return bytes.ToString("0 B"); // Byte + sb.Append(' ', prefix.Length); } - // Divide by 1024 to get fractional value - readable = readable / 1024; - // Return formatted number with suffix - return readable.ToString("0.### ") + suffix; - } - - public static void IncrementByteArray(byte[] array) - { - for (int i = array.Length - 1; i >= 0; i--) + + for (int i = 0; i < max; i++) { - if (++array[i] != 0) - break; + sb.Append($"{data[offset++]:X2}"); } - } - public static void MemDump(this StringBuilder sb, string prefix, byte[] data) - { - int max = 32; - int remaining = data.Length; - bool first = true; - int offset = 0; - - while (remaining > 0) - { - max = Math.Min(max, remaining); - - if (first) - { - sb.Append(prefix); - first = false; - } - else - { - sb.Append(' ', prefix.Length); - } - - for (int i = 0; i < max; i++) - { - sb.Append($"{data[offset++]:X2}"); - } - - sb.AppendLine(); - remaining -= max; - } - } - - public static string GetKeyRevisionSummary(int revision) => revision switch - { - 0 => "1.0.0-2.3.0", - 1 => "3.0.0", - 2 => "3.0.1-3.0.2", - 3 => "4.0.0-4.1.0", - 4 => "5.0.0-5.1.0", - 5 => "6.0.0-6.0.1", - 6 => "6.2.0", - 7 => "7.0.0-8.0.1", - 8 => "8.1.0-8.1.1", - 9 => "9.0.0-9.0.1", - 0xA => "9.1.0-12.0.3", - 0xB => "12.1.0-", - _ => "Unknown" - }; - - public static int GetMasterKeyRevision(int keyGeneration) - { - if (keyGeneration == 0) return 0; - - return keyGeneration - 1; + sb.AppendLine(); + remaining -= max; } } + + public static string GetKeyRevisionSummary(int revision) => revision switch + { + 0 => "1.0.0-2.3.0", + 1 => "3.0.0", + 2 => "3.0.1-3.0.2", + 3 => "4.0.0-4.1.0", + 4 => "5.0.0-5.1.0", + 5 => "6.0.0-6.0.1", + 6 => "6.2.0", + 7 => "7.0.0-8.0.1", + 8 => "8.1.0-8.1.1", + 9 => "9.0.0-9.0.1", + 0xA => "9.1.0-12.0.3", + 0xB => "12.1.0-", + _ => "Unknown" + }; + + public static int GetMasterKeyRevision(int keyGeneration) + { + if (keyGeneration == 0) return 0; + + return keyGeneration - 1; + } } diff --git a/src/LibHac/Validity.cs b/src/LibHac/Validity.cs index 424dae7c..307aa2f8 100644 --- a/src/LibHac/Validity.cs +++ b/src/LibHac/Validity.cs @@ -1,10 +1,9 @@ -namespace LibHac +namespace LibHac; + +public enum Validity : byte { - public enum Validity : byte - { - Unchecked, - Invalid, - Valid, - MissingKey - } + Unchecked, + Invalid, + Valid, + MissingKey } diff --git a/src/LibHac/Xci.cs b/src/LibHac/Xci.cs index d1bc2ed4..ffe31bbe 100644 --- a/src/LibHac/Xci.cs +++ b/src/LibHac/Xci.cs @@ -4,70 +4,69 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace LibHac +namespace LibHac; + +public class Xci { - public class Xci + public XciHeader Header { get; } + + private IStorage BaseStorage { get; } + private object InitLocker { get; } = new object(); + private XciPartition RootPartition { get; set; } + + public Xci(KeySet keySet, IStorage storage) { - public XciHeader Header { get; } - - private IStorage BaseStorage { get; } - private object InitLocker { get; } = new object(); - private XciPartition RootPartition { get; set; } - - public Xci(KeySet keySet, IStorage storage) - { - BaseStorage = storage; - Header = new XciHeader(keySet, storage.AsStream()); - } - - public bool HasPartition(XciPartitionType type) - { - if (type == XciPartitionType.Root) return true; - - return GetRootPartition().FileExists("/" + type.GetFileName()); - } - - public XciPartition OpenPartition(XciPartitionType type) - { - XciPartition root = GetRootPartition(); - if (type == XciPartitionType.Root) return root; - - using var partitionFile = new UniqueRef<IFile>(); - root.OpenFile(ref partitionFile.Ref(), type.GetFileName().ToU8Span(), OpenMode.Read).ThrowIfFailure(); - return new XciPartition(partitionFile.Release().AsStorage()); - } - - private XciPartition GetRootPartition() - { - if (RootPartition != null) return RootPartition; - - InitializeRootPartition(); - - return RootPartition; - } - - private void InitializeRootPartition() - { - lock (InitLocker) - { - if (RootPartition != null) return; - - IStorage rootStorage = BaseStorage.Slice(Header.RootPartitionOffset); - - RootPartition = new XciPartition(rootStorage) - { - Offset = Header.RootPartitionOffset, - HashValidity = Header.PartitionFsHeaderValidity - }; - } - } + BaseStorage = storage; + Header = new XciHeader(keySet, storage.AsStream()); } - public class XciPartition : PartitionFileSystem + public bool HasPartition(XciPartitionType type) { - public long Offset { get; internal set; } - public Validity HashValidity { get; set; } = Validity.Unchecked; + if (type == XciPartitionType.Root) return true; - public XciPartition(IStorage storage) : base(storage) { } + return GetRootPartition().FileExists("/" + type.GetFileName()); + } + + public XciPartition OpenPartition(XciPartitionType type) + { + XciPartition root = GetRootPartition(); + if (type == XciPartitionType.Root) return root; + + using var partitionFile = new UniqueRef<IFile>(); + root.OpenFile(ref partitionFile.Ref(), type.GetFileName().ToU8Span(), OpenMode.Read).ThrowIfFailure(); + return new XciPartition(partitionFile.Release().AsStorage()); + } + + private XciPartition GetRootPartition() + { + if (RootPartition != null) return RootPartition; + + InitializeRootPartition(); + + return RootPartition; + } + + private void InitializeRootPartition() + { + lock (InitLocker) + { + if (RootPartition != null) return; + + IStorage rootStorage = BaseStorage.Slice(Header.RootPartitionOffset); + + RootPartition = new XciPartition(rootStorage) + { + Offset = Header.RootPartitionOffset, + HashValidity = Header.PartitionFsHeaderValidity + }; + } } } + +public class XciPartition : PartitionFileSystem +{ + public long Offset { get; internal set; } + public Validity HashValidity { get; set; } = Validity.Unchecked; + + public XciPartition(IStorage storage) : base(storage) { } +} diff --git a/src/LibHac/XciHeader.cs b/src/LibHac/XciHeader.cs index a00bcc2c..9b03bd59 100644 --- a/src/LibHac/XciHeader.cs +++ b/src/LibHac/XciHeader.cs @@ -5,174 +5,173 @@ using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; -namespace LibHac +namespace LibHac; + +public class XciHeader { - public class XciHeader + private const int SignatureSize = 0x100; + private const string HeaderMagic = "HEAD"; + private const int EncryptedHeaderSize = 0x70; + + private static readonly byte[] XciHeaderPubk = { - private const int SignatureSize = 0x100; - private const string HeaderMagic = "HEAD"; - private const int EncryptedHeaderSize = 0x70; + 0x98, 0xC7, 0x26, 0xB6, 0x0D, 0x0A, 0x50, 0xA7, 0x39, 0x21, 0x0A, 0xE3, 0x2F, 0xE4, 0x3E, 0x2E, + 0x5B, 0xA2, 0x86, 0x75, 0xAA, 0x5C, 0xEE, 0x34, 0xF1, 0xA3, 0x3A, 0x7E, 0xBD, 0x90, 0x4E, 0xF7, + 0x8D, 0xFA, 0x17, 0xAA, 0x6B, 0xC6, 0x36, 0x6D, 0x4C, 0x9A, 0x6D, 0x57, 0x2F, 0x80, 0xA2, 0xBC, + 0x38, 0x4D, 0xDA, 0x99, 0xA1, 0xD8, 0xC3, 0xE2, 0x99, 0x79, 0x36, 0x71, 0x90, 0x20, 0x25, 0x9D, + 0x4D, 0x11, 0xB8, 0x2E, 0x63, 0x6B, 0x5A, 0xFA, 0x1E, 0x9C, 0x04, 0xD1, 0xC5, 0xF0, 0x9C, 0xB1, + 0x0F, 0xB8, 0xC1, 0x7B, 0xBF, 0xE8, 0xB0, 0xD2, 0x2B, 0x47, 0x01, 0x22, 0x6B, 0x23, 0xC9, 0xD0, + 0xBC, 0xEB, 0x75, 0x6E, 0x41, 0x7D, 0x4C, 0x26, 0xA4, 0x73, 0x21, 0xB4, 0xF0, 0x14, 0xE5, 0xD9, + 0x8D, 0xB3, 0x64, 0xEE, 0xA8, 0xFA, 0x84, 0x1B, 0xB8, 0xB8, 0x7C, 0x88, 0x6B, 0xEF, 0xCC, 0x97, + 0x04, 0x04, 0x9A, 0x67, 0x2F, 0xDF, 0xEC, 0x0D, 0xB2, 0x5F, 0xB5, 0xB2, 0xBD, 0xB5, 0x4B, 0xDE, + 0x0E, 0x88, 0xA3, 0xBA, 0xD1, 0xB4, 0xE0, 0x91, 0x81, 0xA7, 0x84, 0xEB, 0x77, 0x85, 0x8B, 0xEF, + 0xA5, 0xE3, 0x27, 0xB2, 0xF2, 0x82, 0x2B, 0x29, 0xF1, 0x75, 0x2D, 0xCE, 0xCC, 0xAE, 0x9B, 0x8D, + 0xED, 0x5C, 0xF1, 0x8E, 0xDB, 0x9A, 0xD7, 0xAF, 0x42, 0x14, 0x52, 0xCD, 0xE3, 0xC5, 0xDD, 0xCE, + 0x08, 0x12, 0x17, 0xD0, 0x7F, 0x1A, 0xAA, 0x1F, 0x7D, 0xE0, 0x93, 0x54, 0xC8, 0xBC, 0x73, 0x8A, + 0xCB, 0xAD, 0x6E, 0x93, 0xE2, 0x19, 0x72, 0x6B, 0xD3, 0x45, 0xF8, 0x73, 0x3D, 0x2B, 0x6A, 0x55, + 0xD2, 0x3A, 0x8B, 0xB0, 0x8A, 0x42, 0xE3, 0x3D, 0xF1, 0x92, 0x23, 0x42, 0x2E, 0xBA, 0xCC, 0x9C, + 0x9A, 0xC1, 0xDD, 0x62, 0x86, 0x9C, 0x2E, 0xE1, 0x2D, 0x6F, 0x62, 0x67, 0x51, 0x08, 0x0E, 0xCF + }; - private static readonly byte[] XciHeaderPubk = + public byte[] Signature { get; set; } + public string Magic { get; set; } + public int RomAreaStartPage { get; set; } + public int BackupAreaStartPage { get; set; } + public byte KekIndex { get; set; } + public byte TitleKeyDecIndex { get; set; } + public GameCardSizeInternal GameCardSize { get; set; } + public byte CardHeaderVersion { get; set; } + public GameCardAttribute Flags { get; set; } + public ulong PackageId { get; set; } + public long ValidDataEndPage { get; set; } + public byte[] AesCbcIv { get; set; } + public long RootPartitionOffset { get; set; } + public long RootPartitionHeaderSize { get; set; } + public byte[] RootPartitionHeaderHash { get; set; } + public byte[] InitialDataHash { get; set; } + public int SelSec { get; set; } + public int SelT1Key { get; set; } + public int SelKey { get; set; } + public int LimAreaPage { get; set; } + + public ulong FwVersion { get; set; } + public CardClockRate AccCtrl1 { get; set; } + public int Wait1TimeRead { get; set; } + public int Wait2TimeRead { get; set; } + public int Wait1TimeWrite { get; set; } + public int Wait2TimeWrite { get; set; } + public int FwMode { get; set; } + public int UppVersion { get; set; } + public byte[] UppHash { get; set; } + public ulong UppId { get; set; } + + public byte[] ImageHash { get; } + + public Validity SignatureValidity { get; set; } + public Validity PartitionFsHeaderValidity { get; set; } + + public XciHeader(KeySet keySet, Stream stream) + { + using (var reader = new BinaryReader(stream, Encoding.Default, true)) { - 0x98, 0xC7, 0x26, 0xB6, 0x0D, 0x0A, 0x50, 0xA7, 0x39, 0x21, 0x0A, 0xE3, 0x2F, 0xE4, 0x3E, 0x2E, - 0x5B, 0xA2, 0x86, 0x75, 0xAA, 0x5C, 0xEE, 0x34, 0xF1, 0xA3, 0x3A, 0x7E, 0xBD, 0x90, 0x4E, 0xF7, - 0x8D, 0xFA, 0x17, 0xAA, 0x6B, 0xC6, 0x36, 0x6D, 0x4C, 0x9A, 0x6D, 0x57, 0x2F, 0x80, 0xA2, 0xBC, - 0x38, 0x4D, 0xDA, 0x99, 0xA1, 0xD8, 0xC3, 0xE2, 0x99, 0x79, 0x36, 0x71, 0x90, 0x20, 0x25, 0x9D, - 0x4D, 0x11, 0xB8, 0x2E, 0x63, 0x6B, 0x5A, 0xFA, 0x1E, 0x9C, 0x04, 0xD1, 0xC5, 0xF0, 0x9C, 0xB1, - 0x0F, 0xB8, 0xC1, 0x7B, 0xBF, 0xE8, 0xB0, 0xD2, 0x2B, 0x47, 0x01, 0x22, 0x6B, 0x23, 0xC9, 0xD0, - 0xBC, 0xEB, 0x75, 0x6E, 0x41, 0x7D, 0x4C, 0x26, 0xA4, 0x73, 0x21, 0xB4, 0xF0, 0x14, 0xE5, 0xD9, - 0x8D, 0xB3, 0x64, 0xEE, 0xA8, 0xFA, 0x84, 0x1B, 0xB8, 0xB8, 0x7C, 0x88, 0x6B, 0xEF, 0xCC, 0x97, - 0x04, 0x04, 0x9A, 0x67, 0x2F, 0xDF, 0xEC, 0x0D, 0xB2, 0x5F, 0xB5, 0xB2, 0xBD, 0xB5, 0x4B, 0xDE, - 0x0E, 0x88, 0xA3, 0xBA, 0xD1, 0xB4, 0xE0, 0x91, 0x81, 0xA7, 0x84, 0xEB, 0x77, 0x85, 0x8B, 0xEF, - 0xA5, 0xE3, 0x27, 0xB2, 0xF2, 0x82, 0x2B, 0x29, 0xF1, 0x75, 0x2D, 0xCE, 0xCC, 0xAE, 0x9B, 0x8D, - 0xED, 0x5C, 0xF1, 0x8E, 0xDB, 0x9A, 0xD7, 0xAF, 0x42, 0x14, 0x52, 0xCD, 0xE3, 0xC5, 0xDD, 0xCE, - 0x08, 0x12, 0x17, 0xD0, 0x7F, 0x1A, 0xAA, 0x1F, 0x7D, 0xE0, 0x93, 0x54, 0xC8, 0xBC, 0x73, 0x8A, - 0xCB, 0xAD, 0x6E, 0x93, 0xE2, 0x19, 0x72, 0x6B, 0xD3, 0x45, 0xF8, 0x73, 0x3D, 0x2B, 0x6A, 0x55, - 0xD2, 0x3A, 0x8B, 0xB0, 0x8A, 0x42, 0xE3, 0x3D, 0xF1, 0x92, 0x23, 0x42, 0x2E, 0xBA, 0xCC, 0x9C, - 0x9A, 0xC1, 0xDD, 0x62, 0x86, 0x9C, 0x2E, 0xE1, 0x2D, 0x6F, 0x62, 0x67, 0x51, 0x08, 0x0E, 0xCF - }; - - public byte[] Signature { get; set; } - public string Magic { get; set; } - public int RomAreaStartPage { get; set; } - public int BackupAreaStartPage { get; set; } - public byte KekIndex { get; set; } - public byte TitleKeyDecIndex { get; set; } - public GameCardSizeInternal GameCardSize { get; set; } - public byte CardHeaderVersion { get; set; } - public GameCardAttribute Flags { get; set; } - public ulong PackageId { get; set; } - public long ValidDataEndPage { get; set; } - public byte[] AesCbcIv { get; set; } - public long RootPartitionOffset { get; set; } - public long RootPartitionHeaderSize { get; set; } - public byte[] RootPartitionHeaderHash { get; set; } - public byte[] InitialDataHash { get; set; } - public int SelSec { get; set; } - public int SelT1Key { get; set; } - public int SelKey { get; set; } - public int LimAreaPage { get; set; } - - public ulong FwVersion { get; set; } - public CardClockRate AccCtrl1 { get; set; } - public int Wait1TimeRead { get; set; } - public int Wait2TimeRead { get; set; } - public int Wait1TimeWrite { get; set; } - public int Wait2TimeWrite { get; set; } - public int FwMode { get; set; } - public int UppVersion { get; set; } - public byte[] UppHash { get; set; } - public ulong UppId { get; set; } - - public byte[] ImageHash { get; } - - public Validity SignatureValidity { get; set; } - public Validity PartitionFsHeaderValidity { get; set; } - - public XciHeader(KeySet keySet, Stream stream) - { - using (var reader = new BinaryReader(stream, Encoding.Default, true)) + Signature = reader.ReadBytes(SignatureSize); + Magic = reader.ReadAscii(4); + if (Magic != HeaderMagic) { - Signature = reader.ReadBytes(SignatureSize); - Magic = reader.ReadAscii(4); - if (Magic != HeaderMagic) - { - throw new InvalidDataException("Invalid XCI file: Header magic invalid."); - } - - reader.BaseStream.Position = SignatureSize; - byte[] sigData = reader.ReadBytes(SignatureSize); - reader.BaseStream.Position = SignatureSize + 4; - - SignatureValidity = CryptoOld.Rsa2048Pkcs1Verify(sigData, Signature, XciHeaderPubk); - - RomAreaStartPage = reader.ReadInt32(); - BackupAreaStartPage = reader.ReadInt32(); - byte keyIndex = reader.ReadByte(); - KekIndex = (byte)(keyIndex >> 4); - TitleKeyDecIndex = (byte)(keyIndex & 7); - GameCardSize = (GameCardSizeInternal)reader.ReadByte(); - CardHeaderVersion = reader.ReadByte(); - Flags = (GameCardAttribute)reader.ReadByte(); - PackageId = reader.ReadUInt64(); - ValidDataEndPage = reader.ReadInt64(); - AesCbcIv = reader.ReadBytes(Aes.KeySize128); - Array.Reverse(AesCbcIv); - RootPartitionOffset = reader.ReadInt64(); - RootPartitionHeaderSize = reader.ReadInt64(); - RootPartitionHeaderHash = reader.ReadBytes(Sha256.DigestSize); - InitialDataHash = reader.ReadBytes(Sha256.DigestSize); - SelSec = reader.ReadInt32(); - SelT1Key = reader.ReadInt32(); - SelKey = reader.ReadInt32(); - LimAreaPage = reader.ReadInt32(); - - if (keySet != null && !keySet.XciHeaderKey.IsZeros()) - { - byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize); - byte[] decHeader = new byte[EncryptedHeaderSize]; - Aes.DecryptCbc128(encHeader, decHeader, keySet.XciHeaderKey, AesCbcIv); - - using (var decReader = new BinaryReader(new MemoryStream(decHeader))) - { - FwVersion = decReader.ReadUInt64(); - AccCtrl1 = (CardClockRate)decReader.ReadInt32(); - Wait1TimeRead = decReader.ReadInt32(); - Wait2TimeRead = decReader.ReadInt32(); - Wait1TimeWrite = decReader.ReadInt32(); - Wait2TimeWrite = decReader.ReadInt32(); - FwMode = decReader.ReadInt32(); - UppVersion = decReader.ReadInt32(); - decReader.BaseStream.Position += 4; - UppHash = decReader.ReadBytes(8); - UppId = decReader.ReadUInt64(); - } - } - - ImageHash = new byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(sigData, ImageHash); - - reader.BaseStream.Position = RootPartitionOffset; - byte[] headerBytes = reader.ReadBytes((int)RootPartitionHeaderSize); - - Span<byte> actualHeaderHash = stackalloc byte[Sha256.DigestSize]; - Sha256.GenerateSha256Hash(headerBytes, actualHeaderHash); - - PartitionFsHeaderValidity = Utilities.SpansEqual(RootPartitionHeaderHash, actualHeaderHash) ? Validity.Valid : Validity.Invalid; + throw new InvalidDataException("Invalid XCI file: Header magic invalid."); } - } - } - public enum CardClockRate - { - ClockRate25 = 10551312, - ClockRate50 - } + reader.BaseStream.Position = SignatureSize; + byte[] sigData = reader.ReadBytes(SignatureSize); + reader.BaseStream.Position = SignatureSize + 4; - public enum XciPartitionType - { - Update, - Normal, - Secure, - Logo, - Root - } + SignatureValidity = CryptoOld.Rsa2048Pkcs1Verify(sigData, Signature, XciHeaderPubk); - public static class XciExtensions - { - public static string GetFileName(this XciPartitionType type) - { - switch (type) + RomAreaStartPage = reader.ReadInt32(); + BackupAreaStartPage = reader.ReadInt32(); + byte keyIndex = reader.ReadByte(); + KekIndex = (byte)(keyIndex >> 4); + TitleKeyDecIndex = (byte)(keyIndex & 7); + GameCardSize = (GameCardSizeInternal)reader.ReadByte(); + CardHeaderVersion = reader.ReadByte(); + Flags = (GameCardAttribute)reader.ReadByte(); + PackageId = reader.ReadUInt64(); + ValidDataEndPage = reader.ReadInt64(); + AesCbcIv = reader.ReadBytes(Aes.KeySize128); + Array.Reverse(AesCbcIv); + RootPartitionOffset = reader.ReadInt64(); + RootPartitionHeaderSize = reader.ReadInt64(); + RootPartitionHeaderHash = reader.ReadBytes(Sha256.DigestSize); + InitialDataHash = reader.ReadBytes(Sha256.DigestSize); + SelSec = reader.ReadInt32(); + SelT1Key = reader.ReadInt32(); + SelKey = reader.ReadInt32(); + LimAreaPage = reader.ReadInt32(); + + if (keySet != null && !keySet.XciHeaderKey.IsZeros()) { - case XciPartitionType.Update: return "update"; - case XciPartitionType.Normal: return "normal"; - case XciPartitionType.Secure: return "secure"; - case XciPartitionType.Logo: return "logo"; - case XciPartitionType.Root: return "root"; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); + byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize); + byte[] decHeader = new byte[EncryptedHeaderSize]; + Aes.DecryptCbc128(encHeader, decHeader, keySet.XciHeaderKey, AesCbcIv); + + using (var decReader = new BinaryReader(new MemoryStream(decHeader))) + { + FwVersion = decReader.ReadUInt64(); + AccCtrl1 = (CardClockRate)decReader.ReadInt32(); + Wait1TimeRead = decReader.ReadInt32(); + Wait2TimeRead = decReader.ReadInt32(); + Wait1TimeWrite = decReader.ReadInt32(); + Wait2TimeWrite = decReader.ReadInt32(); + FwMode = decReader.ReadInt32(); + UppVersion = decReader.ReadInt32(); + decReader.BaseStream.Position += 4; + UppHash = decReader.ReadBytes(8); + UppId = decReader.ReadUInt64(); + } } + + ImageHash = new byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(sigData, ImageHash); + + reader.BaseStream.Position = RootPartitionOffset; + byte[] headerBytes = reader.ReadBytes((int)RootPartitionHeaderSize); + + Span<byte> actualHeaderHash = stackalloc byte[Sha256.DigestSize]; + Sha256.GenerateSha256Hash(headerBytes, actualHeaderHash); + + PartitionFsHeaderValidity = Utilities.SpansEqual(RootPartitionHeaderHash, actualHeaderHash) ? Validity.Valid : Validity.Invalid; + } + } +} + +public enum CardClockRate +{ + ClockRate25 = 10551312, + ClockRate50 +} + +public enum XciPartitionType +{ + Update, + Normal, + Secure, + Logo, + Root +} + +public static class XciExtensions +{ + public static string GetFileName(this XciPartitionType type) + { + switch (type) + { + case XciPartitionType.Update: return "update"; + case XciPartitionType.Normal: return "normal"; + case XciPartitionType.Secure: return "secure"; + case XciPartitionType.Logo: return "logo"; + case XciPartitionType.Root: return "root"; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); } } } diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index 19737715..ab6872bb 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -3,12 +3,12 @@ using System.Globalization; using System.Linq; using System.Text; -namespace hactoolnet +namespace hactoolnet; + +internal static class CliParser { - internal static class CliParser + private static CliOption[] GetCliOptions() => new[] { - private static CliOption[] GetCliOptions() => new[] - { new CliOption("custom", 0, (o, _) => o.RunCustom = true), new CliOption("intype", 't', 1, (o, a) => o.InFileType = ParseFileType(a[0])), new CliOption("raw", 'r', 0, (o, _) => o.Raw = true), @@ -73,249 +73,248 @@ namespace hactoolnet }) }; - public static Options Parse(string[] args) + public static Options Parse(string[] args) + { + var options = new Options(); + bool inputSpecified = false; + + CliOption[] cliOptions = GetCliOptions(); + + for (int i = 0; i < args.Length; i++) { - var options = new Options(); - bool inputSpecified = false; + string arg; - CliOption[] cliOptions = GetCliOptions(); - - for (int i = 0; i < args.Length; i++) + if (args[i].Length == 2 && (args[i][0] == '-' || args[i][0] == '/')) { - string arg; - - if (args[i].Length == 2 && (args[i][0] == '-' || args[i][0] == '/')) + arg = args[i][1].ToString().ToLower(); + } + else if (args[i].Length > 2 && args[i].Substring(0, 2) == "--") + { + arg = args[i].Substring(2).ToLower(); + } + else + { + if (inputSpecified) { - arg = args[i][1].ToString().ToLower(); - } - else if (args[i].Length > 2 && args[i].Substring(0, 2) == "--") - { - arg = args[i].Substring(2).ToLower(); - } - else - { - if (inputSpecified) - { - PrintWithUsage($"Unable to parse option {args[i]}"); - return null; - } - - options.InFile = args[i]; - inputSpecified = true; - continue; - } - - CliOption option = cliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg); - if (option == null) - { - PrintWithUsage($"Unknown option {args[i]}"); + PrintWithUsage($"Unable to parse option {args[i]}"); return null; } - if (i + option.ArgsNeeded >= args.Length) - { - PrintWithUsage($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}"); - return null; - } - - string[] optionArgs = new string[option.ArgsNeeded]; - Array.Copy(args, i + 1, optionArgs, 0, option.ArgsNeeded); - - option.Assigner(options, optionArgs); - i += option.ArgsNeeded; + options.InFile = args[i]; + inputSpecified = true; + continue; } - if (!inputSpecified && options.InFileType != FileType.Keygen && options.InFileType != FileType.Bench && !options.RunCustom) + CliOption option = cliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg); + if (option == null) { - PrintWithUsage("Input file must be specified"); + PrintWithUsage($"Unknown option {args[i]}"); return null; } - return options; - } - - private static FileType ParseFileType(string input) - { - switch (input.ToLower()) + if (i + option.ArgsNeeded >= args.Length) { - case "nca": return FileType.Nca; - case "pfs0": return FileType.Pfs0; - case "pfsbuild": return FileType.PfsBuild; - case "nsp": return FileType.Nsp; - case "romfs": return FileType.Romfs; - case "romfsbuild": return FileType.RomfsBuild; - case "nax0": return FileType.Nax0; - case "xci": return FileType.Xci; - case "switchfs": return FileType.SwitchFs; - case "save": return FileType.Save; - case "keygen": return FileType.Keygen; - case "pk11": return FileType.Pk11; - case "pk21": return FileType.Pk21; - case "kip1": return FileType.Kip1; - case "ini1": return FileType.Ini1; - case "ndv0": return FileType.Ndv0; - case "bench": return FileType.Bench; + PrintWithUsage($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}"); + return null; } - PrintWithUsage("Specified type is invalid."); + string[] optionArgs = new string[option.ArgsNeeded]; + Array.Copy(args, i + 1, optionArgs, 0, option.ArgsNeeded); - return default; + option.Assigner(options, optionArgs); + i += option.ArgsNeeded; } - private static ulong ParseTitleId(string input) + if (!inputSpecified && options.InFileType != FileType.Keygen && options.InFileType != FileType.Bench && !options.RunCustom) { - if (input.Length != 16) - { - PrintWithUsage("Title ID must be 16 hex characters long"); - } - - if (!ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong id)) - { - PrintWithUsage("Could not parse title ID"); - } - - return id; + PrintWithUsage("Input file must be specified"); + return null; } - private static double ParseDouble(string input) + return options; + } + + private static FileType ParseFileType(string input) + { + switch (input.ToLower()) { - if (!double.TryParse(input, out double value)) - { - PrintWithUsage($"Could not parse value \"{input}\""); - } - - return value; + case "nca": return FileType.Nca; + case "pfs0": return FileType.Pfs0; + case "pfsbuild": return FileType.PfsBuild; + case "nsp": return FileType.Nsp; + case "romfs": return FileType.Romfs; + case "romfsbuild": return FileType.RomfsBuild; + case "nax0": return FileType.Nax0; + case "xci": return FileType.Xci; + case "switchfs": return FileType.SwitchFs; + case "save": return FileType.Save; + case "keygen": return FileType.Keygen; + case "pk11": return FileType.Pk11; + case "pk21": return FileType.Pk21; + case "kip1": return FileType.Kip1; + case "ini1": return FileType.Ini1; + case "ndv0": return FileType.Ndv0; + case "bench": return FileType.Bench; } - private static void PrintWithUsage(string toPrint) + PrintWithUsage("Specified type is invalid."); + + return default; + } + + private static ulong ParseTitleId(string input) + { + if (input.Length != 16) { - Console.WriteLine(toPrint); - Console.WriteLine(GetUsage()); - // PrintUsage(); + PrintWithUsage("Title ID must be 16 hex characters long"); } - private static string GetUsage() + if (!ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong id)) { - var sb = new StringBuilder(); - - sb.AppendLine("Usage: hactoolnet.exe [options...] <path>"); - sb.AppendLine("Options:"); - sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack."); - sb.AppendLine(" -y, --verify Verify all hashes in the input file."); - sb.AppendLine(" -h, --enablehash Enable hash checks when reading the input file."); - sb.AppendLine(" -d, --dev Decrypt with development keys instead of retail."); - sb.AppendLine(" -k, --keyset Load keys from an external file."); - sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pfs0, pk11, pk21, ini1, kip1, switchfs, save, ndv0, keygen, romfsbuild, pfsbuild]"); - sb.AppendLine(" --titlekeys <file> Load title keys from an external file."); - sb.AppendLine(" --accesslog <file> Specify the access log file path."); - sb.AppendLine("NCA options:"); - sb.AppendLine(" --plaintext <file> Specify file path for saving a decrypted copy of the NCA."); - sb.AppendLine(" --ciphertext <file> Specify file path for saving an encrypted copy of the NCA."); - sb.AppendLine(" --header <file> Specify Header file path."); - sb.AppendLine(" --section0 <file> Specify Section 0 file path."); - sb.AppendLine(" --section1 <file> Specify Section 1 file path."); - sb.AppendLine(" --section2 <file> Specify Section 2 file path."); - sb.AppendLine(" --section3 <file> Specify Section 3 file path."); - sb.AppendLine(" --section0dir <dir> Specify Section 0 directory path."); - sb.AppendLine(" --section1dir <dir> Specify Section 1 directory path."); - sb.AppendLine(" --section2dir <dir> Specify Section 2 directory path."); - sb.AppendLine(" --section3dir <dir> Specify Section 3 directory path."); - sb.AppendLine(" --exefs <file> Specify ExeFS file path."); - sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path."); - sb.AppendLine(" --romfs <file> Specify RomFS file path."); - sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path."); - sb.AppendLine(" --listromfs List files in RomFS."); - sb.AppendLine(" --basenca Set Base NCA to use with update partitions."); - sb.AppendLine("KIP1 options:"); - sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1."); - sb.AppendLine("RomFS options:"); - sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path."); - sb.AppendLine(" --listromfs List files in RomFS."); - sb.AppendLine("RomFS creation options:"); - sb.AppendLine(" Input path must be a directory"); - sb.AppendLine(" --outfile <file> Specify created RomFS file path."); - sb.AppendLine("Partition FS options:"); - sb.AppendLine(" --outdir <dir> Specify extracted FS directory path."); - sb.AppendLine("Partition FS creation options:"); - sb.AppendLine(" Input path must be a directory"); - sb.AppendLine(" --outfile <file> Specify created Partition FS file path."); - sb.AppendLine(" --hashedfs Create a hashed Partition FS (HFS0)."); - sb.AppendLine("XCI options:"); - sb.AppendLine(" --rootdir <dir> Specify root XCI directory path."); - sb.AppendLine(" --updatedir <dir> Specify update XCI directory path."); - sb.AppendLine(" --normaldir <dir> Specify normal XCI directory path."); - sb.AppendLine(" --securedir <dir> Specify secure XCI directory path."); - sb.AppendLine(" --logodir <dir> Specify logo XCI directory path."); - sb.AppendLine(" --outdir <dir> Specify XCI directory path."); - sb.AppendLine(" --exefs <file> Specify main ExeFS file path."); - sb.AppendLine(" --exefsdir <dir> Specify main ExeFS directory path."); - sb.AppendLine(" --romfs <file> Specify main RomFS file path."); - sb.AppendLine(" --romfsdir <dir> Specify main RomFS directory path."); - sb.AppendLine(" --nspout <file> Specify file for the created NSP."); - sb.AppendLine("Package1 options:"); - sb.AppendLine(" --outdir <dir> Specify Package1 directory path."); - sb.AppendLine("Package2 options:"); - sb.AppendLine(" --outdir <dir> Specify Package2 directory path."); - sb.AppendLine(" --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set)."); - sb.AppendLine(" --ini1dir <dir> Specify INI1 directory path. Overrides default path, if present."); - sb.AppendLine("INI1 options:"); - sb.AppendLine(" --outdir <dir> Specify INI1 directory path."); - sb.AppendLine("Switch FS options:"); - sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption."); - sb.AppendLine(" --listapps List application info."); - sb.AppendLine(" --listtitles List title info for all titles."); - sb.AppendLine(" --listncas List info for all NCAs."); - sb.AppendLine(" --title <title id> Specify title ID to use."); - sb.AppendLine(" --outdir <dir> Specify directory path to save title NCAs to. (--title must be specified)"); - sb.AppendLine(" --exefs <file> Specify ExeFS directory path. (--title must be specified)"); - sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path. (--title must be specified)"); - sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)"); - sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)"); - sb.AppendLine(" --savedir <dir> Specify save file directory path."); - sb.AppendLine(" -y, --verify Verify all titles, or verify a single title if --title is set."); - sb.AppendLine("Save data options:"); - sb.AppendLine(" --outdir <dir> Specify directory path to save contents to."); - sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging."); - sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)"); - sb.AppendLine(" --trim Trim garbage data in the save file. (Requires device_key in key file)"); - sb.AppendLine(" --listfiles List files in save file."); - sb.AppendLine(" --repack <dir> Replaces the contents of the save data with the specified directory."); - sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data"); - sb.AppendLine("NAX0 options:"); - sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption."); - sb.AppendLine(" --sdpath <path> Set relative path for NAX0 key derivation (ex: /registered/000000FF/cafebabecafebabecafebabecafebabe.nca)."); - sb.AppendLine(" --plaintext Specify file path to save decrypted contents."); - sb.AppendLine("NDV0 (Delta) options:"); - sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file."); - sb.AppendLine(" --basefile <file> Specify base file path."); - sb.AppendLine(" --outfile Specify patched file path."); - sb.AppendLine("Keygen options:"); - sb.AppendLine(" --outdir <dir> Specify directory path to save key files to."); - - return sb.ToString(); + PrintWithUsage("Could not parse title ID"); } - private class CliOption + return id; + } + + private static double ParseDouble(string input) + { + if (!double.TryParse(input, out double value)) { - public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner) - { - Long = longName; - Short = shortName.ToString(); - ArgsNeeded = argsNeeded; - Assigner = assigner; - } - - public CliOption(string longName, int argsNeeded, Action<Options, string[]> assigner) - { - Long = longName; - ArgsNeeded = argsNeeded; - Assigner = assigner; - } - - public string Long { get; } - public string Short { get; } - public int ArgsNeeded { get; } - public Action<Options, string[]> Assigner { get; } + PrintWithUsage($"Could not parse value \"{input}\""); } + + return value; + } + + private static void PrintWithUsage(string toPrint) + { + Console.WriteLine(toPrint); + Console.WriteLine(GetUsage()); + // PrintUsage(); + } + + private static string GetUsage() + { + var sb = new StringBuilder(); + + sb.AppendLine("Usage: hactoolnet.exe [options...] <path>"); + sb.AppendLine("Options:"); + sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack."); + sb.AppendLine(" -y, --verify Verify all hashes in the input file."); + sb.AppendLine(" -h, --enablehash Enable hash checks when reading the input file."); + sb.AppendLine(" -d, --dev Decrypt with development keys instead of retail."); + sb.AppendLine(" -k, --keyset Load keys from an external file."); + sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pfs0, pk11, pk21, ini1, kip1, switchfs, save, ndv0, keygen, romfsbuild, pfsbuild]"); + sb.AppendLine(" --titlekeys <file> Load title keys from an external file."); + sb.AppendLine(" --accesslog <file> Specify the access log file path."); + sb.AppendLine("NCA options:"); + sb.AppendLine(" --plaintext <file> Specify file path for saving a decrypted copy of the NCA."); + sb.AppendLine(" --ciphertext <file> Specify file path for saving an encrypted copy of the NCA."); + sb.AppendLine(" --header <file> Specify Header file path."); + sb.AppendLine(" --section0 <file> Specify Section 0 file path."); + sb.AppendLine(" --section1 <file> Specify Section 1 file path."); + sb.AppendLine(" --section2 <file> Specify Section 2 file path."); + sb.AppendLine(" --section3 <file> Specify Section 3 file path."); + sb.AppendLine(" --section0dir <dir> Specify Section 0 directory path."); + sb.AppendLine(" --section1dir <dir> Specify Section 1 directory path."); + sb.AppendLine(" --section2dir <dir> Specify Section 2 directory path."); + sb.AppendLine(" --section3dir <dir> Specify Section 3 directory path."); + sb.AppendLine(" --exefs <file> Specify ExeFS file path."); + sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path."); + sb.AppendLine(" --romfs <file> Specify RomFS file path."); + sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path."); + sb.AppendLine(" --listromfs List files in RomFS."); + sb.AppendLine(" --basenca Set Base NCA to use with update partitions."); + sb.AppendLine("KIP1 options:"); + sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1."); + sb.AppendLine("RomFS options:"); + sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path."); + sb.AppendLine(" --listromfs List files in RomFS."); + sb.AppendLine("RomFS creation options:"); + sb.AppendLine(" Input path must be a directory"); + sb.AppendLine(" --outfile <file> Specify created RomFS file path."); + sb.AppendLine("Partition FS options:"); + sb.AppendLine(" --outdir <dir> Specify extracted FS directory path."); + sb.AppendLine("Partition FS creation options:"); + sb.AppendLine(" Input path must be a directory"); + sb.AppendLine(" --outfile <file> Specify created Partition FS file path."); + sb.AppendLine(" --hashedfs Create a hashed Partition FS (HFS0)."); + sb.AppendLine("XCI options:"); + sb.AppendLine(" --rootdir <dir> Specify root XCI directory path."); + sb.AppendLine(" --updatedir <dir> Specify update XCI directory path."); + sb.AppendLine(" --normaldir <dir> Specify normal XCI directory path."); + sb.AppendLine(" --securedir <dir> Specify secure XCI directory path."); + sb.AppendLine(" --logodir <dir> Specify logo XCI directory path."); + sb.AppendLine(" --outdir <dir> Specify XCI directory path."); + sb.AppendLine(" --exefs <file> Specify main ExeFS file path."); + sb.AppendLine(" --exefsdir <dir> Specify main ExeFS directory path."); + sb.AppendLine(" --romfs <file> Specify main RomFS file path."); + sb.AppendLine(" --romfsdir <dir> Specify main RomFS directory path."); + sb.AppendLine(" --nspout <file> Specify file for the created NSP."); + sb.AppendLine("Package1 options:"); + sb.AppendLine(" --outdir <dir> Specify Package1 directory path."); + sb.AppendLine("Package2 options:"); + sb.AppendLine(" --outdir <dir> Specify Package2 directory path."); + sb.AppendLine(" --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set)."); + sb.AppendLine(" --ini1dir <dir> Specify INI1 directory path. Overrides default path, if present."); + sb.AppendLine("INI1 options:"); + sb.AppendLine(" --outdir <dir> Specify INI1 directory path."); + sb.AppendLine("Switch FS options:"); + sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption."); + sb.AppendLine(" --listapps List application info."); + sb.AppendLine(" --listtitles List title info for all titles."); + sb.AppendLine(" --listncas List info for all NCAs."); + sb.AppendLine(" --title <title id> Specify title ID to use."); + sb.AppendLine(" --outdir <dir> Specify directory path to save title NCAs to. (--title must be specified)"); + sb.AppendLine(" --exefs <file> Specify ExeFS directory path. (--title must be specified)"); + sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path. (--title must be specified)"); + sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)"); + sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)"); + sb.AppendLine(" --savedir <dir> Specify save file directory path."); + sb.AppendLine(" -y, --verify Verify all titles, or verify a single title if --title is set."); + sb.AppendLine("Save data options:"); + sb.AppendLine(" --outdir <dir> Specify directory path to save contents to."); + sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging."); + sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)"); + sb.AppendLine(" --trim Trim garbage data in the save file. (Requires device_key in key file)"); + sb.AppendLine(" --listfiles List files in save file."); + sb.AppendLine(" --repack <dir> Replaces the contents of the save data with the specified directory."); + sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data"); + sb.AppendLine("NAX0 options:"); + sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption."); + sb.AppendLine(" --sdpath <path> Set relative path for NAX0 key derivation (ex: /registered/000000FF/cafebabecafebabecafebabecafebabe.nca)."); + sb.AppendLine(" --plaintext Specify file path to save decrypted contents."); + sb.AppendLine("NDV0 (Delta) options:"); + sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file."); + sb.AppendLine(" --basefile <file> Specify base file path."); + sb.AppendLine(" --outfile Specify patched file path."); + sb.AppendLine("Keygen options:"); + sb.AppendLine(" --outdir <dir> Specify directory path to save key files to."); + + return sb.ToString(); + } + + private class CliOption + { + public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner) + { + Long = longName; + Short = shortName.ToString(); + ArgsNeeded = argsNeeded; + Assigner = assigner; + } + + public CliOption(string longName, int argsNeeded, Action<Options, string[]> assigner) + { + Long = longName; + ArgsNeeded = argsNeeded; + Assigner = assigner; + } + + public string Long { get; } + public string Short { get; } + public int ArgsNeeded { get; } + public Action<Options, string[]> Assigner { get; } } } diff --git a/src/hactoolnet/EnumStrings.cs b/src/hactoolnet/EnumStrings.cs index 77c4d681..8e21b014 100644 --- a/src/hactoolnet/EnumStrings.cs +++ b/src/hactoolnet/EnumStrings.cs @@ -4,114 +4,113 @@ using LibHac.FsSystem.NcaUtils; using LibHac.Ncm; using ContentType = LibHac.Ncm.ContentType; -namespace hactoolnet +namespace hactoolnet; + +internal static class EnumStrings { - internal static class EnumStrings + public static string Print(this ContentType value) { - public static string Print(this ContentType value) + return value switch { - return value switch - { - ContentType.Meta => nameof(ContentType.Meta), - ContentType.Program => nameof(ContentType.Program), - ContentType.Data => nameof(ContentType.Data), - ContentType.Control => nameof(ContentType.Control), - ContentType.HtmlDocument => nameof(ContentType.HtmlDocument), - ContentType.LegalInformation => nameof(ContentType.LegalInformation), - ContentType.DeltaFragment => nameof(ContentType.DeltaFragment), - _ => value.ToString() - }; - } + ContentType.Meta => nameof(ContentType.Meta), + ContentType.Program => nameof(ContentType.Program), + ContentType.Data => nameof(ContentType.Data), + ContentType.Control => nameof(ContentType.Control), + ContentType.HtmlDocument => nameof(ContentType.HtmlDocument), + ContentType.LegalInformation => nameof(ContentType.LegalInformation), + ContentType.DeltaFragment => nameof(ContentType.DeltaFragment), + _ => value.ToString() + }; + } - public static string Print(this ContentMetaType value) + public static string Print(this ContentMetaType value) + { + return value switch { - return value switch - { - ContentMetaType.SystemProgram => nameof(ContentMetaType.SystemProgram), - ContentMetaType.SystemData => nameof(ContentMetaType.SystemData), - ContentMetaType.SystemUpdate => nameof(ContentMetaType.SystemUpdate), - ContentMetaType.BootImagePackage => nameof(ContentMetaType.BootImagePackage), - ContentMetaType.BootImagePackageSafe => nameof(ContentMetaType.BootImagePackageSafe), - ContentMetaType.Application => nameof(ContentMetaType.Application), - ContentMetaType.Patch => nameof(ContentMetaType.Patch), - ContentMetaType.AddOnContent => nameof(ContentMetaType.AddOnContent), - ContentMetaType.Delta => nameof(ContentMetaType.Delta), - _ => value.ToString() - }; - } + ContentMetaType.SystemProgram => nameof(ContentMetaType.SystemProgram), + ContentMetaType.SystemData => nameof(ContentMetaType.SystemData), + ContentMetaType.SystemUpdate => nameof(ContentMetaType.SystemUpdate), + ContentMetaType.BootImagePackage => nameof(ContentMetaType.BootImagePackage), + ContentMetaType.BootImagePackageSafe => nameof(ContentMetaType.BootImagePackageSafe), + ContentMetaType.Application => nameof(ContentMetaType.Application), + ContentMetaType.Patch => nameof(ContentMetaType.Patch), + ContentMetaType.AddOnContent => nameof(ContentMetaType.AddOnContent), + ContentMetaType.Delta => nameof(ContentMetaType.Delta), + _ => value.ToString() + }; + } - public static string Print(this DistributionType value) + public static string Print(this DistributionType value) + { + return value switch { - return value switch - { - DistributionType.Download => nameof(DistributionType.Download), - DistributionType.GameCard => nameof(DistributionType.GameCard), - _ => value.ToString() - }; - } + DistributionType.Download => nameof(DistributionType.Download), + DistributionType.GameCard => nameof(DistributionType.GameCard), + _ => value.ToString() + }; + } - public static string Print(this NcaContentType value) + public static string Print(this NcaContentType value) + { + return value switch { - return value switch - { - NcaContentType.Program => nameof(NcaContentType.Program), - NcaContentType.Meta => nameof(NcaContentType.Meta), - NcaContentType.Control => nameof(NcaContentType.Control), - NcaContentType.Manual => nameof(NcaContentType.Manual), - NcaContentType.Data => nameof(NcaContentType.Data), - NcaContentType.PublicData => nameof(NcaContentType.PublicData), - _ => value.ToString() - }; - } + NcaContentType.Program => nameof(NcaContentType.Program), + NcaContentType.Meta => nameof(NcaContentType.Meta), + NcaContentType.Control => nameof(NcaContentType.Control), + NcaContentType.Manual => nameof(NcaContentType.Manual), + NcaContentType.Data => nameof(NcaContentType.Data), + NcaContentType.PublicData => nameof(NcaContentType.PublicData), + _ => value.ToString() + }; + } - public static string Print(this NcaFormatType value) + public static string Print(this NcaFormatType value) + { + return value switch { - return value switch - { - NcaFormatType.Romfs => nameof(NcaFormatType.Romfs), - NcaFormatType.Pfs0 => nameof(NcaFormatType.Pfs0), - _ => value.ToString() - }; - } + NcaFormatType.Romfs => nameof(NcaFormatType.Romfs), + NcaFormatType.Pfs0 => nameof(NcaFormatType.Pfs0), + _ => value.ToString() + }; + } - public static string Print(this XciPartitionType value) + public static string Print(this XciPartitionType value) + { + return value switch { - return value switch - { - XciPartitionType.Update => nameof(XciPartitionType.Update), - XciPartitionType.Normal => nameof(XciPartitionType.Normal), - XciPartitionType.Secure => nameof(XciPartitionType.Secure), - XciPartitionType.Logo => nameof(XciPartitionType.Logo), - XciPartitionType.Root => nameof(XciPartitionType.Root), - _ => value.ToString() - }; - } + XciPartitionType.Update => nameof(XciPartitionType.Update), + XciPartitionType.Normal => nameof(XciPartitionType.Normal), + XciPartitionType.Secure => nameof(XciPartitionType.Secure), + XciPartitionType.Logo => nameof(XciPartitionType.Logo), + XciPartitionType.Root => nameof(XciPartitionType.Root), + _ => value.ToString() + }; + } - public static string Print(this Validity value) + public static string Print(this Validity value) + { + return value switch { - return value switch - { - Validity.Unchecked => nameof(Validity.Unchecked), - Validity.Invalid => nameof(Validity.Invalid), - Validity.Valid => nameof(Validity.Valid), - Validity.MissingKey => nameof(Validity.MissingKey), - _ => value.ToString() - }; - } + Validity.Unchecked => nameof(Validity.Unchecked), + Validity.Invalid => nameof(Validity.Invalid), + Validity.Valid => nameof(Validity.Valid), + Validity.MissingKey => nameof(Validity.MissingKey), + _ => value.ToString() + }; + } - public static string Print(this SaveDataType value) + public static string Print(this SaveDataType value) + { + return value switch { - return value switch - { - SaveDataType.System => nameof(SaveDataType.System), - SaveDataType.Account => nameof(SaveDataType.Account), - SaveDataType.Bcat => nameof(SaveDataType.Bcat), - SaveDataType.Device => nameof(SaveDataType.Device), - SaveDataType.Temporary => nameof(SaveDataType.Temporary), - SaveDataType.Cache => nameof(SaveDataType.Cache), - SaveDataType.SystemBcat => nameof(SaveDataType.SystemBcat), - _ => value.ToString() - }; - } + SaveDataType.System => nameof(SaveDataType.System), + SaveDataType.Account => nameof(SaveDataType.Account), + SaveDataType.Bcat => nameof(SaveDataType.Bcat), + SaveDataType.Device => nameof(SaveDataType.Device), + SaveDataType.Temporary => nameof(SaveDataType.Temporary), + SaveDataType.Cache => nameof(SaveDataType.Cache), + SaveDataType.SystemBcat => nameof(SaveDataType.SystemBcat), + _ => value.ToString() + }; } } diff --git a/src/hactoolnet/FsUtils.cs b/src/hactoolnet/FsUtils.cs index 1cd8e00b..a1ac250e 100644 --- a/src/hactoolnet/FsUtils.cs +++ b/src/hactoolnet/FsUtils.cs @@ -6,139 +6,138 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -namespace hactoolnet +namespace hactoolnet; + +public static class FsUtils { - public static class FsUtils + public static Result CopyDirectoryWithProgress(FileSystemClient fs, U8Span sourcePath, U8Span destPath, + CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null) { - public static Result CopyDirectoryWithProgress(FileSystemClient fs, U8Span sourcePath, U8Span destPath, - CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null) + try { - try - { - logger?.SetTotal(GetTotalSize(fs, sourcePath)); + logger?.SetTotal(GetTotalSize(fs, sourcePath)); - return CopyDirectoryWithProgressInternal(fs, sourcePath, destPath, options, logger); - } - finally - { - logger?.SetTotal(0); - } + return CopyDirectoryWithProgressInternal(fs, sourcePath, destPath, options, logger); } - - private static Result CopyDirectoryWithProgressInternal(FileSystemClient fs, U8Span sourcePath, U8Span destPath, - CreateFileOptions options, IProgressReport logger) + finally { - string sourcePathStr = sourcePath.ToString(); - string destPathStr = destPath.ToString(); + logger?.SetTotal(0); + } + } - Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath, OpenDirectoryMode.All); - if (rc.IsFailure()) return rc; + private static Result CopyDirectoryWithProgressInternal(FileSystemClient fs, U8Span sourcePath, U8Span destPath, + CreateFileOptions options, IProgressReport logger) + { + string sourcePathStr = sourcePath.ToString(); + string destPathStr = destPath.ToString(); - try + Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath, OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + try + { + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePathStr, "*", SearchOptions.Default)) { - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePathStr, "*", SearchOptions.Default)) + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePathStr, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPathStr, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) { - string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePathStr, entry.Name)); - string subDstPath = PathTools.Normalize(PathTools.Combine(destPathStr, entry.Name)); + fs.EnsureDirectoryExists(subDstPath); - if (entry.Type == DirectoryEntryType.Directory) - { - fs.EnsureDirectoryExists(subDstPath); + rc = CopyDirectoryWithProgressInternal(fs, subSrcPath.ToU8Span(), subDstPath.ToU8Span(), options, logger); + if (rc.IsFailure()) return rc; + } - rc = CopyDirectoryWithProgressInternal(fs, subSrcPath.ToU8Span(), subDstPath.ToU8Span(), options, logger); - if (rc.IsFailure()) return rc; - } + if (entry.Type == DirectoryEntryType.File) + { + logger?.LogMessage(subSrcPath); - if (entry.Type == DirectoryEntryType.File) - { - logger?.LogMessage(subSrcPath); + rc = fs.CreateOrOverwriteFile(subDstPath, entry.Size, options); + if (rc.IsFailure()) return rc; - rc = fs.CreateOrOverwriteFile(subDstPath, entry.Size, options); - if (rc.IsFailure()) return rc; - - rc = CopyFileWithProgress(fs, subSrcPath.ToU8Span(), subDstPath.ToU8Span(), logger); - if (rc.IsFailure()) return rc; - } + rc = CopyFileWithProgress(fs, subSrcPath.ToU8Span(), subDstPath.ToU8Span(), logger); + if (rc.IsFailure()) return rc; } } - finally - { - if (sourceHandle.IsValid) - fs.CloseDirectory(sourceHandle); - } - - return Result.Success; + } + finally + { + if (sourceHandle.IsValid) + fs.CloseDirectory(sourceHandle); } - public static long GetTotalSize(FileSystemClient fs, U8Span path, string searchPattern = "*") + return Result.Success; + } + + public static long GetTotalSize(FileSystemClient fs, U8Span path, string searchPattern = "*") + { + long size = 0; + + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path.ToString(), searchPattern)) { - long size = 0; - - foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path.ToString(), searchPattern)) - { - size += entry.Size; - } - - return size; + size += entry.Size; } - public static Result CopyFileWithProgress(FileSystemClient fs, U8Span sourcePath, U8Span destPath, IProgressReport logger = null) + return size; + } + + public static Result CopyFileWithProgress(FileSystemClient fs, U8Span sourcePath, U8Span destPath, IProgressReport logger = null) + { + Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + try { - Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath, OpenMode.Read); + rc = fs.OpenFile(out FileHandle destHandle, destPath, OpenMode.Write | OpenMode.AllowAppend); if (rc.IsFailure()) return rc; try { - rc = fs.OpenFile(out FileHandle destHandle, destPath, OpenMode.Write | OpenMode.AllowAppend); + const int maxBufferSize = 1024 * 1024; + + rc = fs.GetFileSize(out long fileSize, sourceHandle); if (rc.IsFailure()) return rc; + int bufferSize = (int)Math.Min(maxBufferSize, fileSize); + + byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); try { - const int maxBufferSize = 1024 * 1024; - - rc = fs.GetFileSize(out long fileSize, sourceHandle); - if (rc.IsFailure()) return rc; - - int bufferSize = (int)Math.Min(maxBufferSize, fileSize); - - byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); - try + for (long offset = 0; offset < fileSize; offset += bufferSize) { - for (long offset = 0; offset < fileSize; offset += bufferSize) - { - int toRead = (int)Math.Min(fileSize - offset, bufferSize); - Span<byte> buf = buffer.AsSpan(0, toRead); + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span<byte> buf = buffer.AsSpan(0, toRead); - rc = fs.ReadFile(out long _, sourceHandle, offset, buf); - if (rc.IsFailure()) return rc; + rc = fs.ReadFile(out long _, sourceHandle, offset, buf); + if (rc.IsFailure()) return rc; - rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); - if (rc.IsFailure()) return rc; + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); + if (rc.IsFailure()) return rc; - logger?.ReportAdd(toRead); - } + logger?.ReportAdd(toRead); } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - - rc = fs.FlushFile(destHandle); - if (rc.IsFailure()) return rc; } finally { - if (destHandle.IsValid) - fs.CloseFile(destHandle); + ArrayPool<byte>.Shared.Return(buffer); } + + rc = fs.FlushFile(destHandle); + if (rc.IsFailure()) return rc; } finally { - if (sourceHandle.IsValid) - fs.CloseFile(sourceHandle); + if (destHandle.IsValid) + fs.CloseFile(destHandle); } - - return Result.Success; } + finally + { + if (sourceHandle.IsValid) + fs.CloseFile(sourceHandle); + } + + return Result.Success; } } diff --git a/src/hactoolnet/MultiBenchmark.cs b/src/hactoolnet/MultiBenchmark.cs index ede8bfe6..77b4306a 100644 --- a/src/hactoolnet/MultiBenchmark.cs +++ b/src/hactoolnet/MultiBenchmark.cs @@ -2,76 +2,75 @@ using System.Collections.Generic; using System.Diagnostics; -namespace hactoolnet +namespace hactoolnet; + +internal class MultiBenchmark { - internal class MultiBenchmark + public int DefaultRunsNeeded { get; set; } = 500; + + private List<BenchmarkItem> Benchmarks { get; } = new List<BenchmarkItem>(); + + public void Register(string name, Action setupAction, Action runAction, Func<double, string> resultPrinter, int runsNeeded = -1) { - public int DefaultRunsNeeded { get; set; } = 500; - - private List<BenchmarkItem> Benchmarks { get; } = new List<BenchmarkItem>(); - - public void Register(string name, Action setupAction, Action runAction, Func<double, string> resultPrinter, int runsNeeded = -1) + var benchmark = new BenchmarkItem { - var benchmark = new BenchmarkItem - { - Name = name, - Setup = setupAction, - Run = runAction, - PrintResult = resultPrinter, - RunsNeeded = runsNeeded == -1 ? DefaultRunsNeeded : runsNeeded - }; + Name = name, + Setup = setupAction, + Run = runAction, + PrintResult = resultPrinter, + RunsNeeded = runsNeeded == -1 ? DefaultRunsNeeded : runsNeeded + }; - Benchmarks.Add(benchmark); - } + Benchmarks.Add(benchmark); + } - public void Run() + public void Run() + { + foreach (BenchmarkItem item in Benchmarks) { - foreach (BenchmarkItem item in Benchmarks) - { - RunBenchmark(item); + RunBenchmark(item); - Console.WriteLine($"{item.Name}: {item.Result}"); - } - } - - private void RunBenchmark(BenchmarkItem item) - { - double fastestRun = double.MaxValue; - var watch = new Stopwatch(); - - int runsSinceLastBest = 0; - - while (runsSinceLastBest < item.RunsNeeded) - { - runsSinceLastBest++; - item.Setup(); - - watch.Restart(); - item.Run(); - watch.Stop(); - - if (fastestRun > watch.Elapsed.TotalSeconds) - { - fastestRun = watch.Elapsed.TotalSeconds; - - runsSinceLastBest = 0; - } - } - - item.Time = fastestRun; - item.Result = item.PrintResult(item.Time); - } - - private class BenchmarkItem - { - public string Name { get; set; } - public int RunsNeeded { get; set; } - public double Time { get; set; } - public string Result { get; set; } - - public Action Setup { get; set; } - public Action Run { get; set; } - public Func<double, string> PrintResult { get; set; } + Console.WriteLine($"{item.Name}: {item.Result}"); } } + + private void RunBenchmark(BenchmarkItem item) + { + double fastestRun = double.MaxValue; + var watch = new Stopwatch(); + + int runsSinceLastBest = 0; + + while (runsSinceLastBest < item.RunsNeeded) + { + runsSinceLastBest++; + item.Setup(); + + watch.Restart(); + item.Run(); + watch.Stop(); + + if (fastestRun > watch.Elapsed.TotalSeconds) + { + fastestRun = watch.Elapsed.TotalSeconds; + + runsSinceLastBest = 0; + } + } + + item.Time = fastestRun; + item.Result = item.PrintResult(item.Time); + } + + private class BenchmarkItem + { + public string Name { get; set; } + public int RunsNeeded { get; set; } + public double Time { get; set; } + public string Result { get; set; } + + public Action Setup { get; set; } + public Action Run { get; set; } + public Func<double, string> PrintResult { get; set; } + } } diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index 33b723eb..9f0f3fdd 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -2,103 +2,102 @@ using LibHac.Common.Keys; using LibHac.FsSystem; -namespace hactoolnet +namespace hactoolnet; + +internal class Options { - internal class Options - { - public bool RunCustom; - public string InFile; - public FileType InFileType = FileType.Nca; - public bool Raw; - public bool Validate; - public bool UseDevKeys; - public bool EnableHash; - public string Keyfile; - public string TitleKeyFile; - public string ConsoleKeyFile; - public string AccessLog; - public string ResultLog; - public string[] SectionOut = new string[4]; - public string[] SectionOutDir = new string[4]; - public string HeaderOut; - public string ExefsOut; - public string ExefsOutDir; - public string RomfsOut; - public string RomfsOutDir; - public string DebugOutDir; - public string SaveOutDir; - public string Ini1OutDir; - public string OutDir; - public string OutFile; - public string PlaintextOut; - public string CiphertextOut; - public string UncompressedOut; - public string SdSeed; - public string NspOut; - public string SdPath; - public string BaseNca; - public string BaseFile; - public string RootDir; - public string UpdateDir; - public string NormalDir; - public string SecureDir; - public string LogoDir; - public string ReplaceFileSource; - public string ReplaceFileDest; - public string RepackSource; - public bool ListApps; - public bool ListTitles; - public bool ListNcas; - public bool ListRomFs; - public bool ListFiles; - public bool SignSave; - public bool TrimSave; - public bool ReadBench; - public bool BuildHfs; - public bool ExtractIni1; - public ulong TitleId; - public string BenchType; - public double CpuFrequencyGhz; + public bool RunCustom; + public string InFile; + public FileType InFileType = FileType.Nca; + public bool Raw; + public bool Validate; + public bool UseDevKeys; + public bool EnableHash; + public string Keyfile; + public string TitleKeyFile; + public string ConsoleKeyFile; + public string AccessLog; + public string ResultLog; + public string[] SectionOut = new string[4]; + public string[] SectionOutDir = new string[4]; + public string HeaderOut; + public string ExefsOut; + public string ExefsOutDir; + public string RomfsOut; + public string RomfsOutDir; + public string DebugOutDir; + public string SaveOutDir; + public string Ini1OutDir; + public string OutDir; + public string OutFile; + public string PlaintextOut; + public string CiphertextOut; + public string UncompressedOut; + public string SdSeed; + public string NspOut; + public string SdPath; + public string BaseNca; + public string BaseFile; + public string RootDir; + public string UpdateDir; + public string NormalDir; + public string SecureDir; + public string LogoDir; + public string ReplaceFileSource; + public string ReplaceFileDest; + public string RepackSource; + public bool ListApps; + public bool ListTitles; + public bool ListNcas; + public bool ListRomFs; + public bool ListFiles; + public bool SignSave; + public bool TrimSave; + public bool ReadBench; + public bool BuildHfs; + public bool ExtractIni1; + public ulong TitleId; + public string BenchType; + public double CpuFrequencyGhz; - public IntegrityCheckLevel IntegrityLevel + public IntegrityCheckLevel IntegrityLevel + { + get { - get - { - if (Validate) return IntegrityCheckLevel.IgnoreOnInvalid; - if (EnableHash) return IntegrityCheckLevel.ErrorOnInvalid; - return IntegrityCheckLevel.None; - } + if (Validate) return IntegrityCheckLevel.IgnoreOnInvalid; + if (EnableHash) return IntegrityCheckLevel.ErrorOnInvalid; + return IntegrityCheckLevel.None; } - - public KeySet.Mode KeyMode => UseDevKeys ? KeySet.Mode.Dev : KeySet.Mode.Prod; } - internal enum FileType - { - Nca, - Pfs0, - PfsBuild, - Nsp, - Romfs, - RomfsBuild, - Nax0, - Xci, - SwitchFs, - Save, - Keygen, - Pk11, - Pk21, - Kip1, - Ini1, - Ndv0, - Bench - } - - internal class Context - { - public Options Options; - public KeySet KeySet; - public ProgressBar Logger; - public HorizonClient Horizon; - } + public KeySet.Mode KeyMode => UseDevKeys ? KeySet.Mode.Dev : KeySet.Mode.Prod; +} + +internal enum FileType +{ + Nca, + Pfs0, + PfsBuild, + Nsp, + Romfs, + RomfsBuild, + Nax0, + Xci, + SwitchFs, + Save, + Keygen, + Pk11, + Pk21, + Kip1, + Ini1, + Ndv0, + Bench +} + +internal class Context +{ + public Options Options; + public KeySet KeySet; + public ProgressBar Logger; + public HorizonClient Horizon; } diff --git a/src/hactoolnet/Print.cs b/src/hactoolnet/Print.cs index 06050332..10bc6948 100644 --- a/src/hactoolnet/Print.cs +++ b/src/hactoolnet/Print.cs @@ -5,111 +5,110 @@ using LibHac; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; -namespace hactoolnet +namespace hactoolnet; + +internal static class Print { - internal static class Print + public static void PrintItem(StringBuilder sb, int colLen, string prefix, object data) { - public static void PrintItem(StringBuilder sb, int colLen, string prefix, object data) + if (data is byte[] byteData) { - if (data is byte[] byteData) - { - sb.MemDump(prefix.PadRight(colLen), byteData); - } - else - { - sb.AppendLine(prefix.PadRight(colLen) + data); - } + sb.MemDump(prefix.PadRight(colLen), byteData); } - - public static string GetValidityString(this Validity validity) + else { - switch (validity) - { - case Validity.Invalid: return " (FAIL)"; - case Validity.Valid: return " (GOOD)"; - default: return string.Empty; - } - } - - public static void PrintIvfcHash(StringBuilder sb, int colLen, int indentSize, IvfcHeader ivfcInfo, IntegrityStorageType type) - { - string prefix = new string(' ', indentSize); - string prefix2 = new string(' ', indentSize + 4); - - if (type == IntegrityStorageType.RomFs) - PrintItem(sb, colLen, $"{prefix}Master Hash{ivfcInfo.LevelHeaders[0].HashValidity.GetValidityString()}:", ivfcInfo.MasterHash); - - PrintItem(sb, colLen, $"{prefix}Magic:", ivfcInfo.Magic); - PrintItem(sb, colLen, $"{prefix}Version:", ivfcInfo.Version); - - if (type == IntegrityStorageType.Save) - PrintItem(sb, colLen, $"{prefix}Salt Seed:", ivfcInfo.SaltSource); - - int levelCount = Math.Max(ivfcInfo.NumLevels - 1, 0); - if (type == IntegrityStorageType.Save) levelCount = 4; - - int offsetLen = type == IntegrityStorageType.Save ? 16 : 12; - - for (int i = 0; i < levelCount; i++) - { - IvfcLevelHeader level = ivfcInfo.LevelHeaders[i]; - long hashOffset = 0; - - if (i != 0) - { - hashOffset = ivfcInfo.LevelHeaders[i - 1].Offset; - } - - sb.AppendLine($"{prefix}Level {i}{level.HashValidity.GetValidityString()}:"); - PrintItem(sb, colLen, $"{prefix2}Data Offset:", $"0x{level.Offset.ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Data Size:", $"0x{level.Size.ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Hash Offset:", $"0x{hashOffset.ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Hash BlockSize:", $"0x{1 << level.BlockSizePower:x8}"); - } - } - - public static void PrintIvfcHashNew(StringBuilder sb, int colLen, int indentSize, NcaFsIntegrityInfoIvfc ivfcInfo, IntegrityStorageType type, Validity masterHashValidity) - { - string prefix = new string(' ', indentSize); - string prefix2 = new string(' ', indentSize + 4); - - if (type == IntegrityStorageType.RomFs) - PrintItem(sb, colLen, $"{prefix}Master Hash{masterHashValidity.GetValidityString()}:", ivfcInfo.MasterHash.ToArray()); - - PrintItem(sb, colLen, $"{prefix}Magic:", MagicToString(ivfcInfo.Magic)); - PrintItem(sb, colLen, $"{prefix}Version:", ivfcInfo.Version >> 16); - - if (type == IntegrityStorageType.Save) - PrintItem(sb, colLen, $"{prefix}Salt Seed:", ivfcInfo.SaltSource.ToArray()); - - int levelCount = Math.Max(ivfcInfo.LevelCount - 1, 0); - if (type == IntegrityStorageType.Save) levelCount = 4; - - int offsetLen = type == IntegrityStorageType.Save ? 16 : 12; - - for (int i = 0; i < levelCount; i++) - { - long hashOffset = 0; - - if (i != 0) - { - hashOffset = ivfcInfo.GetLevelOffset(i - 1); - } - - sb.AppendLine($"{prefix}Level {i}:"); - PrintItem(sb, colLen, $"{prefix2}Data Offset:", $"0x{ivfcInfo.GetLevelOffset(i).ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Data Size:", $"0x{ivfcInfo.GetLevelSize(i).ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Hash Offset:", $"0x{hashOffset.ToString($"x{offsetLen}")}"); - PrintItem(sb, colLen, $"{prefix2}Hash BlockSize:", $"0x{1 << ivfcInfo.GetLevelBlockSize(i):x8}"); - } - } - - public static string MagicToString(uint value) - { - byte[] buf = new byte[4]; - BinaryPrimitives.WriteUInt32LittleEndian(buf, value); - - return Encoding.ASCII.GetString(buf); + sb.AppendLine(prefix.PadRight(colLen) + data); } } + + public static string GetValidityString(this Validity validity) + { + switch (validity) + { + case Validity.Invalid: return " (FAIL)"; + case Validity.Valid: return " (GOOD)"; + default: return string.Empty; + } + } + + public static void PrintIvfcHash(StringBuilder sb, int colLen, int indentSize, IvfcHeader ivfcInfo, IntegrityStorageType type) + { + string prefix = new string(' ', indentSize); + string prefix2 = new string(' ', indentSize + 4); + + if (type == IntegrityStorageType.RomFs) + PrintItem(sb, colLen, $"{prefix}Master Hash{ivfcInfo.LevelHeaders[0].HashValidity.GetValidityString()}:", ivfcInfo.MasterHash); + + PrintItem(sb, colLen, $"{prefix}Magic:", ivfcInfo.Magic); + PrintItem(sb, colLen, $"{prefix}Version:", ivfcInfo.Version); + + if (type == IntegrityStorageType.Save) + PrintItem(sb, colLen, $"{prefix}Salt Seed:", ivfcInfo.SaltSource); + + int levelCount = Math.Max(ivfcInfo.NumLevels - 1, 0); + if (type == IntegrityStorageType.Save) levelCount = 4; + + int offsetLen = type == IntegrityStorageType.Save ? 16 : 12; + + for (int i = 0; i < levelCount; i++) + { + IvfcLevelHeader level = ivfcInfo.LevelHeaders[i]; + long hashOffset = 0; + + if (i != 0) + { + hashOffset = ivfcInfo.LevelHeaders[i - 1].Offset; + } + + sb.AppendLine($"{prefix}Level {i}{level.HashValidity.GetValidityString()}:"); + PrintItem(sb, colLen, $"{prefix2}Data Offset:", $"0x{level.Offset.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Data Size:", $"0x{level.Size.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash Offset:", $"0x{hashOffset.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash BlockSize:", $"0x{1 << level.BlockSizePower:x8}"); + } + } + + public static void PrintIvfcHashNew(StringBuilder sb, int colLen, int indentSize, NcaFsIntegrityInfoIvfc ivfcInfo, IntegrityStorageType type, Validity masterHashValidity) + { + string prefix = new string(' ', indentSize); + string prefix2 = new string(' ', indentSize + 4); + + if (type == IntegrityStorageType.RomFs) + PrintItem(sb, colLen, $"{prefix}Master Hash{masterHashValidity.GetValidityString()}:", ivfcInfo.MasterHash.ToArray()); + + PrintItem(sb, colLen, $"{prefix}Magic:", MagicToString(ivfcInfo.Magic)); + PrintItem(sb, colLen, $"{prefix}Version:", ivfcInfo.Version >> 16); + + if (type == IntegrityStorageType.Save) + PrintItem(sb, colLen, $"{prefix}Salt Seed:", ivfcInfo.SaltSource.ToArray()); + + int levelCount = Math.Max(ivfcInfo.LevelCount - 1, 0); + if (type == IntegrityStorageType.Save) levelCount = 4; + + int offsetLen = type == IntegrityStorageType.Save ? 16 : 12; + + for (int i = 0; i < levelCount; i++) + { + long hashOffset = 0; + + if (i != 0) + { + hashOffset = ivfcInfo.GetLevelOffset(i - 1); + } + + sb.AppendLine($"{prefix}Level {i}:"); + PrintItem(sb, colLen, $"{prefix2}Data Offset:", $"0x{ivfcInfo.GetLevelOffset(i).ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Data Size:", $"0x{ivfcInfo.GetLevelSize(i).ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash Offset:", $"0x{hashOffset.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash BlockSize:", $"0x{1 << ivfcInfo.GetLevelBlockSize(i):x8}"); + } + } + + public static string MagicToString(uint value) + { + byte[] buf = new byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian(buf, value); + + return Encoding.ASCII.GetString(buf); + } } diff --git a/src/hactoolnet/ProcessBench.cs b/src/hactoolnet/ProcessBench.cs index 9046977b..4f9d7714 100644 --- a/src/hactoolnet/ProcessBench.cs +++ b/src/hactoolnet/ProcessBench.cs @@ -10,386 +10,386 @@ using LibHac.Crypto.Impl; using LibHac.Fs; using LibHac.FsSystem; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessBench { - internal static class ProcessBench + private const int Size = 1024 * 1024 * 10; + private const int Iterations = 100; + private const int BlockSizeBlocked = 0x10; + private const int BlockSizeSeparate = 0x10; + + private const int BatchCipherBenchSize = 1024 * 1024; + // ReSharper disable once UnusedMember.Local + private const int SingleBlockCipherBenchSize = 1024 * 128; + private const int ShaBenchSize = 1024 * 128; + + private static double CpuFrequency { get; set; } + + private static void CopyBenchmark(IStorage src, IStorage dst, int iterations, string label, IProgressReport logger) { - private const int Size = 1024 * 1024 * 10; - private const int Iterations = 100; - private const int BlockSizeBlocked = 0x10; - private const int BlockSizeSeparate = 0x10; + // Warmup + src.CopyTo(dst); - private const int BatchCipherBenchSize = 1024 * 1024; - // ReSharper disable once UnusedMember.Local - private const int SingleBlockCipherBenchSize = 1024 * 128; - private const int ShaBenchSize = 1024 * 128; + logger.SetTotal(iterations); - private static double CpuFrequency { get; set; } - - private static void CopyBenchmark(IStorage src, IStorage dst, int iterations, string label, IProgressReport logger) + var encryptWatch = Stopwatch.StartNew(); + for (int i = 0; i < iterations; i++) { - // Warmup src.CopyTo(dst); + logger.ReportAdd(1); + } + encryptWatch.Stop(); + logger.SetTotal(0); - logger.SetTotal(iterations); + src.GetSize(out long srcSize).ThrowIfFailure(); - var encryptWatch = Stopwatch.StartNew(); - for (int i = 0; i < iterations; i++) - { - src.CopyTo(dst); - logger.ReportAdd(1); - } - encryptWatch.Stop(); - logger.SetTotal(0); + string rate = Utilities.GetBytesReadable((long)(srcSize * iterations / encryptWatch.Elapsed.TotalSeconds)); + logger.LogMessage($"{label}{rate}/s"); + } - src.GetSize(out long srcSize).ThrowIfFailure(); + private static void CipherBenchmark(ReadOnlySpan<byte> src, Span<byte> dst, Func<ICipher> cipherGenerator, + int iterations, string label, IProgressReport logger) + { + cipherGenerator().Transform(src, dst); - string rate = Utilities.GetBytesReadable((long)(srcSize * iterations / encryptWatch.Elapsed.TotalSeconds)); - logger.LogMessage($"{label}{rate}/s"); + var watch = new Stopwatch(); + double[] runTimes = new double[iterations]; + + logger.SetTotal(iterations); + + for (int i = 0; i < iterations; i++) + { + ICipher cipher = cipherGenerator(); + + watch.Restart(); + cipher.Transform(src, dst); + watch.Stop(); + + logger.ReportAdd(1); + runTimes[i] = watch.Elapsed.TotalSeconds; } - private static void CipherBenchmark(ReadOnlySpan<byte> src, Span<byte> dst, Func<ICipher> cipherGenerator, - int iterations, string label, IProgressReport logger) + logger.SetTotal(0); + + long srcSize = src.Length; + + double fastestRun = runTimes.Min(); + double averageRun = runTimes.Average(); + double slowestRun = runTimes.Max(); + + string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); + string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); + string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); + + logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + } + + private static void CipherBenchmarkBlocked(ReadOnlySpan<byte> src, Span<byte> dst, Func<ICipher> cipherGenerator, + int iterations, string label, IProgressReport logger) + { + cipherGenerator().Transform(src, dst); + + var watch = new Stopwatch(); + double[] runTimes = new double[iterations]; + + logger.SetTotal(iterations); + + int blockCount = src.Length / BlockSizeBlocked; + + for (int i = 0; i < iterations; i++) { - cipherGenerator().Transform(src, dst); + ICipher cipher = cipherGenerator(); - var watch = new Stopwatch(); - double[] runTimes = new double[iterations]; + watch.Restart(); - logger.SetTotal(iterations); - - for (int i = 0; i < iterations; i++) + for (int b = 0; b < blockCount; b++) { - ICipher cipher = cipherGenerator(); - - watch.Restart(); - cipher.Transform(src, dst); - watch.Stop(); - - logger.ReportAdd(1); - runTimes[i] = watch.Elapsed.TotalSeconds; + cipher.Transform(src.Slice(b * BlockSizeBlocked, BlockSizeBlocked), + dst.Slice(b * BlockSizeBlocked, BlockSizeBlocked)); } - logger.SetTotal(0); + watch.Stop(); - long srcSize = src.Length; - - double fastestRun = runTimes.Min(); - double averageRun = runTimes.Average(); - double slowestRun = runTimes.Max(); - - string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); - string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); - string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); - - logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + logger.ReportAdd(1); + runTimes[i] = watch.Elapsed.TotalSeconds; } - private static void CipherBenchmarkBlocked(ReadOnlySpan<byte> src, Span<byte> dst, Func<ICipher> cipherGenerator, - int iterations, string label, IProgressReport logger) + logger.SetTotal(0); + + long srcSize = src.Length; + + double fastestRun = runTimes.Min(); + double averageRun = runTimes.Average(); + double slowestRun = runTimes.Max(); + + string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); + string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); + string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); + + logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + } + + private delegate void CipherTaskSeparate(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key1, + ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false); + + // Benchmarks encrypting each block separately, initializing a new cipher object for each one + private static void CipherBenchmarkSeparate(ReadOnlySpan<byte> src, Span<byte> dst, CipherTaskSeparate function, + int iterations, string label, bool dotNetCrypto, IProgressReport logger) + { + Debug.Assert(src.Length == dst.Length); + + var watch = new Stopwatch(); + double[] runTimes = new double[iterations]; + + ReadOnlySpan<byte> key1 = stackalloc byte[0x10]; + ReadOnlySpan<byte> key2 = stackalloc byte[0x10]; + ReadOnlySpan<byte> iv = stackalloc byte[0x10]; + + logger.SetTotal(iterations); + + const int blockSize = BlockSizeSeparate; + int blockCount = src.Length / blockSize; + + for (int i = 0; i < iterations; i++) { - cipherGenerator().Transform(src, dst); + watch.Restart(); - var watch = new Stopwatch(); - double[] runTimes = new double[iterations]; - - logger.SetTotal(iterations); - - int blockCount = src.Length / BlockSizeBlocked; - - for (int i = 0; i < iterations; i++) + for (int b = 0; b < blockCount; b++) { - ICipher cipher = cipherGenerator(); - - watch.Restart(); - - for (int b = 0; b < blockCount; b++) - { - cipher.Transform(src.Slice(b * BlockSizeBlocked, BlockSizeBlocked), - dst.Slice(b * BlockSizeBlocked, BlockSizeBlocked)); - } - - watch.Stop(); - - logger.ReportAdd(1); - runTimes[i] = watch.Elapsed.TotalSeconds; + function(src.Slice(b * blockSize, blockSize), dst.Slice(b * blockSize, blockSize), + key1, key2, iv, dotNetCrypto); } - logger.SetTotal(0); + watch.Stop(); - long srcSize = src.Length; - - double fastestRun = runTimes.Min(); - double averageRun = runTimes.Average(); - double slowestRun = runTimes.Max(); - - string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); - string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); - string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); - - logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + logger.ReportAdd(1); + runTimes[i] = watch.Elapsed.TotalSeconds; } - private delegate void CipherTaskSeparate(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key1, - ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false); + logger.SetTotal(0); - // Benchmarks encrypting each block separately, initializing a new cipher object for each one - private static void CipherBenchmarkSeparate(ReadOnlySpan<byte> src, Span<byte> dst, CipherTaskSeparate function, - int iterations, string label, bool dotNetCrypto, IProgressReport logger) + long srcSize = src.Length; + + double fastestRun = runTimes.Min(); + double averageRun = runTimes.Average(); + double slowestRun = runTimes.Max(); + + string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); + string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); + string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); + + logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + } + + private static void RegisterAesSequentialBenchmarks(MultiBenchmark bench) + { + byte[] input = new byte[BatchCipherBenchSize]; + byte[] output = new byte[BatchCipherBenchSize]; + + Func<double, string> resultPrinter = time => GetPerformanceString(time, BatchCipherBenchSize); + + // Skip the first benchmark set if we don't have AES-NI intrinsics + for (int i = Aes.IsAesNiSupported() ? 0 : 1; i < 2; i++) { - Debug.Assert(src.Length == dst.Length); + // Prefer .NET crypto on the second set + string nameSuffix = i == 1 ? "built-in " : string.Empty; + bool preferDotNetImpl = i == 1; - var watch = new Stopwatch(); - double[] runTimes = new double[iterations]; + RegisterCipher($"AES-ECB {nameSuffix}encrypt", + () => Aes.CreateEcbEncryptor(new byte[0x10], preferDotNetImpl)); - ReadOnlySpan<byte> key1 = stackalloc byte[0x10]; - ReadOnlySpan<byte> key2 = stackalloc byte[0x10]; - ReadOnlySpan<byte> iv = stackalloc byte[0x10]; + RegisterCipher($"AES-ECB {nameSuffix}decrypt", + () => Aes.CreateEcbDecryptor(new byte[0x10], preferDotNetImpl)); - logger.SetTotal(iterations); + RegisterCipher($"AES-CBC {nameSuffix}encrypt", + () => Aes.CreateCbcEncryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - const int blockSize = BlockSizeSeparate; - int blockCount = src.Length / blockSize; + RegisterCipher($"AES-CBC {nameSuffix}decrypt", + () => Aes.CreateCbcDecryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - for (int i = 0; i < iterations; i++) - { - watch.Restart(); + RegisterCipher($"AES-CTR {nameSuffix}decrypt", + () => Aes.CreateCtrDecryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - for (int b = 0; b < blockCount; b++) - { - function(src.Slice(b * blockSize, blockSize), dst.Slice(b * blockSize, blockSize), - key1, key2, iv, dotNetCrypto); - } + RegisterCipher($"AES-XTS {nameSuffix}encrypt", + () => Aes.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10], preferDotNetImpl)); - watch.Stop(); - - logger.ReportAdd(1); - runTimes[i] = watch.Elapsed.TotalSeconds; - } - - logger.SetTotal(0); - - long srcSize = src.Length; - - double fastestRun = runTimes.Min(); - double averageRun = runTimes.Average(); - double slowestRun = runTimes.Max(); - - string fastestRate = Utilities.GetBytesReadable((long)(srcSize / fastestRun)); - string averageRate = Utilities.GetBytesReadable((long)(srcSize / averageRun)); - string slowestRate = Utilities.GetBytesReadable((long)(srcSize / slowestRun)); - - logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s"); + RegisterCipher($"AES-XTS {nameSuffix}decrypt", + () => Aes.CreateXtsDecryptor(new byte[0x10], new byte[0x10], new byte[0x10], preferDotNetImpl)); } - private static void RegisterAesSequentialBenchmarks(MultiBenchmark bench) + void RegisterCipher(string name, Func<ICipher> cipherGenerator) { - byte[] input = new byte[BatchCipherBenchSize]; - byte[] output = new byte[BatchCipherBenchSize]; + ICipher cipher = null; - Func<double, string> resultPrinter = time => GetPerformanceString(time, BatchCipherBenchSize); + Action setup = () => cipher = cipherGenerator(); + Action action = () => cipher.Transform(input, output); - // Skip the first benchmark set if we don't have AES-NI intrinsics - for (int i = Aes.IsAesNiSupported() ? 0 : 1; i < 2; i++) + bench.Register(name, setup, action, resultPrinter); + } + } + + // ReSharper disable once UnusedParameter.Local + private static void RegisterAesSingleBlockBenchmarks(MultiBenchmark bench) + { + byte[] input = new byte[SingleBlockCipherBenchSize]; + byte[] output = new byte[SingleBlockCipherBenchSize]; + + Func<double, string> resultPrinter = time => GetPerformanceString(time, SingleBlockCipherBenchSize); + + bench.Register("AES single-block encrypt", () => { }, EncryptBlocks, resultPrinter); + bench.Register("AES single-block decrypt", () => { }, DecryptBlocks, resultPrinter); + + bench.DefaultRunsNeeded = 1000; + + void EncryptBlocks() + { + ref byte inBlock = ref MemoryMarshal.GetReference(input.AsSpan()); + ref byte outBlock = ref MemoryMarshal.GetReference(output.AsSpan()); + + Vector128<byte> keyVec = Vector128<byte>.Zero; + + ref byte end = ref Unsafe.Add(ref inBlock, input.Length); + + while (Unsafe.IsAddressLessThan(ref inBlock, ref end)) { - // Prefer .NET crypto on the second set - string nameSuffix = i == 1 ? "built-in " : string.Empty; - bool preferDotNetImpl = i == 1; + var inputVec = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBlock); + Vector128<byte> outputVec = AesCoreNi.EncryptBlock(inputVec, keyVec); + Unsafe.WriteUnaligned(ref outBlock, outputVec); - RegisterCipher($"AES-ECB {nameSuffix}encrypt", - () => Aes.CreateEcbEncryptor(new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-ECB {nameSuffix}decrypt", - () => Aes.CreateEcbDecryptor(new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-CBC {nameSuffix}encrypt", - () => Aes.CreateCbcEncryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-CBC {nameSuffix}decrypt", - () => Aes.CreateCbcDecryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-CTR {nameSuffix}decrypt", - () => Aes.CreateCtrDecryptor(new byte[0x10], new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-XTS {nameSuffix}encrypt", - () => Aes.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10], preferDotNetImpl)); - - RegisterCipher($"AES-XTS {nameSuffix}decrypt", - () => Aes.CreateXtsDecryptor(new byte[0x10], new byte[0x10], new byte[0x10], preferDotNetImpl)); - } - - void RegisterCipher(string name, Func<ICipher> cipherGenerator) - { - ICipher cipher = null; - - Action setup = () => cipher = cipherGenerator(); - Action action = () => cipher.Transform(input, output); - - bench.Register(name, setup, action, resultPrinter); + inBlock = ref Unsafe.Add(ref inBlock, Aes.BlockSize); + outBlock = ref Unsafe.Add(ref outBlock, Aes.BlockSize); } } - // ReSharper disable once UnusedParameter.Local - private static void RegisterAesSingleBlockBenchmarks(MultiBenchmark bench) + void DecryptBlocks() { - byte[] input = new byte[SingleBlockCipherBenchSize]; - byte[] output = new byte[SingleBlockCipherBenchSize]; + ref byte inBlock = ref MemoryMarshal.GetReference(input.AsSpan()); + ref byte outBlock = ref MemoryMarshal.GetReference(output.AsSpan()); - Func<double, string> resultPrinter = time => GetPerformanceString(time, SingleBlockCipherBenchSize); + Vector128<byte> keyVec = Vector128<byte>.Zero; - bench.Register("AES single-block encrypt", () => { }, EncryptBlocks, resultPrinter); - bench.Register("AES single-block decrypt", () => { }, DecryptBlocks, resultPrinter); + ref byte end = ref Unsafe.Add(ref inBlock, input.Length); - bench.DefaultRunsNeeded = 1000; - - void EncryptBlocks() + while (Unsafe.IsAddressLessThan(ref inBlock, ref end)) { - ref byte inBlock = ref MemoryMarshal.GetReference(input.AsSpan()); - ref byte outBlock = ref MemoryMarshal.GetReference(output.AsSpan()); + var inputVec = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBlock); + Vector128<byte> outputVec = AesCoreNi.DecryptBlock(inputVec, keyVec); + Unsafe.WriteUnaligned(ref outBlock, outputVec); - Vector128<byte> keyVec = Vector128<byte>.Zero; - - ref byte end = ref Unsafe.Add(ref inBlock, input.Length); - - while (Unsafe.IsAddressLessThan(ref inBlock, ref end)) - { - var inputVec = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBlock); - Vector128<byte> outputVec = AesCoreNi.EncryptBlock(inputVec, keyVec); - Unsafe.WriteUnaligned(ref outBlock, outputVec); - - inBlock = ref Unsafe.Add(ref inBlock, Aes.BlockSize); - outBlock = ref Unsafe.Add(ref outBlock, Aes.BlockSize); - } - } - - void DecryptBlocks() - { - ref byte inBlock = ref MemoryMarshal.GetReference(input.AsSpan()); - ref byte outBlock = ref MemoryMarshal.GetReference(output.AsSpan()); - - Vector128<byte> keyVec = Vector128<byte>.Zero; - - ref byte end = ref Unsafe.Add(ref inBlock, input.Length); - - while (Unsafe.IsAddressLessThan(ref inBlock, ref end)) - { - var inputVec = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBlock); - Vector128<byte> outputVec = AesCoreNi.DecryptBlock(inputVec, keyVec); - Unsafe.WriteUnaligned(ref outBlock, outputVec); - - inBlock = ref Unsafe.Add(ref inBlock, Aes.BlockSize); - outBlock = ref Unsafe.Add(ref outBlock, Aes.BlockSize); - } + inBlock = ref Unsafe.Add(ref inBlock, Aes.BlockSize); + outBlock = ref Unsafe.Add(ref outBlock, Aes.BlockSize); } } + } - private static void RegisterShaBenchmarks(MultiBenchmark bench) + private static void RegisterShaBenchmarks(MultiBenchmark bench) + { + byte[] input = new byte[ShaBenchSize]; + byte[] digest = new byte[Sha256.DigestSize]; + + Func<double, string> resultPrinter = time => GetPerformanceString(time, ShaBenchSize); + + RegisterHash("SHA-256 built-in", () => new Sha256Generator()); + + void RegisterHash(string name, Func<IHash> hashGenerator) { - byte[] input = new byte[ShaBenchSize]; - byte[] digest = new byte[Sha256.DigestSize]; + IHash hash = null; - Func<double, string> resultPrinter = time => GetPerformanceString(time, ShaBenchSize); - - RegisterHash("SHA-256 built-in", () => new Sha256Generator()); - - void RegisterHash(string name, Func<IHash> hashGenerator) + Action setup = () => { - IHash hash = null; + hash = hashGenerator(); + hash.Initialize(); + }; - Action setup = () => - { - hash = hashGenerator(); - hash.Initialize(); - }; + Action action = () => + { + hash.Update(input); + hash.GetHash(digest); + }; - Action action = () => - { - hash.Update(input); - hash.GetHash(digest); - }; - - bench.Register(name, setup, action, resultPrinter); - } + bench.Register(name, setup, action, resultPrinter); } + } - private static void RunCipherBenchmark(Func<ICipher> cipherNet, Func<ICipher> cipherLibHac, - CipherTaskSeparate function, bool benchBlocked, string label, IProgressReport logger) + private static void RunCipherBenchmark(Func<ICipher> cipherNet, Func<ICipher> cipherLibHac, + CipherTaskSeparate function, bool benchBlocked, string label, IProgressReport logger) + { + byte[] srcData = new byte[Size]; + + byte[] dstDataLh = new byte[Size]; + byte[] dstDataNet = new byte[Size]; + byte[] dstDataBlockedLh = new byte[Size]; + byte[] dstDataBlockedNet = new byte[Size]; + byte[] dstDataSeparateLh = new byte[Size]; + byte[] dstDataSeparateNet = new byte[Size]; + + logger.LogMessage(string.Empty); + logger.LogMessage(label); + + if (Aes.IsAesNiSupported()) + CipherBenchmark(srcData, dstDataLh, cipherLibHac, Iterations, "LibHac impl: ", logger); + CipherBenchmark(srcData, dstDataNet, cipherNet, Iterations, ".NET impl: ", logger); + + if (benchBlocked) { - byte[] srcData = new byte[Size]; - - byte[] dstDataLh = new byte[Size]; - byte[] dstDataNet = new byte[Size]; - byte[] dstDataBlockedLh = new byte[Size]; - byte[] dstDataBlockedNet = new byte[Size]; - byte[] dstDataSeparateLh = new byte[Size]; - byte[] dstDataSeparateNet = new byte[Size]; - - logger.LogMessage(string.Empty); - logger.LogMessage(label); - if (Aes.IsAesNiSupported()) - CipherBenchmark(srcData, dstDataLh, cipherLibHac, Iterations, "LibHac impl: ", logger); - CipherBenchmark(srcData, dstDataNet, cipherNet, Iterations, ".NET impl: ", logger); + CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, + "LibHac impl (blocked): ", logger); + + CipherBenchmarkBlocked(srcData, dstDataBlockedNet, cipherNet, Iterations / 5, ".NET impl (blocked): ", + logger); + } + + if (function != null) + { + if (Aes.IsAesNiSupported()) + CipherBenchmarkSeparate(srcData, dstDataSeparateLh, function, Iterations / 5, + "LibHac impl (separate): ", false, logger); + + CipherBenchmarkSeparate(srcData, dstDataSeparateNet, function, Iterations / 20, + ".NET impl (separate): ", true, logger); + } + + if (Aes.IsAesNiSupported()) + { + logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataNet)}"); if (benchBlocked) { - if (Aes.IsAesNiSupported()) - CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, - "LibHac impl (blocked): ", logger); - - CipherBenchmarkBlocked(srcData, dstDataBlockedNet, cipherNet, Iterations / 5, ".NET impl (blocked): ", - logger); + logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedLh)}"); + logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedNet)}"); } if (function != null) { - if (Aes.IsAesNiSupported()) - CipherBenchmarkSeparate(srcData, dstDataSeparateLh, function, Iterations / 5, - "LibHac impl (separate): ", false, logger); - - CipherBenchmarkSeparate(srcData, dstDataSeparateNet, function, Iterations / 20, - ".NET impl (separate): ", true, logger); - } - - if (Aes.IsAesNiSupported()) - { - logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataNet)}"); - - if (benchBlocked) - { - logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedLh)}"); - logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedNet)}"); - } - - if (function != null) - { - logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataSeparateLh)}"); - logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataSeparateNet)}"); - } + logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataSeparateLh)}"); + logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataSeparateNet)}"); } } + } - private static string GetPerformanceString(double seconds, long bytes) + private static string GetPerformanceString(double seconds, long bytes) + { + string cyclesPerByteString = string.Empty; + double bytesPerSec = bytes / seconds; + + if (CpuFrequency > 0) { - string cyclesPerByteString = string.Empty; - double bytesPerSec = bytes / seconds; - - if (CpuFrequency > 0) - { - double cyclesPerByte = CpuFrequency / bytesPerSec; - cyclesPerByteString = $" ({cyclesPerByte:N3}x)"; - } - - return Utilities.GetBytesReadable((long)bytesPerSec) + "/s" + cyclesPerByteString; + double cyclesPerByte = CpuFrequency / bytesPerSec; + cyclesPerByteString = $" ({cyclesPerByte:N3}x)"; } - public static void Process(Context ctx) - { - CpuFrequency = ctx.Options.CpuFrequencyGhz * 1_000_000_000; + return Utilities.GetBytesReadable((long)bytesPerSec) + "/s" + cyclesPerByteString; + } - switch (ctx.Options.BenchType?.ToLower()) - { - case "aesctr": + public static void Process(Context ctx) + { + CpuFrequency = ctx.Options.CpuFrequencyGhz * 1_000_000_000; + + switch (ctx.Options.BenchType?.ToLower()) + { + case "aesctr": { IStorage decStorage = new MemoryStorage(new byte[Size]); IStorage encStorage = new Aes128CtrStorage(new MemoryStorage(new byte[Size]), new byte[0x10], new byte[0x10], true); @@ -412,7 +412,7 @@ namespace hactoolnet break; } - case "aesxts": + case "aesxts": { IStorage decStorage = new MemoryStorage(new byte[Size]); IStorage encStorage = new Aes128XtsStorage(new MemoryStorage(new byte[Size]), new byte[0x20], 81920, true); @@ -434,7 +434,7 @@ namespace hactoolnet break; } - case "aesecbnew": + case "aesecbnew": { Func<ICipher> encryptorNet = () => Aes.CreateEcbEncryptor(new byte[0x10], true); Func<ICipher> encryptorLh = () => Aes.CreateEcbEncryptor(new byte[0x10]); @@ -453,7 +453,7 @@ namespace hactoolnet break; } - case "aescbcnew": + case "aescbcnew": { Func<ICipher> encryptorNet = () => Aes.CreateCbcEncryptor(new byte[0x10], new byte[0x10], true); Func<ICipher> encryptorLh = () => Aes.CreateCbcEncryptor(new byte[0x10], new byte[0x10]); @@ -472,7 +472,7 @@ namespace hactoolnet break; } - case "aesctrnew": + case "aesctrnew": { Func<ICipher> encryptorNet = () => Aes.CreateCtrEncryptor(new byte[0x10], new byte[0x10], true); Func<ICipher> encryptorLh = () => Aes.CreateCtrEncryptor(new byte[0x10], new byte[0x10]); @@ -484,7 +484,7 @@ namespace hactoolnet break; } - case "aesxtsnew": + case "aesxtsnew": { Func<ICipher> encryptorNet = () => Aes.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10], true); Func<ICipher> encryptorLh = () => Aes.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10]); @@ -503,7 +503,7 @@ namespace hactoolnet break; } - case "crypto": + case "crypto": { var bench = new MultiBenchmark(); @@ -515,10 +515,9 @@ namespace hactoolnet break; } - default: - ctx.Logger.LogMessage("Unknown benchmark type."); - return; - } + default: + ctx.Logger.LogMessage("Unknown benchmark type."); + return; } } } diff --git a/src/hactoolnet/ProcessDelta.cs b/src/hactoolnet/ProcessDelta.cs index 077a8fed..1e234c85 100644 --- a/src/hactoolnet/ProcessDelta.cs +++ b/src/hactoolnet/ProcessDelta.cs @@ -9,80 +9,79 @@ using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using static hactoolnet.Print; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessDelta { - internal static class ProcessDelta + private const uint Ndv0Magic = 0x3056444E; + private const string FragmentFileName = "fragment"; + + public static void Process(Context ctx) { - private const uint Ndv0Magic = 0x3056444E; - private const string FragmentFileName = "fragment"; - - public static void Process(Context ctx) + using (IStorage deltaFile = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - using (IStorage deltaFile = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) + IStorage deltaStorage = deltaFile; + Span<byte> magic = stackalloc byte[4]; + deltaFile.Read(0, magic).ThrowIfFailure(); + + if (MemoryMarshal.Read<uint>(magic) != Ndv0Magic) { - IStorage deltaStorage = deltaFile; - Span<byte> magic = stackalloc byte[4]; - deltaFile.Read(0, magic).ThrowIfFailure(); - - if (MemoryMarshal.Read<uint>(magic) != Ndv0Magic) + try { - try + var nca = new Nca(ctx.KeySet, deltaStorage); + IFileSystem fs = nca.OpenFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid); + + if (!fs.FileExists(FragmentFileName)) { - var nca = new Nca(ctx.KeySet, deltaStorage); - IFileSystem fs = nca.OpenFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid); - - if (!fs.FileExists(FragmentFileName)) - { - throw new FileNotFoundException("Specified NCA does not contain a delta fragment"); - } - - using var deltaFragmentFile = new UniqueRef<IFile>(); - fs.OpenFile(ref deltaFragmentFile.Ref(), FragmentFileName.ToU8String(), OpenMode.Read).ThrowIfFailure(); - - deltaStorage = deltaFragmentFile.Release().AsStorage(); + throw new FileNotFoundException("Specified NCA does not contain a delta fragment"); } - catch (InvalidDataException) { } // Ignore non-NCA3 files + + using var deltaFragmentFile = new UniqueRef<IFile>(); + fs.OpenFile(ref deltaFragmentFile.Ref(), FragmentFileName.ToU8String(), OpenMode.Read).ThrowIfFailure(); + + deltaStorage = deltaFragmentFile.Release().AsStorage(); } - - var delta = new Delta(deltaStorage); - - if (ctx.Options.BaseFile != null) - { - using (IStorage baseFile = new LocalStorage(ctx.Options.BaseFile, FileAccess.Read)) - { - delta.SetBaseStorage(baseFile); - - if (ctx.Options.OutFile != null) - { - using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - { - IStorage patchedStorage = delta.GetPatchedStorage(); - patchedStorage.GetSize(out long patchedStorageSize).ThrowIfFailure(); - - patchedStorage.CopyToStream(outFile, patchedStorageSize, ctx.Logger); - } - } - } - } - - ctx.Logger.LogMessage(delta.Print()); + catch (InvalidDataException) { } // Ignore non-NCA3 files } - } - private static string Print(this Delta delta) - { - int colLen = 36; - var sb = new StringBuilder(); - sb.AppendLine(); + var delta = new Delta(deltaStorage); - sb.AppendLine("Delta File:"); - PrintItem(sb, colLen, "Magic:", delta.Header.Magic); - PrintItem(sb, colLen, "Base file size:", $"0x{delta.Header.OriginalSize:x12}"); - PrintItem(sb, colLen, "New file size:", $"0x{delta.Header.NewSize:x12}"); - PrintItem(sb, colLen, "Delta header size:", $"0x{delta.Header.HeaderSize:x12}"); - PrintItem(sb, colLen, "Delta body size:", $"0x{delta.Header.BodySize:x12}"); + if (ctx.Options.BaseFile != null) + { + using (IStorage baseFile = new LocalStorage(ctx.Options.BaseFile, FileAccess.Read)) + { + delta.SetBaseStorage(baseFile); - return sb.ToString(); + if (ctx.Options.OutFile != null) + { + using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + IStorage patchedStorage = delta.GetPatchedStorage(); + patchedStorage.GetSize(out long patchedStorageSize).ThrowIfFailure(); + + patchedStorage.CopyToStream(outFile, patchedStorageSize, ctx.Logger); + } + } + } + } + + ctx.Logger.LogMessage(delta.Print()); } } + + private static string Print(this Delta delta) + { + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("Delta File:"); + PrintItem(sb, colLen, "Magic:", delta.Header.Magic); + PrintItem(sb, colLen, "Base file size:", $"0x{delta.Header.OriginalSize:x12}"); + PrintItem(sb, colLen, "New file size:", $"0x{delta.Header.NewSize:x12}"); + PrintItem(sb, colLen, "Delta header size:", $"0x{delta.Header.HeaderSize:x12}"); + PrintItem(sb, colLen, "Delta body size:", $"0x{delta.Header.BodySize:x12}"); + + return sb.ToString(); + } } diff --git a/src/hactoolnet/ProcessFsBuild.cs b/src/hactoolnet/ProcessFsBuild.cs index 521a69aa..3f020c1d 100644 --- a/src/hactoolnet/ProcessFsBuild.cs +++ b/src/hactoolnet/ProcessFsBuild.cs @@ -3,62 +3,61 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.RomFs; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessFsBuild { - internal static class ProcessFsBuild + public static void ProcessRomFs(Context ctx) { - public static void ProcessRomFs(Context ctx) + if (ctx.Options.OutFile == null) { - if (ctx.Options.OutFile == null) - { - ctx.Logger.LogMessage("Output file must be specified."); - return; - } - - LocalFileSystem.Create(out LocalFileSystem localFs, ctx.Options.InFile).ThrowIfFailure(); - - var builder = new RomFsBuilder(localFs); - IStorage romFs = builder.Build(); - - ctx.Logger.LogMessage($"Building RomFS as {ctx.Options.OutFile}"); - - romFs.GetSize(out long romFsSize).ThrowIfFailure(); - - using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.Create, FileAccess.ReadWrite)) - { - romFs.CopyToStream(outFile, romFsSize, ctx.Logger); - } - - ctx.Logger.LogMessage($"Finished writing {ctx.Options.OutFile}"); + ctx.Logger.LogMessage("Output file must be specified."); + return; } - public static void ProcessPartitionFs(Context ctx) + LocalFileSystem.Create(out LocalFileSystem localFs, ctx.Options.InFile).ThrowIfFailure(); + + var builder = new RomFsBuilder(localFs); + IStorage romFs = builder.Build(); + + ctx.Logger.LogMessage($"Building RomFS as {ctx.Options.OutFile}"); + + romFs.GetSize(out long romFsSize).ThrowIfFailure(); + + using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.Create, FileAccess.ReadWrite)) { - if (ctx.Options.OutFile == null) - { - ctx.Logger.LogMessage("Output file must be specified."); - return; - } - - PartitionFileSystemType type = ctx.Options.BuildHfs - ? PartitionFileSystemType.Hashed - : PartitionFileSystemType.Standard; - - var localFs = new LocalFileSystem(ctx.Options.InFile); - - var builder = new PartitionFileSystemBuilder(localFs); - IStorage partitionFs = builder.Build(type); - - ctx.Logger.LogMessage($"Building Partition FS as {ctx.Options.OutFile}"); - - partitionFs.GetSize(out long partitionFsSize).ThrowIfFailure(); - - using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.Create, FileAccess.ReadWrite)) - { - partitionFs.CopyToStream(outFile, partitionFsSize, ctx.Logger); - } - - ctx.Logger.LogMessage($"Finished writing {ctx.Options.OutFile}"); + romFs.CopyToStream(outFile, romFsSize, ctx.Logger); } + + ctx.Logger.LogMessage($"Finished writing {ctx.Options.OutFile}"); + } + + public static void ProcessPartitionFs(Context ctx) + { + if (ctx.Options.OutFile == null) + { + ctx.Logger.LogMessage("Output file must be specified."); + return; + } + + PartitionFileSystemType type = ctx.Options.BuildHfs + ? PartitionFileSystemType.Hashed + : PartitionFileSystemType.Standard; + + var localFs = new LocalFileSystem(ctx.Options.InFile); + + var builder = new PartitionFileSystemBuilder(localFs); + IStorage partitionFs = builder.Build(type); + + ctx.Logger.LogMessage($"Building Partition FS as {ctx.Options.OutFile}"); + + partitionFs.GetSize(out long partitionFsSize).ThrowIfFailure(); + + using (var outFile = new FileStream(ctx.Options.OutFile, FileMode.Create, FileAccess.ReadWrite)) + { + partitionFs.CopyToStream(outFile, partitionFsSize, ctx.Logger); + } + + ctx.Logger.LogMessage($"Finished writing {ctx.Options.OutFile}"); } } diff --git a/src/hactoolnet/ProcessKip.cs b/src/hactoolnet/ProcessKip.cs index b7ba88fe..30d9d905 100644 --- a/src/hactoolnet/ProcessKip.cs +++ b/src/hactoolnet/ProcessKip.cs @@ -4,57 +4,56 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Kernel; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessKip { - internal static class ProcessKip + public static void ProcessKip1(Context ctx) { - public static void ProcessKip1(Context ctx) + using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); + + using var kip = new KipReader(); + kip.Initialize(in file).ThrowIfFailure(); + + if (!string.IsNullOrWhiteSpace(ctx.Options.UncompressedOut)) { - using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); + byte[] uncompressed = new byte[kip.GetUncompressedSize()]; - using var kip = new KipReader(); - kip.Initialize(in file).ThrowIfFailure(); + kip.ReadUncompressedKip(uncompressed).ThrowIfFailure(); - if (!string.IsNullOrWhiteSpace(ctx.Options.UncompressedOut)) - { - byte[] uncompressed = new byte[kip.GetUncompressedSize()]; - - kip.ReadUncompressedKip(uncompressed).ThrowIfFailure(); - - File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed); - } + File.WriteAllBytes(ctx.Options.UncompressedOut, uncompressed); } + } - public static void ProcessIni1(Context ctx) + public static void ProcessIni1(Context ctx) + { + using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); + + string outDir = ctx.Options.OutDir; + + if (outDir != null) { - using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); - - string outDir = ctx.Options.OutDir; - - if (outDir != null) - { - ExtractIni1(in file, outDir); - } + ExtractIni1(in file, outDir); } + } - public static void ExtractIni1(in SharedRef<IStorage> iniStorage, string outDir) + public static void ExtractIni1(in SharedRef<IStorage> iniStorage, string outDir) + { + using var ini1 = new InitialProcessBinaryReader(); + ini1.Initialize(iniStorage).ThrowIfFailure(); + + Directory.CreateDirectory(outDir); + using var kipReader = new KipReader(); + + for (int i = 0; i < ini1.ProcessCount; i++) { - using var ini1 = new InitialProcessBinaryReader(); - ini1.Initialize(iniStorage).ThrowIfFailure(); + using var kipStorage = new UniqueRef<IStorage>(); + ini1.OpenKipStorage(ref kipStorage.Ref(), i).ThrowIfFailure(); - Directory.CreateDirectory(outDir); - using var kipReader = new KipReader(); + using SharedRef<IStorage> sharedKipStorage = SharedRef<IStorage>.Create(ref kipStorage.Ref()); + kipReader.Initialize(in sharedKipStorage).ThrowIfFailure(); - for (int i = 0; i < ini1.ProcessCount; i++) - { - using var kipStorage = new UniqueRef<IStorage>(); - ini1.OpenKipStorage(ref kipStorage.Ref(), i).ThrowIfFailure(); - - using SharedRef<IStorage> sharedKipStorage = SharedRef<IStorage>.Create(ref kipStorage.Ref()); - kipReader.Initialize(in sharedKipStorage).ThrowIfFailure(); - - sharedKipStorage.Get.WriteAllBytes(System.IO.Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1")); - } + sharedKipStorage.Get.WriteAllBytes(System.IO.Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1")); } } } diff --git a/src/hactoolnet/ProcessNax0.cs b/src/hactoolnet/ProcessNax0.cs index a8fa7b24..dc42a6bd 100644 --- a/src/hactoolnet/ProcessNax0.cs +++ b/src/hactoolnet/ProcessNax0.cs @@ -10,81 +10,80 @@ using LibHac.FsSystem; using LibHac.Util; using static hactoolnet.Print; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessNax0 { - internal static class ProcessNax0 + public static void Process(Context ctx) { - public static void Process(Context ctx) + if (ctx.Options.SdPath == null) { - if (ctx.Options.SdPath == null) - { - ctx.Logger.LogMessage("SD path must be specified."); - return; - } - - Span<AesXtsKey> keys = ctx.KeySet.SdCardEncryptionKeys; - - using var baseFile = new UniqueRef<IFile>(new LocalFile(ctx.Options.InFile, OpenMode.Read)); - - AesXtsFile xtsFile = null; - int contentType = 0; - - for (int i = 0; i < keys.Length; i++) - { - byte[] kekSource = keys[i].SubKeys[0].DataRo.ToArray(); - byte[] validationKey = keys[i].SubKeys[1].DataRo.ToArray(); - - try - { - xtsFile = new AesXtsFile(OpenMode.Read, ref baseFile.Ref(), ctx.Options.SdPath.ToU8String(), kekSource, validationKey, 0x4000); - contentType = i; - - break; - } - catch (HorizonResultException) { } - } - - if (xtsFile == null) - { - ctx.Logger.LogMessage("Error: NAX0 key derivation failed. Check SD card seed and relative path."); - return; - } - - ctx.Logger.LogMessage(xtsFile.Print(contentType)); - - if (string.IsNullOrWhiteSpace(ctx.Options.PlaintextOut)) return; - - xtsFile.AsStorage().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger); - ctx.Logger.LogMessage($"Saved Decrypted NAX0 Content to {ctx.Options.PlaintextOut}..."); + ctx.Logger.LogMessage("SD path must be specified."); + return; } - private static string Print(this AesXtsFile xtsFile, int contentType) + Span<AesXtsKey> keys = ctx.KeySet.SdCardEncryptionKeys; + + using var baseFile = new UniqueRef<IFile>(new LocalFile(ctx.Options.InFile, OpenMode.Read)); + + AesXtsFile xtsFile = null; + int contentType = 0; + + for (int i = 0; i < keys.Length; i++) { - int colLen = 36; - var sb = new StringBuilder(); - sb.AppendLine(); + byte[] kekSource = keys[i].SubKeys[0].DataRo.ToArray(); + byte[] validationKey = keys[i].SubKeys[1].DataRo.ToArray(); - sb.AppendLine("NAX0:"); + try + { + xtsFile = new AesXtsFile(OpenMode.Read, ref baseFile.Ref(), ctx.Options.SdPath.ToU8String(), kekSource, validationKey, 0x4000); + contentType = i; - AesXtsFileHeader header = xtsFile.Header; - uint magic = header.Magic; - - PrintItem(sb, colLen, " Magic:", StringUtils.Utf8ToString(SpanHelpers.AsReadOnlyByteSpan(in magic))); - PrintItem(sb, colLen, " Content Type:", GetContentType(contentType)); - PrintItem(sb, colLen, " Content Size:", $"{header.Size:x12}"); - PrintItem(sb, colLen, " Header HMAC:", header.Signature); - PrintItem(sb, colLen, " Encrypted Keys:", header.EncryptedKey1.Concat(header.EncryptedKey2).ToArray()); - PrintItem(sb, colLen, " Decrypted Keys:", header.DecryptedKey1.Concat(header.DecryptedKey2).ToArray()); - - return sb.ToString(); + break; + } + catch (HorizonResultException) { } } - private static string GetContentType(int type) => type switch + if (xtsFile == null) { - 0 => "Save", - 1 => "NCA", - 2 => "Custom storage", - _ => "Unknown" - }; + ctx.Logger.LogMessage("Error: NAX0 key derivation failed. Check SD card seed and relative path."); + return; + } + + ctx.Logger.LogMessage(xtsFile.Print(contentType)); + + if (string.IsNullOrWhiteSpace(ctx.Options.PlaintextOut)) return; + + xtsFile.AsStorage().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger); + ctx.Logger.LogMessage($"Saved Decrypted NAX0 Content to {ctx.Options.PlaintextOut}..."); } + + private static string Print(this AesXtsFile xtsFile, int contentType) + { + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("NAX0:"); + + AesXtsFileHeader header = xtsFile.Header; + uint magic = header.Magic; + + PrintItem(sb, colLen, " Magic:", StringUtils.Utf8ToString(SpanHelpers.AsReadOnlyByteSpan(in magic))); + PrintItem(sb, colLen, " Content Type:", GetContentType(contentType)); + PrintItem(sb, colLen, " Content Size:", $"{header.Size:x12}"); + PrintItem(sb, colLen, " Header HMAC:", header.Signature); + PrintItem(sb, colLen, " Encrypted Keys:", header.EncryptedKey1.Concat(header.EncryptedKey2).ToArray()); + PrintItem(sb, colLen, " Decrypted Keys:", header.DecryptedKey1.Concat(header.DecryptedKey2).ToArray()); + + return sb.ToString(); + } + + private static string GetContentType(int type) => type switch + { + 0 => "Save", + 1 => "NCA", + 2 => "Custom storage", + _ => "Unknown" + }; } diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index a511e54b..1d591b52 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -10,397 +10,396 @@ using LibHac.FsSystem.NcaUtils; using LibHac.Npdm; using static hactoolnet.Print; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessNca { - internal static class ProcessNca + public static void Process(Context ctx) { - public static void Process(Context ctx) + using (IStorage file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - using (IStorage file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) + var nca = new Nca(ctx.KeySet, file); + Nca baseNca = null; + + var ncaHolder = new NcaHolder { Nca = nca }; + + if (ctx.Options.HeaderOut != null) { - var nca = new Nca(ctx.KeySet, file); - Nca baseNca = null; - - var ncaHolder = new NcaHolder { Nca = nca }; - - if (ctx.Options.HeaderOut != null) + using (var outHeader = new FileStream(ctx.Options.HeaderOut, FileMode.Create, FileAccess.ReadWrite)) { - using (var outHeader = new FileStream(ctx.Options.HeaderOut, FileMode.Create, FileAccess.ReadWrite)) - { - nca.OpenDecryptedHeaderStorage().Slice(0, 0xc00).CopyToStream(outHeader); - } + nca.OpenDecryptedHeaderStorage().Slice(0, 0xc00).CopyToStream(outHeader); + } + } + + if (ctx.Options.BaseNca != null) + { + IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read); + baseNca = new Nca(ctx.KeySet, baseFile); + } + + for (int i = 0; i < 3; i++) + { + if (ctx.Options.SectionOut[i] != null) + { + OpenStorage(i).WriteAllBytes(ctx.Options.SectionOut[i], ctx.Logger); } - if (ctx.Options.BaseNca != null) + if (ctx.Options.SectionOutDir[i] != null) { - IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read); - baseNca = new Nca(ctx.KeySet, baseFile); + FileSystemClient fs = ctx.Horizon.Fs; + + string mountName = $"section{i}"; + + using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystem(i)); + using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.SectionOutDir[i])); + + fs.Register(mountName.ToU8Span(), ref inputFs.Ref()); + fs.Register("output".ToU8Span(), ref outputFs.Ref()); + + fs.Impl.EnableFileSystemAccessorAccessLog(mountName.ToU8Span()); + fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); + + FsUtils.CopyDirectoryWithProgress(fs, (mountName + ":/").ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); + + fs.Unmount(mountName.ToU8Span()); + fs.Unmount("output".ToU8Span()); } - for (int i = 0; i < 3; i++) + if (ctx.Options.Validate && nca.SectionExists(i)) { - if (ctx.Options.SectionOut[i] != null) + if (nca.GetFsHeader(i).IsPatchSection() && baseNca != null) { - OpenStorage(i).WriteAllBytes(ctx.Options.SectionOut[i], ctx.Logger); + ncaHolder.Validities[i] = baseNca.VerifySection(nca, i, ctx.Logger); } - - if (ctx.Options.SectionOutDir[i] != null) + else { - FileSystemClient fs = ctx.Horizon.Fs; - - string mountName = $"section{i}"; - - using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystem(i)); - using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.SectionOutDir[i])); - - fs.Register(mountName.ToU8Span(), ref inputFs.Ref()); - fs.Register("output".ToU8Span(), ref outputFs.Ref()); - - fs.Impl.EnableFileSystemAccessorAccessLog(mountName.ToU8Span()); - fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); - - FsUtils.CopyDirectoryWithProgress(fs, (mountName + ":/").ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); - - fs.Unmount(mountName.ToU8Span()); - fs.Unmount("output".ToU8Span()); - } - - if (ctx.Options.Validate && nca.SectionExists(i)) - { - if (nca.GetFsHeader(i).IsPatchSection() && baseNca != null) - { - ncaHolder.Validities[i] = baseNca.VerifySection(nca, i, ctx.Logger); - } - else - { - ncaHolder.Validities[i] = nca.VerifySection(i, ctx.Logger); - } + ncaHolder.Validities[i] = nca.VerifySection(i, ctx.Logger); } } + } - if (ctx.Options.ListRomFs && nca.CanOpenSection(NcaSectionType.Data)) + if (ctx.Options.ListRomFs && nca.CanOpenSection(NcaSectionType.Data)) + { + IFileSystem romfs = OpenFileSystemByType(NcaSectionType.Data); + + foreach (DirectoryEntryEx entry in romfs.EnumerateEntries()) { - IFileSystem romfs = OpenFileSystemByType(NcaSectionType.Data); + ctx.Logger.LogMessage(entry.FullPath); + } + } - foreach (DirectoryEntryEx entry in romfs.EnumerateEntries()) - { - ctx.Logger.LogMessage(entry.FullPath); - } + if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ReadBench) + { + if (!nca.SectionExists(NcaSectionType.Data)) + { + ctx.Logger.LogMessage("NCA has no RomFS section"); + return; } - if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ReadBench) + if (ctx.Options.RomfsOut != null) { - if (!nca.SectionExists(NcaSectionType.Data)) + OpenStorageByType(NcaSectionType.Data).WriteAllBytes(ctx.Options.RomfsOut, ctx.Logger); + } + + if (ctx.Options.RomfsOutDir != null) + { + FileSystemClient fs = ctx.Horizon.Fs; + + using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystemByType(NcaSectionType.Data)); + using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RomfsOutDir)); + + fs.Register("rom".ToU8Span(), ref inputFs.Ref()); + fs.Register("output".ToU8Span(), ref outputFs.Ref()); + + fs.Impl.EnableFileSystemAccessorAccessLog("rom".ToU8Span()); + fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); + + FsUtils.CopyDirectoryWithProgress(fs, "rom:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); + + fs.Unmount("rom".ToU8Span()); + fs.Unmount("output".ToU8Span()); + } + + if (ctx.Options.ReadBench) + { + long bytesToRead = 1024L * 1024 * 1024 * 5; + IStorage storage = OpenStorageByType(NcaSectionType.Data); + + storage.GetSize(out long sectionSize).ThrowIfFailure(); + + var dest = new NullStorage(sectionSize); + + int iterations = (int)(bytesToRead / sectionSize) + 1; + ctx.Logger.LogMessage(iterations.ToString()); + + ctx.Logger.StartNewStopWatch(); + + for (int i = 0; i < iterations; i++) { - ctx.Logger.LogMessage("NCA has no RomFS section"); - return; - } - - if (ctx.Options.RomfsOut != null) - { - OpenStorageByType(NcaSectionType.Data).WriteAllBytes(ctx.Options.RomfsOut, ctx.Logger); - } - - if (ctx.Options.RomfsOutDir != null) - { - FileSystemClient fs = ctx.Horizon.Fs; - - using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystemByType(NcaSectionType.Data)); - using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RomfsOutDir)); - - fs.Register("rom".ToU8Span(), ref inputFs.Ref()); - fs.Register("output".ToU8Span(), ref outputFs.Ref()); - - fs.Impl.EnableFileSystemAccessorAccessLog("rom".ToU8Span()); - fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); - - FsUtils.CopyDirectoryWithProgress(fs, "rom:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); - - fs.Unmount("rom".ToU8Span()); - fs.Unmount("output".ToU8Span()); - } - - if (ctx.Options.ReadBench) - { - long bytesToRead = 1024L * 1024 * 1024 * 5; - IStorage storage = OpenStorageByType(NcaSectionType.Data); - - storage.GetSize(out long sectionSize).ThrowIfFailure(); - - var dest = new NullStorage(sectionSize); - - int iterations = (int)(bytesToRead / sectionSize) + 1; - ctx.Logger.LogMessage(iterations.ToString()); - - ctx.Logger.StartNewStopWatch(); - - for (int i = 0; i < iterations; i++) - { - storage.CopyTo(dest, ctx.Logger); - ctx.Logger.LogMessage(ctx.Logger.GetRateString()); - } - - ctx.Logger.PauseStopWatch(); + storage.CopyTo(dest, ctx.Logger); ctx.Logger.LogMessage(ctx.Logger.GetRateString()); } - } - if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) - { - if (nca.Header.ContentType != NcaContentType.Program) - { - ctx.Logger.LogMessage("NCA's content type is not \"Program\""); - return; - } - - if (!nca.SectionExists(NcaSectionType.Code)) - { - ctx.Logger.LogMessage("Could not find an ExeFS section"); - return; - } - - if (ctx.Options.ExefsOut != null) - { - OpenStorageByType(NcaSectionType.Code).WriteAllBytes(ctx.Options.ExefsOut, ctx.Logger); - } - - if (ctx.Options.ExefsOutDir != null) - { - FileSystemClient fs = ctx.Horizon.Fs; - - using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystemByType(NcaSectionType.Data)); - using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RomfsOutDir)); - - fs.Register("code".ToU8Span(), ref inputFs.Ref()); - fs.Register("output".ToU8Span(), ref outputFs.Ref()); - - fs.Impl.EnableFileSystemAccessorAccessLog("code".ToU8Span()); - fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); - - FsUtils.CopyDirectoryWithProgress(fs, "code:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); - - fs.Unmount("code".ToU8Span()); - fs.Unmount("output".ToU8Span()); - } - } - - if (ctx.Options.PlaintextOut != null) - { - nca.OpenDecryptedNca().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger); - } - - if (ctx.Options.CiphertextOut != null) - { - nca.OpenEncryptedNca().WriteAllBytes(ctx.Options.CiphertextOut, ctx.Logger); - } - - if (!ctx.Options.ReadBench) ctx.Logger.LogMessage(ncaHolder.Print()); - - IStorage OpenStorage(int index) - { - if (ctx.Options.Raw) - { - if (baseNca != null) return baseNca.OpenRawStorageWithPatch(nca, index); - - return nca.OpenRawStorage(index); - } - - if (baseNca != null) return baseNca.OpenStorageWithPatch(nca, index, ctx.Options.IntegrityLevel); - - return nca.OpenStorage(index, ctx.Options.IntegrityLevel); - } - - IFileSystem OpenFileSystem(int index) - { - if (baseNca != null) return baseNca.OpenFileSystemWithPatch(nca, index, ctx.Options.IntegrityLevel); - - return nca.OpenFileSystem(index, ctx.Options.IntegrityLevel); - } - - IStorage OpenStorageByType(NcaSectionType type) - { - return OpenStorage(Nca.GetSectionIndexFromType(type, nca.Header.ContentType)); - } - - IFileSystem OpenFileSystemByType(NcaSectionType type) - { - return OpenFileSystem(Nca.GetSectionIndexFromType(type, nca.Header.ContentType)); - } - } - } - - private static Validity VerifySignature2(this Nca nca) - { - if (nca.Header.ContentType != NcaContentType.Program) return Validity.Unchecked; - - IFileSystem pfs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid); - if (!pfs.FileExists("main.npdm")) return Validity.Unchecked; - - using var npdmFile = new UniqueRef<IFile>(); - pfs.OpenFile(ref npdmFile.Ref(), "main.npdm".ToU8String(), OpenMode.Read).ThrowIfFailure(); - var npdm = new NpdmBinary(npdmFile.Release().AsStream()); - - return nca.Header.VerifySignature2(npdm.AciD.Rsa2048Modulus); - } - - public static int GetMasterKeyRevisionFromKeyGeneration(int keyGeneration) - { - if (keyGeneration == 0) return 0; - - return keyGeneration - 1; - } - - private static string Print(this NcaHolder ncaHolder) - { - Nca nca = ncaHolder.Nca; - int masterKey = GetMasterKeyRevisionFromKeyGeneration(nca.Header.KeyGeneration); - - int colLen = 36; - var sb = new StringBuilder(); - sb.AppendLine(); - - sb.AppendLine("NCA:"); - PrintItem(sb, colLen, "Magic:", MagicToString(nca.Header.Magic)); - PrintItem(sb, colLen, $"Fixed-Key Signature{nca.VerifyHeaderSignature().GetValidityString()}:", nca.Header.Signature1.ToArray()); - PrintItem(sb, colLen, $"NPDM Signature{nca.VerifySignature2().GetValidityString()}:", nca.Header.Signature2.ToArray()); - PrintItem(sb, colLen, "Content Size:", $"0x{nca.Header.NcaSize:x12}"); - PrintItem(sb, colLen, "TitleID:", $"{nca.Header.TitleId:X16}"); - if (nca.CanOpenSection(NcaSectionType.Code)) - { - IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.None); - - using var file = new UniqueRef<IFile>(); - Result r = fs.OpenFile(ref file.Ref(), "/main.npdm".ToU8String(), OpenMode.Read); - if (r.IsSuccess()) - { - var npdm = new NpdmBinary(file.Release().AsStream(), null); - PrintItem(sb, colLen, "Title Name:", npdm.TitleName); + ctx.Logger.PauseStopWatch(); + ctx.Logger.LogMessage(ctx.Logger.GetRateString()); } } - PrintItem(sb, colLen, "SDK Version:", nca.Header.SdkVersion); - PrintItem(sb, colLen, "Distribution type:", nca.Header.DistributionType.Print()); - PrintItem(sb, colLen, "Content Type:", nca.Header.ContentType.Print()); - PrintItem(sb, colLen, "Master Key Revision:", $"{masterKey} ({Utilities.GetKeyRevisionSummary(masterKey)})"); - PrintItem(sb, colLen, "Encryption Type:", $"{(nca.Header.HasRightsId ? "Titlekey crypto" : "Standard crypto")}"); - - if (nca.Header.HasRightsId) + if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) { - PrintItem(sb, colLen, "Rights ID:", nca.Header.RightsId.ToArray()); + if (nca.Header.ContentType != NcaContentType.Program) + { + ctx.Logger.LogMessage("NCA's content type is not \"Program\""); + return; + } + + if (!nca.SectionExists(NcaSectionType.Code)) + { + ctx.Logger.LogMessage("Could not find an ExeFS section"); + return; + } + + if (ctx.Options.ExefsOut != null) + { + OpenStorageByType(NcaSectionType.Code).WriteAllBytes(ctx.Options.ExefsOut, ctx.Logger); + } + + if (ctx.Options.ExefsOutDir != null) + { + FileSystemClient fs = ctx.Horizon.Fs; + + using var inputFs = new UniqueRef<IFileSystem>(OpenFileSystemByType(NcaSectionType.Data)); + using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RomfsOutDir)); + + fs.Register("code".ToU8Span(), ref inputFs.Ref()); + fs.Register("output".ToU8Span(), ref outputFs.Ref()); + + fs.Impl.EnableFileSystemAccessorAccessLog("code".ToU8Span()); + fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); + + FsUtils.CopyDirectoryWithProgress(fs, "code:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); + + fs.Unmount("code".ToU8Span()); + fs.Unmount("output".ToU8Span()); + } + } + + if (ctx.Options.PlaintextOut != null) + { + nca.OpenDecryptedNca().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger); + } + + if (ctx.Options.CiphertextOut != null) + { + nca.OpenEncryptedNca().WriteAllBytes(ctx.Options.CiphertextOut, ctx.Logger); + } + + if (!ctx.Options.ReadBench) ctx.Logger.LogMessage(ncaHolder.Print()); + + IStorage OpenStorage(int index) + { + if (ctx.Options.Raw) + { + if (baseNca != null) return baseNca.OpenRawStorageWithPatch(nca, index); + + return nca.OpenRawStorage(index); + } + + if (baseNca != null) return baseNca.OpenStorageWithPatch(nca, index, ctx.Options.IntegrityLevel); + + return nca.OpenStorage(index, ctx.Options.IntegrityLevel); + } + + IFileSystem OpenFileSystem(int index) + { + if (baseNca != null) return baseNca.OpenFileSystemWithPatch(nca, index, ctx.Options.IntegrityLevel); + + return nca.OpenFileSystem(index, ctx.Options.IntegrityLevel); + } + + IStorage OpenStorageByType(NcaSectionType type) + { + return OpenStorage(Nca.GetSectionIndexFromType(type, nca.Header.ContentType)); + } + + IFileSystem OpenFileSystemByType(NcaSectionType type) + { + return OpenFileSystem(Nca.GetSectionIndexFromType(type, nca.Header.ContentType)); + } + } + } + + private static Validity VerifySignature2(this Nca nca) + { + if (nca.Header.ContentType != NcaContentType.Program) return Validity.Unchecked; + + IFileSystem pfs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid); + if (!pfs.FileExists("main.npdm")) return Validity.Unchecked; + + using var npdmFile = new UniqueRef<IFile>(); + pfs.OpenFile(ref npdmFile.Ref(), "main.npdm".ToU8String(), OpenMode.Read).ThrowIfFailure(); + var npdm = new NpdmBinary(npdmFile.Release().AsStream()); + + return nca.Header.VerifySignature2(npdm.AciD.Rsa2048Modulus); + } + + public static int GetMasterKeyRevisionFromKeyGeneration(int keyGeneration) + { + if (keyGeneration == 0) return 0; + + return keyGeneration - 1; + } + + private static string Print(this NcaHolder ncaHolder) + { + Nca nca = ncaHolder.Nca; + int masterKey = GetMasterKeyRevisionFromKeyGeneration(nca.Header.KeyGeneration); + + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("NCA:"); + PrintItem(sb, colLen, "Magic:", MagicToString(nca.Header.Magic)); + PrintItem(sb, colLen, $"Fixed-Key Signature{nca.VerifyHeaderSignature().GetValidityString()}:", nca.Header.Signature1.ToArray()); + PrintItem(sb, colLen, $"NPDM Signature{nca.VerifySignature2().GetValidityString()}:", nca.Header.Signature2.ToArray()); + PrintItem(sb, colLen, "Content Size:", $"0x{nca.Header.NcaSize:x12}"); + PrintItem(sb, colLen, "TitleID:", $"{nca.Header.TitleId:X16}"); + if (nca.CanOpenSection(NcaSectionType.Code)) + { + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.None); + + using var file = new UniqueRef<IFile>(); + Result r = fs.OpenFile(ref file.Ref(), "/main.npdm".ToU8String(), OpenMode.Read); + if (r.IsSuccess()) + { + var npdm = new NpdmBinary(file.Release().AsStream(), null); + PrintItem(sb, colLen, "Title Name:", npdm.TitleName); + } + } + + PrintItem(sb, colLen, "SDK Version:", nca.Header.SdkVersion); + PrintItem(sb, colLen, "Distribution type:", nca.Header.DistributionType.Print()); + PrintItem(sb, colLen, "Content Type:", nca.Header.ContentType.Print()); + PrintItem(sb, colLen, "Master Key Revision:", $"{masterKey} ({Utilities.GetKeyRevisionSummary(masterKey)})"); + PrintItem(sb, colLen, "Encryption Type:", $"{(nca.Header.HasRightsId ? "Titlekey crypto" : "Standard crypto")}"); + + if (nca.Header.HasRightsId) + { + PrintItem(sb, colLen, "Rights ID:", nca.Header.RightsId.ToArray()); + } + else + { + PrintKeyArea(); + } + + PrintSections(); + + return sb.ToString(); + + void PrintKeyArea() + { + NcaVersion version = nca.Header.FormatVersion; + + if (version == NcaVersion.Nca0RsaOaep) + { + sb.AppendLine("Key Area (Encrypted):"); + PrintItem(sb, colLen, "Key (RSA-OAEP Encrypted):", nca.Header.GetKeyArea().ToArray()); + + sb.AppendLine("Key Area (Decrypted):"); + for (int i = 0; i < 2; i++) + { + PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i)); + } + } + else if (version == NcaVersion.Nca0FixedKey) + { + sb.AppendLine("Key Area:"); + for (int i = 0; i < 2; i++) + { + PrintItem(sb, colLen, $" Key {i}:", nca.Header.GetEncryptedKey(i).ToArray()); + } } else { - PrintKeyArea(); - } + int keyCount = version == NcaVersion.Nca0 ? 2 : 4; - PrintSections(); - - return sb.ToString(); - - void PrintKeyArea() - { - NcaVersion version = nca.Header.FormatVersion; - - if (version == NcaVersion.Nca0RsaOaep) + PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex); + sb.AppendLine("Key Area (Encrypted):"); + for (int i = 0; i < keyCount; i++) { - sb.AppendLine("Key Area (Encrypted):"); - PrintItem(sb, colLen, "Key (RSA-OAEP Encrypted):", nca.Header.GetKeyArea().ToArray()); - - sb.AppendLine("Key Area (Decrypted):"); - for (int i = 0; i < 2; i++) - { - PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i)); - } + PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray()); } - else if (version == NcaVersion.Nca0FixedKey) + + sb.AppendLine("Key Area (Decrypted):"); + for (int i = 0; i < keyCount; i++) { - sb.AppendLine("Key Area:"); - for (int i = 0; i < 2; i++) - { - PrintItem(sb, colLen, $" Key {i}:", nca.Header.GetEncryptedKey(i).ToArray()); - } + PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i)); } - else - { - int keyCount = version == NcaVersion.Nca0 ? 2 : 4; - - PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex); - sb.AppendLine("Key Area (Encrypted):"); - for (int i = 0; i < keyCount; i++) - { - PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray()); - } - - sb.AppendLine("Key Area (Decrypted):"); - for (int i = 0; i < keyCount; i++) - { - PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i)); - } - } - } - - void PrintSections() - { - sb.AppendLine("Sections:"); - - for (int i = 0; i < 4; i++) - { - if (!nca.Header.IsSectionEnabled(i)) continue; - - NcaFsHeader sectHeader = nca.GetFsHeader(i); - bool isExefs = nca.Header.ContentType == NcaContentType.Program && i == 0; - - sb.AppendLine($" Section {i}:"); - PrintItem(sb, colLen, " Offset:", $"0x{nca.Header.GetSectionStartOffset(i):x12}"); - PrintItem(sb, colLen, " Size:", $"0x{nca.Header.GetSectionSize(i):x12}"); - PrintItem(sb, colLen, " Partition Type:", GetPartitionType(sectHeader, isExefs, nca.Header.IsNca0())); - PrintItem(sb, colLen, " Section CTR:", $"{sectHeader.Counter:x16}"); - PrintItem(sb, colLen, " Section Validity:", $"{ncaHolder.Validities[i].Print()}"); - - switch (sectHeader.HashType) - { - case NcaHashType.Sha256: - PrintSha256Hash(sectHeader, i); - break; - case NcaHashType.Ivfc: - Validity masterHashValidity = nca.ValidateSectionMasterHash(i); - - PrintIvfcHashNew(sb, colLen, 8, sectHeader.GetIntegrityInfoIvfc(), IntegrityStorageType.RomFs, masterHashValidity); - break; - default: - sb.AppendLine(" Unknown/invalid superblock!"); - break; - } - } - } - - static string GetPartitionType(NcaFsHeader fsHeader, bool isExefs, bool isNca0) - { - if (isExefs) return "ExeFS"; - if (isNca0 && fsHeader.FormatType == NcaFormatType.Romfs) return "NCA0 RomFS"; - - return fsHeader.FormatType.Print() + (fsHeader.IsPatchSection() ? " patch" : ""); - } - - void PrintSha256Hash(NcaFsHeader sect, int index) - { - NcaFsIntegrityInfoSha256 hashInfo = sect.GetIntegrityInfoSha256(); - - PrintItem(sb, colLen, $" Master Hash{nca.ValidateSectionMasterHash(index).GetValidityString()}:", hashInfo.MasterHash.ToArray()); - sb.AppendLine(" Hash Table:"); - - PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.GetLevelOffset(0):x12}"); - PrintItem(sb, colLen, " Size:", $"0x{hashInfo.GetLevelSize(0):x12}"); - PrintItem(sb, colLen, " Block Size:", $"0x{hashInfo.BlockSize:x}"); - PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.GetLevelOffset(1):x12}"); - PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.GetLevelSize(1):x12}"); } } - private class NcaHolder + void PrintSections() { - public Nca Nca; - public Validity[] Validities = new Validity[4]; + sb.AppendLine("Sections:"); + + for (int i = 0; i < 4; i++) + { + if (!nca.Header.IsSectionEnabled(i)) continue; + + NcaFsHeader sectHeader = nca.GetFsHeader(i); + bool isExefs = nca.Header.ContentType == NcaContentType.Program && i == 0; + + sb.AppendLine($" Section {i}:"); + PrintItem(sb, colLen, " Offset:", $"0x{nca.Header.GetSectionStartOffset(i):x12}"); + PrintItem(sb, colLen, " Size:", $"0x{nca.Header.GetSectionSize(i):x12}"); + PrintItem(sb, colLen, " Partition Type:", GetPartitionType(sectHeader, isExefs, nca.Header.IsNca0())); + PrintItem(sb, colLen, " Section CTR:", $"{sectHeader.Counter:x16}"); + PrintItem(sb, colLen, " Section Validity:", $"{ncaHolder.Validities[i].Print()}"); + + switch (sectHeader.HashType) + { + case NcaHashType.Sha256: + PrintSha256Hash(sectHeader, i); + break; + case NcaHashType.Ivfc: + Validity masterHashValidity = nca.ValidateSectionMasterHash(i); + + PrintIvfcHashNew(sb, colLen, 8, sectHeader.GetIntegrityInfoIvfc(), IntegrityStorageType.RomFs, masterHashValidity); + break; + default: + sb.AppendLine(" Unknown/invalid superblock!"); + break; + } + } + } + + static string GetPartitionType(NcaFsHeader fsHeader, bool isExefs, bool isNca0) + { + if (isExefs) return "ExeFS"; + if (isNca0 && fsHeader.FormatType == NcaFormatType.Romfs) return "NCA0 RomFS"; + + return fsHeader.FormatType.Print() + (fsHeader.IsPatchSection() ? " patch" : ""); + } + + void PrintSha256Hash(NcaFsHeader sect, int index) + { + NcaFsIntegrityInfoSha256 hashInfo = sect.GetIntegrityInfoSha256(); + + PrintItem(sb, colLen, $" Master Hash{nca.ValidateSectionMasterHash(index).GetValidityString()}:", hashInfo.MasterHash.ToArray()); + sb.AppendLine(" Hash Table:"); + + PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.GetLevelOffset(0):x12}"); + PrintItem(sb, colLen, " Size:", $"0x{hashInfo.GetLevelSize(0):x12}"); + PrintItem(sb, colLen, " Block Size:", $"0x{hashInfo.BlockSize:x}"); + PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.GetLevelOffset(1):x12}"); + PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.GetLevelSize(1):x12}"); } } + + private class NcaHolder + { + public Nca Nca; + public Validity[] Validities = new Validity[4]; + } } diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index b1cb7515..96d7177b 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -9,173 +9,172 @@ using LibHac.FsSystem; using static hactoolnet.Print; using Path = System.IO.Path; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessPackage { - internal static class ProcessPackage + public static void ProcessPk11(Context ctx) { - public static void ProcessPk11(Context ctx) + using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); + + var package1 = new LibHac.Boot.Package1(); + package1.Initialize(ctx.KeySet, in file).ThrowIfFailure(); + + ctx.Logger.LogMessage(package1.Print()); + + string outDir = ctx.Options.OutDir; + + if (package1.IsDecrypted && outDir != null) { - using var file = new SharedRef<IStorage>(new LocalStorage(ctx.Options.InFile, FileAccess.Read)); + Directory.CreateDirectory(outDir); - var package1 = new LibHac.Boot.Package1(); - package1.Initialize(ctx.KeySet, in file).ThrowIfFailure(); + IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage(); - ctx.Logger.LogMessage(package1.Print()); - - string outDir = ctx.Options.OutDir; - - if (package1.IsDecrypted && outDir != null) - { - Directory.CreateDirectory(outDir); - - IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage(); - - WriteFile(decryptedStorage, "Decrypted.bin"); - WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin"); - WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin"); - WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin"); - - if (package1.IsMariko) - { - WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin"); - - var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(), - package1.MarikoOemHeader.Size); - - WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin"); - } - } - - void WriteFile(IStorage storage, string filename) - { - string path = Path.Combine(outDir, filename); - ctx.Logger.LogMessage($"Writing {path}..."); - storage.WriteAllBytes(path, ctx.Logger); - } - } - - private static string Print(this LibHac.Boot.Package1 package1) - { - int colLen = 36; - var sb = new StringBuilder(); - sb.AppendLine(); + WriteFile(decryptedStorage, "Decrypted.bin"); + WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin"); + WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin"); + WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin"); if (package1.IsMariko) { - sb.AppendLine("Mariko OEM Header:"); - PrintItem(sb, colLen, " Signature:", package1.MarikoOemHeader.RsaSig.ToArray()); - PrintItem(sb, colLen, " Random Salt:", package1.MarikoOemHeader.Salt.ToArray()); - PrintItem(sb, colLen, " OEM Bootloader Hash:", package1.MarikoOemHeader.Hash.ToArray()); - PrintItem(sb, colLen, " OEM Bootloader Version:", $"{package1.MarikoOemHeader.Version:x2}"); - PrintItem(sb, colLen, " OEM Bootloader Size:", $"{package1.MarikoOemHeader.Size:x8}"); - PrintItem(sb, colLen, " OEM Bootloader Load Address:", $"{package1.MarikoOemHeader.LoadAddress:x8}"); - PrintItem(sb, colLen, " OEM Bootloader Entrypoint:", $"{package1.MarikoOemHeader.EntryPoint:x8}"); - } + WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin"); - sb.AppendLine("Package1 Metadata:"); - PrintItem(sb, colLen, " Build Date:", package1.MetaData.BuildDate.ToString()); - PrintItem(sb, colLen, " Package1ldr Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.LoaderHash).ToArray()); - PrintItem(sb, colLen, " Secure Monitor Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.SecureMonitorHash).ToArray()); - PrintItem(sb, colLen, " NX Bootloader Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.BootloaderHash).ToArray()); - PrintItem(sb, colLen, " Version:", $"{package1.MetaData.Version:x2}"); + var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(), + package1.MarikoOemHeader.Size); - if (!package1.IsMariko && package1.IsModern) - { - PrintItem(sb, colLen, " PK11 MAC:", package1.Pk11Mac); - } - - if (package1.IsDecrypted) - { - sb.AppendLine("PK11:"); - - if (!package1.IsMariko) - { - PrintItem(sb, colLen, " Key Revision:", $"{package1.KeyRevision:x2} ({Utilities.GetKeyRevisionSummary(package1.KeyRevision)})"); - } - - PrintItem(sb, colLen, " PK11 Size:", $"{package1.Pk11Size:x8}"); - PrintItem(sb, colLen, " Warmboot.bin Size:", $"{package1.GetSectionSize(Package1Section.WarmBoot):x8}"); - PrintItem(sb, colLen, " NX_Bootloader.bin Size:", $"{package1.GetSectionSize(Package1Section.Bootloader):x8}"); - PrintItem(sb, colLen, " Secure_Monitor.bin Size:", $"{package1.GetSectionSize(Package1Section.SecureMonitor):x8}"); - } - - return sb.ToString(); - } - - public static void ProcessPk21(Context ctx) - { - using var file = new SharedRef<IStorage>(new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)); - - using var package2 = new Package2StorageReader(); - package2.Initialize(ctx.KeySet, in file).ThrowIfFailure(); - - ctx.Logger.LogMessage(package2.Print()); - - string outDir = ctx.Options.OutDir; - string iniDir = ctx.Options.Ini1OutDir; - - if (iniDir == null && ctx.Options.ExtractIni1) - { - iniDir = Path.Combine(outDir, "INI1"); - } - - if (outDir != null) - { - Directory.CreateDirectory(outDir); - - using var kernelStorage = new UniqueRef<IStorage>(); - package2.OpenPayload(ref kernelStorage.Ref(), 0).ThrowIfFailure(); - kernelStorage.Get.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger); - - using var ini1Storage = new UniqueRef<IStorage>(); - package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure(); - ini1Storage.Get.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger); - - using var decPackageStorage = new UniqueRef<IStorage>(); - package2.OpenDecryptedPackage(ref decPackageStorage.Ref()).ThrowIfFailure(); - decPackageStorage.Get.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); - } - - if (iniDir != null) - { - Directory.CreateDirectory(iniDir); - - using var ini1Storage = new UniqueRef<IStorage>(); - package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure(); - - using SharedRef<IStorage> sharedIni1Storage = SharedRef<IStorage>.Create(ref ini1Storage.Ref()); - ProcessKip.ExtractIni1(in sharedIni1Storage, iniDir); + WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin"); } } - private static readonly string[] Package2SectionNames = { "Kernel", "INI1", "Empty" }; - - private static string Print(this Package2StorageReader package2) + void WriteFile(IStorage storage, string filename) { - Result rc = package2.VerifySignature(); - - Validity signatureValidity = rc.IsSuccess() ? Validity.Valid : Validity.Invalid; - - int colLen = 36; - var sb = new StringBuilder(); - sb.AppendLine(); - - sb.AppendLine("PK21:"); - PrintItem(sb, colLen, $"Signature{signatureValidity.GetValidityString()}:", package2.Header.Signature.ToArray()); - PrintItem(sb, colLen, "Header Version:", $"{package2.Header.Meta.KeyGeneration:x2}"); - - for (int i = 0; i < 3; i++) - { - string name = package2.Header.Meta.PayloadSizes[i] != 0 ? Package2SectionNames[i] : "Empty"; - sb.AppendLine($"Section {i} ({name}):"); - - PrintItem(sb, colLen, " Hash:", package2.Header.Meta.PayloadHashes[i]); - PrintItem(sb, colLen, " CTR:", package2.Header.Meta.PayloadIvs[i]); - PrintItem(sb, colLen, " Load Address:", $"{package2.Header.Meta.PayloadOffsets[i] + 0x80000000:x8}"); - PrintItem(sb, colLen, " Size:", $"{package2.Header.Meta.PayloadSizes[i]:x8}"); - } - - return sb.ToString(); + string path = Path.Combine(outDir, filename); + ctx.Logger.LogMessage($"Writing {path}..."); + storage.WriteAllBytes(path, ctx.Logger); } } + + private static string Print(this LibHac.Boot.Package1 package1) + { + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + if (package1.IsMariko) + { + sb.AppendLine("Mariko OEM Header:"); + PrintItem(sb, colLen, " Signature:", package1.MarikoOemHeader.RsaSig.ToArray()); + PrintItem(sb, colLen, " Random Salt:", package1.MarikoOemHeader.Salt.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Hash:", package1.MarikoOemHeader.Hash.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Version:", $"{package1.MarikoOemHeader.Version:x2}"); + PrintItem(sb, colLen, " OEM Bootloader Size:", $"{package1.MarikoOemHeader.Size:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Load Address:", $"{package1.MarikoOemHeader.LoadAddress:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Entrypoint:", $"{package1.MarikoOemHeader.EntryPoint:x8}"); + } + + sb.AppendLine("Package1 Metadata:"); + PrintItem(sb, colLen, " Build Date:", package1.MetaData.BuildDate.ToString()); + PrintItem(sb, colLen, " Package1ldr Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.LoaderHash).ToArray()); + PrintItem(sb, colLen, " Secure Monitor Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.SecureMonitorHash).ToArray()); + PrintItem(sb, colLen, " NX Bootloader Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.BootloaderHash).ToArray()); + PrintItem(sb, colLen, " Version:", $"{package1.MetaData.Version:x2}"); + + if (!package1.IsMariko && package1.IsModern) + { + PrintItem(sb, colLen, " PK11 MAC:", package1.Pk11Mac); + } + + if (package1.IsDecrypted) + { + sb.AppendLine("PK11:"); + + if (!package1.IsMariko) + { + PrintItem(sb, colLen, " Key Revision:", $"{package1.KeyRevision:x2} ({Utilities.GetKeyRevisionSummary(package1.KeyRevision)})"); + } + + PrintItem(sb, colLen, " PK11 Size:", $"{package1.Pk11Size:x8}"); + PrintItem(sb, colLen, " Warmboot.bin Size:", $"{package1.GetSectionSize(Package1Section.WarmBoot):x8}"); + PrintItem(sb, colLen, " NX_Bootloader.bin Size:", $"{package1.GetSectionSize(Package1Section.Bootloader):x8}"); + PrintItem(sb, colLen, " Secure_Monitor.bin Size:", $"{package1.GetSectionSize(Package1Section.SecureMonitor):x8}"); + } + + return sb.ToString(); + } + + public static void ProcessPk21(Context ctx) + { + using var file = new SharedRef<IStorage>(new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)); + + using var package2 = new Package2StorageReader(); + package2.Initialize(ctx.KeySet, in file).ThrowIfFailure(); + + ctx.Logger.LogMessage(package2.Print()); + + string outDir = ctx.Options.OutDir; + string iniDir = ctx.Options.Ini1OutDir; + + if (iniDir == null && ctx.Options.ExtractIni1) + { + iniDir = Path.Combine(outDir, "INI1"); + } + + if (outDir != null) + { + Directory.CreateDirectory(outDir); + + using var kernelStorage = new UniqueRef<IStorage>(); + package2.OpenPayload(ref kernelStorage.Ref(), 0).ThrowIfFailure(); + kernelStorage.Get.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger); + + using var ini1Storage = new UniqueRef<IStorage>(); + package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure(); + ini1Storage.Get.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger); + + using var decPackageStorage = new UniqueRef<IStorage>(); + package2.OpenDecryptedPackage(ref decPackageStorage.Ref()).ThrowIfFailure(); + decPackageStorage.Get.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); + } + + if (iniDir != null) + { + Directory.CreateDirectory(iniDir); + + using var ini1Storage = new UniqueRef<IStorage>(); + package2.OpenIni(ref ini1Storage.Ref()).ThrowIfFailure(); + + using SharedRef<IStorage> sharedIni1Storage = SharedRef<IStorage>.Create(ref ini1Storage.Ref()); + ProcessKip.ExtractIni1(in sharedIni1Storage, iniDir); + } + } + + private static readonly string[] Package2SectionNames = { "Kernel", "INI1", "Empty" }; + + private static string Print(this Package2StorageReader package2) + { + Result rc = package2.VerifySignature(); + + Validity signatureValidity = rc.IsSuccess() ? Validity.Valid : Validity.Invalid; + + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("PK21:"); + PrintItem(sb, colLen, $"Signature{signatureValidity.GetValidityString()}:", package2.Header.Signature.ToArray()); + PrintItem(sb, colLen, "Header Version:", $"{package2.Header.Meta.KeyGeneration:x2}"); + + for (int i = 0; i < 3; i++) + { + string name = package2.Header.Meta.PayloadSizes[i] != 0 ? Package2SectionNames[i] : "Empty"; + sb.AppendLine($"Section {i} ({name}):"); + + PrintItem(sb, colLen, " Hash:", package2.Header.Meta.PayloadHashes[i]); + PrintItem(sb, colLen, " CTR:", package2.Header.Meta.PayloadIvs[i]); + PrintItem(sb, colLen, " Load Address:", $"{package2.Header.Meta.PayloadOffsets[i] + 0x80000000:x8}"); + PrintItem(sb, colLen, " Size:", $"{package2.Header.Meta.PayloadSizes[i]:x8}"); + } + + return sb.ToString(); + } } diff --git a/src/hactoolnet/ProcessPfs.cs b/src/hactoolnet/ProcessPfs.cs index f2f474ad..5294f284 100644 --- a/src/hactoolnet/ProcessPfs.cs +++ b/src/hactoolnet/ProcessPfs.cs @@ -7,98 +7,97 @@ using LibHac.FsSystem; using LibHac.Util; using static hactoolnet.Print; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessPfs { - internal static class ProcessPfs + public static void Process(Context ctx) { - public static void Process(Context ctx) + using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) + var pfs = new PartitionFileSystem(file); + ctx.Logger.LogMessage(pfs.Print()); + + if (ctx.Options.OutDir != null) { - var pfs = new PartitionFileSystem(file); - ctx.Logger.LogMessage(pfs.Print()); - - if (ctx.Options.OutDir != null) - { - pfs.Extract(ctx.Options.OutDir, ctx.Logger); - } - } - } - - private static string Print(this PartitionFileSystem pfs) - { - const int colLen = 36; - const int fileNameLen = 39; - - var sb = new StringBuilder(); - sb.AppendLine(); - - sb.AppendLine("PFS0:"); - - PrintItem(sb, colLen, "Magic:", pfs.Header.Magic); - PrintItem(sb, colLen, "Number of files:", pfs.Header.NumFiles); - - for (int i = 0; i < pfs.Files.Length; i++) - { - PartitionFileEntry file = pfs.Files[i]; - - string label = i == 0 ? "Files:" : ""; - string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}"; - string data = $"pfs0:/{file.Name}".PadRight(fileNameLen) + offsets; - - PrintItem(sb, colLen, label, data); - } - - return sb.ToString(); - } - - public static void CreateNsp(Context ctx, SwitchFs switchFs) - { - ulong id = ctx.Options.TitleId; - if (id == 0) - { - ctx.Logger.LogMessage("Title ID must be specified to save title"); - return; - } - - if (!switchFs.Titles.TryGetValue(id, out Title title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - var builder = new PartitionFileSystemBuilder(); - - foreach (SwitchFsNca nca in title.Ncas) - { - builder.AddFile(nca.Filename, nca.Nca.BaseStorage.AsFile(OpenMode.Read)); - } - - var ticket = new Ticket - { - SignatureType = TicketSigType.Rsa2048Sha256, - Signature = new byte[0x200], - Issuer = "Root-CA00000003-XS00000020", - FormatVersion = 2, - RightsId = title.MainNca.Nca.Header.RightsId.ToArray(), - TitleKeyBlock = title.MainNca.Nca.GetDecryptedTitleKey(), - CryptoType = title.MainNca.Nca.Header.KeyGeneration, - SectHeaderOffset = 0x2C0 - }; - byte[] ticketBytes = ticket.GetBytes(); - builder.AddFile($"{ticket.RightsId.ToHexString()}.tik", new MemoryStream(ticketBytes).AsIFile(OpenMode.ReadWrite)); - - var thisAssembly = Assembly.GetExecutingAssembly(); - Stream cert = thisAssembly.GetManifestResourceStream("hactoolnet.CA00000003_XS00000020"); - builder.AddFile($"{ticket.RightsId.ToHexString()}.cert", cert.AsIFile(OpenMode.Read)); - - using (var outStream = new FileStream(ctx.Options.NspOut, FileMode.Create, FileAccess.ReadWrite)) - { - IStorage builtPfs = builder.Build(PartitionFileSystemType.Standard); - builtPfs.GetSize(out long pfsSize).ThrowIfFailure(); - - builtPfs.CopyToStream(outStream, pfsSize, ctx.Logger); + pfs.Extract(ctx.Options.OutDir, ctx.Logger); } } } + + private static string Print(this PartitionFileSystem pfs) + { + const int colLen = 36; + const int fileNameLen = 39; + + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("PFS0:"); + + PrintItem(sb, colLen, "Magic:", pfs.Header.Magic); + PrintItem(sb, colLen, "Number of files:", pfs.Header.NumFiles); + + for (int i = 0; i < pfs.Files.Length; i++) + { + PartitionFileEntry file = pfs.Files[i]; + + string label = i == 0 ? "Files:" : ""; + string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}"; + string data = $"pfs0:/{file.Name}".PadRight(fileNameLen) + offsets; + + PrintItem(sb, colLen, label, data); + } + + return sb.ToString(); + } + + public static void CreateNsp(Context ctx, SwitchFs switchFs) + { + ulong id = ctx.Options.TitleId; + if (id == 0) + { + ctx.Logger.LogMessage("Title ID must be specified to save title"); + return; + } + + if (!switchFs.Titles.TryGetValue(id, out Title title)) + { + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; + } + + var builder = new PartitionFileSystemBuilder(); + + foreach (SwitchFsNca nca in title.Ncas) + { + builder.AddFile(nca.Filename, nca.Nca.BaseStorage.AsFile(OpenMode.Read)); + } + + var ticket = new Ticket + { + SignatureType = TicketSigType.Rsa2048Sha256, + Signature = new byte[0x200], + Issuer = "Root-CA00000003-XS00000020", + FormatVersion = 2, + RightsId = title.MainNca.Nca.Header.RightsId.ToArray(), + TitleKeyBlock = title.MainNca.Nca.GetDecryptedTitleKey(), + CryptoType = title.MainNca.Nca.Header.KeyGeneration, + SectHeaderOffset = 0x2C0 + }; + byte[] ticketBytes = ticket.GetBytes(); + builder.AddFile($"{ticket.RightsId.ToHexString()}.tik", new MemoryStream(ticketBytes).AsIFile(OpenMode.ReadWrite)); + + var thisAssembly = Assembly.GetExecutingAssembly(); + Stream cert = thisAssembly.GetManifestResourceStream("hactoolnet.CA00000003_XS00000020"); + builder.AddFile($"{ticket.RightsId.ToHexString()}.cert", cert.AsIFile(OpenMode.Read)); + + using (var outStream = new FileStream(ctx.Options.NspOut, FileMode.Create, FileAccess.ReadWrite)) + { + IStorage builtPfs = builder.Build(PartitionFileSystemType.Standard); + builtPfs.GetSize(out long pfsSize).ThrowIfFailure(); + + builtPfs.CopyToStream(outStream, pfsSize, ctx.Logger); + } + } } diff --git a/src/hactoolnet/ProcessRomfs.cs b/src/hactoolnet/ProcessRomfs.cs index 093816ee..88e86962 100644 --- a/src/hactoolnet/ProcessRomfs.cs +++ b/src/hactoolnet/ProcessRomfs.cs @@ -3,44 +3,43 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.RomFs; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessRomfs { - internal static class ProcessRomfs + public static void Process(Context ctx) { - public static void Process(Context ctx) + using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) + Process(ctx, file); + } + } + + public static void Process(Context ctx, IStorage romfsStorage) + { + var romfs = new RomFsFileSystem(romfsStorage); + + if (ctx.Options.ListRomFs) + { + foreach (DirectoryEntryEx entry in romfs.EnumerateEntries()) { - Process(ctx, file); + ctx.Logger.LogMessage(entry.FullPath); } } - public static void Process(Context ctx, IStorage romfsStorage) + if (ctx.Options.RomfsOut != null) { - var romfs = new RomFsFileSystem(romfsStorage); + romfsStorage.GetSize(out long romFsSize).ThrowIfFailure(); - if (ctx.Options.ListRomFs) + using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite)) { - foreach (DirectoryEntryEx entry in romfs.EnumerateEntries()) - { - ctx.Logger.LogMessage(entry.FullPath); - } + romfsStorage.CopyToStream(outFile, romFsSize, ctx.Logger); } + } - if (ctx.Options.RomfsOut != null) - { - romfsStorage.GetSize(out long romFsSize).ThrowIfFailure(); - - using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite)) - { - romfsStorage.CopyToStream(outFile, romFsSize, ctx.Logger); - } - } - - if (ctx.Options.RomfsOutDir != null) - { - romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); - } + if (ctx.Options.RomfsOutDir != null) + { + romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); } } } diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index 8e14b702..7cd19efd 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -14,123 +14,100 @@ using LibHac.FsSystem.Save; using static hactoolnet.Print; using Path = System.IO.Path; -namespace hactoolnet -{ - internal static class ProcessSave - { - public static void Process(Context ctx) - { - var accessNeeded = FileAccess.Read; +namespace hactoolnet; - if (ctx.Options.SignSave || ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null || - ctx.Options.RepackSource != null || ctx.Options.TrimSave) +internal static class ProcessSave +{ + public static void Process(Context ctx) + { + var accessNeeded = FileAccess.Read; + + if (ctx.Options.SignSave || ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null || + ctx.Options.RepackSource != null || ctx.Options.TrimSave) + { + accessNeeded = FileAccess.ReadWrite; + } + + using (var file = new LocalStorage(ctx.Options.InFile, accessNeeded)) + { + bool signNeeded = ctx.Options.SignSave; + + var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true); + FileSystemClient fs = ctx.Horizon.Fs; + + using var saveUnique = new UniqueRef<IFileSystem>(save); + fs.Register("save".ToU8Span(), ref saveUnique.Ref()); + fs.Impl.EnableFileSystemAccessorAccessLog("save".ToU8Span()); + + if (ctx.Options.Validate) { - accessNeeded = FileAccess.ReadWrite; + save.Verify(ctx.Logger); } - using (var file = new LocalStorage(ctx.Options.InFile, accessNeeded)) + if (ctx.Options.OutDir != null) { - bool signNeeded = ctx.Options.SignSave; + using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.OutDir)); + fs.Register("output".ToU8Span(), ref outputFs.Ref()); + fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); - var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true); - FileSystemClient fs = ctx.Horizon.Fs; + FsUtils.CopyDirectoryWithProgress(fs, "save:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); - using var saveUnique = new UniqueRef<IFileSystem>(save); - fs.Register("save".ToU8Span(), ref saveUnique.Ref()); - fs.Impl.EnableFileSystemAccessorAccessLog("save".ToU8Span()); + fs.Unmount("output".ToU8Span()); + } - if (ctx.Options.Validate) + if (ctx.Options.DebugOutDir != null) + { + string dir = ctx.Options.DebugOutDir; + + ExportSaveDebug(ctx, dir, save); + } + + try + { + if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) { - save.Verify(ctx.Logger); - } + string destFilename = ctx.Options.ReplaceFileDest; + if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename; - if (ctx.Options.OutDir != null) - { - using var outputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.OutDir)); - fs.Register("output".ToU8Span(), ref outputFs.Ref()); - fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span()); + using var inFile = new UniqueRef<IFile>(new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read)); - FsUtils.CopyDirectoryWithProgress(fs, "save:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); + using var outFile = new UniqueRef<IFile>(); + save.OpenFile(ref outFile.Ref(), destFilename.ToU8String(), OpenMode.ReadWrite).ThrowIfFailure(); - fs.Unmount("output".ToU8Span()); - } + inFile.Get.GetSize(out long inFileSize).ThrowIfFailure(); + outFile.Get.GetSize(out long outFileSize).ThrowIfFailure(); - if (ctx.Options.DebugOutDir != null) - { - string dir = ctx.Options.DebugOutDir; - - ExportSaveDebug(ctx, dir, save); - } - - try - { - if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) + if (inFileSize != outFileSize) { - string destFilename = ctx.Options.ReplaceFileDest; - if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename; - - using var inFile = new UniqueRef<IFile>(new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read)); - - using var outFile = new UniqueRef<IFile>(); - save.OpenFile(ref outFile.Ref(), destFilename.ToU8String(), OpenMode.ReadWrite).ThrowIfFailure(); - - inFile.Get.GetSize(out long inFileSize).ThrowIfFailure(); - outFile.Get.GetSize(out long outFileSize).ThrowIfFailure(); - - if (inFileSize != outFileSize) - { - outFile.Get.SetSize(inFileSize).ThrowIfFailure(); - } - - inFile.Get.CopyTo(outFile.Get, ctx.Logger); - - ctx.Logger.LogMessage($"Replaced file {destFilename}"); - - signNeeded = true; + outFile.Get.SetSize(inFileSize).ThrowIfFailure(); } - if (ctx.Options.RepackSource != null) - { - using var inputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RepackSource)); - fs.Register("input".ToU8Span(), ref inputFs.Ref()); - fs.Impl.EnableFileSystemAccessorAccessLog("input".ToU8Span()); + inFile.Get.CopyTo(outFile.Get, ctx.Logger); - fs.CleanDirectoryRecursively("save:/".ToU8Span()); - fs.Commit("save".ToU8Span()); + ctx.Logger.LogMessage($"Replaced file {destFilename}"); - FsUtils.CopyDirectoryWithProgress(fs, "input:/".ToU8Span(), "save:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); - - fs.Commit("save".ToU8Span()); - fs.Unmount("input".ToU8Span()); - - signNeeded = true; - } - } - finally - { - if (signNeeded) - { - if (save.Commit(ctx.KeySet).IsSuccess()) - { - ctx.Logger.LogMessage( - $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); - } - else - { - ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); - } - - signNeeded = false; - } - } - - if (ctx.Options.TrimSave) - { - save.FsTrim(); signNeeded = true; - ctx.Logger.LogMessage("Trimmed save file"); } + if (ctx.Options.RepackSource != null) + { + using var inputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(ctx.Options.RepackSource)); + fs.Register("input".ToU8Span(), ref inputFs.Ref()); + fs.Impl.EnableFileSystemAccessorAccessLog("input".ToU8Span()); + + fs.CleanDirectoryRecursively("save:/".ToU8Span()); + fs.Commit("save".ToU8Span()); + + FsUtils.CopyDirectoryWithProgress(fs, "input:/".ToU8Span(), "save:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); + + fs.Commit("save".ToU8Span()); + fs.Unmount("input".ToU8Span()); + + signNeeded = true; + } + } + finally + { if (signNeeded) { if (save.Commit(ctx.KeySet).IsSuccess()) @@ -143,215 +120,237 @@ namespace hactoolnet ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } - fs.Unmount("save".ToU8Span()); - return; + signNeeded = false; } + } - if (ctx.Options.ListFiles) + if (ctx.Options.TrimSave) + { + save.FsTrim(); + signNeeded = true; + ctx.Logger.LogMessage("Trimmed save file"); + } + + if (signNeeded) + { + if (save.Commit(ctx.KeySet).IsSuccess()) { - foreach (DirectoryEntryEx entry in save.EnumerateEntries()) - { - ctx.Logger.LogMessage(entry.FullPath); - } + ctx.Logger.LogMessage( + $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); + } + else + { + ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } - - ctx.Logger.LogMessage(save.Print(ctx.KeySet)); - //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore)); fs.Unmount("save".ToU8Span()); - } - } - - internal static void ExportSaveDebug(Context ctx, string dir, SaveDataFileSystem save) - { - Directory.CreateDirectory(dir); - - FsLayout layout = save.Header.Layout; - - string mainRemapDir = Path.Combine(dir, "main_remap"); - Directory.CreateDirectory(mainRemapDir); - - save.DataRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Data")); - save.DataRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Header")); - save.DataRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Map entries")); - - string metadataRemapDir = Path.Combine(dir, "metadata_remap"); - Directory.CreateDirectory(metadataRemapDir); - - save.MetaRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Data")); - save.MetaRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Header")); - save.MetaRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Map entries")); - - string journalDir = Path.Combine(dir, "journal"); - Directory.CreateDirectory(journalDir); - - save.JournalStorage.GetBaseStorage().WriteAllBytes(Path.Combine(journalDir, "Data")); - save.JournalStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Header")); - save.JournalStorage.Map.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Map_header")); - save.JournalStorage.Map.GetMapStorage().WriteAllBytes(Path.Combine(journalDir, "Map")); - save.JournalStorage.Map.GetModifiedPhysicalBlocksStorage() - .WriteAllBytes(Path.Combine(journalDir, "ModifiedPhysicalBlocks")); - save.JournalStorage.Map.GetModifiedVirtualBlocksStorage() - .WriteAllBytes(Path.Combine(journalDir, "ModifiedVirtualBlocks")); - save.JournalStorage.Map.GetFreeBlocksStorage().WriteAllBytes(Path.Combine(journalDir, "FreeBlocks")); - - string saveDir = Path.Combine(dir, "save"); - Directory.CreateDirectory(saveDir); - - save.SaveDataFileSystemCore.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Header")); - save.SaveDataFileSystemCore.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Data")); - save.SaveDataFileSystemCore.AllocationTable.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_header")); - save.SaveDataFileSystemCore.AllocationTable.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_Data")); - - save.Header.DataIvfcMaster.WriteAllBytes(Path.Combine(saveDir, "Save_MasterHash")); - - IStorage saveLayer1Hash = save.MetaRemapStorage.Slice(layout.IvfcL1Offset, layout.IvfcL1Size); - IStorage saveLayer2Hash = save.MetaRemapStorage.Slice(layout.IvfcL2Offset, layout.IvfcL2Size); - IStorage saveLayer3Hash = save.MetaRemapStorage.Slice(layout.IvfcL3Offset, layout.IvfcL3Size); - - saveLayer1Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer1Hash"), ctx.Logger); - saveLayer2Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer2Hash"), ctx.Logger); - saveLayer3Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer3Hash"), ctx.Logger); - - if (layout.Version >= 0x50000) - { - save.Header.FatIvfcMaster.WriteAllBytes(Path.Combine(saveDir, "Fat_MasterHash")); - - IStorage fatLayer1Hash = save.MetaRemapStorage.Slice(layout.FatIvfcL1Offset, layout.FatIvfcL1Size); - IStorage fatLayer2Hash = save.MetaRemapStorage.Slice(layout.FatIvfcL2Offset, layout.FatIvfcL1Size); - - fatLayer1Hash.WriteAllBytes(Path.Combine(saveDir, "Fat_Layer1Hash"), ctx.Logger); - fatLayer2Hash.WriteAllBytes(Path.Combine(saveDir, "Fat_Layer2Hash"), ctx.Logger); + return; } - string duplexDir = Path.Combine(dir, "duplex"); - Directory.CreateDirectory(duplexDir); - - save.Header.DuplexMasterBitmapA.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapA")); - save.Header.DuplexMasterBitmapB.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapB")); - - IStorage duplexL1A = save.DataRemapStorage.Slice(layout.DuplexL1OffsetA, layout.DuplexL1Size); - IStorage duplexL1B = save.DataRemapStorage.Slice(layout.DuplexL1OffsetB, layout.DuplexL1Size); - IStorage duplexDataA = save.DataRemapStorage.Slice(layout.DuplexDataOffsetA, layout.DuplexDataSize); - IStorage duplexDataB = save.DataRemapStorage.Slice(layout.DuplexDataOffsetB, layout.DuplexDataSize); - - duplexL1A.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapA"), ctx.Logger); - duplexL1B.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapB"), ctx.Logger); - duplexDataA.WriteAllBytes(Path.Combine(duplexDir, "DataA"), ctx.Logger); - duplexDataB.WriteAllBytes(Path.Combine(duplexDir, "DataB"), ctx.Logger); - } - - // ReSharper disable once UnusedMember.Local - public static string PrintFatLayout(this SaveDataFileSystemCore save) - { - var sb = new StringBuilder(); - - foreach (DirectoryEntryEx entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) + if (ctx.Options.ListFiles) { - save.FileTable.TryOpenFile(entry.FullPath.ToU8Span(), out SaveFileInfo fileInfo); - if (fileInfo.StartBlock < 0) continue; - - IEnumerable<(int block, int length)> chain = save.AllocationTable.DumpChain(fileInfo.StartBlock); - - sb.AppendLine(entry.FullPath); - sb.AppendLine(PrintBlockChain(chain)); - sb.AppendLine(); - } - - sb.AppendLine("Directory Table"); - sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(0))); - sb.AppendLine(); - - sb.AppendLine("File Table"); - sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(1))); - sb.AppendLine(); - - sb.AppendLine("Free blocks"); - sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(-1))); - sb.AppendLine(); - - return sb.ToString(); - } - - private static string PrintBlockChain(IEnumerable<(int block, int length)> chain) - { - var sb = new StringBuilder(); - int segmentCount = 0; - int segmentStart = -1; - int segmentEnd = -1; - - foreach ((int block, int length) in chain) - { - if (segmentStart == -1) + foreach (DirectoryEntryEx entry in save.EnumerateEntries()) { - segmentStart = block; - segmentEnd = block + length - 1; - continue; + ctx.Logger.LogMessage(entry.FullPath); } + } - if (block == segmentEnd + 1) - { - segmentEnd += length; - continue; - } + ctx.Logger.LogMessage(save.Print(ctx.KeySet)); + //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore)); - PrintSegment(); + fs.Unmount("save".ToU8Span()); + } + } + internal static void ExportSaveDebug(Context ctx, string dir, SaveDataFileSystem save) + { + Directory.CreateDirectory(dir); + + FsLayout layout = save.Header.Layout; + + string mainRemapDir = Path.Combine(dir, "main_remap"); + Directory.CreateDirectory(mainRemapDir); + + save.DataRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Data")); + save.DataRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Header")); + save.DataRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Map entries")); + + string metadataRemapDir = Path.Combine(dir, "metadata_remap"); + Directory.CreateDirectory(metadataRemapDir); + + save.MetaRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Data")); + save.MetaRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Header")); + save.MetaRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Map entries")); + + string journalDir = Path.Combine(dir, "journal"); + Directory.CreateDirectory(journalDir); + + save.JournalStorage.GetBaseStorage().WriteAllBytes(Path.Combine(journalDir, "Data")); + save.JournalStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Header")); + save.JournalStorage.Map.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Map_header")); + save.JournalStorage.Map.GetMapStorage().WriteAllBytes(Path.Combine(journalDir, "Map")); + save.JournalStorage.Map.GetModifiedPhysicalBlocksStorage() + .WriteAllBytes(Path.Combine(journalDir, "ModifiedPhysicalBlocks")); + save.JournalStorage.Map.GetModifiedVirtualBlocksStorage() + .WriteAllBytes(Path.Combine(journalDir, "ModifiedVirtualBlocks")); + save.JournalStorage.Map.GetFreeBlocksStorage().WriteAllBytes(Path.Combine(journalDir, "FreeBlocks")); + + string saveDir = Path.Combine(dir, "save"); + Directory.CreateDirectory(saveDir); + + save.SaveDataFileSystemCore.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Header")); + save.SaveDataFileSystemCore.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Data")); + save.SaveDataFileSystemCore.AllocationTable.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_header")); + save.SaveDataFileSystemCore.AllocationTable.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_Data")); + + save.Header.DataIvfcMaster.WriteAllBytes(Path.Combine(saveDir, "Save_MasterHash")); + + IStorage saveLayer1Hash = save.MetaRemapStorage.Slice(layout.IvfcL1Offset, layout.IvfcL1Size); + IStorage saveLayer2Hash = save.MetaRemapStorage.Slice(layout.IvfcL2Offset, layout.IvfcL2Size); + IStorage saveLayer3Hash = save.MetaRemapStorage.Slice(layout.IvfcL3Offset, layout.IvfcL3Size); + + saveLayer1Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer1Hash"), ctx.Logger); + saveLayer2Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer2Hash"), ctx.Logger); + saveLayer3Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer3Hash"), ctx.Logger); + + if (layout.Version >= 0x50000) + { + save.Header.FatIvfcMaster.WriteAllBytes(Path.Combine(saveDir, "Fat_MasterHash")); + + IStorage fatLayer1Hash = save.MetaRemapStorage.Slice(layout.FatIvfcL1Offset, layout.FatIvfcL1Size); + IStorage fatLayer2Hash = save.MetaRemapStorage.Slice(layout.FatIvfcL2Offset, layout.FatIvfcL1Size); + + fatLayer1Hash.WriteAllBytes(Path.Combine(saveDir, "Fat_Layer1Hash"), ctx.Logger); + fatLayer2Hash.WriteAllBytes(Path.Combine(saveDir, "Fat_Layer2Hash"), ctx.Logger); + } + + string duplexDir = Path.Combine(dir, "duplex"); + Directory.CreateDirectory(duplexDir); + + save.Header.DuplexMasterBitmapA.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapA")); + save.Header.DuplexMasterBitmapB.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapB")); + + IStorage duplexL1A = save.DataRemapStorage.Slice(layout.DuplexL1OffsetA, layout.DuplexL1Size); + IStorage duplexL1B = save.DataRemapStorage.Slice(layout.DuplexL1OffsetB, layout.DuplexL1Size); + IStorage duplexDataA = save.DataRemapStorage.Slice(layout.DuplexDataOffsetA, layout.DuplexDataSize); + IStorage duplexDataB = save.DataRemapStorage.Slice(layout.DuplexDataOffsetB, layout.DuplexDataSize); + + duplexL1A.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapA"), ctx.Logger); + duplexL1B.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapB"), ctx.Logger); + duplexDataA.WriteAllBytes(Path.Combine(duplexDir, "DataA"), ctx.Logger); + duplexDataB.WriteAllBytes(Path.Combine(duplexDir, "DataB"), ctx.Logger); + } + + // ReSharper disable once UnusedMember.Local + public static string PrintFatLayout(this SaveDataFileSystemCore save) + { + var sb = new StringBuilder(); + + foreach (DirectoryEntryEx entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) + { + save.FileTable.TryOpenFile(entry.FullPath.ToU8Span(), out SaveFileInfo fileInfo); + if (fileInfo.StartBlock < 0) continue; + + IEnumerable<(int block, int length)> chain = save.AllocationTable.DumpChain(fileInfo.StartBlock); + + sb.AppendLine(entry.FullPath); + sb.AppendLine(PrintBlockChain(chain)); + sb.AppendLine(); + } + + sb.AppendLine("Directory Table"); + sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(0))); + sb.AppendLine(); + + sb.AppendLine("File Table"); + sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(1))); + sb.AppendLine(); + + sb.AppendLine("Free blocks"); + sb.AppendLine(PrintBlockChain(save.AllocationTable.DumpChain(-1))); + sb.AppendLine(); + + return sb.ToString(); + } + + private static string PrintBlockChain(IEnumerable<(int block, int length)> chain) + { + var sb = new StringBuilder(); + int segmentCount = 0; + int segmentStart = -1; + int segmentEnd = -1; + + foreach ((int block, int length) in chain) + { + if (segmentStart == -1) + { segmentStart = block; segmentEnd = block + length - 1; + continue; + } + + if (block == segmentEnd + 1) + { + segmentEnd += length; + continue; } PrintSegment(); - return sb.ToString(); - - void PrintSegment() - { - if (segmentCount > 0) sb.Append(", "); - - if (segmentStart == segmentEnd) - { - sb.Append(segmentStart); - } - else - { - sb.Append($"{segmentStart}-{segmentEnd}"); - } - - segmentCount++; - segmentStart = -1; - segmentEnd = -1; - } + segmentStart = block; + segmentEnd = block + length - 1; } - private static string Print(this SaveDataFileSystem save, KeySet keySet) + PrintSegment(); + + return sb.ToString(); + + void PrintSegment() { - int colLen = 25; - var sb = new StringBuilder(); - sb.AppendLine(); + if (segmentCount > 0) sb.Append(", "); - using var emptyPath = new LibHac.Fs.Path(); - emptyPath.InitializeAsEmpty().ThrowIfFailure(); - save.GetFreeSpaceSize(out long freeSpace, in emptyPath).ThrowIfFailure(); + if (segmentStart == segmentEnd) + { + sb.Append(segmentStart); + } + else + { + sb.Append($"{segmentStart}-{segmentEnd}"); + } - sb.AppendLine("Savefile:"); - PrintItem(sb, colLen, "CMAC Key Used:", keySet.DeviceUniqueSaveMacKeys[0].DataRo.ToArray()); - PrintItem(sb, colLen, $"CMAC Signature{save.Header.SignatureValidity.GetValidityString()}:", save.Header.Cmac); - PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}"); - PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId); - PrintItem(sb, colLen, "Save ID:", $"{save.Header.ExtraData.SaveId:x16}"); - PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type.Print()}"); - PrintItem(sb, colLen, "Owner ID:", $"{save.Header.ExtraData.SaveOwnerId:x16}"); - PrintItem(sb, colLen, "Timestamp:", $"{DateTimeOffset.FromUnixTimeSeconds(save.Header.ExtraData.Timestamp):yyyy-MM-dd HH:mm:ss} UTC"); - PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.DataSize)})"); - PrintItem(sb, colLen, "Journal Size:", $"0x{save.Header.ExtraData.JournalSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.JournalSize)})"); - PrintItem(sb, colLen, "Free Space:", $"0x{freeSpace:x16} ({Utilities.GetBytesReadable(freeSpace)})"); - PrintItem(sb, colLen, $"Header Hash{save.Header.HeaderHashValidity.GetValidityString()}:", save.Header.Layout.Hash); - PrintItem(sb, colLen, "Number of Files:", save.EnumerateEntries().Count(x => x.Type == DirectoryEntryType.File)); - - PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStorageType.Save); - - return sb.ToString(); + segmentCount++; + segmentStart = -1; + segmentEnd = -1; } } + + private static string Print(this SaveDataFileSystem save, KeySet keySet) + { + int colLen = 25; + var sb = new StringBuilder(); + sb.AppendLine(); + + using var emptyPath = new LibHac.Fs.Path(); + emptyPath.InitializeAsEmpty().ThrowIfFailure(); + save.GetFreeSpaceSize(out long freeSpace, in emptyPath).ThrowIfFailure(); + + sb.AppendLine("Savefile:"); + PrintItem(sb, colLen, "CMAC Key Used:", keySet.DeviceUniqueSaveMacKeys[0].DataRo.ToArray()); + PrintItem(sb, colLen, $"CMAC Signature{save.Header.SignatureValidity.GetValidityString()}:", save.Header.Cmac); + PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}"); + PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId); + PrintItem(sb, colLen, "Save ID:", $"{save.Header.ExtraData.SaveId:x16}"); + PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type.Print()}"); + PrintItem(sb, colLen, "Owner ID:", $"{save.Header.ExtraData.SaveOwnerId:x16}"); + PrintItem(sb, colLen, "Timestamp:", $"{DateTimeOffset.FromUnixTimeSeconds(save.Header.ExtraData.Timestamp):yyyy-MM-dd HH:mm:ss} UTC"); + PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.DataSize)})"); + PrintItem(sb, colLen, "Journal Size:", $"0x{save.Header.ExtraData.JournalSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.JournalSize)})"); + PrintItem(sb, colLen, "Free Space:", $"0x{freeSpace:x16} ({Utilities.GetBytesReadable(freeSpace)})"); + PrintItem(sb, colLen, $"Header Hash{save.Header.HeaderHashValidity.GetValidityString()}:", save.Header.Layout.Hash); + PrintItem(sb, colLen, "Number of Files:", save.EnumerateEntries().Count(x => x.Type == DirectoryEntryType.File)); + + PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStorageType.Save); + + return sb.ToString(); + } } diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index d6c8f988..a0f542ae 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -13,202 +13,56 @@ using LibHac.FsSystem.Save; using LibHac.Ns; using Path = System.IO.Path; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessSwitchFs { - internal static class ProcessSwitchFs + public static void Process(Context ctx) { - public static void Process(Context ctx) + SwitchFs switchFs; + var baseFs = new LocalFileSystem(ctx.Options.InFile); + + if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Nintendo", "Contents", "registered"))) { - SwitchFs switchFs; - var baseFs = new LocalFileSystem(ctx.Options.InFile); + ctx.Logger.LogMessage("Treating path as SD card storage"); + switchFs = SwitchFs.OpenSdCard(ctx.KeySet, baseFs); - if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Nintendo", "Contents", "registered"))) - { - ctx.Logger.LogMessage("Treating path as SD card storage"); - switchFs = SwitchFs.OpenSdCard(ctx.KeySet, baseFs); + CheckForNcaFolders(ctx, switchFs); + } + else if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Contents", "registered"))) + { + ctx.Logger.LogMessage("Treating path as NAND storage"); + switchFs = SwitchFs.OpenNandPartition(ctx.KeySet, baseFs); - CheckForNcaFolders(ctx, switchFs); - } - else if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Contents", "registered"))) - { - ctx.Logger.LogMessage("Treating path as NAND storage"); - switchFs = SwitchFs.OpenNandPartition(ctx.KeySet, baseFs); - - CheckForNcaFolders(ctx, switchFs); - } - else - { - ctx.Logger.LogMessage("Treating path as a directory of loose NCAs"); - switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, baseFs); - } - - if (ctx.Options.ListNcas) - { - ctx.Logger.LogMessage(ListNcas(switchFs)); - } - - if (ctx.Options.ListTitles) - { - ctx.Logger.LogMessage(ListTitles(switchFs)); - } - - if (ctx.Options.ListApps) - { - ctx.Logger.LogMessage(ListApplications(switchFs)); - } - - if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) - { - ulong id = ctx.Options.TitleId; - if (id == 0) - { - ctx.Logger.LogMessage("Title ID must be specified to dump ExeFS"); - return; - } - - if (!switchFs.Titles.TryGetValue(id, out Title title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - if (title.MainNca == null) - { - ctx.Logger.LogMessage($"Could not find main data for title {id:X16}"); - return; - } - - if (!title.MainNca.Nca.SectionExists(NcaSectionType.Code)) - { - ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no ExeFS section"); - return; - } - - if (ctx.Options.ExefsOutDir != null) - { - IFileSystem fs = title.MainNca.OpenFileSystem(NcaSectionType.Code, ctx.Options.IntegrityLevel); - fs.Extract(ctx.Options.ExefsOutDir, ctx.Logger); - } - - if (ctx.Options.ExefsOut != null) - { - title.MainNca.OpenStorage(NcaSectionType.Code, ctx.Options.IntegrityLevel).WriteAllBytes(ctx.Options.ExefsOut, ctx.Logger); - } - } - - if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null) - { - ulong id = ctx.Options.TitleId; - if (id == 0) - { - ctx.Logger.LogMessage("Title ID must be specified to dump RomFS"); - return; - } - - if (!switchFs.Titles.TryGetValue(id, out Title title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - if (title.MainNca == null) - { - ctx.Logger.LogMessage($"Could not find main data for title {id:X16}"); - return; - } - - if (!title.MainNca.Nca.SectionExists(NcaSectionType.Data)) - { - ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section"); - return; - } - - ProcessRomfs.Process(ctx, title.MainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel)); - } - - if (ctx.Options.OutDir != null) - { - SaveTitle(ctx, switchFs); - } - - if (ctx.Options.NspOut != null) - { - ProcessPfs.CreateNsp(ctx, switchFs); - } - - if (ctx.Options.SaveOutDir != null) - { - ExportSdSaves(ctx, switchFs); - } - - if (ctx.Options.Validate) - { - ValidateSwitchFs(ctx, switchFs); - } + CheckForNcaFolders(ctx, switchFs); + } + else + { + ctx.Logger.LogMessage("Treating path as a directory of loose NCAs"); + switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, baseFs); } - private static void ValidateSwitchFs(Context ctx, SwitchFs switchFs) + if (ctx.Options.ListNcas) { - if (ctx.Options.TitleId != 0) - { - ulong id = ctx.Options.TitleId; - - if (!switchFs.Titles.TryGetValue(id, out Title title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - ValidateTitle(ctx, title, ""); - - return; - } - - foreach (Application app in switchFs.Applications.Values) - { - ctx.Logger.LogMessage($"Checking {app.Name}..."); - - Title mainTitle = app.Patch ?? app.Main; - - if (mainTitle != null) - { - ValidateTitle(ctx, mainTitle, "Main title"); - } - - foreach (Title title in app.AddOnContent) - { - ValidateTitle(ctx, title, "Add-on content"); - } - } + ctx.Logger.LogMessage(ListNcas(switchFs)); } - private static void ValidateTitle(Context ctx, Title title, string caption) + if (ctx.Options.ListTitles) { - try - { - ctx.Logger.LogMessage($" {caption} {title.Id:x16}"); - - foreach (SwitchFsNca nca in title.Ncas) - { - ctx.Logger.LogMessage($" {nca.Nca.Header.ContentType.Print()}"); - - Validity validity = nca.VerifyNca(ctx.Logger, true); - - ctx.Logger.LogMessage($" {validity.Print()}"); - } - } - catch (Exception ex) - { - ctx.Logger.LogMessage($"Error processing title {title.Id:x16}:\n{ex.Message}"); - } + ctx.Logger.LogMessage(ListTitles(switchFs)); } - private static void SaveTitle(Context ctx, SwitchFs switchFs) + if (ctx.Options.ListApps) + { + ctx.Logger.LogMessage(ListApplications(switchFs)); + } + + if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) { ulong id = ctx.Options.TitleId; if (id == 0) { - ctx.Logger.LogMessage("Title ID must be specified to save title"); + ctx.Logger.LogMessage("Title ID must be specified to dump ExeFS"); return; } @@ -218,126 +72,271 @@ namespace hactoolnet return; } - string saveDir = Path.Combine(ctx.Options.OutDir, $"{title.Id:X16}v{title.Version.Version}"); - Directory.CreateDirectory(saveDir); - - foreach (SwitchFsNca nca in title.Ncas) + if (title.MainNca == null) { - Stream stream = nca.Nca.BaseStorage.AsStream(); - string outFile = Path.Combine(saveDir, nca.Filename); - ctx.Logger.LogMessage(nca.Filename); - using (var outStream = new FileStream(outFile, FileMode.Create, FileAccess.ReadWrite)) - { - stream.CopyStream(outStream, stream.Length, ctx.Logger); - } + ctx.Logger.LogMessage($"Could not find main data for title {id:X16}"); + return; + } + + if (!title.MainNca.Nca.SectionExists(NcaSectionType.Code)) + { + ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no ExeFS section"); + return; + } + + if (ctx.Options.ExefsOutDir != null) + { + IFileSystem fs = title.MainNca.OpenFileSystem(NcaSectionType.Code, ctx.Options.IntegrityLevel); + fs.Extract(ctx.Options.ExefsOutDir, ctx.Logger); + } + + if (ctx.Options.ExefsOut != null) + { + title.MainNca.OpenStorage(NcaSectionType.Code, ctx.Options.IntegrityLevel).WriteAllBytes(ctx.Options.ExefsOut, ctx.Logger); } } - static string ListTitles(SwitchFs sdfs) + if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null) { - var table = new TableBuilder("Title ID", "Version", "", "Type", "Size", "Display Version", "Name"); - - foreach (Title title in sdfs.Titles.Values.OrderBy(x => x.Id)) + ulong id = ctx.Options.TitleId; + if (id == 0) { - table.AddRow($"{title.Id:X16}", - $"v{title.Version?.Version}", - title.Version?.ToString(), - title.Metadata?.Type.Print(), - Utilities.GetBytesReadable(title.GetSize()), - title.Control.Value.DisplayVersion.ToString(), - title.Name); + ctx.Logger.LogMessage("Title ID must be specified to dump RomFS"); + return; } - return table.Print(); + if (!switchFs.Titles.TryGetValue(id, out Title title)) + { + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; + } + + if (title.MainNca == null) + { + ctx.Logger.LogMessage($"Could not find main data for title {id:X16}"); + return; + } + + if (!title.MainNca.Nca.SectionExists(NcaSectionType.Data)) + { + ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section"); + return; + } + + ProcessRomfs.Process(ctx, title.MainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel)); } - static string ListNcas(SwitchFs sdfs) + if (ctx.Options.OutDir != null) { - var table = new TableBuilder("NCA ID", "Type", "Title ID"); - - foreach (SwitchFsNca nca in sdfs.Ncas.Values.OrderBy(x => x.NcaId)) - { - table.AddRow(nca.NcaId, nca.Nca.Header.ContentType.Print(), nca.Nca.Header.TitleId.ToString("X16")); - } - - return table.Print(); + SaveTitle(ctx, switchFs); } - static string ListApplications(SwitchFs sdfs) + if (ctx.Options.NspOut != null) { - var sb = new StringBuilder(); - - foreach (Application app in sdfs.Applications.Values.OrderBy(x => x.Name)) - { - if (app.Main != null) - { - sb.AppendLine($"{app.Name} v{app.DisplayVersion} ({app.Main.Id.ToString("X16")})"); - sb.AppendLine($"Software: {Utilities.GetBytesReadable(app.Main.GetSize())}"); - } - else - { - sb.AppendLine($"{app.Name} v{app.DisplayVersion}"); - } - - if (app.Patch != null) - { - sb.AppendLine($"Update Data: {Utilities.GetBytesReadable(app.Patch.GetSize())}"); - } - - if (app.AddOnContent.Count > 0) - { - sb.AppendLine($"DLC: {Utilities.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); - } - - ref ApplicationControlProperty nacp = ref app.Nacp.Value; - - long userTotalSaveDataSize = nacp.UserAccountSaveDataSize + nacp.UserAccountSaveDataJournalSize; - long deviceTotalSaveDataSize = nacp.DeviceSaveDataSize + nacp.DeviceSaveDataJournalSize; - - if (userTotalSaveDataSize > 0) - sb.AppendLine($"User save: {Utilities.GetBytesReadable(userTotalSaveDataSize)}"); - if (deviceTotalSaveDataSize > 0) - sb.AppendLine($"System save: {Utilities.GetBytesReadable(deviceTotalSaveDataSize)}"); - if (nacp.BcatDeliveryCacheStorageSize > 0) - sb.AppendLine($"BCAT save: {Utilities.GetBytesReadable(nacp.BcatDeliveryCacheStorageSize)}"); - - sb.AppendLine(); - } - - return sb.ToString(); + ProcessPfs.CreateNsp(ctx, switchFs); } - private static void ExportSdSaves(Context ctx, SwitchFs switchFs) + if (ctx.Options.SaveOutDir != null) { - foreach (KeyValuePair<string, SaveDataFileSystem> save in switchFs.Saves) - { - string outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key); - save.Value.Extract(outDir, ctx.Logger); - } + ExportSdSaves(ctx, switchFs); } - private static void CheckForNcaFolders(Context ctx, SwitchFs switchFs) + if (ctx.Options.Validate) { - // Skip this until Linux gets FAT attribute support - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + ValidateSwitchFs(ctx, switchFs); + } + } - IFileSystem fs = switchFs.ContentFs; + private static void ValidateSwitchFs(Context ctx, SwitchFs switchFs) + { + if (ctx.Options.TitleId != 0) + { + ulong id = ctx.Options.TitleId; - DirectoryEntryEx[] ncaDirs = fs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories) - .Where(x => x.Type == DirectoryEntryType.Directory) - .Where(x => fs.FileExists($"{x.FullPath}/00")) - .ToArray(); - - if (ncaDirs.Length > 0) + if (!switchFs.Titles.TryGetValue(id, out Title title)) { - ctx.Logger.LogMessage("Warning: NCA folders without the archive flag were found. Fixing..."); + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; } - foreach (DirectoryEntryEx file in ncaDirs) + ValidateTitle(ctx, title, ""); + + return; + } + + foreach (Application app in switchFs.Applications.Values) + { + ctx.Logger.LogMessage($"Checking {app.Name}..."); + + Title mainTitle = app.Patch ?? app.Main; + + if (mainTitle != null) { - fs.SetConcatenationFileAttribute(file.FullPath); - ctx.Logger.LogMessage($"{file.FullPath}"); + ValidateTitle(ctx, mainTitle, "Main title"); + } + + foreach (Title title in app.AddOnContent) + { + ValidateTitle(ctx, title, "Add-on content"); } } } + + private static void ValidateTitle(Context ctx, Title title, string caption) + { + try + { + ctx.Logger.LogMessage($" {caption} {title.Id:x16}"); + + foreach (SwitchFsNca nca in title.Ncas) + { + ctx.Logger.LogMessage($" {nca.Nca.Header.ContentType.Print()}"); + + Validity validity = nca.VerifyNca(ctx.Logger, true); + + ctx.Logger.LogMessage($" {validity.Print()}"); + } + } + catch (Exception ex) + { + ctx.Logger.LogMessage($"Error processing title {title.Id:x16}:\n{ex.Message}"); + } + } + + private static void SaveTitle(Context ctx, SwitchFs switchFs) + { + ulong id = ctx.Options.TitleId; + if (id == 0) + { + ctx.Logger.LogMessage("Title ID must be specified to save title"); + return; + } + + if (!switchFs.Titles.TryGetValue(id, out Title title)) + { + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; + } + + string saveDir = Path.Combine(ctx.Options.OutDir, $"{title.Id:X16}v{title.Version.Version}"); + Directory.CreateDirectory(saveDir); + + foreach (SwitchFsNca nca in title.Ncas) + { + Stream stream = nca.Nca.BaseStorage.AsStream(); + string outFile = Path.Combine(saveDir, nca.Filename); + ctx.Logger.LogMessage(nca.Filename); + using (var outStream = new FileStream(outFile, FileMode.Create, FileAccess.ReadWrite)) + { + stream.CopyStream(outStream, stream.Length, ctx.Logger); + } + } + } + + static string ListTitles(SwitchFs sdfs) + { + var table = new TableBuilder("Title ID", "Version", "", "Type", "Size", "Display Version", "Name"); + + foreach (Title title in sdfs.Titles.Values.OrderBy(x => x.Id)) + { + table.AddRow($"{title.Id:X16}", + $"v{title.Version?.Version}", + title.Version?.ToString(), + title.Metadata?.Type.Print(), + Utilities.GetBytesReadable(title.GetSize()), + title.Control.Value.DisplayVersion.ToString(), + title.Name); + } + + return table.Print(); + } + + static string ListNcas(SwitchFs sdfs) + { + var table = new TableBuilder("NCA ID", "Type", "Title ID"); + + foreach (SwitchFsNca nca in sdfs.Ncas.Values.OrderBy(x => x.NcaId)) + { + table.AddRow(nca.NcaId, nca.Nca.Header.ContentType.Print(), nca.Nca.Header.TitleId.ToString("X16")); + } + + return table.Print(); + } + + static string ListApplications(SwitchFs sdfs) + { + var sb = new StringBuilder(); + + foreach (Application app in sdfs.Applications.Values.OrderBy(x => x.Name)) + { + if (app.Main != null) + { + sb.AppendLine($"{app.Name} v{app.DisplayVersion} ({app.Main.Id.ToString("X16")})"); + sb.AppendLine($"Software: {Utilities.GetBytesReadable(app.Main.GetSize())}"); + } + else + { + sb.AppendLine($"{app.Name} v{app.DisplayVersion}"); + } + + if (app.Patch != null) + { + sb.AppendLine($"Update Data: {Utilities.GetBytesReadable(app.Patch.GetSize())}"); + } + + if (app.AddOnContent.Count > 0) + { + sb.AppendLine($"DLC: {Utilities.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); + } + + ref ApplicationControlProperty nacp = ref app.Nacp.Value; + + long userTotalSaveDataSize = nacp.UserAccountSaveDataSize + nacp.UserAccountSaveDataJournalSize; + long deviceTotalSaveDataSize = nacp.DeviceSaveDataSize + nacp.DeviceSaveDataJournalSize; + + if (userTotalSaveDataSize > 0) + sb.AppendLine($"User save: {Utilities.GetBytesReadable(userTotalSaveDataSize)}"); + if (deviceTotalSaveDataSize > 0) + sb.AppendLine($"System save: {Utilities.GetBytesReadable(deviceTotalSaveDataSize)}"); + if (nacp.BcatDeliveryCacheStorageSize > 0) + sb.AppendLine($"BCAT save: {Utilities.GetBytesReadable(nacp.BcatDeliveryCacheStorageSize)}"); + + sb.AppendLine(); + } + + return sb.ToString(); + } + + private static void ExportSdSaves(Context ctx, SwitchFs switchFs) + { + foreach (KeyValuePair<string, SaveDataFileSystem> save in switchFs.Saves) + { + string outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key); + save.Value.Extract(outDir, ctx.Logger); + } + } + + private static void CheckForNcaFolders(Context ctx, SwitchFs switchFs) + { + // Skip this until Linux gets FAT attribute support + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + IFileSystem fs = switchFs.ContentFs; + + DirectoryEntryEx[] ncaDirs = fs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories) + .Where(x => x.Type == DirectoryEntryType.Directory) + .Where(x => fs.FileExists($"{x.FullPath}/00")) + .ToArray(); + + if (ncaDirs.Length > 0) + { + ctx.Logger.LogMessage("Warning: NCA folders without the archive flag were found. Fixing..."); + } + + foreach (DirectoryEntryEx file in ncaDirs) + { + fs.SetConcatenationFileAttribute(file.FullPath); + ctx.Logger.LogMessage($"{file.FullPath}"); + } + } } diff --git a/src/hactoolnet/ProcessXci.cs b/src/hactoolnet/ProcessXci.cs index ac333423..2b7f30e3 100644 --- a/src/hactoolnet/ProcessXci.cs +++ b/src/hactoolnet/ProcessXci.cs @@ -7,203 +7,202 @@ using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using static hactoolnet.Print; -namespace hactoolnet +namespace hactoolnet; + +internal static class ProcessXci { - internal static class ProcessXci + public static void Process(Context ctx) { - public static void Process(Context ctx) + using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) + var xci = new Xci(ctx.KeySet, file); + + ctx.Logger.LogMessage(xci.Print()); + + if (ctx.Options.RootDir != null) { - var xci = new Xci(ctx.KeySet, file); - - ctx.Logger.LogMessage(xci.Print()); - - if (ctx.Options.RootDir != null) - { - xci.OpenPartition(XciPartitionType.Root).Extract(ctx.Options.RootDir, ctx.Logger); - } - - if (ctx.Options.UpdateDir != null && xci.HasPartition(XciPartitionType.Update)) - { - xci.OpenPartition(XciPartitionType.Update).Extract(ctx.Options.UpdateDir, ctx.Logger); - } - - if (ctx.Options.NormalDir != null && xci.HasPartition(XciPartitionType.Normal)) - { - xci.OpenPartition(XciPartitionType.Normal).Extract(ctx.Options.NormalDir, ctx.Logger); - } - - if (ctx.Options.SecureDir != null && xci.HasPartition(XciPartitionType.Secure)) - { - xci.OpenPartition(XciPartitionType.Secure).Extract(ctx.Options.SecureDir, ctx.Logger); - } - - if (ctx.Options.LogoDir != null && xci.HasPartition(XciPartitionType.Logo)) - { - xci.OpenPartition(XciPartitionType.Logo).Extract(ctx.Options.LogoDir, ctx.Logger); - } - - if (ctx.Options.OutDir != null) - { - XciPartition root = xci.OpenPartition(XciPartitionType.Root); - if (root == null) - { - ctx.Logger.LogMessage("Could not find root partition"); - return; - } - - foreach (PartitionFileEntry sub in root.Files) - { - var subPfs = new PartitionFileSystem(root.OpenFile(sub, OpenMode.Read).AsStorage()); - string subDir = System.IO.Path.Combine(ctx.Options.OutDir, sub.Name); - - subPfs.Extract(subDir, ctx.Logger); - } - } - - if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) - { - Nca mainNca = GetXciMainNca(xci, ctx); - - if (mainNca == null) - { - ctx.Logger.LogMessage("Could not find Program NCA"); - return; - } - - if (!mainNca.SectionExists(NcaSectionType.Code)) - { - ctx.Logger.LogMessage("NCA has no ExeFS section"); - return; - } - - if (ctx.Options.ExefsOutDir != null) - { - mainNca.ExtractSection(NcaSectionType.Code, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger); - } - - if (ctx.Options.ExefsOut != null) - { - mainNca.ExportSection(NcaSectionType.Code, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger); - } - } - - if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ListRomFs) - { - Nca mainNca = GetXciMainNca(xci, ctx); - - if (mainNca == null) - { - ctx.Logger.LogMessage("Could not find Program NCA"); - return; - } - - if (!mainNca.SectionExists(NcaSectionType.Data)) - { - ctx.Logger.LogMessage("NCA has no RomFS section"); - return; - } - - ProcessRomfs.Process(ctx, mainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel, false)); - } - } - } - - private static Nca GetXciMainNca(Xci xci, Context ctx) - { - XciPartition partition = xci.OpenPartition(XciPartitionType.Secure); - - if (partition == null) - { - ctx.Logger.LogMessage("Could not find secure partition"); - return null; + xci.OpenPartition(XciPartitionType.Root).Extract(ctx.Options.RootDir, ctx.Logger); } - Nca mainNca = null; - - foreach (PartitionFileEntry fileEntry in partition.Files.Where(x => x.Name.EndsWith(".nca"))) + if (ctx.Options.UpdateDir != null && xci.HasPartition(XciPartitionType.Update)) { - IStorage ncaStorage = partition.OpenFile(fileEntry, OpenMode.Read).AsStorage(); - var nca = new Nca(ctx.KeySet, ncaStorage); + xci.OpenPartition(XciPartitionType.Update).Extract(ctx.Options.UpdateDir, ctx.Logger); + } - if (nca.Header.ContentType == NcaContentType.Program) + if (ctx.Options.NormalDir != null && xci.HasPartition(XciPartitionType.Normal)) + { + xci.OpenPartition(XciPartitionType.Normal).Extract(ctx.Options.NormalDir, ctx.Logger); + } + + if (ctx.Options.SecureDir != null && xci.HasPartition(XciPartitionType.Secure)) + { + xci.OpenPartition(XciPartitionType.Secure).Extract(ctx.Options.SecureDir, ctx.Logger); + } + + if (ctx.Options.LogoDir != null && xci.HasPartition(XciPartitionType.Logo)) + { + xci.OpenPartition(XciPartitionType.Logo).Extract(ctx.Options.LogoDir, ctx.Logger); + } + + if (ctx.Options.OutDir != null) + { + XciPartition root = xci.OpenPartition(XciPartitionType.Root); + if (root == null) { - mainNca = nca; + ctx.Logger.LogMessage("Could not find root partition"); + return; + } + + foreach (PartitionFileEntry sub in root.Files) + { + var subPfs = new PartitionFileSystem(root.OpenFile(sub, OpenMode.Read).AsStorage()); + string subDir = System.IO.Path.Combine(ctx.Options.OutDir, sub.Name); + + subPfs.Extract(subDir, ctx.Logger); } } - return mainNca; - } - - private static string Print(this Xci xci) - { - const int colLen = 36; - - var sb = new StringBuilder(); - sb.AppendLine(); - - sb.AppendLine("XCI:"); - - PrintItem(sb, colLen, "Magic:", xci.Header.Magic); - PrintItem(sb, colLen, $"Header Signature{xci.Header.SignatureValidity.GetValidityString()}:", xci.Header.Signature); - PrintItem(sb, colLen, $"Header Hash{xci.Header.PartitionFsHeaderValidity.GetValidityString()}:", xci.Header.RootPartitionHeaderHash); - PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.GameCardSize)); - PrintItem(sb, colLen, "Cartridge Size:", $"0x{Utilities.MediaToReal(xci.Header.ValidDataEndPage + 1):x12}"); - PrintItem(sb, colLen, "Header IV:", xci.Header.AesCbcIv); - - PrintPartition(sb, colLen, xci.OpenPartition(XciPartitionType.Root), XciPartitionType.Root); - - for (int i = 0; i <= (int)XciPartitionType.Root; i++) + if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) { - var type = (XciPartitionType)i; - if (type == XciPartitionType.Root || !xci.HasPartition(type)) continue; + Nca mainNca = GetXciMainNca(xci, ctx); - XciPartition partition = xci.OpenPartition(type); - PrintPartition(sb, colLen, partition, type); - } - - return sb.ToString(); - } - - private static void PrintPartition(StringBuilder sb, int colLen, XciPartition partition, XciPartitionType type) - { - const int fileNameLen = 57; - - sb.AppendLine($"{type.Print()} Partition:{partition.HashValidity.GetValidityString()}"); - PrintItem(sb, colLen, " Magic:", partition.Header.Magic); - PrintItem(sb, colLen, " Offset:", $"{partition.Offset:x12}"); - PrintItem(sb, colLen, " Number of files:", partition.Files.Length); - - string name = type.GetFileName(); - - if (partition.Files.Length > 0 && partition.Files.Length < 100) - { - for (int i = 0; i < partition.Files.Length; i++) + if (mainNca == null) { - PartitionFileEntry file = partition.Files[i]; + ctx.Logger.LogMessage("Could not find Program NCA"); + return; + } - string label = i == 0 ? " Files:" : ""; - string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}"; - string data = $"{name}:/{file.Name}".PadRight(fileNameLen) + offsets; + if (!mainNca.SectionExists(NcaSectionType.Code)) + { + ctx.Logger.LogMessage("NCA has no ExeFS section"); + return; + } - PrintItem(sb, colLen, label, data); + if (ctx.Options.ExefsOutDir != null) + { + mainNca.ExtractSection(NcaSectionType.Code, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger); + } + + if (ctx.Options.ExefsOut != null) + { + mainNca.ExportSection(NcaSectionType.Code, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger); } } - } - private static string GetCartridgeType(GameCardSizeInternal size) - { - switch (size) + if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ListRomFs) { - case GameCardSizeInternal.Size1Gb: return "1GB"; - case GameCardSizeInternal.Size2Gb: return "2GB"; - case GameCardSizeInternal.Size4Gb: return "4GB"; - case GameCardSizeInternal.Size8Gb: return "8GB"; - case GameCardSizeInternal.Size16Gb: return "16GB"; - case GameCardSizeInternal.Size32Gb: return "32GB"; - default: return string.Empty; + Nca mainNca = GetXciMainNca(xci, ctx); + + if (mainNca == null) + { + ctx.Logger.LogMessage("Could not find Program NCA"); + return; + } + + if (!mainNca.SectionExists(NcaSectionType.Data)) + { + ctx.Logger.LogMessage("NCA has no RomFS section"); + return; + } + + ProcessRomfs.Process(ctx, mainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel, false)); } } } + + private static Nca GetXciMainNca(Xci xci, Context ctx) + { + XciPartition partition = xci.OpenPartition(XciPartitionType.Secure); + + if (partition == null) + { + ctx.Logger.LogMessage("Could not find secure partition"); + return null; + } + + Nca mainNca = null; + + foreach (PartitionFileEntry fileEntry in partition.Files.Where(x => x.Name.EndsWith(".nca"))) + { + IStorage ncaStorage = partition.OpenFile(fileEntry, OpenMode.Read).AsStorage(); + var nca = new Nca(ctx.KeySet, ncaStorage); + + if (nca.Header.ContentType == NcaContentType.Program) + { + mainNca = nca; + } + } + + return mainNca; + } + + private static string Print(this Xci xci) + { + const int colLen = 36; + + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("XCI:"); + + PrintItem(sb, colLen, "Magic:", xci.Header.Magic); + PrintItem(sb, colLen, $"Header Signature{xci.Header.SignatureValidity.GetValidityString()}:", xci.Header.Signature); + PrintItem(sb, colLen, $"Header Hash{xci.Header.PartitionFsHeaderValidity.GetValidityString()}:", xci.Header.RootPartitionHeaderHash); + PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.GameCardSize)); + PrintItem(sb, colLen, "Cartridge Size:", $"0x{Utilities.MediaToReal(xci.Header.ValidDataEndPage + 1):x12}"); + PrintItem(sb, colLen, "Header IV:", xci.Header.AesCbcIv); + + PrintPartition(sb, colLen, xci.OpenPartition(XciPartitionType.Root), XciPartitionType.Root); + + for (int i = 0; i <= (int)XciPartitionType.Root; i++) + { + var type = (XciPartitionType)i; + if (type == XciPartitionType.Root || !xci.HasPartition(type)) continue; + + XciPartition partition = xci.OpenPartition(type); + PrintPartition(sb, colLen, partition, type); + } + + return sb.ToString(); + } + + private static void PrintPartition(StringBuilder sb, int colLen, XciPartition partition, XciPartitionType type) + { + const int fileNameLen = 57; + + sb.AppendLine($"{type.Print()} Partition:{partition.HashValidity.GetValidityString()}"); + PrintItem(sb, colLen, " Magic:", partition.Header.Magic); + PrintItem(sb, colLen, " Offset:", $"{partition.Offset:x12}"); + PrintItem(sb, colLen, " Number of files:", partition.Files.Length); + + string name = type.GetFileName(); + + if (partition.Files.Length > 0 && partition.Files.Length < 100) + { + for (int i = 0; i < partition.Files.Length; i++) + { + PartitionFileEntry file = partition.Files[i]; + + string label = i == 0 ? " Files:" : ""; + string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}"; + string data = $"{name}:/{file.Name}".PadRight(fileNameLen) + offsets; + + PrintItem(sb, colLen, label, data); + } + } + } + + private static string GetCartridgeType(GameCardSizeInternal size) + { + switch (size) + { + case GameCardSizeInternal.Size1Gb: return "1GB"; + case GameCardSizeInternal.Size2Gb: return "2GB"; + case GameCardSizeInternal.Size4Gb: return "4GB"; + case GameCardSizeInternal.Size8Gb: return "8GB"; + case GameCardSizeInternal.Size16Gb: return "16GB"; + case GameCardSizeInternal.Size32Gb: return "32GB"; + default: return string.Empty; + } + } } diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index ac8b0c86..4c9f9797 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -8,281 +8,280 @@ using LibHac.Fs; using LibHac.Util; using Path = System.IO.Path; -namespace hactoolnet +namespace hactoolnet; + +public static class Program { - public static class Program + public static int Main(string[] args) { - public static int Main(string[] args) + try { - try + if (Run(args)) { - if (Run(args)) - { - return 0; - } + return 0; } - catch (MissingKeyException ex) + } + catch (MissingKeyException ex) + { + string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name; + Console.Error.WriteLine($"\nERROR: {ex.Message}\nA required key is missing.\nKey name: {name}\n"); + } + catch (HorizonResultException ex) + { + Console.Error.WriteLine($"\nERROR: {ex.Message}"); + + if (ex.ResultValue != ex.InternalResultValue) { - string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name; - Console.Error.WriteLine($"\nERROR: {ex.Message}\nA required key is missing.\nKey name: {name}\n"); + Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}"); } - catch (HorizonResultException ex) - { - Console.Error.WriteLine($"\nERROR: {ex.Message}"); - if (ex.ResultValue != ex.InternalResultValue) - { - Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}"); - } + Console.Error.WriteLine(); + Console.Error.WriteLine("Additional information:"); + Console.Error.WriteLine(ex.StackTrace); + } + catch (Exception ex) + { + Console.Error.WriteLine($"\nERROR: {ex.Message}\n"); - Console.Error.WriteLine(); - Console.Error.WriteLine("Additional information:"); - Console.Error.WriteLine(ex.StackTrace); - } - catch (Exception ex) - { - Console.Error.WriteLine($"\nERROR: {ex.Message}\n"); - - Console.Error.WriteLine("Additional information:"); + Console.Error.WriteLine("Additional information:"); #if !CORERT_NO_REFLECTION - Console.Error.WriteLine(ex.GetType().FullName); + Console.Error.WriteLine(ex.GetType().FullName); #endif - Console.Error.WriteLine(ex.StackTrace); - } - - return 1; + Console.Error.WriteLine(ex.StackTrace); } - private static bool Run(string[] args) + return 1; + } + + private static bool Run(string[] args) + { + Console.OutputEncoding = Encoding.UTF8; + var ctx = new Context(); + ctx.Options = CliParser.Parse(args); + if (ctx.Options == null) return false; + + StreamWriter logWriter = null; + ResultLogger resultLogger = null; + LogObserverHolder logObserver = null; + + try { - Console.OutputEncoding = Encoding.UTF8; - var ctx = new Context(); - ctx.Options = CliParser.Parse(args); - if (ctx.Options == null) return false; - - StreamWriter logWriter = null; - ResultLogger resultLogger = null; - LogObserverHolder logObserver = null; - - try + using (var logger = new ProgressBar()) { - using (var logger = new ProgressBar()) + ctx.Logger = logger; + OpenKeySet(ctx); + InitializeHorizon(ctx); + + if (ctx.Options.AccessLog != null) { - ctx.Logger = logger; - OpenKeySet(ctx); - InitializeHorizon(ctx); + logWriter = new StreamWriter(ctx.Options.AccessLog); + logObserver = new LogObserverHolder(); - if (ctx.Options.AccessLog != null) - { - logWriter = new StreamWriter(ctx.Options.AccessLog); - logObserver = new LogObserverHolder(); + // ReSharper disable once AccessToDisposedClosure + // References to logWriter should be gone by the time it's disposed + ctx.Horizon.Diag.InitializeLogObserverHolder(ref logObserver, + (in LogMetaData data, in LogBody body, object arguments) => + logWriter.Write(body.Message.ToString()), null); - // ReSharper disable once AccessToDisposedClosure - // References to logWriter should be gone by the time it's disposed - ctx.Horizon.Diag.InitializeLogObserverHolder(ref logObserver, - (in LogMetaData data, in LogBody body, object arguments) => - logWriter.Write(body.Message.ToString()), null); + ctx.Horizon.Diag.RegisterLogObserver(logObserver); - ctx.Horizon.Diag.RegisterLogObserver(logObserver); - - ctx.Horizon.Fs.SetLocalSystemAccessLogForDebug(true); - ctx.Horizon.Fs.SetGlobalAccessLogMode(GlobalAccessLogMode.Log).ThrowIfFailure(); - } - - if (ctx.Options.ResultLog != null) - { - resultLogger = new ResultLogger(new StreamWriter(ctx.Options.ResultLog), - printStackTrace: true, printSourceInfo: true, combineRepeats: true); - - Result.SetLogger(resultLogger); - } - - if (ctx.Options.RunCustom) - { - CustomTask(ctx); - return true; - } - - RunTask(ctx); + ctx.Horizon.Fs.SetLocalSystemAccessLogForDebug(true); + ctx.Horizon.Fs.SetGlobalAccessLogMode(GlobalAccessLogMode.Log).ThrowIfFailure(); } + + if (ctx.Options.ResultLog != null) + { + resultLogger = new ResultLogger(new StreamWriter(ctx.Options.ResultLog), + printStackTrace: true, printSourceInfo: true, combineRepeats: true); + + Result.SetLogger(resultLogger); + } + + if (ctx.Options.RunCustom) + { + CustomTask(ctx); + return true; + } + + RunTask(ctx); } - finally - { - if (logObserver != null) - { - ctx.Horizon.Diag.UnregisterLogObserver(logObserver); - } - - logWriter?.Dispose(); - - if (resultLogger != null) - { - Result.SetLogger(null); - resultLogger.Dispose(); - } - } - - return true; } - - private static void RunTask(Context ctx) + finally { - switch (ctx.Options.InFileType) + if (logObserver != null) { - case FileType.Nca: - ProcessNca.Process(ctx); - break; - case FileType.Pfs0: - case FileType.Nsp: - ProcessPfs.Process(ctx); - break; - case FileType.PfsBuild: - ProcessFsBuild.ProcessPartitionFs(ctx); - break; - case FileType.Romfs: - ProcessRomfs.Process(ctx); - break; - case FileType.RomfsBuild: - ProcessFsBuild.ProcessRomFs(ctx); - break; - case FileType.Nax0: - ProcessNax0.Process(ctx); - break; - case FileType.SwitchFs: - ProcessSwitchFs.Process(ctx); - break; - case FileType.Save: - ProcessSave.Process(ctx); - break; - case FileType.Xci: - ProcessXci.Process(ctx); - break; - case FileType.Keygen: - ProcessKeygen(ctx); - break; - case FileType.Pk11: - ProcessPackage.ProcessPk11(ctx); - break; - case FileType.Pk21: - ProcessPackage.ProcessPk21(ctx); - break; - case FileType.Kip1: - ProcessKip.ProcessKip1(ctx); - break; - case FileType.Ini1: - ProcessKip.ProcessIni1(ctx); - break; - case FileType.Ndv0: - ProcessDelta.Process(ctx); - break; - case FileType.Bench: - ProcessBench.Process(ctx); - break; - default: - throw new ArgumentOutOfRangeException(); + ctx.Horizon.Diag.UnregisterLogObserver(logObserver); + } + + logWriter?.Dispose(); + + if (resultLogger != null) + { + Result.SetLogger(null); + resultLogger.Dispose(); } } - private static void OpenKeySet(Context ctx) + return true; + } + + private static void RunTask(Context ctx) + { + switch (ctx.Options.InFileType) { + case FileType.Nca: + ProcessNca.Process(ctx); + break; + case FileType.Pfs0: + case FileType.Nsp: + ProcessPfs.Process(ctx); + break; + case FileType.PfsBuild: + ProcessFsBuild.ProcessPartitionFs(ctx); + break; + case FileType.Romfs: + ProcessRomfs.Process(ctx); + break; + case FileType.RomfsBuild: + ProcessFsBuild.ProcessRomFs(ctx); + break; + case FileType.Nax0: + ProcessNax0.Process(ctx); + break; + case FileType.SwitchFs: + ProcessSwitchFs.Process(ctx); + break; + case FileType.Save: + ProcessSave.Process(ctx); + break; + case FileType.Xci: + ProcessXci.Process(ctx); + break; + case FileType.Keygen: + ProcessKeygen(ctx); + break; + case FileType.Pk11: + ProcessPackage.ProcessPk11(ctx); + break; + case FileType.Pk21: + ProcessPackage.ProcessPk21(ctx); + break; + case FileType.Kip1: + ProcessKip.ProcessKip1(ctx); + break; + case FileType.Ini1: + ProcessKip.ProcessIni1(ctx); + break; + case FileType.Ndv0: + ProcessDelta.Process(ctx); + break; + case FileType.Bench: + ProcessBench.Process(ctx); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void OpenKeySet(Context ctx) + { #if CORERT_NO_REFLECTION string home = HomeFolder.GetFolderPath(Environment.SpecialFolder.UserProfile); #else - string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); #endif - string homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys"); - string homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys"); + string homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys"); + string homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys"); - string prodKeyFile = Path.Combine(home, ".switch", "prod.keys"); - string devKeyFile = Path.Combine(home, ".switch", "dev.keys"); - string titleKeyFile = ctx.Options.TitleKeyFile; - string consoleKeyFile = ctx.Options.ConsoleKeyFile; + string prodKeyFile = Path.Combine(home, ".switch", "prod.keys"); + string devKeyFile = Path.Combine(home, ".switch", "dev.keys"); + string titleKeyFile = ctx.Options.TitleKeyFile; + string consoleKeyFile = ctx.Options.ConsoleKeyFile; - // Check if the files from the command line exist - if (titleKeyFile != null && !File.Exists(titleKeyFile)) - titleKeyFile = null; + // Check if the files from the command line exist + if (titleKeyFile != null && !File.Exists(titleKeyFile)) + titleKeyFile = null; - if (consoleKeyFile != null && !File.Exists(consoleKeyFile)) - consoleKeyFile = null; + if (consoleKeyFile != null && !File.Exists(consoleKeyFile)) + consoleKeyFile = null; - if (!File.Exists(prodKeyFile)) - prodKeyFile = null; + if (!File.Exists(prodKeyFile)) + prodKeyFile = null; - if (!File.Exists(devKeyFile)) - devKeyFile = null; + if (!File.Exists(devKeyFile)) + devKeyFile = null; - // Check the home directory if no existing key files were specified - if (consoleKeyFile == null && File.Exists(homeConsoleKeyFile)) - consoleKeyFile = homeConsoleKeyFile; + // Check the home directory if no existing key files were specified + if (consoleKeyFile == null && File.Exists(homeConsoleKeyFile)) + consoleKeyFile = homeConsoleKeyFile; - if (titleKeyFile == null && File.Exists(homeTitleKeyFile)) - titleKeyFile = homeTitleKeyFile; + if (titleKeyFile == null && File.Exists(homeTitleKeyFile)) + titleKeyFile = homeTitleKeyFile; - var keySet = KeySet.CreateDefaultKeySet(); - - // If the user specifies a key file then only load that file into the mode they specified, - // otherwise load both prod.keys and dev.keys. - // Todo: Should we add a way that both dev-only key files and mixed prod/dev key files - // can both be loaded when specifying a key file in dev mode? - if (ctx.Options.Keyfile != null && File.Exists(ctx.Options.Keyfile)) - { - keySet.SetMode(ctx.Options.KeyMode); - ExternalKeyReader.ReadKeyFile(keySet, ctx.Options.Keyfile, titleKeyFile, consoleKeyFile, ctx.Logger); - } - else - { - ExternalKeyReader.ReadKeyFile(keySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, ctx.Logger); - } + var keySet = KeySet.CreateDefaultKeySet(); + // If the user specifies a key file then only load that file into the mode they specified, + // otherwise load both prod.keys and dev.keys. + // Todo: Should we add a way that both dev-only key files and mixed prod/dev key files + // can both be loaded when specifying a key file in dev mode? + if (ctx.Options.Keyfile != null && File.Exists(ctx.Options.Keyfile)) + { keySet.SetMode(ctx.Options.KeyMode); - - if (ctx.Options.SdSeed != null) - { - keySet.SetSdSeed(ctx.Options.SdSeed.ToBytes()); - } - - ctx.KeySet = keySet; + ExternalKeyReader.ReadKeyFile(keySet, ctx.Options.Keyfile, titleKeyFile, consoleKeyFile, ctx.Logger); + } + else + { + ExternalKeyReader.ReadKeyFile(keySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, ctx.Logger); } - private static void InitializeHorizon(Context ctx) + keySet.SetMode(ctx.Options.KeyMode); + + if (ctx.Options.SdSeed != null) { - var horizon = new Horizon(new HorizonConfiguration()); - ctx.Horizon = horizon.CreatePrivilegedHorizonClient(); - ctx.Horizon.Fs.SetServerlessAccessLog(true); + keySet.SetSdSeed(ctx.Options.SdSeed.ToBytes()); } - private static void ProcessKeygen(Context ctx) + ctx.KeySet = keySet; + } + + private static void InitializeHorizon(Context ctx) + { + var horizon = new Horizon(new HorizonConfiguration()); + ctx.Horizon = horizon.CreatePrivilegedHorizonClient(); + ctx.Horizon.Fs.SetServerlessAccessLog(true); + } + + private static void ProcessKeygen(Context ctx) + { + Console.WriteLine(ExternalKeyWriter.PrintAllKeys(ctx.KeySet)); + + if (ctx.Options.OutDir != null) { - Console.WriteLine(ExternalKeyWriter.PrintAllKeys(ctx.KeySet)); + KeySet.Mode originalMode = ctx.KeySet.CurrentMode; - if (ctx.Options.OutDir != null) - { - KeySet.Mode originalMode = ctx.KeySet.CurrentMode; + string dir = ctx.Options.OutDir; + Directory.CreateDirectory(dir); - string dir = ctx.Options.OutDir; - Directory.CreateDirectory(dir); + ctx.KeySet.SetMode(KeySet.Mode.Prod); + File.WriteAllText(Path.Combine(dir, "prod.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); - ctx.KeySet.SetMode(KeySet.Mode.Prod); - File.WriteAllText(Path.Combine(dir, "prod.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); + ctx.KeySet.SetMode(KeySet.Mode.Dev); + File.WriteAllText(Path.Combine(dir, "dev.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); - ctx.KeySet.SetMode(KeySet.Mode.Dev); - File.WriteAllText(Path.Combine(dir, "dev.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); - - ctx.KeySet.SetMode(originalMode); - File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyWriter.PrintDeviceKeys(ctx.KeySet)); - File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyWriter.PrintTitleKeys(ctx.KeySet)); - - File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), - ExternalKeyWriter.PrintCommonKeysWithDev(ctx.KeySet)); - } - } - - // For running random stuff - // ReSharper disable once UnusedParameter.Local - private static void CustomTask(Context ctx) - { + ctx.KeySet.SetMode(originalMode); + File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyWriter.PrintDeviceKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyWriter.PrintTitleKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), + ExternalKeyWriter.PrintCommonKeysWithDev(ctx.KeySet)); } } + + // For running random stuff + // ReSharper disable once UnusedParameter.Local + private static void CustomTask(Context ctx) + { + + } } diff --git a/src/hactoolnet/ResultLogger.cs b/src/hactoolnet/ResultLogger.cs index 69674a50..8ac621fb 100644 --- a/src/hactoolnet/ResultLogger.cs +++ b/src/hactoolnet/ResultLogger.cs @@ -6,232 +6,231 @@ using LibHac; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace hactoolnet +namespace hactoolnet; + +internal class ResultLogger : Result.IResultLogger, IDisposable { - internal class ResultLogger : Result.IResultLogger, IDisposable + private TextWriter Writer { get; } + private bool PrintStackTrace { get; } + private bool PrintSourceInfo { get; } + private bool CombineRepeats { get; } + + private LogEntry _pendingEntry; + private bool LastEntryPrintedNewLine { get; set; } = true; + + public ResultLogger(TextWriter writer, bool printStackTrace, bool printSourceInfo, bool combineRepeats) { - private TextWriter Writer { get; } - private bool PrintStackTrace { get; } - private bool PrintSourceInfo { get; } - private bool CombineRepeats { get; } + Writer = writer ?? throw new ArgumentNullException(nameof(writer)); + PrintStackTrace = printStackTrace; + PrintSourceInfo = printSourceInfo; + CombineRepeats = combineRepeats; - private LogEntry _pendingEntry; - private bool LastEntryPrintedNewLine { get; set; } = true; + bool isDebugMode = false; + CheckIfDebugMode(ref isDebugMode); - public ResultLogger(TextWriter writer, bool printStackTrace, bool printSourceInfo, bool combineRepeats) + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (!isDebugMode) { - Writer = writer ?? throw new ArgumentNullException(nameof(writer)); - PrintStackTrace = printStackTrace; - PrintSourceInfo = printSourceInfo; - CombineRepeats = combineRepeats; + Writer.WriteLine("The result log is only enabled when running in debug mode."); + } + } - bool isDebugMode = false; - CheckIfDebugMode(ref isDebugMode); + public void LogResult(Result result) + { + StackTrace st = GetStackTrace(); + MethodBase method = st.GetFrame(0)?.GetMethod(); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (!isDebugMode) + if (method is null) + return; + + // This result from these functions is usually noise because they + // are frequently used to detect if a file exists + if (ResultFs.PathNotFound.Includes(result) && + typeof(IFileSystem).IsAssignableFrom(method.DeclaringType) && + method.Name.StartsWith(nameof(IFileSystem.GetEntryType)) || + method.Name.StartsWith(nameof(IAttributeFileSystem.GetFileAttributes))) + { + return; + } + + AddLogEntry(new LogEntry(result, st)); + } + + public void LogConvertedResult(Result result, Result originalResult) + { + StackTrace st = GetStackTrace(); + + AddLogEntry(new LogEntry(result, st, originalResult)); + } + + private void AddLogEntry(LogEntry entry) + { + if (CombineRepeats && _pendingEntry.IsRepeat(entry, PrintStackTrace && !entry.IsConvertedResult)) + { + _pendingEntry.TimesCalled++; + return; + } + + PrintPendingEntry(); + + if (CombineRepeats) + { + _pendingEntry = entry; + } + else + { + PrintLogEntry(entry); + } + } + + private void PrintPendingEntry() + { + if (_pendingEntry.StackTrace != null) + { + PrintLogEntry(_pendingEntry); + _pendingEntry = default; + } + } + + private void PrintLogEntry(LogEntry entry) + { + MethodBase method = entry.StackTrace.GetFrame(0)?.GetMethod(); + + if (method is null) + return; + + string methodName = $"{method.DeclaringType?.FullName}.{method.Name}"; + + bool printStackTrace = PrintStackTrace && !entry.IsConvertedResult; + + // Make sure there's a new line if printing a stack trace + // A stack trace includes a new line at the end of it, so add the new line only if needed + string entryText = printStackTrace && !LastEntryPrintedNewLine ? Environment.NewLine : string.Empty; + + string lineNumber = entry.LineNumber > 0 ? $":line{entry.LineNumber}" : string.Empty; + + if (entry.IsConvertedResult) + { + entryText += $"{entry.OriginalResult.ToStringWithName()} was converted to {entry.Result.ToStringWithName()} by {methodName}{lineNumber}"; + } + else + { + entryText += $"{entry.Result.ToStringWithName()} was returned by {methodName}{lineNumber}"; + } + + if (entry.TimesCalled > 1) + { + entryText += $" {entry.TimesCalled} times"; + } + + Writer.WriteLine(entryText); + + if (printStackTrace) + { + Writer.WriteLine(entry.StackTraceText); + } + + LastEntryPrintedNewLine = printStackTrace; + } + + // Returns the stack trace starting at the method that called Log() + private StackTrace GetStackTrace() + { + var st = new StackTrace(); + int framesToSkip = 0; + + for (; framesToSkip < st.FrameCount; framesToSkip++) + { + Type declaringType = st.GetFrame(framesToSkip)?.GetMethod()?.DeclaringType; + + if (declaringType == null) { - Writer.WriteLine("The result log is only enabled when running in debug mode."); + framesToSkip = 0; + break; + } + + if (declaringType != typeof(ResultLogger) && + declaringType != typeof(Result) && + declaringType != typeof(Result.Base)) + { + break; } } - public void LogResult(Result result) + return new StackTrace(framesToSkip, PrintSourceInfo); + } + + // You can't negate a conditional attribute, so this is a hacky workaround + [Conditional("DEBUG")] + // ReSharper disable once RedundantAssignment + private void CheckIfDebugMode(ref bool isDebugMode) + { + isDebugMode = true; + } + + public void Dispose() + { + PrintPendingEntry(); + Writer.Dispose(); + } + + private struct LogEntry + { + public Result Result { get; } + public Result OriginalResult { get; } + public string CallingMethod { get; } + public StackTrace StackTrace { get; } + public string StackTraceText { get; } + + // Line number will be 0 if there's no source info + public int LineNumber { get; } + public int TimesCalled { get; set; } + public bool IsConvertedResult { get; } + + public LogEntry(Result result, StackTrace stackTrace) : this(result, stackTrace, false, default) { } + public LogEntry(Result result, StackTrace stackTrace, Result originalResult) : this(result, stackTrace, true, originalResult) { } + + private LogEntry(Result result, StackTrace stackTrace, bool isConverted, Result originalResult) { - StackTrace st = GetStackTrace(); - MethodBase method = st.GetFrame(0)?.GetMethod(); + Result = result; + StackTrace = stackTrace; + IsConvertedResult = isConverted; + OriginalResult = originalResult; + + MethodBase method = stackTrace.GetFrame(0)?.GetMethod(); if (method is null) - return; - - // This result from these functions is usually noise because they - // are frequently used to detect if a file exists - if (ResultFs.PathNotFound.Includes(result) && - typeof(IFileSystem).IsAssignableFrom(method.DeclaringType) && - method.Name.StartsWith(nameof(IFileSystem.GetEntryType)) || - method.Name.StartsWith(nameof(IAttributeFileSystem.GetFileAttributes))) { - return; - } - - AddLogEntry(new LogEntry(result, st)); - } - - public void LogConvertedResult(Result result, Result originalResult) - { - StackTrace st = GetStackTrace(); - - AddLogEntry(new LogEntry(result, st, originalResult)); - } - - private void AddLogEntry(LogEntry entry) - { - if (CombineRepeats && _pendingEntry.IsRepeat(entry, PrintStackTrace && !entry.IsConvertedResult)) - { - _pendingEntry.TimesCalled++; - return; - } - - PrintPendingEntry(); - - if (CombineRepeats) - { - _pendingEntry = entry; - } - else - { - PrintLogEntry(entry); - } - } - - private void PrintPendingEntry() - { - if (_pendingEntry.StackTrace != null) - { - PrintLogEntry(_pendingEntry); - _pendingEntry = default; - } - } - - private void PrintLogEntry(LogEntry entry) - { - MethodBase method = entry.StackTrace.GetFrame(0)?.GetMethod(); - - if (method is null) - return; - - string methodName = $"{method.DeclaringType?.FullName}.{method.Name}"; - - bool printStackTrace = PrintStackTrace && !entry.IsConvertedResult; - - // Make sure there's a new line if printing a stack trace - // A stack trace includes a new line at the end of it, so add the new line only if needed - string entryText = printStackTrace && !LastEntryPrintedNewLine ? Environment.NewLine : string.Empty; - - string lineNumber = entry.LineNumber > 0 ? $":line{entry.LineNumber}" : string.Empty; - - if (entry.IsConvertedResult) - { - entryText += $"{entry.OriginalResult.ToStringWithName()} was converted to {entry.Result.ToStringWithName()} by {methodName}{lineNumber}"; - } - else - { - entryText += $"{entry.Result.ToStringWithName()} was returned by {methodName}{lineNumber}"; - } - - if (entry.TimesCalled > 1) - { - entryText += $" {entry.TimesCalled} times"; - } - - Writer.WriteLine(entryText); - - if (printStackTrace) - { - Writer.WriteLine(entry.StackTraceText); - } - - LastEntryPrintedNewLine = printStackTrace; - } - - // Returns the stack trace starting at the method that called Log() - private StackTrace GetStackTrace() - { - var st = new StackTrace(); - int framesToSkip = 0; - - for (; framesToSkip < st.FrameCount; framesToSkip++) - { - Type declaringType = st.GetFrame(framesToSkip)?.GetMethod()?.DeclaringType; - - if (declaringType == null) - { - framesToSkip = 0; - break; - } - - if (declaringType != typeof(ResultLogger) && - declaringType != typeof(Result) && - declaringType != typeof(Result.Base)) - { - break; - } - } - - return new StackTrace(framesToSkip, PrintSourceInfo); - } - - // You can't negate a conditional attribute, so this is a hacky workaround - [Conditional("DEBUG")] - // ReSharper disable once RedundantAssignment - private void CheckIfDebugMode(ref bool isDebugMode) - { - isDebugMode = true; - } - - public void Dispose() - { - PrintPendingEntry(); - Writer.Dispose(); - } - - private struct LogEntry - { - public Result Result { get; } - public Result OriginalResult { get; } - public string CallingMethod { get; } - public StackTrace StackTrace { get; } - public string StackTraceText { get; } - - // Line number will be 0 if there's no source info - public int LineNumber { get; } - public int TimesCalled { get; set; } - public bool IsConvertedResult { get; } - - public LogEntry(Result result, StackTrace stackTrace) : this(result, stackTrace, false, default) { } - public LogEntry(Result result, StackTrace stackTrace, Result originalResult) : this(result, stackTrace, true, originalResult) { } - - private LogEntry(Result result, StackTrace stackTrace, bool isConverted, Result originalResult) - { - Result = result; - StackTrace = stackTrace; - IsConvertedResult = isConverted; - OriginalResult = originalResult; - - MethodBase method = stackTrace.GetFrame(0)?.GetMethod(); - - if (method is null) - { - CallingMethod = string.Empty; - StackTraceText = string.Empty; - LineNumber = 0; - TimesCalled = 1; - - return; - } - - CallingMethod = $"{method.DeclaringType?.FullName}.{method.Name}"; - - StackTraceText = stackTrace.ToString(); - LineNumber = stackTrace.GetFrame(0)?.GetFileLineNumber() ?? 0; + CallingMethod = string.Empty; + StackTraceText = string.Empty; + LineNumber = 0; TimesCalled = 1; + + return; } - public bool IsRepeat(LogEntry other, bool compareByStackTrace) + CallingMethod = $"{method.DeclaringType?.FullName}.{method.Name}"; + + StackTraceText = stackTrace.ToString(); + LineNumber = stackTrace.GetFrame(0)?.GetFileLineNumber() ?? 0; + TimesCalled = 1; + } + + public bool IsRepeat(LogEntry other, bool compareByStackTrace) + { + if (Result != other.Result || + IsConvertedResult != other.IsConvertedResult || + (IsConvertedResult && OriginalResult != other.OriginalResult)) { - if (Result != other.Result || - IsConvertedResult != other.IsConvertedResult || - (IsConvertedResult && OriginalResult != other.OriginalResult)) - { - return false; - } - - if (compareByStackTrace) - { - return StackTraceText == other.StackTraceText; - } - - return LineNumber == other.LineNumber && CallingMethod == other.CallingMethod; + return false; } + + if (compareByStackTrace) + { + return StackTraceText == other.StackTraceText; + } + + return LineNumber == other.LineNumber && CallingMethod == other.CallingMethod; } } } diff --git a/src/hactoolnet/ResultNameResolver.cs b/src/hactoolnet/ResultNameResolver.cs index 47f5bca1..6890e161 100644 --- a/src/hactoolnet/ResultNameResolver.cs +++ b/src/hactoolnet/ResultNameResolver.cs @@ -4,37 +4,36 @@ using System.Linq; using System.Reflection; using LibHac; -namespace hactoolnet +namespace hactoolnet; + +internal class ResultNameResolver : Result.IResultNameResolver { - internal class ResultNameResolver : Result.IResultNameResolver + private Lazy<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames); + + public bool TryResolveName(Result result, out string name) { - private Lazy<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames); + return ResultNames.Value.TryGetValue(result, out name); + } - public bool TryResolveName(Result result, out string name) - { - return ResultNames.Value.TryGetValue(result, out name); - } + private static Dictionary<Result, string> GetResultNames() + { + var dict = new Dictionary<Result, string>(); - private static Dictionary<Result, string> GetResultNames() - { - var dict = new Dictionary<Result, string>(); + Assembly assembly = typeof(Result).Assembly; - Assembly assembly = typeof(Result).Assembly; + foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) + foreach (PropertyInfo property in type.DeclaredProperties + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) + { + object value = property.GetValue(null, null); + if (value is null) continue; - foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) - foreach (PropertyInfo property in type.DeclaredProperties - .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) - { - object value = property.GetValue(null, null); - if (value is null) continue; + Result resultValue = ((Result.Base)value).Value; + string name = $"{type.Name}{property.Name}"; - Result resultValue = ((Result.Base)value).Value; - string name = $"{type.Name}{property.Name}"; + dict[resultValue] = name; + } - dict[resultValue] = name; - } - - return dict; - } + return dict; } } diff --git a/src/hactoolnet/TableBuilder.cs b/src/hactoolnet/TableBuilder.cs index d889ad6c..a386ad04 100644 --- a/src/hactoolnet/TableBuilder.cs +++ b/src/hactoolnet/TableBuilder.cs @@ -2,58 +2,57 @@ using System.Collections.Generic; using System.Text; -namespace hactoolnet +namespace hactoolnet; + +public class TableBuilder { - public class TableBuilder + private List<string[]> Rows { get; } = new List<string[]>(); + private int ColumnCount { get; set; } + + public TableBuilder(params string[] header) { - private List<string[]> Rows { get; } = new List<string[]>(); - private int ColumnCount { get; set; } - - public TableBuilder(params string[] header) - { - ColumnCount = header.Length; - Rows.Add(header); - } - - public TableBuilder(int columnCount) - { - ColumnCount = columnCount; - } - - public void AddRow(params string[] row) - { - if (row.Length != ColumnCount) - { - throw new ArgumentOutOfRangeException(nameof(row), "All rows must have the same number of columns"); - } - - Rows.Add(row); - } - - public string Print() - { - var sb = new StringBuilder(); - int[] width = new int[ColumnCount]; - - foreach (string[] row in Rows) - { - for (int i = 0; i < ColumnCount - 1; i++) - { - width[i] = Math.Max(width[i], row[i]?.Length ?? 0); - } - } - - foreach (string[] row in Rows) - { - for (int i = 0; i < ColumnCount; i++) - { - sb.Append($"{(row[i] ?? string.Empty).PadRight(width[i] + 1, ' ')}"); - } - - sb.AppendLine(); - } - - return sb.ToString(); - } + ColumnCount = header.Length; + Rows.Add(header); } -} \ No newline at end of file + + public TableBuilder(int columnCount) + { + ColumnCount = columnCount; + } + + public void AddRow(params string[] row) + { + if (row.Length != ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(row), "All rows must have the same number of columns"); + } + + Rows.Add(row); + } + + public string Print() + { + var sb = new StringBuilder(); + int[] width = new int[ColumnCount]; + + foreach (string[] row in Rows) + { + for (int i = 0; i < ColumnCount - 1; i++) + { + width[i] = Math.Max(width[i], row[i]?.Length ?? 0); + } + } + + foreach (string[] row in Rows) + { + for (int i = 0; i < ColumnCount; i++) + { + sb.Append($"{(row[i] ?? string.Empty).PadRight(width[i] + 1, ' ')}"); + } + + sb.AppendLine(); + } + + return sb.ToString(); + } +} diff --git a/src/hactoolnet/hactoolnet.csproj b/src/hactoolnet/hactoolnet.csproj index b9968327..206f6822 100644 --- a/src/hactoolnet/hactoolnet.csproj +++ b/src/hactoolnet/hactoolnet.csproj @@ -3,6 +3,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> + <LangVersion>10.0</LangVersion> </PropertyGroup> <PropertyGroup> diff --git a/tests/LibHac.Tests/AesCmac.cs b/tests/LibHac.Tests/AesCmac.cs index 200f7740..41f45ef9 100644 --- a/tests/LibHac.Tests/AesCmac.cs +++ b/tests/LibHac.Tests/AesCmac.cs @@ -3,11 +3,11 @@ using LibHac.Crypto; using LibHac.Util; using Xunit; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class AesCmac { - public class AesCmac - { - public static readonly TheoryData<TestData> TestVectors = new TheoryData<TestData> + public static readonly TheoryData<TestData> TestVectors = new TheoryData<TestData> { new TestData { @@ -54,24 +54,23 @@ namespace LibHac.Tests } }; - [Theory] - [MemberData(nameof(TestVectors))] - public static void TestCmacTestVectors(TestData data) - { - byte[] actual = new byte[0x10]; + [Theory] + [MemberData(nameof(TestVectors))] + public static void TestCmacTestVectors(TestData data) + { + byte[] actual = new byte[0x10]; - Aes.CalculateCmac(actual, data.Message.AsSpan(data.Start, data.Length), data.Key); + Aes.CalculateCmac(actual, data.Message.AsSpan(data.Start, data.Length), data.Key); - Assert.Equal(data.Expected, actual); - } + Assert.Equal(data.Expected, actual); + } - public struct TestData - { - public byte[] Key; - public byte[] Message; - public byte[] Expected; - public int Start; - public int Length; - } + public struct TestData + { + public byte[] Key; + public byte[] Message; + public byte[] Expected; + public int Start; + public int Length; } } diff --git a/tests/LibHac.Tests/AesXts.cs b/tests/LibHac.Tests/AesXts.cs index ef36380d..48485847 100644 --- a/tests/LibHac.Tests/AesXts.cs +++ b/tests/LibHac.Tests/AesXts.cs @@ -3,11 +3,11 @@ using LibHac.FsSystem; using LibHac.Util; using Xunit; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class AesXts { - public class AesXts - { - public static readonly TheoryData<TestData> TestVectors = new TheoryData<TestData> + public static readonly TheoryData<TestData> TestVectors = new TheoryData<TestData> { // #1 32 byte key, 32 byte PTX new TestData @@ -110,33 +110,32 @@ namespace LibHac.Tests } }; - [Theory] - [MemberData(nameof(TestVectors))] - public static void Encrypt(TestData data) => TestTransform(data, false); + [Theory] + [MemberData(nameof(TestVectors))] + public static void Encrypt(TestData data) => TestTransform(data, false); - [Theory] - [MemberData(nameof(TestVectors))] - public static void Decrypt(TestData data) => TestTransform(data, true); + [Theory] + [MemberData(nameof(TestVectors))] + public static void Decrypt(TestData data) => TestTransform(data, true); - private static void TestTransform(TestData data, bool decrypting) - { - var transform = new Aes128XtsTransform(data.Key1, data.Key2, decrypting); - byte[] transformed = data.GetInitialText(decrypting).ToArray(); + private static void TestTransform(TestData data, bool decrypting) + { + var transform = new Aes128XtsTransform(data.Key1, data.Key2, decrypting); + byte[] transformed = data.GetInitialText(decrypting).ToArray(); - transform.TransformBlock(transformed, 0, transformed.Length, data.Sector); - Assert.Equal(data.GetTransformedText(decrypting), transformed); - } + transform.TransformBlock(transformed, 0, transformed.Length, data.Sector); + Assert.Equal(data.GetTransformedText(decrypting), transformed); + } - public struct TestData - { - public byte[] CipherText; - public byte[] PlainText; - public byte[] Key1; - public byte[] Key2; - public ulong Sector; + public struct TestData + { + public byte[] CipherText; + public byte[] PlainText; + public byte[] Key1; + public byte[] Key2; + public ulong Sector; - public byte[] GetInitialText(bool decrypting) => decrypting ? CipherText : PlainText; - public byte[] GetTransformedText(bool decrypting) => decrypting ? PlainText : CipherText; - } + public byte[] GetInitialText(bool decrypting) => decrypting ? CipherText : PlainText; + public byte[] GetTransformedText(bool decrypting) => decrypting ? PlainText : CipherText; } } diff --git a/tests/LibHac.Tests/Boot/TypeSizeTests.cs b/tests/LibHac.Tests/Boot/TypeSizeTests.cs index 19a93762..7b62c316 100644 --- a/tests/LibHac.Tests/Boot/TypeSizeTests.cs +++ b/tests/LibHac.Tests/Boot/TypeSizeTests.cs @@ -3,20 +3,19 @@ using System.Runtime.CompilerServices; using LibHac.Boot; using Xunit; -namespace LibHac.Tests.Boot -{ - public class TypeSizeTests - { - [Fact] - public static void EncryptedKeyBlobSizeIs0xB0() - { - Assert.Equal(0xB0, Unsafe.SizeOf<EncryptedKeyBlob>()); - } +namespace LibHac.Tests.Boot; - [Fact] - public static void KeyBlobSizeIs0x90() - { - Assert.Equal(0x90, Unsafe.SizeOf<KeyBlob>()); - } +public class TypeSizeTests +{ + [Fact] + public static void EncryptedKeyBlobSizeIs0xB0() + { + Assert.Equal(0xB0, Unsafe.SizeOf<EncryptedKeyBlob>()); + } + + [Fact] + public static void KeyBlobSizeIs0x90() + { + Assert.Equal(0x90, Unsafe.SizeOf<KeyBlob>()); } } diff --git a/tests/LibHac.Tests/BufferStructTests.cs b/tests/LibHac.Tests/BufferStructTests.cs index 9f60e3d2..a9236981 100644 --- a/tests/LibHac.Tests/BufferStructTests.cs +++ b/tests/LibHac.Tests/BufferStructTests.cs @@ -4,85 +4,84 @@ using System.Runtime.InteropServices; using LibHac.Common; using Xunit; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class BufferStructTests { - public class BufferStructTests + [Fact] + public static void BufferIndexer() { - [Fact] - public static void BufferIndexer() - { - var buffer = new Buffer16(); + var buffer = new Buffer16(); - buffer[0] = 5; - buffer[1] = 6; + buffer[0] = 5; + buffer[1] = 6; - Assert.Equal(5, buffer[0]); - Assert.Equal(6, buffer[1]); - } - - [Fact] - public static void CastBufferToByteSpan() - { - var buffer = new Buffer16(); - - Span<byte> byteSpan = buffer.Bytes; - - Assert.Equal(16, byteSpan.Length); - Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0])); - } - - [Fact] - public static void CastBufferToByteSpanImplicit() - { - var buffer = new Buffer16(); - - Span<byte> byteSpan = buffer; - - Assert.Equal(16, byteSpan.Length); - Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0])); - } - - [Fact] - public static void CastBufferToReadOnlyByteSpanImplicit() - { - var buffer = new Buffer16(); - - ReadOnlySpan<byte> byteSpan = buffer; - - Assert.Equal(16, byteSpan.Length); - Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref Unsafe.AsRef(byteSpan[0]))); - } - - [Fact] - public static void CastBufferToSpan() - { - var buffer = new Buffer16(); - - Span<ulong> ulongSpan = buffer.AsSpan<ulong>(); - - Assert.Equal(2, ulongSpan.Length); - Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan[0])); - } - - [Fact] - public static void CastBufferToStruct() - { - var buffer = new Buffer16(); - - ref ulong ulongSpan = ref buffer.As<ulong>(); - - Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan)); - } - - [Fact] - public static void CastBufferToLargerStruct() - { - var buffer = new Buffer16(); - - Assert.Throws<ArgumentException>(() => buffer.As<Struct32Bytes>()); - } - - [StructLayout(LayoutKind.Sequential, Size = 32)] - private struct Struct32Bytes { } + Assert.Equal(5, buffer[0]); + Assert.Equal(6, buffer[1]); } + + [Fact] + public static void CastBufferToByteSpan() + { + var buffer = new Buffer16(); + + Span<byte> byteSpan = buffer.Bytes; + + Assert.Equal(16, byteSpan.Length); + Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0])); + } + + [Fact] + public static void CastBufferToByteSpanImplicit() + { + var buffer = new Buffer16(); + + Span<byte> byteSpan = buffer; + + Assert.Equal(16, byteSpan.Length); + Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0])); + } + + [Fact] + public static void CastBufferToReadOnlyByteSpanImplicit() + { + var buffer = new Buffer16(); + + ReadOnlySpan<byte> byteSpan = buffer; + + Assert.Equal(16, byteSpan.Length); + Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref Unsafe.AsRef(byteSpan[0]))); + } + + [Fact] + public static void CastBufferToSpan() + { + var buffer = new Buffer16(); + + Span<ulong> ulongSpan = buffer.AsSpan<ulong>(); + + Assert.Equal(2, ulongSpan.Length); + Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan[0])); + } + + [Fact] + public static void CastBufferToStruct() + { + var buffer = new Buffer16(); + + ref ulong ulongSpan = ref buffer.As<ulong>(); + + Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan)); + } + + [Fact] + public static void CastBufferToLargerStruct() + { + var buffer = new Buffer16(); + + Assert.Throws<ArgumentException>(() => buffer.As<Struct32Bytes>()); + } + + [StructLayout(LayoutKind.Sequential, Size = 32)] + private struct Struct32Bytes { } } diff --git a/tests/LibHac.Tests/Common/FixedArraySizeTests.cs b/tests/LibHac.Tests/Common/FixedArraySizeTests.cs index d17bbb64..19ae0fce 100644 --- a/tests/LibHac.Tests/Common/FixedArraySizeTests.cs +++ b/tests/LibHac.Tests/Common/FixedArraySizeTests.cs @@ -2,40 +2,39 @@ using LibHac.Common.FixedArrays; using Xunit; -namespace LibHac.Tests.Common +namespace LibHac.Tests.Common; + +public class FixedArraySizeTests { - public class FixedArraySizeTests - { - [Fact] public static void Array1SpanSizeIsCorrect() => Assert.Equal(1, new Array1<byte>().Items.Length); - [Fact] public static void Array1ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 1, Unsafe.SizeOf<Array1<byte>>()); - [Fact] public static void Array1LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 1, Unsafe.SizeOf<Array1<long>>()); + [Fact] public static void Array1SpanSizeIsCorrect() => Assert.Equal(1, new Array1<byte>().Items.Length); + [Fact] public static void Array1ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 1, Unsafe.SizeOf<Array1<byte>>()); + [Fact] public static void Array1LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 1, Unsafe.SizeOf<Array1<long>>()); - [Fact] public static void Array3SpanSizeIsCorrect() => Assert.Equal(3, new Array3<byte>().Items.Length); - [Fact] public static void Array3ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 3, Unsafe.SizeOf<Array3<byte>>()); - [Fact] public static void Array3LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 3, Unsafe.SizeOf<Array3<long>>()); + [Fact] public static void Array3SpanSizeIsCorrect() => Assert.Equal(3, new Array3<byte>().Items.Length); + [Fact] public static void Array3ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 3, Unsafe.SizeOf<Array3<byte>>()); + [Fact] public static void Array3LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 3, Unsafe.SizeOf<Array3<long>>()); - [Fact] public static void Array8SpanSizeIsCorrect() => Assert.Equal(8, new Array8<byte>().Items.Length); - [Fact] public static void Array8ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 8, Unsafe.SizeOf<Array8<byte>>()); - [Fact] public static void Array8LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 8, Unsafe.SizeOf<Array8<long>>()); + [Fact] public static void Array8SpanSizeIsCorrect() => Assert.Equal(8, new Array8<byte>().Items.Length); + [Fact] public static void Array8ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 8, Unsafe.SizeOf<Array8<byte>>()); + [Fact] public static void Array8LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 8, Unsafe.SizeOf<Array8<long>>()); - [Fact] public static void Array12SpanSizeIsCorrect() => Assert.Equal(12, new Array12<byte>().Items.Length); - [Fact] public static void Array12ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 12, Unsafe.SizeOf<Array12<byte>>()); - [Fact] public static void Array12LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 12, Unsafe.SizeOf<Array12<long>>()); + [Fact] public static void Array12SpanSizeIsCorrect() => Assert.Equal(12, new Array12<byte>().Items.Length); + [Fact] public static void Array12ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 12, Unsafe.SizeOf<Array12<byte>>()); + [Fact] public static void Array12LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 12, Unsafe.SizeOf<Array12<long>>()); - [Fact] public static void Array32SpanSizeIsCorrect() => Assert.Equal(32, new Array32<byte>().Items.Length); - [Fact] public static void Array32ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 32, Unsafe.SizeOf<Array32<byte>>()); - [Fact] public static void Array32LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 32, Unsafe.SizeOf<Array32<long>>()); + [Fact] public static void Array32SpanSizeIsCorrect() => Assert.Equal(32, new Array32<byte>().Items.Length); + [Fact] public static void Array32ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 32, Unsafe.SizeOf<Array32<byte>>()); + [Fact] public static void Array32LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 32, Unsafe.SizeOf<Array32<long>>()); - [Fact] public static void Array64SpanSizeIsCorrect() => Assert.Equal(64, new Array64<byte>().Items.Length); - [Fact] public static void Array64ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 64, Unsafe.SizeOf<Array64<byte>>()); - [Fact] public static void Array64LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 64, Unsafe.SizeOf<Array64<long>>()); + [Fact] public static void Array64SpanSizeIsCorrect() => Assert.Equal(64, new Array64<byte>().Items.Length); + [Fact] public static void Array64ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 64, Unsafe.SizeOf<Array64<byte>>()); + [Fact] public static void Array64LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 64, Unsafe.SizeOf<Array64<long>>()); - [Fact] public static void Array128SpanSizeIsCorrect() => Assert.Equal(128, new Array128<byte>().Items.Length); - [Fact] public static void Array128ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 128, Unsafe.SizeOf<Array128<byte>>()); - [Fact] public static void Array128LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 128, Unsafe.SizeOf<Array128<long>>()); + [Fact] public static void Array128SpanSizeIsCorrect() => Assert.Equal(128, new Array128<byte>().Items.Length); + [Fact] public static void Array128ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 128, Unsafe.SizeOf<Array128<byte>>()); + [Fact] public static void Array128LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 128, Unsafe.SizeOf<Array128<long>>()); - [Fact] public static void Array256SpanSizeIsCorrect() => Assert.Equal(256, new Array256<byte>().Items.Length); - [Fact] public static void Array256ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 256, Unsafe.SizeOf<Array256<byte>>()); - [Fact] public static void Array256LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 256, Unsafe.SizeOf<Array256<long>>()); - } + [Fact] public static void Array256SpanSizeIsCorrect() => Assert.Equal(256, new Array256<byte>().Items.Length); + [Fact] public static void Array256ByteSizeIsCorrect() => Assert.Equal(sizeof(byte) * 256, Unsafe.SizeOf<Array256<byte>>()); + [Fact] public static void Array256LongSizeIsCorrect() => Assert.Equal(sizeof(long) * 256, Unsafe.SizeOf<Array256<long>>()); } diff --git a/tests/LibHac.Tests/CryptoTests/AesCbcTests.cs b/tests/LibHac.Tests/CryptoTests/AesCbcTests.cs index 5751e96c..a82c0141 100644 --- a/tests/LibHac.Tests/CryptoTests/AesCbcTests.cs +++ b/tests/LibHac.Tests/CryptoTests/AesCbcTests.cs @@ -1,136 +1,135 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class AesCbcTests { - public class AesCbcTests + public static EncryptionTestVector[] EncryptTestVectors = + RspReader.ReadEncryptionTestVectorsArray(true, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + + public static EncryptionTestVector[] DecryptTestVectors = + RspReader.ReadEncryptionTestVectorsArray(false, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + + public static EncryptionTestVector[] EncryptMultiTestVectors = + RspReader.ReadEncryptionTestVectorsArray(true, "CBCMMT128.rsp"); + + public static EncryptionTestVector[] DecryptMultiTestVectors = + RspReader.ReadEncryptionTestVectorsArray(false, "CBCMMT128.rsp"); + + [Fact] + public static void Encrypt() { - public static EncryptionTestVector[] EncryptTestVectors = - RspReader.ReadEncryptionTestVectorsArray(true, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv, true)); + } - public static EncryptionTestVector[] DecryptTestVectors = - RspReader.ReadEncryptionTestVectorsArray(false, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + [Fact] + public static void Decrypt() + { + Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv, true)); + } - public static EncryptionTestVector[] EncryptMultiTestVectors = - RspReader.ReadEncryptionTestVectorsArray(true, "CBCMMT128.rsp"); + [Fact] + public static void EncryptMulti() + { + Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv, true)); + } - public static EncryptionTestVector[] DecryptMultiTestVectors = - RspReader.ReadEncryptionTestVectorsArray(false, "CBCMMT128.rsp"); + [Fact] + public static void DecryptMulti() + { + Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv, true)); + } - [Fact] - public static void Encrypt() - { - Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv, true)); - } + [AesIntrinsicsRequiredFact] + public static void EncryptIntrinsics() + { + Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv)); + } - [Fact] - public static void Decrypt() - { - Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv, true)); - } + [AesIntrinsicsRequiredFact] + public static void DecryptIntrinsics() + { + Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv)); + } - [Fact] - public static void EncryptMulti() - { - Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv, true)); - } + [AesIntrinsicsRequiredFact] + public static void EncryptMultiIntrinsics() + { + Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv)); + } - [Fact] - public static void DecryptMulti() - { - Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv, true)); - } - - [AesIntrinsicsRequiredFact] - public static void EncryptIntrinsics() - { - Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv)); - } - - [AesIntrinsicsRequiredFact] - public static void DecryptIntrinsics() - { - Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv)); - } - - [AesIntrinsicsRequiredFact] - public static void EncryptMultiIntrinsics() - { - Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateCbcEncryptor(key, iv)); - } - - [AesIntrinsicsRequiredFact] - public static void DecryptMultiIntrinsics() - { - Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv)); - } + [AesIntrinsicsRequiredFact] + public static void DecryptMultiIntrinsics() + { + Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateCbcDecryptor(key, iv)); + } - // The above tests run all the test vectors in a single test to avoid having thousands of tests. - // Use the below tests if running each test vector as an individual test is needed. + // The above tests run all the test vectors in a single test to avoid having thousands of tests. + // Use the below tests if running each test vector as an individual test is needed. - // ReSharper disable InconsistentNaming + // ReSharper disable InconsistentNaming #pragma warning disable xUnit1013 // Public method should be marked as test - public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(true, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(true, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); - public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(false, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); + public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(false, "CBCVarKey128.rsp", "CBCVarTxt128.rsp", "CBCKeySbox128.rsp", "CBCGFSbox128.rsp"); - public static TheoryData<EncryptionTestVector> EncryptMultiTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(true, "CBCMMT128.rsp"); + public static TheoryData<EncryptionTestVector> EncryptMultiTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(true, "CBCMMT128.rsp"); - public static TheoryData<EncryptionTestVector> DecryptMultiTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(false, "CBCMMT128.rsp"); + public static TheoryData<EncryptionTestVector> DecryptMultiTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(false, "CBCMMT128.rsp"); - //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void Encrypt_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv, true)); - } + //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void Encrypt_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv, true)); + } - //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void Decrypt_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv, true)); - } + //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void Decrypt_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv, true)); + } - //[Theory, MemberData(nameof(EncryptMultiTestVectors_Individual))] - public static void EncryptMulti_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv, true)); - } + //[Theory, MemberData(nameof(EncryptMultiTestVectors_Individual))] + public static void EncryptMulti_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv, true)); + } - //[Theory, MemberData(nameof(DecryptMultiTestVectors_Individual))] - public static void DecryptMulti_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv, true)); - } + //[Theory, MemberData(nameof(DecryptMultiTestVectors_Individual))] + public static void DecryptMulti_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv, true)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptMultiTestVectors_Individual))] - public static void EncryptMultiIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptMultiTestVectors_Individual))] + public static void EncryptMultiIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCbcEncryptor(tv.Key, tv.Iv)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptMultiTestVectors_Individual))] - public static void DecryptMultiIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptMultiTestVectors_Individual))] + public static void DecryptMultiIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateCbcDecryptor(tv.Key, tv.Iv)); } } diff --git a/tests/LibHac.Tests/CryptoTests/AesCtrTests.cs b/tests/LibHac.Tests/CryptoTests/AesCtrTests.cs index 24fcd10d..981388cc 100644 --- a/tests/LibHac.Tests/CryptoTests/AesCtrTests.cs +++ b/tests/LibHac.Tests/CryptoTests/AesCtrTests.cs @@ -1,24 +1,23 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class AesCtrTests { - public class AesCtrTests + public static TheoryData<EncryptionTestVector> TestVectors = RspReader.ReadEncryptionTestVectors(true, "CTR128.rsp"); + + [Theory] + [MemberData(nameof(TestVectors))] + public static void Transform(EncryptionTestVector tv) { - public static TheoryData<EncryptionTestVector> TestVectors = RspReader.ReadEncryptionTestVectors(true, "CTR128.rsp"); + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCtrEncryptor(tv.Key, tv.Iv, true)); + } - [Theory] - [MemberData(nameof(TestVectors))] - public static void Transform(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCtrEncryptor(tv.Key, tv.Iv, true)); - } - - [AesIntrinsicsRequiredTheory] - [MemberData(nameof(TestVectors))] - public static void TransformIntrinsics(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCtrEncryptor(tv.Key, tv.Iv)); - } + [AesIntrinsicsRequiredTheory] + [MemberData(nameof(TestVectors))] + public static void TransformIntrinsics(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateCtrEncryptor(tv.Key, tv.Iv)); } } diff --git a/tests/LibHac.Tests/CryptoTests/AesEcbTests.cs b/tests/LibHac.Tests/CryptoTests/AesEcbTests.cs index 3c31586a..bdf21ddc 100644 --- a/tests/LibHac.Tests/CryptoTests/AesEcbTests.cs +++ b/tests/LibHac.Tests/CryptoTests/AesEcbTests.cs @@ -1,136 +1,135 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class AesEcbTests { - public class AesEcbTests + public static EncryptionTestVector[] EncryptTestVectors = + RspReader.ReadEncryptionTestVectorsArray(true, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + + public static EncryptionTestVector[] DecryptTestVectors = + RspReader.ReadEncryptionTestVectorsArray(false, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + + public static EncryptionTestVector[] EncryptMultiTestVectors = + RspReader.ReadEncryptionTestVectorsArray(true, "ECBMMT128.rsp"); + + public static EncryptionTestVector[] DecryptMultiTestVectors = + RspReader.ReadEncryptionTestVectorsArray(false, "ECBMMT128.rsp"); + + [Fact] + public static void Encrypt() { - public static EncryptionTestVector[] EncryptTestVectors = - RspReader.ReadEncryptionTestVectorsArray(true, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key, true)); + } - public static EncryptionTestVector[] DecryptTestVectors = - RspReader.ReadEncryptionTestVectorsArray(false, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + [Fact] + public static void Decrypt() + { + Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key, true)); + } - public static EncryptionTestVector[] EncryptMultiTestVectors = - RspReader.ReadEncryptionTestVectorsArray(true, "ECBMMT128.rsp"); + [Fact] + public static void EncryptMulti() + { + Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key, true)); + } - public static EncryptionTestVector[] DecryptMultiTestVectors = - RspReader.ReadEncryptionTestVectorsArray(false, "ECBMMT128.rsp"); + [Fact] + public static void DecryptMulti() + { + Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key, true)); + } - [Fact] - public static void Encrypt() - { - Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key, true)); - } + [AesIntrinsicsRequiredFact] + public static void EncryptIntrinsics() + { + Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key)); + } - [Fact] - public static void Decrypt() - { - Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key, true)); - } + [AesIntrinsicsRequiredFact] + public static void DecryptIntrinsics() + { + Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key)); + } - [Fact] - public static void EncryptMulti() - { - Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key, true)); - } + [AesIntrinsicsRequiredFact] + public static void EncryptMultiIntrinsics() + { + Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key)); + } - [Fact] - public static void DecryptMulti() - { - Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key, true)); - } - - [AesIntrinsicsRequiredFact] - public static void EncryptIntrinsics() - { - Common.EncryptCipherTest(EncryptTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key)); - } - - [AesIntrinsicsRequiredFact] - public static void DecryptIntrinsics() - { - Common.DecryptCipherTest(DecryptTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key)); - } - - [AesIntrinsicsRequiredFact] - public static void EncryptMultiIntrinsics() - { - Common.EncryptCipherTest(EncryptMultiTestVectors, (key, iv) => Aes.CreateEcbEncryptor(key)); - } - - [AesIntrinsicsRequiredFact] - public static void DecryptMultiIntrinsics() - { - Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key)); - } + [AesIntrinsicsRequiredFact] + public static void DecryptMultiIntrinsics() + { + Common.DecryptCipherTest(DecryptMultiTestVectors, (key, iv) => Aes.CreateEcbDecryptor(key)); + } - // The above tests run all the test vectors in a single test to avoid having thousands of tests. - // Use the below tests if running each test vector as an individual test is needed. + // The above tests run all the test vectors in a single test to avoid having thousands of tests. + // Use the below tests if running each test vector as an individual test is needed. - // ReSharper disable InconsistentNaming + // ReSharper disable InconsistentNaming #pragma warning disable xUnit1013 // Public method should be marked as test - public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(true, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(true, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); - public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(false, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); + public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(false, "ECBVarKey128.rsp", "ECBVarTxt128.rsp", "ECBKeySbox128.rsp", "ECBGFSbox128.rsp"); - public static TheoryData<EncryptionTestVector> EncryptMultiTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(true, "ECBMMT128.rsp"); + public static TheoryData<EncryptionTestVector> EncryptMultiTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(true, "ECBMMT128.rsp"); - public static TheoryData<EncryptionTestVector> DecryptMultiTestVectors_Individual = - RspReader.ReadEncryptionTestVectors(false, "ECBMMT128.rsp"); + public static TheoryData<EncryptionTestVector> DecryptMultiTestVectors_Individual = + RspReader.ReadEncryptionTestVectors(false, "ECBMMT128.rsp"); - //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void Encrypt_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key, true)); - } + //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void Encrypt_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key, true)); + } - //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void Decrypt_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key, true)); - } + //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void Decrypt_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key, true)); + } - //[Theory, MemberData(nameof(EncryptMultiTestVectors_Individual))] - public static void EncryptMulti_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key, true)); - } + //[Theory, MemberData(nameof(EncryptMultiTestVectors_Individual))] + public static void EncryptMulti_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key, true)); + } - //[Theory, MemberData(nameof(DecryptMultiTestVectors_Individual))] - public static void DecryptMulti_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key, true)); - } + //[Theory, MemberData(nameof(DecryptMultiTestVectors_Individual))] + public static void DecryptMulti_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key, true)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptMultiTestVectors_Individual))] - public static void EncryptMultiIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptMultiTestVectors_Individual))] + public static void EncryptMultiIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateEcbEncryptor(tv.Key)); + } - //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptMultiTestVectors_Individual))] - public static void DecryptMultiIntrinsics_Individual(EncryptionTestVector tv) - { - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptMultiTestVectors_Individual))] + public static void DecryptMultiIntrinsics_Individual(EncryptionTestVector tv) + { + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateEcbDecryptor(tv.Key)); } } diff --git a/tests/LibHac.Tests/CryptoTests/AesIntrinsicsRequiredAttributes.cs b/tests/LibHac.Tests/CryptoTests/AesIntrinsicsRequiredAttributes.cs index 34379ad3..e03a13e5 100644 --- a/tests/LibHac.Tests/CryptoTests/AesIntrinsicsRequiredAttributes.cs +++ b/tests/LibHac.Tests/CryptoTests/AesIntrinsicsRequiredAttributes.cs @@ -1,27 +1,26 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests -{ - public sealed class AesIntrinsicsRequiredTheoryAttribute : TheoryAttribute - { - public AesIntrinsicsRequiredTheoryAttribute() - { - if (!Aes.IsAesNiSupported()) - { - Skip = "AES intrinsics required"; - } - } - } +namespace LibHac.Tests.CryptoTests; - public sealed class AesIntrinsicsRequiredFactAttribute : FactAttribute +public sealed class AesIntrinsicsRequiredTheoryAttribute : TheoryAttribute +{ + public AesIntrinsicsRequiredTheoryAttribute() { - public AesIntrinsicsRequiredFactAttribute() + if (!Aes.IsAesNiSupported()) { - if (!Aes.IsAesNiSupported()) - { - Skip = "AES intrinsics required"; - } + Skip = "AES intrinsics required"; } } -} \ No newline at end of file +} + +public sealed class AesIntrinsicsRequiredFactAttribute : FactAttribute +{ + public AesIntrinsicsRequiredFactAttribute() + { + if (!Aes.IsAesNiSupported()) + { + Skip = "AES intrinsics required"; + } + } +} diff --git a/tests/LibHac.Tests/CryptoTests/AesXtsTests.cs b/tests/LibHac.Tests/CryptoTests/AesXtsTests.cs index 87797e59..998dc29b 100644 --- a/tests/LibHac.Tests/CryptoTests/AesXtsTests.cs +++ b/tests/LibHac.Tests/CryptoTests/AesXtsTests.cs @@ -4,125 +4,124 @@ using System.Linq; using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class AesXtsTests { - public class AesXtsTests + public static EncryptionTestVector[] EncryptTestVectors = + RemovePartialByteTests(RspReader.ReadEncryptionTestVectorsArray(true, "XTSGenAES128.rsp")); + + public static EncryptionTestVector[] DecryptTestVectors = + RemovePartialByteTests(RspReader.ReadEncryptionTestVectorsArray(false, "XTSGenAES128.rsp")); + + // The XTS implementation only supports multiples of whole bytes + private static EncryptionTestVector[] RemovePartialByteTests(EncryptionTestVector[] input) { - public static EncryptionTestVector[] EncryptTestVectors = - RemovePartialByteTests(RspReader.ReadEncryptionTestVectorsArray(true, "XTSGenAES128.rsp")); + IEnumerable<EncryptionTestVector> filteredTestVectors = input + .Where(x => x.DataUnitLength % 8 == 0); - public static EncryptionTestVector[] DecryptTestVectors = - RemovePartialByteTests(RspReader.ReadEncryptionTestVectorsArray(false, "XTSGenAES128.rsp")); + var output = new List<EncryptionTestVector>(); - // The XTS implementation only supports multiples of whole bytes - private static EncryptionTestVector[] RemovePartialByteTests(EncryptionTestVector[] input) + foreach (EncryptionTestVector item in filteredTestVectors) { - IEnumerable<EncryptionTestVector> filteredTestVectors = input - .Where(x => x.DataUnitLength % 8 == 0); - - var output = new List<EncryptionTestVector>(); - - foreach (EncryptionTestVector item in filteredTestVectors) - { - output.Add(item); - } - - return output.ToArray(); + output.Add(item); } - [Fact] - public static void Encrypt() - { - Common.EncryptCipherTest(EncryptTestVectors, - (key, iv) => Aes.CreateXtsEncryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv, true)); - } + return output.ToArray(); + } - [Fact] - public static void Decrypt() - { - Common.DecryptCipherTest(DecryptTestVectors, - (key, iv) => Aes.CreateXtsDecryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv, true)); - } + [Fact] + public static void Encrypt() + { + Common.EncryptCipherTest(EncryptTestVectors, + (key, iv) => Aes.CreateXtsEncryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv, true)); + } - [AesIntrinsicsRequiredFact] - public static void EncryptIntrinsics() - { - Common.EncryptCipherTest(EncryptTestVectors, - (key, iv) => Aes.CreateXtsEncryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv)); - } + [Fact] + public static void Decrypt() + { + Common.DecryptCipherTest(DecryptTestVectors, + (key, iv) => Aes.CreateXtsDecryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv, true)); + } - [AesIntrinsicsRequiredFact] - public static void DecryptIntrinsics() - { - Common.DecryptCipherTest(DecryptTestVectors, - (key, iv) => Aes.CreateXtsDecryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv)); - } + [AesIntrinsicsRequiredFact] + public static void EncryptIntrinsics() + { + Common.EncryptCipherTest(EncryptTestVectors, + (key, iv) => Aes.CreateXtsEncryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv)); + } + + [AesIntrinsicsRequiredFact] + public static void DecryptIntrinsics() + { + Common.DecryptCipherTest(DecryptTestVectors, + (key, iv) => Aes.CreateXtsDecryptor(key.AsSpan(0, 0x10), key.AsSpan(0x10, 0x10), iv)); + } - // The above tests run all the test vectors in a single test to avoid having thousands of tests. - // Use the below tests if running each test vector as an individual test is needed. + // The above tests run all the test vectors in a single test to avoid having thousands of tests. + // Use the below tests if running each test vector as an individual test is needed. - // ReSharper disable InconsistentNaming + // ReSharper disable InconsistentNaming #pragma warning disable xUnit1013 // Public method should be marked as test - public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = - RemovePartialByteTests(RspReader.ReadEncryptionTestVectors(true, "XTSGenAES128.rsp")); + public static TheoryData<EncryptionTestVector> EncryptTestVectors_Individual = + RemovePartialByteTests(RspReader.ReadEncryptionTestVectors(true, "XTSGenAES128.rsp")); - public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = - RemovePartialByteTests(RspReader.ReadEncryptionTestVectors(false, "XTSGenAES128.rsp")); + public static TheoryData<EncryptionTestVector> DecryptTestVectors_Individual = + RemovePartialByteTests(RspReader.ReadEncryptionTestVectors(false, "XTSGenAES128.rsp")); - private static TheoryData<EncryptionTestVector> RemovePartialByteTests(TheoryData<EncryptionTestVector> input) + private static TheoryData<EncryptionTestVector> RemovePartialByteTests(TheoryData<EncryptionTestVector> input) + { + IEnumerable<EncryptionTestVector> filteredTestVectors = input + .Select(x => x[0]) + .Cast<EncryptionTestVector>() + .Where(x => x.DataUnitLength % 8 == 0); + + var output = new TheoryData<EncryptionTestVector>(); + + foreach (EncryptionTestVector item in filteredTestVectors) { - IEnumerable<EncryptionTestVector> filteredTestVectors = input - .Select(x => x[0]) - .Cast<EncryptionTestVector>() - .Where(x => x.DataUnitLength % 8 == 0); - - var output = new TheoryData<EncryptionTestVector>(); - - foreach (EncryptionTestVector item in filteredTestVectors) - { - output.Add(item); - } - - return output; + output.Add(item); } - //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void Encrypt_Individual(EncryptionTestVector tv) - { - Span<byte> key1 = tv.Key.AsSpan(0, 0x10); - Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); + return output; + } - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateXtsEncryptor(key1, key2, tv.Iv, true)); - } + //[Theory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void Encrypt_Individual(EncryptionTestVector tv) + { + Span<byte> key1 = tv.Key.AsSpan(0, 0x10); + Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); - //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void Decrypt_Individual(EncryptionTestVector tv) - { - Span<byte> key1 = tv.Key.AsSpan(0, 0x10); - Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateXtsEncryptor(key1, key2, tv.Iv, true)); + } - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateXtsDecryptor(key1, key2, tv.Iv, true)); - } + //[Theory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void Decrypt_Individual(EncryptionTestVector tv) + { + Span<byte> key1 = tv.Key.AsSpan(0, 0x10); + Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); - //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] - public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) - { - Span<byte> key1 = tv.Key.AsSpan(0, 0x10); - Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateXtsDecryptor(key1, key2, tv.Iv, true)); + } - Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateXtsEncryptor(key1, key2, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(EncryptTestVectors_Individual))] + public static void EncryptIntrinsics_Individual(EncryptionTestVector tv) + { + Span<byte> key1 = tv.Key.AsSpan(0, 0x10); + Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); - //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] - public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) - { - Span<byte> key1 = tv.Key.AsSpan(0, 0x10); - Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); + Common.CipherTestCore(tv.PlainText, tv.CipherText, Aes.CreateXtsEncryptor(key1, key2, tv.Iv)); + } - Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateXtsDecryptor(key1, key2, tv.Iv)); - } + //[AesIntrinsicsRequiredTheory, MemberData(nameof(DecryptTestVectors_Individual))] + public static void DecryptIntrinsics_Individual(EncryptionTestVector tv) + { + Span<byte> key1 = tv.Key.AsSpan(0, 0x10); + Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10); + + Common.CipherTestCore(tv.CipherText, tv.PlainText, Aes.CreateXtsDecryptor(key1, key2, tv.Iv)); } } diff --git a/tests/LibHac.Tests/CryptoTests/Common.cs b/tests/LibHac.Tests/CryptoTests/Common.cs index 885295de..b0d65587 100644 --- a/tests/LibHac.Tests/CryptoTests/Common.cs +++ b/tests/LibHac.Tests/CryptoTests/Common.cs @@ -2,47 +2,46 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +internal static class Common { - internal static class Common + internal delegate ICipher CipherCreator(byte[] key, byte[] iv); + + internal static void CipherTestCore(byte[] inputData, byte[] expected, ICipher cipher) { - internal delegate ICipher CipherCreator(byte[] key, byte[] iv); + byte[] transformBuffer = new byte[inputData.Length]; + Buffer.BlockCopy(inputData, 0, transformBuffer, 0, inputData.Length); - internal static void CipherTestCore(byte[] inputData, byte[] expected, ICipher cipher) + cipher.Transform(transformBuffer, transformBuffer); + + Assert.Equal(expected, transformBuffer); + } + + internal static void EncryptCipherTest(EncryptionTestVector[] testVectors, CipherCreator cipherGenerator) + { + foreach (EncryptionTestVector tv in testVectors) { - byte[] transformBuffer = new byte[inputData.Length]; - Buffer.BlockCopy(inputData, 0, transformBuffer, 0, inputData.Length); - - cipher.Transform(transformBuffer, transformBuffer); - - Assert.Equal(expected, transformBuffer); - } - - internal static void EncryptCipherTest(EncryptionTestVector[] testVectors, CipherCreator cipherGenerator) - { - foreach (EncryptionTestVector tv in testVectors) - { - CipherTestCore(tv.PlainText, tv.CipherText, cipherGenerator(tv.Key, tv.Iv)); - } - } - - internal static void DecryptCipherTest(EncryptionTestVector[] testVectors, CipherCreator cipherGenerator) - { - foreach (EncryptionTestVector tv in testVectors) - { - CipherTestCore(tv.CipherText, tv.PlainText, cipherGenerator(tv.Key, tv.Iv)); - } - } - - internal static void HashTestCore(ReadOnlySpan<byte> message, byte[] expectedDigest, IHash hash) - { - byte[] digestBuffer = new byte[Sha256.DigestSize]; - - hash.Initialize(); - hash.Update(message); - hash.GetHash(digestBuffer); - - Assert.Equal(expectedDigest, digestBuffer); + CipherTestCore(tv.PlainText, tv.CipherText, cipherGenerator(tv.Key, tv.Iv)); } } + + internal static void DecryptCipherTest(EncryptionTestVector[] testVectors, CipherCreator cipherGenerator) + { + foreach (EncryptionTestVector tv in testVectors) + { + CipherTestCore(tv.CipherText, tv.PlainText, cipherGenerator(tv.Key, tv.Iv)); + } + } + + internal static void HashTestCore(ReadOnlySpan<byte> message, byte[] expectedDigest, IHash hash) + { + byte[] digestBuffer = new byte[Sha256.DigestSize]; + + hash.Initialize(); + hash.Update(message); + hash.GetHash(digestBuffer); + + Assert.Equal(expectedDigest, digestBuffer); + } } diff --git a/tests/LibHac.Tests/CryptoTests/RsaTests.cs b/tests/LibHac.Tests/CryptoTests/RsaTests.cs index d46a461c..f06dd0f0 100644 --- a/tests/LibHac.Tests/CryptoTests/RsaTests.cs +++ b/tests/LibHac.Tests/CryptoTests/RsaTests.cs @@ -2,128 +2,127 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class RsaTests { - public class RsaTests + [Fact] + public void RecoverRsaParameters_ReturnsCorrectParameters() { - [Fact] - public void RecoverRsaParameters_ReturnsCorrectParameters() - { - RSAParameters rsaParams = Rsa.RecoverParameters(Modulus, PublicExponent, PrivateExponent); + RSAParameters rsaParams = Rsa.RecoverParameters(Modulus, PublicExponent, PrivateExponent); - Assert.Equal(PrivateExponent, rsaParams.D); - Assert.Equal(ExpectedDp, rsaParams.DP); - Assert.Equal(ExpectedDq, rsaParams.DQ); - Assert.Equal(PublicExponent, rsaParams.Exponent); - Assert.Equal(ExpectedInverseQ, rsaParams.InverseQ); - Assert.Equal(Modulus, rsaParams.Modulus); - Assert.Equal(ExpectedP, rsaParams.P); - Assert.Equal(ExpectedQ, rsaParams.Q); - } - - public readonly byte[] Modulus = -{ - 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, - 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, - 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, - 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, - 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, - 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, - 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, - 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, - 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, - 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, - 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, - 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, - 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, - 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, - 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, - 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 - }; - - public readonly byte[] PrivateExponent = - { - 0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA, - 0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E, - 0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA, - 0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00, - 0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9, - 0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC, - 0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58, - 0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD, - 0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7, - 0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81, - 0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1, - 0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B, - 0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6, - 0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7, - 0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6, - 0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65 - }; - - public readonly byte[] PublicExponent = - { - 0x01, 0x00, 0x01 - }; - - public readonly byte[] ExpectedDp = - { - 0x80, 0x1f, 0x3e, 0xe6, 0xed, 0xa2, 0xff, 0x60, 0x0e, 0xc3, 0xb7, 0xf3, 0xed, 0x73, 0x95, 0x05, - 0x6d, 0x1f, 0x8a, 0xab, 0x14, 0x42, 0xa0, 0x21, 0x07, 0xe9, 0x92, 0xc7, 0x38, 0x26, 0x84, 0x60, - 0xf9, 0xb3, 0x54, 0xad, 0x13, 0xf0, 0xc7, 0xd5, 0xfb, 0xac, 0xe2, 0x40, 0x68, 0x65, 0xf5, 0xb8, - 0xc7, 0x4f, 0xd1, 0x58, 0xd6, 0xd2, 0x19, 0x26, 0x0a, 0xdb, 0x39, 0x2d, 0x23, 0xdd, 0x6f, 0x52, - 0x48, 0xf2, 0x5d, 0x86, 0xa8, 0xe5, 0x38, 0x6c, 0x7e, 0xa4, 0x99, 0x1a, 0x40, 0xbf, 0x88, 0x11, - 0x38, 0x08, 0x64, 0xff, 0xb7, 0xe9, 0x4c, 0x4d, 0xae, 0x2c, 0xfe, 0xb0, 0xe6, 0x66, 0x63, 0x14, - 0x26, 0x85, 0x88, 0xe6, 0xc4, 0xe2, 0xe9, 0x15, 0xfa, 0xbb, 0xd1, 0x58, 0x70, 0x73, 0xe2, 0xbb, - 0x3d, 0x4d, 0x28, 0x8b, 0xa9, 0x04, 0x7c, 0x16, 0xc6, 0xd1, 0x8e, 0x5b, 0x6b, 0xad, 0x4d, 0xdb - }; - - public readonly byte[] ExpectedDq = - { - 0x84, 0x03, 0x42, 0x57, 0x72, 0xa5, 0x7a, 0x5a, 0x71, 0x1e, 0xb6, 0xba, 0x94, 0xd1, 0xfe, 0xea, - 0x91, 0x2f, 0xce, 0x75, 0x22, 0xb3, 0x0a, 0xd1, 0x39, 0xce, 0xab, 0xe2, 0xee, 0x14, 0xa4, 0x24, - 0xc2, 0x12, 0x74, 0xdd, 0xa8, 0x4d, 0xae, 0x2f, 0x99, 0xd7, 0x34, 0x2e, 0x5a, 0xe9, 0xfc, 0x5e, - 0x69, 0xdf, 0xfb, 0xcb, 0x63, 0x91, 0x80, 0xbe, 0x00, 0x4d, 0x93, 0x8b, 0x24, 0x64, 0xd5, 0x24, - 0x9a, 0x40, 0xea, 0xef, 0x25, 0x49, 0x14, 0xe6, 0xca, 0xb0, 0xae, 0xc4, 0xb4, 0x7d, 0x0e, 0x4f, - 0xf4, 0x76, 0x00, 0xb6, 0x12, 0xc0, 0x7f, 0x86, 0x2b, 0xbb, 0xd8, 0x91, 0xcd, 0x49, 0x32, 0xad, - 0x52, 0x11, 0x9c, 0x56, 0x7a, 0x28, 0xc6, 0x97, 0x80, 0x3e, 0xfa, 0xb1, 0x34, 0x9e, 0xb2, 0xb8, - 0x1d, 0x16, 0xee, 0xa9, 0x22, 0xdc, 0x77, 0x5a, 0xde, 0x0b, 0xeb, 0xeb, 0x4e, 0xe8, 0x03, 0x41 - }; - - public readonly byte[] ExpectedInverseQ = - { - 0xb5, 0x71, 0x24, 0x42, 0xf4, 0x4c, 0xbd, 0xe2, 0x27, 0x6a, 0x22, 0x32, 0x9f, 0xe6, 0x88, 0x3e, - 0xcf, 0xc8, 0x5d, 0x15, 0x04, 0x2c, 0x88, 0xac, 0x4f, 0xb9, 0xf1, 0x70, 0xef, 0x04, 0x07, 0xb4, - 0x51, 0xd6, 0xab, 0x48, 0xa2, 0x70, 0x0b, 0xbc, 0xb9, 0xd9, 0xbc, 0xd8, 0x81, 0x29, 0x80, 0x6e, - 0x0e, 0xab, 0xa0, 0x8a, 0x39, 0x62, 0x8f, 0x8e, 0x4b, 0xc3, 0xc9, 0x19, 0x64, 0x3b, 0x58, 0x9e, - 0x94, 0x3c, 0xab, 0xfd, 0x53, 0xe1, 0xc0, 0xc6, 0x0f, 0x20, 0x30, 0x76, 0x16, 0xd1, 0xf0, 0xc6, - 0x1d, 0x94, 0x58, 0xd3, 0x4b, 0xe8, 0x5e, 0x73, 0x02, 0x54, 0x88, 0x02, 0xc6, 0x8d, 0x62, 0xfd, - 0xf1, 0x56, 0x35, 0x06, 0x81, 0x57, 0x79, 0x00, 0xc1, 0x7c, 0x9e, 0xd3, 0x61, 0x81, 0x65, 0xae, - 0x70, 0x56, 0x24, 0x32, 0xfc, 0x92, 0x17, 0xc4, 0xed, 0x09, 0x27, 0xe3, 0x40, 0xc5, 0xfe, 0xe4 - }; - - public readonly byte[] ExpectedP = - { - 0xd2, 0x96, 0x44, 0xee, 0x2c, 0x8e, 0x97, 0x24, 0x83, 0x7c, 0xf0, 0x59, 0xeb, 0x8a, 0xa0, 0x24, - 0xca, 0x2b, 0xd9, 0x92, 0x51, 0xf2, 0xa9, 0x33, 0x5b, 0x5f, 0x53, 0xf0, 0x53, 0xcf, 0x5c, 0xd0, - 0xf5, 0x56, 0x73, 0xf1, 0x22, 0x64, 0xa1, 0xb5, 0x6e, 0x36, 0x22, 0xde, 0xf8, 0xa6, 0xa9, 0x3e, - 0xff, 0x3d, 0x63, 0xb8, 0x1e, 0x52, 0xb5, 0x0a, 0xfc, 0x3d, 0x46, 0xbf, 0xa1, 0x26, 0x20, 0x1c, - 0xeb, 0x4c, 0x66, 0x31, 0x84, 0x47, 0x46, 0x55, 0x5f, 0x4e, 0x06, 0x7a, 0x2a, 0x86, 0xf9, 0x7c, - 0xf5, 0xe9, 0x3d, 0x6c, 0xaf, 0x06, 0xb5, 0xef, 0x2e, 0x81, 0xd6, 0xad, 0x7f, 0xa6, 0xe5, 0x01, - 0x77, 0xbb, 0x52, 0xe8, 0x8b, 0x83, 0x4d, 0x98, 0x97, 0x95, 0x7f, 0xc9, 0x5c, 0x79, 0x92, 0x37, - 0xf9, 0x1e, 0xb1, 0xe3, 0x70, 0x77, 0x80, 0xd8, 0x90, 0xbe, 0x48, 0x35, 0xbd, 0x24, 0x8c, 0x5b - }; - - public readonly byte[] ExpectedQ = - { - 0xd2, 0xba, 0xcd, 0x49, 0x28, 0xe2, 0x8c, 0xde, 0x8c, 0xd3, 0xc0, 0xb4, 0xd9, 0x2b, 0x6b, 0xec, - 0xf1, 0xff, 0x66, 0x30, 0xfe, 0x0e, 0x40, 0x46, 0xa9, 0xcd, 0x27, 0x9d, 0xfd, 0xf3, 0xbe, 0x1c, - 0xf8, 0x90, 0x01, 0x7f, 0x48, 0xb3, 0x51, 0xfd, 0xa7, 0xd9, 0x60, 0xce, 0xcf, 0x44, 0x2f, 0x8a, - 0x83, 0xae, 0x6d, 0x37, 0x11, 0x23, 0xf1, 0x54, 0xce, 0xc9, 0xa6, 0xc3, 0x7d, 0xce, 0x13, 0x9c, - 0xed, 0xad, 0x6d, 0x0b, 0x79, 0x77, 0x7b, 0xcf, 0x1c, 0xad, 0xf3, 0x90, 0x15, 0x29, 0x52, 0x16, - 0xc3, 0x94, 0x2f, 0x5d, 0x4d, 0x8f, 0x2d, 0xbc, 0x07, 0x98, 0x45, 0x1f, 0x90, 0x07, 0xfb, 0x26, - 0xf0, 0x3a, 0xf3, 0xad, 0xee, 0xd5, 0xe7, 0x42, 0x7c, 0xa5, 0xfa, 0xb6, 0xaa, 0xe6, 0x1b, 0x47, - 0x54, 0xe5, 0x22, 0xeb, 0x28, 0x8a, 0x3d, 0x66, 0xe4, 0x81, 0xc2, 0x17, 0x1d, 0x0d, 0x7b, 0x5f - }; + Assert.Equal(PrivateExponent, rsaParams.D); + Assert.Equal(ExpectedDp, rsaParams.DP); + Assert.Equal(ExpectedDq, rsaParams.DQ); + Assert.Equal(PublicExponent, rsaParams.Exponent); + Assert.Equal(ExpectedInverseQ, rsaParams.InverseQ); + Assert.Equal(Modulus, rsaParams.Modulus); + Assert.Equal(ExpectedP, rsaParams.P); + Assert.Equal(ExpectedQ, rsaParams.Q); } + + public readonly byte[] Modulus = + { + 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, + 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, + 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, + 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, + 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, + 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, + 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, + 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, + 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, + 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, + 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, + 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, + 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, + 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, + 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, + 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 + }; + + public readonly byte[] PrivateExponent = + { + 0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA, + 0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E, + 0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA, + 0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00, + 0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9, + 0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC, + 0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58, + 0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD, + 0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7, + 0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81, + 0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1, + 0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B, + 0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6, + 0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7, + 0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6, + 0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65 + }; + + public readonly byte[] PublicExponent = + { + 0x01, 0x00, 0x01 + }; + + public readonly byte[] ExpectedDp = + { + 0x80, 0x1f, 0x3e, 0xe6, 0xed, 0xa2, 0xff, 0x60, 0x0e, 0xc3, 0xb7, 0xf3, 0xed, 0x73, 0x95, 0x05, + 0x6d, 0x1f, 0x8a, 0xab, 0x14, 0x42, 0xa0, 0x21, 0x07, 0xe9, 0x92, 0xc7, 0x38, 0x26, 0x84, 0x60, + 0xf9, 0xb3, 0x54, 0xad, 0x13, 0xf0, 0xc7, 0xd5, 0xfb, 0xac, 0xe2, 0x40, 0x68, 0x65, 0xf5, 0xb8, + 0xc7, 0x4f, 0xd1, 0x58, 0xd6, 0xd2, 0x19, 0x26, 0x0a, 0xdb, 0x39, 0x2d, 0x23, 0xdd, 0x6f, 0x52, + 0x48, 0xf2, 0x5d, 0x86, 0xa8, 0xe5, 0x38, 0x6c, 0x7e, 0xa4, 0x99, 0x1a, 0x40, 0xbf, 0x88, 0x11, + 0x38, 0x08, 0x64, 0xff, 0xb7, 0xe9, 0x4c, 0x4d, 0xae, 0x2c, 0xfe, 0xb0, 0xe6, 0x66, 0x63, 0x14, + 0x26, 0x85, 0x88, 0xe6, 0xc4, 0xe2, 0xe9, 0x15, 0xfa, 0xbb, 0xd1, 0x58, 0x70, 0x73, 0xe2, 0xbb, + 0x3d, 0x4d, 0x28, 0x8b, 0xa9, 0x04, 0x7c, 0x16, 0xc6, 0xd1, 0x8e, 0x5b, 0x6b, 0xad, 0x4d, 0xdb + }; + + public readonly byte[] ExpectedDq = + { + 0x84, 0x03, 0x42, 0x57, 0x72, 0xa5, 0x7a, 0x5a, 0x71, 0x1e, 0xb6, 0xba, 0x94, 0xd1, 0xfe, 0xea, + 0x91, 0x2f, 0xce, 0x75, 0x22, 0xb3, 0x0a, 0xd1, 0x39, 0xce, 0xab, 0xe2, 0xee, 0x14, 0xa4, 0x24, + 0xc2, 0x12, 0x74, 0xdd, 0xa8, 0x4d, 0xae, 0x2f, 0x99, 0xd7, 0x34, 0x2e, 0x5a, 0xe9, 0xfc, 0x5e, + 0x69, 0xdf, 0xfb, 0xcb, 0x63, 0x91, 0x80, 0xbe, 0x00, 0x4d, 0x93, 0x8b, 0x24, 0x64, 0xd5, 0x24, + 0x9a, 0x40, 0xea, 0xef, 0x25, 0x49, 0x14, 0xe6, 0xca, 0xb0, 0xae, 0xc4, 0xb4, 0x7d, 0x0e, 0x4f, + 0xf4, 0x76, 0x00, 0xb6, 0x12, 0xc0, 0x7f, 0x86, 0x2b, 0xbb, 0xd8, 0x91, 0xcd, 0x49, 0x32, 0xad, + 0x52, 0x11, 0x9c, 0x56, 0x7a, 0x28, 0xc6, 0x97, 0x80, 0x3e, 0xfa, 0xb1, 0x34, 0x9e, 0xb2, 0xb8, + 0x1d, 0x16, 0xee, 0xa9, 0x22, 0xdc, 0x77, 0x5a, 0xde, 0x0b, 0xeb, 0xeb, 0x4e, 0xe8, 0x03, 0x41 + }; + + public readonly byte[] ExpectedInverseQ = + { + 0xb5, 0x71, 0x24, 0x42, 0xf4, 0x4c, 0xbd, 0xe2, 0x27, 0x6a, 0x22, 0x32, 0x9f, 0xe6, 0x88, 0x3e, + 0xcf, 0xc8, 0x5d, 0x15, 0x04, 0x2c, 0x88, 0xac, 0x4f, 0xb9, 0xf1, 0x70, 0xef, 0x04, 0x07, 0xb4, + 0x51, 0xd6, 0xab, 0x48, 0xa2, 0x70, 0x0b, 0xbc, 0xb9, 0xd9, 0xbc, 0xd8, 0x81, 0x29, 0x80, 0x6e, + 0x0e, 0xab, 0xa0, 0x8a, 0x39, 0x62, 0x8f, 0x8e, 0x4b, 0xc3, 0xc9, 0x19, 0x64, 0x3b, 0x58, 0x9e, + 0x94, 0x3c, 0xab, 0xfd, 0x53, 0xe1, 0xc0, 0xc6, 0x0f, 0x20, 0x30, 0x76, 0x16, 0xd1, 0xf0, 0xc6, + 0x1d, 0x94, 0x58, 0xd3, 0x4b, 0xe8, 0x5e, 0x73, 0x02, 0x54, 0x88, 0x02, 0xc6, 0x8d, 0x62, 0xfd, + 0xf1, 0x56, 0x35, 0x06, 0x81, 0x57, 0x79, 0x00, 0xc1, 0x7c, 0x9e, 0xd3, 0x61, 0x81, 0x65, 0xae, + 0x70, 0x56, 0x24, 0x32, 0xfc, 0x92, 0x17, 0xc4, 0xed, 0x09, 0x27, 0xe3, 0x40, 0xc5, 0xfe, 0xe4 + }; + + public readonly byte[] ExpectedP = + { + 0xd2, 0x96, 0x44, 0xee, 0x2c, 0x8e, 0x97, 0x24, 0x83, 0x7c, 0xf0, 0x59, 0xeb, 0x8a, 0xa0, 0x24, + 0xca, 0x2b, 0xd9, 0x92, 0x51, 0xf2, 0xa9, 0x33, 0x5b, 0x5f, 0x53, 0xf0, 0x53, 0xcf, 0x5c, 0xd0, + 0xf5, 0x56, 0x73, 0xf1, 0x22, 0x64, 0xa1, 0xb5, 0x6e, 0x36, 0x22, 0xde, 0xf8, 0xa6, 0xa9, 0x3e, + 0xff, 0x3d, 0x63, 0xb8, 0x1e, 0x52, 0xb5, 0x0a, 0xfc, 0x3d, 0x46, 0xbf, 0xa1, 0x26, 0x20, 0x1c, + 0xeb, 0x4c, 0x66, 0x31, 0x84, 0x47, 0x46, 0x55, 0x5f, 0x4e, 0x06, 0x7a, 0x2a, 0x86, 0xf9, 0x7c, + 0xf5, 0xe9, 0x3d, 0x6c, 0xaf, 0x06, 0xb5, 0xef, 0x2e, 0x81, 0xd6, 0xad, 0x7f, 0xa6, 0xe5, 0x01, + 0x77, 0xbb, 0x52, 0xe8, 0x8b, 0x83, 0x4d, 0x98, 0x97, 0x95, 0x7f, 0xc9, 0x5c, 0x79, 0x92, 0x37, + 0xf9, 0x1e, 0xb1, 0xe3, 0x70, 0x77, 0x80, 0xd8, 0x90, 0xbe, 0x48, 0x35, 0xbd, 0x24, 0x8c, 0x5b + }; + + public readonly byte[] ExpectedQ = + { + 0xd2, 0xba, 0xcd, 0x49, 0x28, 0xe2, 0x8c, 0xde, 0x8c, 0xd3, 0xc0, 0xb4, 0xd9, 0x2b, 0x6b, 0xec, + 0xf1, 0xff, 0x66, 0x30, 0xfe, 0x0e, 0x40, 0x46, 0xa9, 0xcd, 0x27, 0x9d, 0xfd, 0xf3, 0xbe, 0x1c, + 0xf8, 0x90, 0x01, 0x7f, 0x48, 0xb3, 0x51, 0xfd, 0xa7, 0xd9, 0x60, 0xce, 0xcf, 0x44, 0x2f, 0x8a, + 0x83, 0xae, 0x6d, 0x37, 0x11, 0x23, 0xf1, 0x54, 0xce, 0xc9, 0xa6, 0xc3, 0x7d, 0xce, 0x13, 0x9c, + 0xed, 0xad, 0x6d, 0x0b, 0x79, 0x77, 0x7b, 0xcf, 0x1c, 0xad, 0xf3, 0x90, 0x15, 0x29, 0x52, 0x16, + 0xc3, 0x94, 0x2f, 0x5d, 0x4d, 0x8f, 0x2d, 0xbc, 0x07, 0x98, 0x45, 0x1f, 0x90, 0x07, 0xfb, 0x26, + 0xf0, 0x3a, 0xf3, 0xad, 0xee, 0xd5, 0xe7, 0x42, 0x7c, 0xa5, 0xfa, 0xb6, 0xaa, 0xe6, 0x1b, 0x47, + 0x54, 0xe5, 0x22, 0xeb, 0x28, 0x8a, 0x3d, 0x66, 0xe4, 0x81, 0xc2, 0x17, 0x1d, 0x0d, 0x7b, 0x5f + }; } diff --git a/tests/LibHac.Tests/CryptoTests/RspReader.cs b/tests/LibHac.Tests/CryptoTests/RspReader.cs index 12eab6e5..ed309196 100644 --- a/tests/LibHac.Tests/CryptoTests/RspReader.cs +++ b/tests/LibHac.Tests/CryptoTests/RspReader.cs @@ -6,215 +6,214 @@ using System.Reflection; using LibHac.Util; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class RspReader { - public class RspReader + private StreamReader Reader { get; } + + public RspReader(Stream stream) { - private StreamReader Reader { get; } + Reader = new StreamReader(stream); + } - public RspReader(Stream stream) + public IEnumerable<EncryptionTestVector> GetEncryptionTestVectors() + { + string line; + bool isEncryptType = false; + + var testVector = new EncryptionTestVector(); + bool canOutputVector = false; + + while ((line = Reader.ReadLine()?.Trim()) != null) { - Reader = new StreamReader(stream); - } - - public IEnumerable<EncryptionTestVector> GetEncryptionTestVectors() - { - string line; - bool isEncryptType = false; - - var testVector = new EncryptionTestVector(); - bool canOutputVector = false; - - while ((line = Reader.ReadLine()?.Trim()) != null) + if (line.Length == 0) { - if (line.Length == 0) + if (canOutputVector) { - if (canOutputVector) - { - testVector.Encrypt = isEncryptType; + testVector.Encrypt = isEncryptType; - yield return testVector; + yield return testVector; - testVector = new EncryptionTestVector(); - canOutputVector = false; - } - - continue; + testVector = new EncryptionTestVector(); + canOutputVector = false; } - if (line[0] == '#') continue; - - if (line[0] == '[') - { - if (line == "[ENCRYPT]") isEncryptType = true; - if (line == "[DECRYPT]") isEncryptType = false; - - continue; - } - - string[] kvp = line.Split(new[] { " = " }, StringSplitOptions.None); - if (kvp.Length != 2) throw new InvalidDataException(); - - canOutputVector = true; - - switch (kvp[0].ToUpperInvariant()) - { - case "COUNT": - testVector.Count = int.Parse(kvp[1]); - break; - case "DATAUNITLEN": - testVector.DataUnitLength = int.Parse(kvp[1]); - break; - case "KEY": - testVector.Key = kvp[1].ToBytes(); - break; - case "IV": - case "I": - testVector.Iv = kvp[1].ToBytes(); - break; - case "PLAINTEXT": - case "PT": - testVector.PlainText = kvp[1].ToBytes(); - break; - case "CIPHERTEXT": - case "CT": - testVector.CipherText = kvp[1].ToBytes(); - break; - } - } - } - - public static TheoryData<EncryptionTestVector> ReadEncryptionTestVectors(bool getEncryptTests, params string[] filenames) - { - EncryptionTestVector[] vectorArray = ReadEncryptionTestVectorsArray(getEncryptTests, filenames); - var testVectors = new TheoryData<EncryptionTestVector>(); - - foreach (EncryptionTestVector test in vectorArray) - { - testVectors.Add(test); + continue; } - return testVectors; - } + if (line[0] == '#') continue; - public static EncryptionTestVector[] ReadEncryptionTestVectorsArray(bool getEncryptTests, params string[] filenames) - { - IEnumerable<string> resourcePaths = filenames.Select(x => $"LibHac.Tests.CryptoTests.TestVectors.{x}"); - var testVectors = new List<EncryptionTestVector>(); - - foreach (string path in resourcePaths) + if (line[0] == '[') { - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)) - { - var reader = new RspReader(stream); + if (line == "[ENCRYPT]") isEncryptType = true; + if (line == "[DECRYPT]") isEncryptType = false; - foreach (EncryptionTestVector tv in reader.GetEncryptionTestVectors().Where(x => x.Encrypt == getEncryptTests)) - { - testVectors.Add(tv); - } - } + continue; } - return testVectors.ToArray(); - } + string[] kvp = line.Split(new[] { " = " }, StringSplitOptions.None); + if (kvp.Length != 2) throw new InvalidDataException(); - public IEnumerable<HashTestVector> GetHashTestVectors() - { - string line; + canOutputVector = true; - var testVector = new HashTestVector(); - bool canOutputVector = false; - - while ((line = Reader.ReadLine()?.Trim()) != null) + switch (kvp[0].ToUpperInvariant()) { - if (line.Length == 0) - { - if (canOutputVector) - { - yield return testVector; - - testVector = new HashTestVector(); - canOutputVector = false; - } - - continue; - } - - if (line[0] == '#') continue; - if (line[0] == '[') continue; - - string[] kvp = line.Split(new[] { " = " }, StringSplitOptions.None); - if (kvp.Length != 2) throw new InvalidDataException(); - - canOutputVector = true; - - switch (kvp[0].ToUpperInvariant()) - { - case "LEN": - testVector.LengthBits = int.Parse(kvp[1]); - testVector.LengthBytes = testVector.LengthBits / 8; - break; - case "MSG": - testVector.Message = kvp[1].ToBytes(); - break; - case "MD": - testVector.Digest = kvp[1].ToBytes(); - break; - } + case "COUNT": + testVector.Count = int.Parse(kvp[1]); + break; + case "DATAUNITLEN": + testVector.DataUnitLength = int.Parse(kvp[1]); + break; + case "KEY": + testVector.Key = kvp[1].ToBytes(); + break; + case "IV": + case "I": + testVector.Iv = kvp[1].ToBytes(); + break; + case "PLAINTEXT": + case "PT": + testVector.PlainText = kvp[1].ToBytes(); + break; + case "CIPHERTEXT": + case "CT": + testVector.CipherText = kvp[1].ToBytes(); + break; } } - - public static TheoryData<HashTestVector> ReadHashTestVectors(params string[] filenames) - { - HashTestVector[] vectorArray = ReadHashTestVectorsArray(filenames); - var testVectors = new TheoryData<HashTestVector>(); - - foreach (HashTestVector test in vectorArray) - { - testVectors.Add(test); - } - - return testVectors; - } - - public static HashTestVector[] ReadHashTestVectorsArray(params string[] filenames) - { - IEnumerable<string> resourcePaths = filenames.Select(x => $"LibHac.Tests.CryptoTests.TestVectors.{x}"); - var testVectors = new List<HashTestVector>(); - - foreach (string path in resourcePaths) - { - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)) - { - var reader = new RspReader(stream); - - foreach (HashTestVector tv in reader.GetHashTestVectors()) - { - testVectors.Add(tv); - } - } - } - - return testVectors.ToArray(); - } } - public class EncryptionTestVector + public static TheoryData<EncryptionTestVector> ReadEncryptionTestVectors(bool getEncryptTests, params string[] filenames) { - public bool Encrypt { get; set; } - public int Count { get; set; } - public int DataUnitLength { get; set; } - public byte[] Key { get; set; } - public byte[] Iv { get; set; } - public byte[] PlainText { get; set; } - public byte[] CipherText { get; set; } + EncryptionTestVector[] vectorArray = ReadEncryptionTestVectorsArray(getEncryptTests, filenames); + var testVectors = new TheoryData<EncryptionTestVector>(); + + foreach (EncryptionTestVector test in vectorArray) + { + testVectors.Add(test); + } + + return testVectors; } - public class HashTestVector + public static EncryptionTestVector[] ReadEncryptionTestVectorsArray(bool getEncryptTests, params string[] filenames) { - public int LengthBits { get; set; } - public int LengthBytes { get; set; } - public byte[] Message { get; set; } - public byte[] Digest { get; set; } + IEnumerable<string> resourcePaths = filenames.Select(x => $"LibHac.Tests.CryptoTests.TestVectors.{x}"); + var testVectors = new List<EncryptionTestVector>(); + + foreach (string path in resourcePaths) + { + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)) + { + var reader = new RspReader(stream); + + foreach (EncryptionTestVector tv in reader.GetEncryptionTestVectors().Where(x => x.Encrypt == getEncryptTests)) + { + testVectors.Add(tv); + } + } + } + + return testVectors.ToArray(); + } + + public IEnumerable<HashTestVector> GetHashTestVectors() + { + string line; + + var testVector = new HashTestVector(); + bool canOutputVector = false; + + while ((line = Reader.ReadLine()?.Trim()) != null) + { + if (line.Length == 0) + { + if (canOutputVector) + { + yield return testVector; + + testVector = new HashTestVector(); + canOutputVector = false; + } + + continue; + } + + if (line[0] == '#') continue; + if (line[0] == '[') continue; + + string[] kvp = line.Split(new[] { " = " }, StringSplitOptions.None); + if (kvp.Length != 2) throw new InvalidDataException(); + + canOutputVector = true; + + switch (kvp[0].ToUpperInvariant()) + { + case "LEN": + testVector.LengthBits = int.Parse(kvp[1]); + testVector.LengthBytes = testVector.LengthBits / 8; + break; + case "MSG": + testVector.Message = kvp[1].ToBytes(); + break; + case "MD": + testVector.Digest = kvp[1].ToBytes(); + break; + } + } + } + + public static TheoryData<HashTestVector> ReadHashTestVectors(params string[] filenames) + { + HashTestVector[] vectorArray = ReadHashTestVectorsArray(filenames); + var testVectors = new TheoryData<HashTestVector>(); + + foreach (HashTestVector test in vectorArray) + { + testVectors.Add(test); + } + + return testVectors; + } + + public static HashTestVector[] ReadHashTestVectorsArray(params string[] filenames) + { + IEnumerable<string> resourcePaths = filenames.Select(x => $"LibHac.Tests.CryptoTests.TestVectors.{x}"); + var testVectors = new List<HashTestVector>(); + + foreach (string path in resourcePaths) + { + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)) + { + var reader = new RspReader(stream); + + foreach (HashTestVector tv in reader.GetHashTestVectors()) + { + testVectors.Add(tv); + } + } + } + + return testVectors.ToArray(); } } + +public class EncryptionTestVector +{ + public bool Encrypt { get; set; } + public int Count { get; set; } + public int DataUnitLength { get; set; } + public byte[] Key { get; set; } + public byte[] Iv { get; set; } + public byte[] PlainText { get; set; } + public byte[] CipherText { get; set; } +} + +public class HashTestVector +{ + public int LengthBits { get; set; } + public int LengthBytes { get; set; } + public byte[] Message { get; set; } + public byte[] Digest { get; set; } +} diff --git a/tests/LibHac.Tests/CryptoTests/Sha256Tests.cs b/tests/LibHac.Tests/CryptoTests/Sha256Tests.cs index 36b4aef1..6994368b 100644 --- a/tests/LibHac.Tests/CryptoTests/Sha256Tests.cs +++ b/tests/LibHac.Tests/CryptoTests/Sha256Tests.cs @@ -2,37 +2,36 @@ using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class Sha256Tests { - public class Sha256Tests + public static HashTestVector[] TestVectors = + RspReader.ReadHashTestVectorsArray("SHA256ShortMsg.rsp", "SHA256LongMsg.rsp"); + + [Fact] + public static void Encrypt() { - public static HashTestVector[] TestVectors = - RspReader.ReadHashTestVectorsArray("SHA256ShortMsg.rsp", "SHA256LongMsg.rsp"); - - [Fact] - public static void Encrypt() - { - foreach (HashTestVector tv in TestVectors) - { - Common.HashTestCore(tv.Message.AsSpan(0, tv.LengthBytes), tv.Digest, Sha256.CreateSha256Generator()); - } - } - - - // The above tests run all the test vectors in a single test to avoid having thousands of tests. - // Use the below tests if running each test vector as an individual test is needed. - - // ReSharper disable InconsistentNaming - -#pragma warning disable xUnit1013 // Public method should be marked as test - - public static TheoryData<HashTestVector> TestVectors_Individual = - RspReader.ReadHashTestVectors("SHA256ShortMsg.rsp", "SHA256LongMsg.rsp"); - - //[Theory, MemberData(nameof(TestVectors_Individual))] - public static void Encrypt_Individual(HashTestVector tv) + foreach (HashTestVector tv in TestVectors) { Common.HashTestCore(tv.Message.AsSpan(0, tv.LengthBytes), tv.Digest, Sha256.CreateSha256Generator()); } } + + + // The above tests run all the test vectors in a single test to avoid having thousands of tests. + // Use the below tests if running each test vector as an individual test is needed. + + // ReSharper disable InconsistentNaming + +#pragma warning disable xUnit1013 // Public method should be marked as test + + public static TheoryData<HashTestVector> TestVectors_Individual = + RspReader.ReadHashTestVectors("SHA256ShortMsg.rsp", "SHA256LongMsg.rsp"); + + //[Theory, MemberData(nameof(TestVectors_Individual))] + public static void Encrypt_Individual(HashTestVector tv) + { + Common.HashTestCore(tv.Message.AsSpan(0, tv.LengthBytes), tv.Digest, Sha256.CreateSha256Generator()); + } } diff --git a/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs b/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs index ac2802e5..ed5c980c 100644 --- a/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs +++ b/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs @@ -3,32 +3,31 @@ using System.Runtime.CompilerServices; using LibHac.Crypto; using Xunit; -namespace LibHac.Tests.CryptoTests +namespace LibHac.Tests.CryptoTests; + +public class TypeSizeTests { - public class TypeSizeTests + [Fact] + public static void AesKeySizeIs0x10() { - [Fact] - public static void AesKeySizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf<AesKey>()); - } + Assert.Equal(0x10, Unsafe.SizeOf<AesKey>()); + } - [Fact] - public static void AesXtsKeySizeIs0x20() - { - Assert.Equal(0x20, Unsafe.SizeOf<AesXtsKey>()); - } + [Fact] + public static void AesXtsKeySizeIs0x20() + { + Assert.Equal(0x20, Unsafe.SizeOf<AesXtsKey>()); + } - [Fact] - public static void AesIvSizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf<AesIv>()); - } + [Fact] + public static void AesIvSizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf<AesIv>()); + } - [Fact] - public static void AesCmacSizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf<Crypto.AesCmac>()); - } + [Fact] + public static void AesCmacSizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf<Crypto.AesCmac>()); } } diff --git a/tests/LibHac.Tests/DebugAssertHandler.cs b/tests/LibHac.Tests/DebugAssertHandler.cs index 17a29969..af045e7c 100644 --- a/tests/LibHac.Tests/DebugAssertHandler.cs +++ b/tests/LibHac.Tests/DebugAssertHandler.cs @@ -1,12 +1,11 @@ using System.Diagnostics; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class DebugAssertHandler : DefaultTraceListener { - public class DebugAssertHandler : DefaultTraceListener + public override void Fail(string message, string detailMessage) { - public override void Fail(string message, string detailMessage) - { - throw new LibHacException(message + detailMessage); - } + throw new LibHacException(message + detailMessage); } } diff --git a/tests/LibHac.Tests/Fs/AesXtsFileSystemTests.cs b/tests/LibHac.Tests/Fs/AesXtsFileSystemTests.cs index e4c62906..67ca8505 100644 --- a/tests/LibHac.Tests/Fs/AesXtsFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/AesXtsFileSystemTests.cs @@ -3,18 +3,17 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.Tests.Fs.IFileSystemTestBase; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class AesXtsFileSystemTests : IFileSystemTests { - public class AesXtsFileSystemTests : IFileSystemTests + protected override IFileSystem CreateFileSystem() { - protected override IFileSystem CreateFileSystem() - { - var baseFs = new InMemoryFileSystem(); + var baseFs = new InMemoryFileSystem(); - byte[] keys = new byte[0x20]; - var xtsFs = new AesXtsFileSystem(baseFs, keys, 0x4000); + byte[] keys = new byte[0x20]; + var xtsFs = new AesXtsFileSystem(baseFs, keys, 0x4000); - return xtsFs; - } + return xtsFs; } } diff --git a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs index 9b4d5039..ceff4a10 100644 --- a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs @@ -9,444 +9,443 @@ using LibHac.FsSystem; using LibHac.Tests.Fs.IFileSystemTestBase; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests { - public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests + protected override IFileSystem CreateFileSystem() { - protected override IFileSystem CreateFileSystem() + return CreateFileSystemInternal().saveFs; + } + + protected override IReopenableFileSystemCreator GetFileSystemCreator() + { + return new DirectorySaveDataFileSystemCreator(); + } + + private class DirectorySaveDataFileSystemCreator : IReopenableFileSystemCreator + { + private IFileSystem BaseFileSystem { get; } + + public DirectorySaveDataFileSystemCreator() { - return CreateFileSystemInternal().saveFs; + BaseFileSystem = new InMemoryFileSystem(); } - protected override IReopenableFileSystemCreator GetFileSystemCreator() + public IFileSystem Create() { - return new DirectorySaveDataFileSystemCreator(); + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true, true) + .ThrowIfFailure(); + + return saveFs; + } + } + + public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, + ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, + bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled, + FileSystemClient fsClient) + { + var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient); + Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported, + isJournalingEnabled); + + if (rc.IsSuccess()) + { + created = obj; + return Result.Success; } - private class DirectorySaveDataFileSystemCreator : IReopenableFileSystemCreator + obj.Dispose(); + UnsafeHelpers.SkipParamInit(out created); + return rc; + } + + public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, + bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + { + return CreateDirSaveFs(out created, baseFileSystem, null, null, isJournalingSupported, isMultiCommitSupported, + isJournalingEnabled, null); + } + + private (IFileSystem baseFs, DirectorySaveDataFileSystem saveFs) CreateFileSystemInternal() + { + var baseFs = new InMemoryFileSystem(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + return (baseFs, saveFs); + } + + [Fact] + public void CreateFile_CreatedInWorkingDirectory() + { + (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); + + saveFs.CreateFile("/file", 0, CreateFileOptions.None); + + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/1/file")); + Assert.Equal(DirectoryEntryType.File, type); + } + + [Fact] + public void CreateFile_NotCreatedInCommittedDirectory() + { + (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); + + saveFs.CreateFile("/file", 0, CreateFileOptions.None); + + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); + } + + [Fact] + public void Commit_FileExistsInCommittedDirectory() + { + (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); + + saveFs.CreateFile("/file", 0, CreateFileOptions.None); + + Assert.Success(saveFs.Commit()); + + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/0/file")); + Assert.Equal(DirectoryEntryType.File, type); + } + + [Fact] + public void Rollback_FileDoesNotExistInBaseAfterRollback() + { + (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); + + saveFs.CreateFile("/file", 0, CreateFileOptions.None); + + // Rollback should succeed + Assert.Success(saveFs.Rollback()); + + // Make sure all the files are gone + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file")); + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); + Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/1/file")); + } + + [Fact] + public void Rollback_DeletedFileIsRestoredInBaseAfterRollback() + { + (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); + + saveFs.CreateFile("/file", 0, CreateFileOptions.None); + saveFs.Commit(); + saveFs.DeleteFile("/file"); + + // Rollback should succeed + Assert.Success(saveFs.Rollback()); + + // Make sure all the files are restored + Assert.Success(saveFs.GetEntryType(out _, "/file")); + Assert.Success(baseFs.GetEntryType(out _, "/0/file")); + Assert.Success(baseFs.GetEntryType(out _, "/1/file")); + } + + [Fact] + public void Initialize_NormalState_UsesCommittedData() + { + var baseFs = new InMemoryFileSystem(); + + baseFs.CreateDirectory("/0").ThrowIfFailure(); + baseFs.CreateDirectory("/1").ThrowIfFailure(); + + // Set the existing files before initializing the save FS + baseFs.CreateFile("/0/file1", 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + Assert.Success(saveFs.GetEntryType(out _, "/file1")); + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file2")); + } + + [Fact] + public void Initialize_InterruptedAfterCommitPart1_UsesWorkingData() + { + var baseFs = new InMemoryFileSystem(); + + baseFs.CreateDirectory("/_").ThrowIfFailure(); + baseFs.CreateDirectory("/1").ThrowIfFailure(); + + // Set the existing files before initializing the save FS + baseFs.CreateFile("/_/file1", 0, CreateFileOptions.None).ThrowIfFailure(); + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); + Assert.Success(saveFs.GetEntryType(out _, "/file2")); + } + + [Fact] + public void Initialize_InterruptedDuringCommitPart2_UsesWorkingData() + { + var baseFs = new InMemoryFileSystem(); + + baseFs.CreateDirectory("/1").ThrowIfFailure(); + + // Set the existing files before initializing the save FS + baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); + Assert.Success(saveFs.GetEntryType(out _, "/file2")); + } + + [Fact] + public void Initialize_InitialExtraDataIsEmpty() + { + (IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal(); + + Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); + Assert.True(SpanHelpers.AsByteSpan(ref extraData).IsZeros()); + } + + [Fact] + public void WriteExtraData_CanReadBackExtraData() + { + (IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal(); + + var originalExtraData = new SaveDataExtraData(); + originalExtraData.DataSize = 0x12345; + + Assert.Success(saveFs.WriteExtraData(in originalExtraData)); + Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); + Assert.Equal(originalExtraData, extraData); + } + + [Fact] + public void Commit_AfterSuccessfulCommit_CanReadCommittedExtraData() + { + var baseFs = new InMemoryFileSystem(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + var originalExtraData = new SaveDataExtraData(); + originalExtraData.DataSize = 0x12345; + + saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); + Assert.Success(saveFs.CommitExtraData(false)); + + saveFs.Dispose(); + CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); + + Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); + Assert.Equal(originalExtraData, extraData); + } + + [Fact] + public void Rollback_WriteExtraDataThenRollback_ExtraDataIsRolledBack() + { + var baseFs = new InMemoryFileSystem(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + var originalExtraData = new SaveDataExtraData(); + originalExtraData.DataSize = 0x12345; + + saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); + saveFs.CommitExtraData(false).ThrowIfFailure(); + + saveFs.Dispose(); + CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); + + var newExtraData = new SaveDataExtraData(); + newExtraData.DataSize = 0x67890; + + saveFs.WriteExtraData(in newExtraData).ThrowIfFailure(); + + Assert.Success(saveFs.Rollback()); + Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); + + Assert.Equal(originalExtraData, extraData); + } + + [Fact] + public void Rollback_WriteExtraDataThenCloseFs_ExtraDataIsRolledBack() + { + var baseFs = new InMemoryFileSystem(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + // Write extra data and close with committing + var originalExtraData = new SaveDataExtraData(); + originalExtraData.DataSize = 0x12345; + + saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); + saveFs.CommitExtraData(false).ThrowIfFailure(); + + saveFs.Dispose(); + CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); + + // Write a new extra data and close without committing + var newExtraData = new SaveDataExtraData(); + newExtraData.DataSize = 0x67890; + + saveFs.WriteExtraData(in newExtraData).ThrowIfFailure(); + saveFs.Dispose(); + + // Read extra data should match the first one + CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); + Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); + + Assert.Equal(originalExtraData, extraData); + } + + [Fact] + public void Initialize_InterruptedAfterCommitPart1_UsesWorkingExtraData() + { + var baseFs = new InMemoryFileSystem(); + + CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure(); + CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure(); + + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); + + saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); + + Assert.Equal(0x67890, extraData.DataSize); + } + + [Fact] + public void CommitSaveData_MultipleCommits_CommitIdIsUpdatedSkippingInvalidIds() + { + var random = new RandomGenerator(); + RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); + var timeStampGetter = new TimeStampGetter(); + + var baseFs = new InMemoryFileSystem(); + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, + randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); + Assert.Equal(2, extraData.CommitId); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(3, extraData.CommitId); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(6, extraData.CommitId); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(2, extraData.CommitId); + } + + [Fact] + public void CommitSaveData_MultipleCommits_TimeStampUpdated() + { + var random = new RandomGenerator(); + RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); + var timeStampGetter = new TimeStampGetter(); + + var baseFs = new InMemoryFileSystem(); + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, + randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); + Assert.Equal(1u, extraData.TimeStamp); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(2u, extraData.TimeStamp); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(3u, extraData.TimeStamp); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(4u, extraData.TimeStamp); + } + + [Fact] + public void CommitSaveData_UpdateTimeStampIsFalse_TimeStampAndCommitIdAreNotUpdated() + { + var random = new RandomGenerator(); + RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); + var timeStampGetter = new TimeStampGetter(); + + var baseFs = new InMemoryFileSystem(); + CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, + randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); + Assert.Equal(1u, extraData.TimeStamp); + Assert.Equal(2, extraData.CommitId); + + saveFs.CommitExtraData(false).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(1u, extraData.TimeStamp); + Assert.Equal(2, extraData.CommitId); + + saveFs.CommitExtraData(true).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(2u, extraData.TimeStamp); + Assert.Equal(3, extraData.CommitId); + + saveFs.CommitExtraData(false).ThrowIfFailure(); + saveFs.ReadExtraData(out extraData).ThrowIfFailure(); + Assert.Equal(2u, extraData.TimeStamp); + Assert.Equal(3, extraData.CommitId); + } + + private class TimeStampGetter : ISaveDataCommitTimeStampGetter + { + private long _currentTimeStamp = 1; + + public Result Get(out long timeStamp) { - private IFileSystem BaseFileSystem { get; } - - public DirectorySaveDataFileSystemCreator() - { - BaseFileSystem = new InMemoryFileSystem(); - } - - public IFileSystem Create() - { - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true, true) - .ThrowIfFailure(); - - return saveFs; - } - } - - public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, - ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, - bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled, - FileSystemClient fsClient) - { - var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient); - Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported, - isJournalingEnabled); - - if (rc.IsSuccess()) - { - created = obj; - return Result.Success; - } - - obj.Dispose(); - UnsafeHelpers.SkipParamInit(out created); - return rc; - } - - public static Result CreateDirSaveFs(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem, - bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) - { - return CreateDirSaveFs(out created, baseFileSystem, null, null, isJournalingSupported, isMultiCommitSupported, - isJournalingEnabled, null); - } - - private (IFileSystem baseFs, DirectorySaveDataFileSystem saveFs) CreateFileSystemInternal() - { - var baseFs = new InMemoryFileSystem(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - return (baseFs, saveFs); - } - - [Fact] - public void CreateFile_CreatedInWorkingDirectory() - { - (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - - saveFs.CreateFile("/file", 0, CreateFileOptions.None); - - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/1/file")); - Assert.Equal(DirectoryEntryType.File, type); - } - - [Fact] - public void CreateFile_NotCreatedInCommittedDirectory() - { - (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - - saveFs.CreateFile("/file", 0, CreateFileOptions.None); - - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); - } - - [Fact] - public void Commit_FileExistsInCommittedDirectory() - { - (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - - saveFs.CreateFile("/file", 0, CreateFileOptions.None); - - Assert.Success(saveFs.Commit()); - - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/0/file")); - Assert.Equal(DirectoryEntryType.File, type); - } - - [Fact] - public void Rollback_FileDoesNotExistInBaseAfterRollback() - { - (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - - saveFs.CreateFile("/file", 0, CreateFileOptions.None); - - // Rollback should succeed - Assert.Success(saveFs.Rollback()); - - // Make sure all the files are gone - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file")); - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/0/file")); - Assert.Result(ResultFs.PathNotFound, baseFs.GetEntryType(out _, "/1/file")); - } - - [Fact] - public void Rollback_DeletedFileIsRestoredInBaseAfterRollback() - { - (IFileSystem baseFs, IFileSystem saveFs) = CreateFileSystemInternal(); - - saveFs.CreateFile("/file", 0, CreateFileOptions.None); - saveFs.Commit(); - saveFs.DeleteFile("/file"); - - // Rollback should succeed - Assert.Success(saveFs.Rollback()); - - // Make sure all the files are restored - Assert.Success(saveFs.GetEntryType(out _, "/file")); - Assert.Success(baseFs.GetEntryType(out _, "/0/file")); - Assert.Success(baseFs.GetEntryType(out _, "/1/file")); - } - - [Fact] - public void Initialize_NormalState_UsesCommittedData() - { - var baseFs = new InMemoryFileSystem(); - - baseFs.CreateDirectory("/0").ThrowIfFailure(); - baseFs.CreateDirectory("/1").ThrowIfFailure(); - - // Set the existing files before initializing the save FS - baseFs.CreateFile("/0/file1", 0, CreateFileOptions.None).ThrowIfFailure(); - baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - Assert.Success(saveFs.GetEntryType(out _, "/file1")); - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file2")); - } - - [Fact] - public void Initialize_InterruptedAfterCommitPart1_UsesWorkingData() - { - var baseFs = new InMemoryFileSystem(); - - baseFs.CreateDirectory("/_").ThrowIfFailure(); - baseFs.CreateDirectory("/1").ThrowIfFailure(); - - // Set the existing files before initializing the save FS - baseFs.CreateFile("/_/file1", 0, CreateFileOptions.None).ThrowIfFailure(); - baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); - Assert.Success(saveFs.GetEntryType(out _, "/file2")); - } - - [Fact] - public void Initialize_InterruptedDuringCommitPart2_UsesWorkingData() - { - var baseFs = new InMemoryFileSystem(); - - baseFs.CreateDirectory("/1").ThrowIfFailure(); - - // Set the existing files before initializing the save FS - baseFs.CreateFile("/1/file2", 0, CreateFileOptions.None).ThrowIfFailure(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - Assert.Result(ResultFs.PathNotFound, saveFs.GetEntryType(out _, "/file1")); - Assert.Success(saveFs.GetEntryType(out _, "/file2")); - } - - [Fact] - public void Initialize_InitialExtraDataIsEmpty() - { - (IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal(); - - Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); - Assert.True(SpanHelpers.AsByteSpan(ref extraData).IsZeros()); - } - - [Fact] - public void WriteExtraData_CanReadBackExtraData() - { - (IFileSystem _, DirectorySaveDataFileSystem saveFs) = CreateFileSystemInternal(); - - var originalExtraData = new SaveDataExtraData(); - originalExtraData.DataSize = 0x12345; - - Assert.Success(saveFs.WriteExtraData(in originalExtraData)); - Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); - Assert.Equal(originalExtraData, extraData); - } - - [Fact] - public void Commit_AfterSuccessfulCommit_CanReadCommittedExtraData() - { - var baseFs = new InMemoryFileSystem(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - var originalExtraData = new SaveDataExtraData(); - originalExtraData.DataSize = 0x12345; - - saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); - Assert.Success(saveFs.CommitExtraData(false)); - - saveFs.Dispose(); - CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); - - Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); - Assert.Equal(originalExtraData, extraData); - } - - [Fact] - public void Rollback_WriteExtraDataThenRollback_ExtraDataIsRolledBack() - { - var baseFs = new InMemoryFileSystem(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - var originalExtraData = new SaveDataExtraData(); - originalExtraData.DataSize = 0x12345; - - saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); - saveFs.CommitExtraData(false).ThrowIfFailure(); - - saveFs.Dispose(); - CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); - - var newExtraData = new SaveDataExtraData(); - newExtraData.DataSize = 0x67890; - - saveFs.WriteExtraData(in newExtraData).ThrowIfFailure(); - - Assert.Success(saveFs.Rollback()); - Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); - - Assert.Equal(originalExtraData, extraData); - } - - [Fact] - public void Rollback_WriteExtraDataThenCloseFs_ExtraDataIsRolledBack() - { - var baseFs = new InMemoryFileSystem(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - // Write extra data and close with committing - var originalExtraData = new SaveDataExtraData(); - originalExtraData.DataSize = 0x12345; - - saveFs.WriteExtraData(in originalExtraData).ThrowIfFailure(); - saveFs.CommitExtraData(false).ThrowIfFailure(); - - saveFs.Dispose(); - CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); - - // Write a new extra data and close without committing - var newExtraData = new SaveDataExtraData(); - newExtraData.DataSize = 0x67890; - - saveFs.WriteExtraData(in newExtraData).ThrowIfFailure(); - saveFs.Dispose(); - - // Read extra data should match the first one - CreateDirSaveFs(out saveFs, baseFs, true, true, true).ThrowIfFailure(); - Assert.Success(saveFs.ReadExtraData(out SaveDataExtraData extraData)); - - Assert.Equal(originalExtraData, extraData); - } - - [Fact] - public void Initialize_InterruptedAfterCommitPart1_UsesWorkingExtraData() - { - var baseFs = new InMemoryFileSystem(); - - CreateExtraDataForTest(baseFs, "/ExtraData_", 0x12345).ThrowIfFailure(); - CreateExtraDataForTest(baseFs, "/ExtraData1", 0x67890).ThrowIfFailure(); - - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, true, true, true).ThrowIfFailure(); - - saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); - - Assert.Equal(0x67890, extraData.DataSize); - } - - [Fact] - public void CommitSaveData_MultipleCommits_CommitIdIsUpdatedSkippingInvalidIds() - { - var random = new RandomGenerator(); - RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); - var timeStampGetter = new TimeStampGetter(); - - var baseFs = new InMemoryFileSystem(); - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, - randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); - Assert.Equal(2, extraData.CommitId); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(3, extraData.CommitId); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(6, extraData.CommitId); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(2, extraData.CommitId); - } - - [Fact] - public void CommitSaveData_MultipleCommits_TimeStampUpdated() - { - var random = new RandomGenerator(); - RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); - var timeStampGetter = new TimeStampGetter(); - - var baseFs = new InMemoryFileSystem(); - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, - randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); - Assert.Equal(1u, extraData.TimeStamp); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(2u, extraData.TimeStamp); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(3u, extraData.TimeStamp); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(4u, extraData.TimeStamp); - } - - [Fact] - public void CommitSaveData_UpdateTimeStampIsFalse_TimeStampAndCommitIdAreNotUpdated() - { - var random = new RandomGenerator(); - RandomDataGenerator randomGeneratorFunc = buffer => random.GenerateRandom(buffer); - var timeStampGetter = new TimeStampGetter(); - - var baseFs = new InMemoryFileSystem(); - CreateDirSaveFs(out DirectorySaveDataFileSystem saveFs, baseFs, timeStampGetter, - randomGeneratorFunc, true, true, true, null).ThrowIfFailure(); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out SaveDataExtraData extraData).ThrowIfFailure(); - Assert.Equal(1u, extraData.TimeStamp); - Assert.Equal(2, extraData.CommitId); - - saveFs.CommitExtraData(false).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(1u, extraData.TimeStamp); - Assert.Equal(2, extraData.CommitId); - - saveFs.CommitExtraData(true).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(2u, extraData.TimeStamp); - Assert.Equal(3, extraData.CommitId); - - saveFs.CommitExtraData(false).ThrowIfFailure(); - saveFs.ReadExtraData(out extraData).ThrowIfFailure(); - Assert.Equal(2u, extraData.TimeStamp); - Assert.Equal(3, extraData.CommitId); - } - - private class TimeStampGetter : ISaveDataCommitTimeStampGetter - { - private long _currentTimeStamp = 1; - - public Result Get(out long timeStamp) - { - timeStamp = _currentTimeStamp++; - return Result.Success; - } - } - - private class RandomGenerator - { - private static readonly int[] Values = { 2, 0, 3, 3, 6, 0 }; - - private int _index; - - public Result GenerateRandom(Span<byte> output) - { - if (output.Length != 8) - throw new ArgumentException(); - - Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index]; - - _index = (_index + 1) % Values.Length; - return Result.Success; - } - } - - private Result CreateExtraDataForTest(IFileSystem fileSystem, string path, int saveDataSize) - { - fileSystem.DeleteFile(path).IgnoreResult(); - - Result rc = fileSystem.CreateFile(path, Unsafe.SizeOf<SaveDataExtraData>()); - if (rc.IsFailure()) return rc; - - var extraData = new SaveDataExtraData(); - extraData.DataSize = saveDataSize; - - using var file = new UniqueRef<IFile>(); - rc = fileSystem.OpenFile(ref file.Ref(), path, OpenMode.ReadWrite); - if (rc.IsFailure()) return rc; - - rc = file.Get.Write(0, SpanHelpers.AsByteSpan(ref extraData), WriteOption.Flush); - if (rc.IsFailure()) return rc; - + timeStamp = _currentTimeStamp++; return Result.Success; } } -} \ No newline at end of file + + private class RandomGenerator + { + private static readonly int[] Values = { 2, 0, 3, 3, 6, 0 }; + + private int _index; + + public Result GenerateRandom(Span<byte> output) + { + if (output.Length != 8) + throw new ArgumentException(); + + Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index]; + + _index = (_index + 1) % Values.Length; + return Result.Success; + } + } + + private Result CreateExtraDataForTest(IFileSystem fileSystem, string path, int saveDataSize) + { + fileSystem.DeleteFile(path).IgnoreResult(); + + Result rc = fileSystem.CreateFile(path, Unsafe.SizeOf<SaveDataExtraData>()); + if (rc.IsFailure()) return rc; + + var extraData = new SaveDataExtraData(); + extraData.DataSize = saveDataSize; + + using var file = new UniqueRef<IFile>(); + rc = fileSystem.OpenFile(ref file.Ref(), path, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc; + + rc = file.Get.Write(0, SpanHelpers.AsByteSpan(ref extraData), WriteOption.Flush); + if (rc.IsFailure()) return rc; + + return Result.Success; + } +} diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs index 793d356d..37eca4fb 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs @@ -7,206 +7,205 @@ using Xunit; using static LibHac.Fs.ApplicationSaveDataManagement; -namespace LibHac.Tests.Fs.FileSystemClientTests +namespace LibHac.Tests.Fs.FileSystemClientTests; + +public class ApplicationSaveDataManagementTests { - public class ApplicationSaveDataManagementTests + [Fact] + public static void EnsureApplicationSaveData_CreatesAccountSaveData() { - [Fact] - public static void EnsureApplicationSaveData_CreatesAccountSaveData() + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + var applicationId = new Ncm.ApplicationId(11); + var userId = new Uid(2, 3); + + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + UserAccountSaveDataSize = 0x1000, + UserAccountSaveDataJournalSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); - var userId = new Uid(2, 3); + Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); - var nacp = new ApplicationControlProperty - { - UserAccountSaveDataSize = 0x1000, - UserAccountSaveDataJournalSize = 0x1000 - }; + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(ConvertAccountUidToFsUserId(userId), info[0].UserId); + Assert.Equal(SaveDataType.Account, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Fact] + public static void EnsureApplicationSaveData_CreatesDeviceSaveData() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(ConvertAccountUidToFsUserId(userId), info[0].UserId); - Assert.Equal(SaveDataType.Account, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); + var userId = new Uid(2, 3); - [Fact] - public static void EnsureApplicationSaveData_CreatesDeviceSaveData() + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + DeviceSaveDataSize = 0x1000, + DeviceSaveDataJournalSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); - var userId = new Uid(2, 3); + Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); - var nacp = new ApplicationControlProperty - { - DeviceSaveDataSize = 0x1000, - DeviceSaveDataJournalSize = 0x1000 - }; + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(UserId.InvalidId, info[0].UserId); + Assert.Equal(SaveDataType.Device, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Fact] + public static void EnsureApplicationSaveData_CreatesBcatCacheStorage() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.InvalidId, info[0].UserId); - Assert.Equal(SaveDataType.Device, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); + var userId = new Uid(2, 3); - [Fact] - public static void EnsureApplicationSaveData_CreatesBcatCacheStorage() + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + BcatDeliveryCacheStorageSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); - var userId = new Uid(2, 3); + Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); - var nacp = new ApplicationControlProperty - { - BcatDeliveryCacheStorageSize = 0x1000 - }; + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(UserId.InvalidId, info[0].UserId); + Assert.Equal(SaveDataType.Bcat, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Fact] + public static void EnsureApplicationSaveData_CreatesTemporaryStorage() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.InvalidId, info[0].UserId); - Assert.Equal(SaveDataType.Bcat, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); + var userId = new Uid(2, 3); - [Fact] - public static void EnsureApplicationSaveData_CreatesTemporaryStorage() + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + TemporaryStorageSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); - var userId = new Uid(2, 3); + Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); - var nacp = new ApplicationControlProperty - { - TemporaryStorageSize = 0x1000 - }; + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.Temporary); - Assert.Success(EnsureApplicationSaveData(fs, out _, applicationId, ref nacp, ref userId)); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.Temporary); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(UserId.InvalidId, info[0].UserId); + Assert.Equal(SaveDataType.Temporary, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Fact] + public static void EnsureApplicationCacheStorage_SdCardAvailable_CreatesCacheStorageOnSd() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.InvalidId, info[0].UserId); - Assert.Equal(SaveDataType.Temporary, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); - [Fact] - public static void EnsureApplicationCacheStorage_SdCardAvailable_CreatesCacheStorageOnSd() + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + CacheStorageSize = 0x1000, + CacheStorageJournalSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); + Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId, + ref nacp)); - var nacp = new ApplicationControlProperty - { - CacheStorageSize = 0x1000, - CacheStorageJournalSize = 0x1000 - }; + Assert.Equal(CacheStorageTargetMedia.SdCard, target); - Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId, - ref nacp)); + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.SdCache); - Assert.Equal(CacheStorageTargetMedia.SdCard, target); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.SdCache); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(SaveDataType.Cache, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Fact] + public static void EnsureApplicationCacheStorage_SdCardNotAvailable_CreatesCacheStorageOnBis() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(SaveDataType.Cache, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); - [Fact] - public static void EnsureApplicationCacheStorage_SdCardNotAvailable_CreatesCacheStorageOnBis() + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + CacheStorageSize = 0x1000, + CacheStorageJournalSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); + Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId, + ref nacp)); - var nacp = new ApplicationControlProperty - { - CacheStorageSize = 0x1000, - CacheStorageJournalSize = 0x1000 - }; + Assert.Equal(CacheStorageTargetMedia.Nand, target); - Assert.Success(fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia target, applicationId, - ref nacp)); + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - Assert.Equal(CacheStorageTargetMedia.Nand, target); + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(SaveDataType.Cache, info[0].Type); + } - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void GetCacheStorageTargetMedia_ReturnsTargetOfNewCacheStorage(bool isSdCardInserted) + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(isSdCardInserted); - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(SaveDataType.Cache, info[0].Type); - } + var applicationId = new Ncm.ApplicationId(11); - [Theory] - [InlineData(true)] - [InlineData(false)] - public static void GetCacheStorageTargetMedia_ReturnsTargetOfNewCacheStorage(bool isSdCardInserted) + var nacp = new ApplicationControlProperty { - FileSystemClient fs = FileSystemServerFactory.CreateClient(isSdCardInserted); + CacheStorageSize = 0x1000, + CacheStorageJournalSize = 0x1000 + }; - var applicationId = new Ncm.ApplicationId(11); + fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia targetFromCreation, applicationId, ref nacp); - var nacp = new ApplicationControlProperty - { - CacheStorageSize = 0x1000, - CacheStorageJournalSize = 0x1000 - }; + Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, applicationId)); + Assert.Equal(targetFromCreation, target); + } - fs.EnsureApplicationCacheStorage(out _, out CacheStorageTargetMedia targetFromCreation, applicationId, ref nacp); + [Fact] + public static void GetCacheStorageTargetMedia_CacheStorageDoesNotExist_ReturnsNone() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, applicationId)); - Assert.Equal(targetFromCreation, target); - } - - [Fact] - public static void GetCacheStorageTargetMedia_CacheStorageDoesNotExist_ReturnsNone() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, new Ncm.ApplicationId(11))); - Assert.Equal(CacheStorageTargetMedia.None, target); - } + Assert.Success(fs.GetCacheStorageTargetMedia(out CacheStorageTargetMedia target, new Ncm.ApplicationId(11))); + Assert.Equal(CacheStorageTargetMedia.None, target); } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs index ef38f2bc..eb7438a9 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs @@ -3,55 +3,54 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; -namespace LibHac.Tests.Fs.FileSystemClientTests +namespace LibHac.Tests.Fs.FileSystemClientTests; + +public static class FileSystemServerFactory { - public static class FileSystemServerFactory + private static Horizon CreateHorizonImpl(bool sdCardInserted, out IFileSystem rootFs) { - private static Horizon CreateHorizonImpl(bool sdCardInserted, out IFileSystem rootFs) - { - rootFs = new InMemoryFileSystem(); - var keySet = new KeySet(); + rootFs = new InMemoryFileSystem(); + var keySet = new KeySet(); - var horizon = new Horizon(new HorizonConfiguration()); + var horizon = new Horizon(new HorizonConfiguration()); - HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); - var fsServer = new FileSystemServer(fsServerClient); + HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); + var fsServer = new FileSystemServer(fsServerClient); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); - defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); + defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); - var config = new FileSystemServerConfig(); - config.FsCreators = defaultObjects.FsCreators; - config.DeviceOperator = defaultObjects.DeviceOperator; - config.ExternalKeySet = new ExternalKeySet(); + var config = new FileSystemServerConfig(); + config.FsCreators = defaultObjects.FsCreators; + config.DeviceOperator = defaultObjects.DeviceOperator; + config.ExternalKeySet = new ExternalKeySet(); - FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); - return horizon; - } + FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); + return horizon; + } - private static FileSystemClient CreateClientImpl(bool sdCardInserted, out IFileSystem rootFs) - { - Horizon horizon = CreateHorizonImpl(sdCardInserted, out rootFs); + private static FileSystemClient CreateClientImpl(bool sdCardInserted, out IFileSystem rootFs) + { + Horizon horizon = CreateHorizonImpl(sdCardInserted, out rootFs); - HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient(); + HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient(); - return horizonClient.Fs; - } + return horizonClient.Fs; + } - public static FileSystemClient CreateClient(bool sdCardInserted) - { - return CreateClientImpl(sdCardInserted, out _); - } + public static FileSystemClient CreateClient(bool sdCardInserted) + { + return CreateClientImpl(sdCardInserted, out _); + } - public static FileSystemClient CreateClient(out IFileSystem rootFs) - { - return CreateClientImpl(false, out rootFs); - } + public static FileSystemClient CreateClient(out IFileSystem rootFs) + { + return CreateClientImpl(false, out rootFs); + } - public static Horizon CreateHorizonServer() - { - return CreateHorizonImpl(true, out _); - } + public static Horizon CreateHorizonServer() + { + return CreateHorizonImpl(true, out _); } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/BcatSaveData.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/BcatSaveData.cs index d35f2fdd..87a2f1b6 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/BcatSaveData.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/BcatSaveData.cs @@ -4,48 +4,47 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using Xunit; -namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class BcatSaveData { - public class BcatSaveData + [Fact] + public void MountBcatSaveData_SaveDoesNotExist_ReturnsTargetNotFound() { - [Fact] - public void MountBcatSaveData_SaveDoesNotExist_ReturnsTargetNotFound() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Result(ResultFs.TargetNotFound, fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); - } + Assert.Result(ResultFs.TargetNotFound, fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); + } - [Fact] - public void MountBcatSaveData_SaveExists_ReturnsSuccess() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void MountBcatSaveData_SaveExists_ReturnsSuccess() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); - Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); - } + Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); + Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); + } - [Fact] - public void MountBcatSaveData_WrittenDataPersists() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void MountBcatSaveData_WrittenDataPersists() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); - Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); + Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); + Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); - // Check that the path doesn't exist - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "bcat_test:/file".ToU8Span())); + // Check that the path doesn't exist + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "bcat_test:/file".ToU8Span())); - fs.CreateFile("bcat_test:/file".ToU8Span(), 0); - fs.Commit("bcat_test".ToU8Span()); - fs.Unmount("bcat_test".ToU8Span()); + fs.CreateFile("bcat_test:/file".ToU8Span(), 0); + fs.Commit("bcat_test".ToU8Span()); + fs.Unmount("bcat_test".ToU8Span()); - Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "bcat_test:/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + Assert.Success(fs.MountBcatSaveData("bcat_test".ToU8Span(), applicationId)); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "bcat_test:/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs index c5f17ebc..b89f1986 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs @@ -4,94 +4,93 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using Xunit; -namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class Bis { - public class Bis + [Fact] + public void MountBis_MountCalibrationPartition_OpensCorrectDirectory() { - [Fact] - public void MountBis_MountCalibrationPartition_OpensCorrectDirectory() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); - Assert.Success(fs.MountBis("calib".ToU8Span(), BisPartitionId.CalibrationFile)); + Assert.Success(fs.MountBis("calib".ToU8Span(), BisPartitionId.CalibrationFile)); - // Create a file in the opened file system - Assert.Success(fs.CreateFile("calib:/file".ToU8Span(), 0)); + // Create a file in the opened file system + Assert.Success(fs.CreateFile("calib:/file".ToU8Span(), 0)); - // Make sure the file exists on the root file system - Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/cal/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + // Make sure the file exists on the root file system + Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/cal/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountBis_MountSafePartition_OpensCorrectDirectory() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + [Fact] + public void MountBis_MountSafePartition_OpensCorrectDirectory() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); - Assert.Success(fs.MountBis("safe".ToU8Span(), BisPartitionId.SafeMode)); + Assert.Success(fs.MountBis("safe".ToU8Span(), BisPartitionId.SafeMode)); - // Create a file in the opened file system - Assert.Success(fs.CreateFile("safe:/file".ToU8Span(), 0)); + // Create a file in the opened file system + Assert.Success(fs.CreateFile("safe:/file".ToU8Span(), 0)); - // Make sure the file exists on the root file system - Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/safe/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + // Make sure the file exists on the root file system + Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/safe/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountBis_MountSystemPartition_OpensCorrectDirectory() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + [Fact] + public void MountBis_MountSystemPartition_OpensCorrectDirectory() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); - Assert.Success(fs.MountBis("system".ToU8Span(), BisPartitionId.System)); + Assert.Success(fs.MountBis("system".ToU8Span(), BisPartitionId.System)); - // Create a file in the opened file system - Assert.Success(fs.CreateFile("system:/file".ToU8Span(), 0)); + // Create a file in the opened file system + Assert.Success(fs.CreateFile("system:/file".ToU8Span(), 0)); - // Make sure the file exists on the root file system - Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/system/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + // Make sure the file exists on the root file system + Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/system/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountBis_MountUserPartition_OpensCorrectDirectory() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + [Fact] + public void MountBis_MountUserPartition_OpensCorrectDirectory() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); - Assert.Success(fs.MountBis("user".ToU8Span(), BisPartitionId.User)); + Assert.Success(fs.MountBis("user".ToU8Span(), BisPartitionId.User)); - // Create a file in the opened file system - Assert.Success(fs.CreateFile("user:/file".ToU8Span(), 0)); + // Create a file in the opened file system + Assert.Success(fs.CreateFile("user:/file".ToU8Span(), 0)); - // Make sure the file exists on the root file system - Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/user/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + // Make sure the file exists on the root file system + Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/user/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountBis_WithRootPath_IgnoresRootPath() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + [Fact] + public void MountBis_WithRootPath_IgnoresRootPath() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); - Assert.Success(fs.MountBis(BisPartitionId.User, "/sub".ToU8Span())); + Assert.Success(fs.MountBis(BisPartitionId.User, "/sub".ToU8Span())); - // Create a file in the opened file system - Assert.Success(fs.CreateFile("@User:/file".ToU8Span(), 0)); + // Create a file in the opened file system + Assert.Success(fs.CreateFile("@User:/file".ToU8Span(), 0)); - // Make sure the file wasn't created in the sub path - Assert.Result(ResultFs.PathNotFound, rootFs.GetEntryType(out _, "/bis/user/sub/file".ToU8Span())); + // Make sure the file wasn't created in the sub path + Assert.Result(ResultFs.PathNotFound, rootFs.GetEntryType(out _, "/bis/user/sub/file".ToU8Span())); - // Make sure the file was created in the main path - Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/user/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + // Make sure the file was created in the main path + Assert.Success(rootFs.GetEntryType(out DirectoryEntryType type, "/bis/user/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountBis_InvalidPartition_ReturnsInvalidArgument() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem _); + [Fact] + public void MountBis_InvalidPartition_ReturnsInvalidArgument() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem _); - Assert.Result(ResultFs.InvalidArgument, fs.MountBis("boot1".ToU8Span(), BisPartitionId.BootPartition1Root)); - } + Assert.Result(ResultFs.InvalidArgument, fs.MountBis("boot1".ToU8Span(), BisPartitionId.BootPartition1Root)); } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs index e7f557db..01709c9f 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveData.cs @@ -4,65 +4,64 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using Xunit; -namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class SaveData { - public class SaveData + [Fact] + public void MountCacheStorage_CanMountCreatedCacheStorage() { - [Fact] - public void MountCacheStorage_CanMountCreatedCacheStorage() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None); + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None); - Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); - } + Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); + } - [Fact] - public void MountCacheStorage_WrittenDataPersists() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void MountCacheStorage_WrittenDataPersists() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None); - fs.MountCacheStorage("cache".ToU8Span(), applicationId); + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None); + fs.MountCacheStorage("cache".ToU8Span(), applicationId); - fs.CreateFile("cache:/file".ToU8Span(), 0); - fs.Commit("cache".ToU8Span()); - fs.Unmount("cache".ToU8Span()); + fs.CreateFile("cache:/file".ToU8Span(), 0); + fs.Commit("cache".ToU8Span()); + fs.Unmount("cache".ToU8Span()); - Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "cache:/file".ToU8Span())); - Assert.Equal(DirectoryEntryType.File, type); - } + Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "cache:/file".ToU8Span())); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void MountCacheStorage_SdCardIsPreferredOverBis() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void MountCacheStorage_SdCardIsPreferredOverBis() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); - Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); - fs.CreateFile("cache:/sd".ToU8Span(), 0); - fs.Commit("cache".ToU8Span()); - fs.Unmount("cache".ToU8Span()); + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); + Assert.Success(fs.MountCacheStorage("cache".ToU8Span(), applicationId)); + fs.CreateFile("cache:/sd".ToU8Span(), 0); + fs.Commit("cache".ToU8Span()); + fs.Unmount("cache".ToU8Span()); - // Turn off the SD card so the User save is mounted - fs.SetSdCardAccessibility(false); + // Turn off the SD card so the User save is mounted + fs.SetSdCardAccessibility(false); - fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None); - fs.MountCacheStorage("cache".ToU8Span(), applicationId); - fs.CreateFile("cache:/bis".ToU8Span(), 0); - fs.Commit("cache".ToU8Span()); - fs.Unmount("cache".ToU8Span()); + fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None); + fs.MountCacheStorage("cache".ToU8Span(), applicationId); + fs.CreateFile("cache:/bis".ToU8Span(), 0); + fs.Commit("cache".ToU8Span()); + fs.Unmount("cache".ToU8Span()); - fs.SetSdCardAccessibility(true); + fs.SetSdCardAccessibility(true); - Assert.Success(fs.MountCacheStorage("cache".ToU8String(), applicationId)); - Assert.Success(fs.GetEntryType(out _, "cache:/sd".ToU8Span())); - Assert.Failure(fs.GetEntryType(out _, "cache:/bis".ToU8Span())); - } + Assert.Success(fs.MountCacheStorage("cache".ToU8String(), applicationId)); + Assert.Success(fs.GetEntryType(out _, "cache:/sd".ToU8Span())); + Assert.Failure(fs.GetEntryType(out _, "cache:/bis".ToU8Span())); } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs index 7bd357b5..4ab80cfb 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs @@ -8,599 +8,598 @@ using LibHac.Ncm; using LibHac.Time; using Xunit; -namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class SaveDataManagement { - public class SaveDataManagement + [Fact] + public void CreateCacheStorage_InUserSaveSpace_StorageIsCreated() { - [Fact] - public void CreateCacheStorage_InUserSaveSpace_StorageIsCreated() + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + } + + [Fact] + public void CreateCacheStorage_InSdCacheSaveSpace_StorageIsCreated() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.SdCache); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + } + + [Fact] + public void CreateCacheStorage_InSdCacheSaveSpaceWhenNoSdCard_ReturnsSdCardNotFound() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + + Assert.Result(ResultFs.PortSdCardNoDevice, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); + } + + [Fact] + public void CreateCacheStorage_AlreadyExists_ReturnsPathAlreadyExists() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); + Assert.Result(ResultFs.PathAlreadyExists, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); + } + + [Fact] + public void CreateCacheStorage_WithIndex_CreatesMultiple() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, 0, SaveDataFlags.None)); + Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 1, 0, 0, SaveDataFlags.None)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + + var info = new SaveDataInfo[3]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(2, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(applicationId, info[1].ProgramId); + + ushort[] expectedIndexes = { 0, 1 }; + ushort[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray(); + + Assert.Equal(expectedIndexes, actualIndexes); + } + + [Fact] + public void CreateBcatSaveData_DoesNotExist_SaveIsCreated() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(SaveDataType.Bcat, info[0].Type); + } + + [Fact] + public void CreateBcatSaveData_AlreadyExists_ReturnsPathAlreadyExists() + { + var applicationId = new Ncm.ApplicationId(1); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); + Assert.Result(ResultFs.PathAlreadyExists, fs.CreateBcatSaveData(applicationId, 0x400000)); + } + + [Fact] + public void CreateSystemSaveData_DoesNotExist_SaveIsCreatedInSystem() + { + ulong saveId = 0x8000000001234000; + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); + + // Make sure it was placed in the System save space with the right info. + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.System)); + + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + + Assert.Equal(1, entriesRead); + Assert.Equal(SaveDataType.System, info[0].Type); + Assert.Equal(SaveDataSpaceId.System, info[0].SpaceId); + Assert.Equal(saveId, info[0].StaticSaveDataId); + Assert.Equal(saveId, info[0].SaveDataId); + Assert.Equal(SaveDataState.Normal, info[0].State); + } + + [Theory] + [InlineData(AccessControlBits.Bits.SystemSaveData)] + [InlineData(AccessControlBits.Bits.None)] + public void CreateSystemSaveData_HasBuiltInSystemPermission_SaveIsCreatedInSystem(AccessControlBits.Bits permissions) + { + ulong saveId = 0x8000000001234000; + + Horizon hos = FileSystemServerFactory.CreateHorizonServer(); + + var mainProgramId = new ProgramId(0x123456); + + HorizonClient client = hos.CreateHorizonClient(new ProgramLocation(mainProgramId, StorageId.BuiltInSystem), + permissions); + + HorizonClient privilegedClient = hos.CreatePrivilegedHorizonClient(); + + // Create the save + if (permissions.HasFlag(AccessControlBits.Bits.SystemSaveData)) { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); + Assert.Success(client.Fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); + } + else + { + // Creation should fail if we don't have the right permissions. + Assert.Failure(client.Fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); + return; } - [Fact] - public void CreateCacheStorage_InSdCacheSaveSpace_StorageIsCreated() + // Make sure it was placed in the System save space with the right info. + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(privilegedClient.Fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.System)); + + var info = new SaveDataInfo[2]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); + + Assert.Equal(1, entriesRead); + Assert.Equal(SaveDataType.System, info[0].Type); + Assert.Equal(SaveDataSpaceId.System, info[0].SpaceId); + Assert.Equal(saveId, info[0].StaticSaveDataId); + Assert.Equal(saveId, info[0].SaveDataId); + Assert.Equal(SaveDataState.Normal, info[0].State); + } + + [Fact] + public void CreateSaveData_DoesNotExist_SaveIsCreated() + { + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + Assert.Equal(applicationId, info[0].ProgramId); + Assert.Equal(SaveDataType.Account, info[0].Type); + Assert.Equal(userId, info[0].UserId); + } + + [Fact] + public void CreateSaveData_DoesNotExist_HasCorrectOwnerId() + { + uint ownerId = 1; + + var applicationId = new Ncm.ApplicationId(ownerId); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, ownerId, 0x1000, 0x1000, SaveDataFlags.None)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + // Get the created save data's owner ID + Assert.Success(fs.GetSaveDataOwnerId(out ulong actualOwnerId, info[0].SaveDataId)); + + Assert.Equal(ownerId, actualOwnerId); + } + + [Fact] + public void CreateSaveData_DoesNotExist_HasCorrectFlags() + { + SaveDataFlags flags = SaveDataFlags.KeepAfterRefurbishment | SaveDataFlags.NeedsSecureDelete; + + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, flags)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + // Get the created save data's flags + Assert.Success(fs.GetSaveDataFlags(out SaveDataFlags actualFlags, info[0].SaveDataId)); + + Assert.Equal(flags, actualFlags); + } + + [Fact] + public void CreateSaveData_DoesNotExist_HasCorrectSizes() + { + long availableSize = 0x220000; + long journalSize = 0x120000; + + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, availableSize, journalSize, SaveDataFlags.None)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + // Get the created save data's sizes + Assert.Success(fs.GetSaveDataAvailableSize(out long actualAvailableSize, info[0].SaveDataId)); + Assert.Success(fs.GetSaveDataJournalSize(out long actualJournalSize, info[0].SaveDataId)); + + Assert.Equal(availableSize, actualAvailableSize); + Assert.Equal(journalSize, actualJournalSize); + } + + [Fact] + public void CreateSaveData_FromSubProgram_CreatesSaveDataForMainProgram() + { + Horizon hos = FileSystemServerFactory.CreateHorizonServer(); + + Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[5]; + + var mainProgramId = new ProgramId(0x123456); + var programId = new ProgramId(mainProgramId.Value + 2); + + for (int i = 0; i < mapInfo.Length; i++) { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.SdCache); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); + mapInfo[i].MainProgramId = mainProgramId; + mapInfo[i].ProgramId = new ProgramId(mainProgramId.Value + (uint)i); + mapInfo[i].ProgramIndex = (byte)i; } - [Fact] - public void CreateCacheStorage_InSdCacheSaveSpaceWhenNoSdCard_ReturnsSdCardNotFound() + HorizonClient client = hos.CreatePrivilegedHorizonClient(); + HorizonClient subProgramClient = + hos.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), + AccessControlBits.Bits.CreateSaveData); + + Assert.Success(client.Fs.RegisterProgramIndexMapInfo(mapInfo)); + + Assert.Success(subProgramClient.Fs.CreateSaveData(Ncm.ApplicationId.InvalidId, UserId.InvalidId, 0, 0x4000, + 0x4000, SaveDataFlags.None)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(client.Fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + Assert.Equal(mainProgramId, info[0].ProgramId); + } + + [Fact] + public void DeleteSaveData_DoesNotExist_ReturnsTargetNotFound() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Result(ResultFs.TargetNotFound, fs.DeleteSaveData(1)); + } + + [Fact] + public void DeleteSaveData_SaveExistsInUserSaveSpace_SaveIsDeleted() + { + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); + + // Get the ID of the save + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[1]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out _, info)); + + // Delete the save + Assert.Success(fs.DeleteSaveData(info[0].SaveDataId)); + + // Iterate saves again + using var iterator2 = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator2.Ref(), SaveDataSpaceId.User)); + Assert.Success(iterator2.Get.ReadSaveDataInfo(out long entriesRead, info)); + + // Make sure no saves were returned + Assert.Equal(0, entriesRead); + } + + [Theory] + [InlineData(2, 3, 2)] + [InlineData(3, 3, 4)] + [InlineData(5, 3, 5)] + public void DeleteSaveData_SaveDataIteratorsAreOpen_IteratorsPointToSameEntry(int nextEntryWhenRemoving, + int entryToRemove, int expectedNextEntry) + { + const int count = 20; + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create saves + Assert.Success(PopulateSaveData(fs, count)); + + // Open an iterator + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + // Skip ahead a few entries. Entries start counting at 1, so subtract 1 + var infos = new SaveDataInfo[nextEntryWhenRemoving - 1]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, infos)); + Assert.Equal(infos.Length, readCount); + + // Delete the save + Assert.Success(fs.DeleteSaveData(SaveDataSpaceId.User, (ulong)entryToRemove)); + + // Check the program ID of the next entry + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount2, infos.AsSpan(0, 1))); + Assert.Equal(1, readCount2); + + Assert.Equal((ulong)expectedNextEntry, infos[0].ProgramId.Value); + } + + [Theory] + [InlineData(6, 7, 6)] + [InlineData(8, 7, 8)] + public void CreateSaveData_SaveDataIteratorsAreOpen_IteratorsPointToSameEntry(int nextEntryWhenAdding, + int entryToAdd, int expectedNextEntry) + { + // Static save IDs must have the high bit set + const ulong mask = 0x8000000000000000; + const int count = 10; + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create saves + for (int i = 0; i < count * 2; i++) { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - - Assert.Result(ResultFs.PortSdCardNoDevice, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.SdCache, applicationId.Value, 0, 0, SaveDataFlags.None)); - } - - [Fact] - public void CreateCacheStorage_AlreadyExists_ReturnsPathAlreadyExists() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); - Assert.Result(ResultFs.PathAlreadyExists, fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, SaveDataFlags.None)); - } - - [Fact] - public void CreateCacheStorage_WithIndex_CreatesMultiple() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 0, 0, 0, SaveDataFlags.None)); - Assert.Success(fs.CreateCacheStorage(applicationId, SaveDataSpaceId.User, applicationId.Value, 1, 0, 0, SaveDataFlags.None)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - - var info = new SaveDataInfo[3]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(2, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(applicationId, info[1].ProgramId); - - ushort[] expectedIndexes = { 0, 1 }; - ushort[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray(); - - Assert.Equal(expectedIndexes, actualIndexes); - } - - [Fact] - public void CreateBcatSaveData_DoesNotExist_SaveIsCreated() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(SaveDataType.Bcat, info[0].Type); - } - - [Fact] - public void CreateBcatSaveData_AlreadyExists_ReturnsPathAlreadyExists() - { - var applicationId = new Ncm.ApplicationId(1); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateBcatSaveData(applicationId, 0x400000)); - Assert.Result(ResultFs.PathAlreadyExists, fs.CreateBcatSaveData(applicationId, 0x400000)); - } - - [Fact] - public void CreateSystemSaveData_DoesNotExist_SaveIsCreatedInSystem() - { - ulong saveId = 0x8000000001234000; - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); - - // Make sure it was placed in the System save space with the right info. - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.System)); - - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - - Assert.Equal(1, entriesRead); - Assert.Equal(SaveDataType.System, info[0].Type); - Assert.Equal(SaveDataSpaceId.System, info[0].SpaceId); - Assert.Equal(saveId, info[0].StaticSaveDataId); - Assert.Equal(saveId, info[0].SaveDataId); - Assert.Equal(SaveDataState.Normal, info[0].State); - } - - [Theory] - [InlineData(AccessControlBits.Bits.SystemSaveData)] - [InlineData(AccessControlBits.Bits.None)] - public void CreateSystemSaveData_HasBuiltInSystemPermission_SaveIsCreatedInSystem(AccessControlBits.Bits permissions) - { - ulong saveId = 0x8000000001234000; - - Horizon hos = FileSystemServerFactory.CreateHorizonServer(); - - var mainProgramId = new ProgramId(0x123456); - - HorizonClient client = hos.CreateHorizonClient(new ProgramLocation(mainProgramId, StorageId.BuiltInSystem), - permissions); - - HorizonClient privilegedClient = hos.CreatePrivilegedHorizonClient(); - - // Create the save - if (permissions.HasFlag(AccessControlBits.Bits.SystemSaveData)) - { - Assert.Success(client.Fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); - } - else - { - // Creation should fail if we don't have the right permissions. - Assert.Failure(client.Fs.CreateSystemSaveData(saveId, 0x1000, 0x1000, SaveDataFlags.None)); - return; - } - - // Make sure it was placed in the System save space with the right info. - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(privilegedClient.Fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.System)); - - var info = new SaveDataInfo[2]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long entriesRead, info)); - - Assert.Equal(1, entriesRead); - Assert.Equal(SaveDataType.System, info[0].Type); - Assert.Equal(SaveDataSpaceId.System, info[0].SpaceId); - Assert.Equal(saveId, info[0].StaticSaveDataId); - Assert.Equal(saveId, info[0].SaveDataId); - Assert.Equal(SaveDataState.Normal, info[0].State); - } - - [Fact] - public void CreateSaveData_DoesNotExist_SaveIsCreated() - { - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(SaveDataType.Account, info[0].Type); - Assert.Equal(userId, info[0].UserId); - } - - [Fact] - public void CreateSaveData_DoesNotExist_HasCorrectOwnerId() - { - uint ownerId = 1; - - var applicationId = new Ncm.ApplicationId(ownerId); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, ownerId, 0x1000, 0x1000, SaveDataFlags.None)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - // Get the created save data's owner ID - Assert.Success(fs.GetSaveDataOwnerId(out ulong actualOwnerId, info[0].SaveDataId)); - - Assert.Equal(ownerId, actualOwnerId); - } - - [Fact] - public void CreateSaveData_DoesNotExist_HasCorrectFlags() - { - SaveDataFlags flags = SaveDataFlags.KeepAfterRefurbishment | SaveDataFlags.NeedsSecureDelete; - - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, flags)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - // Get the created save data's flags - Assert.Success(fs.GetSaveDataFlags(out SaveDataFlags actualFlags, info[0].SaveDataId)); - - Assert.Equal(flags, actualFlags); - } - - [Fact] - public void CreateSaveData_DoesNotExist_HasCorrectSizes() - { - long availableSize = 0x220000; - long journalSize = 0x120000; - - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, availableSize, journalSize, SaveDataFlags.None)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - // Get the created save data's sizes - Assert.Success(fs.GetSaveDataAvailableSize(out long actualAvailableSize, info[0].SaveDataId)); - Assert.Success(fs.GetSaveDataJournalSize(out long actualJournalSize, info[0].SaveDataId)); - - Assert.Equal(availableSize, actualAvailableSize); - Assert.Equal(journalSize, actualJournalSize); - } - - [Fact] - public void CreateSaveData_FromSubProgram_CreatesSaveDataForMainProgram() - { - Horizon hos = FileSystemServerFactory.CreateHorizonServer(); - - Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[5]; - - var mainProgramId = new ProgramId(0x123456); - var programId = new ProgramId(mainProgramId.Value + 2); - - for (int i = 0; i < mapInfo.Length; i++) - { - mapInfo[i].MainProgramId = mainProgramId; - mapInfo[i].ProgramId = new ProgramId(mainProgramId.Value + (uint)i); - mapInfo[i].ProgramIndex = (byte)i; - } - - HorizonClient client = hos.CreatePrivilegedHorizonClient(); - HorizonClient subProgramClient = - hos.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), - AccessControlBits.Bits.CreateSaveData); - - Assert.Success(client.Fs.RegisterProgramIndexMapInfo(mapInfo)); - - Assert.Success(subProgramClient.Fs.CreateSaveData(Ncm.ApplicationId.InvalidId, UserId.InvalidId, 0, 0x4000, + Assert.Success(fs.CreateSystemSaveData(SaveDataSpaceId.User, ((ulong)i * 2) | mask, 0, 0x4000, 0x4000, SaveDataFlags.None)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(client.Fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - Assert.Equal(mainProgramId, info[0].ProgramId); } - [Fact] - public void DeleteSaveData_DoesNotExist_ReturnsTargetNotFound() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + // Open an iterator + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - Assert.Result(ResultFs.TargetNotFound, fs.DeleteSaveData(1)); + // Skip ahead a few entries. We skipped 0 and added every other ID, so divide by 2 and subtract 1 + var infos = new SaveDataInfo[nextEntryWhenAdding / 2 - 1]; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, infos)); + Assert.Equal(infos.Length, readCount); + + // Create the save + Assert.Success(fs.CreateSystemSaveData(SaveDataSpaceId.User, (uint)entryToAdd | mask, (ulong)entryToAdd, 0x4000, + 0x4000, SaveDataFlags.None)); + + // Check the save ID of the next entry + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount2, infos.AsSpan(0, 1))); + Assert.Equal(1, readCount2); + + Assert.Equal((uint)expectedNextEntry | mask, infos[0].SaveDataId); + } + + [Fact] + public void OpenSaveDataIterator_MultipleSavesExist_IteratorReturnsSavesInOrder() + { + const int count = 20; + const int rngSeed = 359; + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(PopulateSaveData(fs, count, rngSeed)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo(); + for (int i = 0; i < count; i++) + { + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, SpanHelpers.AsSpan(ref info))); + + Assert.Equal(1, readCount); + Assert.Equal((ulong)i, info.ProgramId.Value); } - [Fact] - public void DeleteSaveData_SaveExistsInUserSaveSpace_SaveIsDeleted() + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCountFinal, SpanHelpers.AsSpan(ref info))); + + Assert.Equal(0, readCountFinal); + } + + [Fact] + public void ReadSaveDataInfo_WhenFilteringSavesByUserId_IteratorReturnsAllMatchingSaves() + { + const int count = 10; + const int countUser1 = 5; + + var user1Id = new UserId(0x1234, 0x5678); + var user2Id = new UserId(0x1122, 0x3344); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + for (int i = 1; i <= countUser1; i++) { - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); - - // Get the ID of the save - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[1]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out _, info)); - - // Delete the save - Assert.Success(fs.DeleteSaveData(info[0].SaveDataId)); - - // Iterate saves again - using var iterator2 = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator2.Ref(), SaveDataSpaceId.User)); - Assert.Success(iterator2.Get.ReadSaveDataInfo(out long entriesRead, info)); - - // Make sure no saves were returned - Assert.Equal(0, entriesRead); + var applicationId = new Ncm.ApplicationId((uint)i); + Assert.Success(fs.CreateSaveData(applicationId, user1Id, 0, 0x4000, 0x4000, SaveDataFlags.None)); } - [Theory] - [InlineData(2, 3, 2)] - [InlineData(3, 3, 4)] - [InlineData(5, 3, 5)] - public void DeleteSaveData_SaveDataIteratorsAreOpen_IteratorsPointToSameEntry(int nextEntryWhenRemoving, - int entryToRemove, int expectedNextEntry) + for (int i = countUser1 + 1; i <= count; i++) { - const int count = 20; - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create saves - Assert.Success(PopulateSaveData(fs, count)); - - // Open an iterator - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - // Skip ahead a few entries. Entries start counting at 1, so subtract 1 - var infos = new SaveDataInfo[nextEntryWhenRemoving - 1]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, infos)); - Assert.Equal(infos.Length, readCount); - - // Delete the save - Assert.Success(fs.DeleteSaveData(SaveDataSpaceId.User, (ulong)entryToRemove)); - - // Check the program ID of the next entry - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount2, infos.AsSpan(0, 1))); - Assert.Equal(1, readCount2); - - Assert.Equal((ulong)expectedNextEntry, infos[0].ProgramId.Value); + var applicationId = new Ncm.ApplicationId((uint)i); + Assert.Success(fs.CreateSaveData(applicationId, user2Id, 0, 0x4000, 0x4000, SaveDataFlags.None)); } - [Theory] - [InlineData(6, 7, 6)] - [InlineData(8, 7, 8)] - public void CreateSaveData_SaveDataIteratorsAreOpen_IteratorsPointToSameEntry(int nextEntryWhenAdding, - int entryToAdd, int expectedNextEntry) + Assert.Success(SaveDataFilter.Make(out SaveDataFilter filter, default, default, user2Id, default, default)); + + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User, in filter)); + + var info = new SaveDataInfo(); + for (int i = countUser1 + 1; i <= count; i++) { - // Static save IDs must have the high bit set - const ulong mask = 0x8000000000000000; - const int count = 10; + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, SpanHelpers.AsSpan(ref info))); - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create saves - for (int i = 0; i < count * 2; i++) - { - Assert.Success(fs.CreateSystemSaveData(SaveDataSpaceId.User, ((ulong)i * 2) | mask, 0, 0x4000, - 0x4000, SaveDataFlags.None)); - } - - // Open an iterator - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - // Skip ahead a few entries. We skipped 0 and added every other ID, so divide by 2 and subtract 1 - var infos = new SaveDataInfo[nextEntryWhenAdding / 2 - 1]; - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, infos)); - Assert.Equal(infos.Length, readCount); - - // Create the save - Assert.Success(fs.CreateSystemSaveData(SaveDataSpaceId.User, (uint)entryToAdd | mask, (ulong)entryToAdd, 0x4000, - 0x4000, SaveDataFlags.None)); - - // Check the save ID of the next entry - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount2, infos.AsSpan(0, 1))); - Assert.Equal(1, readCount2); - - Assert.Equal((uint)expectedNextEntry | mask, infos[0].SaveDataId); + Assert.Equal(1, readCount); + Assert.Equal((ulong)i, info.ProgramId.Value); + Assert.Equal(user2Id, info.UserId); } - [Fact] - public void OpenSaveDataIterator_MultipleSavesExist_IteratorReturnsSavesInOrder() + Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCountFinal, SpanHelpers.AsSpan(ref info))); + + Assert.Equal(0, readCountFinal); + } + + [Fact] + public void GetSaveDataCommitId_AfterSetSaveDataCommitIdIsCalled_ReturnsSetCommitId() + { + long commitId = 46506854; + + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + // Set the new commit ID + Assert.Success(fs.SetSaveDataCommitId(info[0].SpaceId, info[0].SaveDataId, commitId)); + + Assert.Success(fs.GetSaveDataCommitId(out long actualCommitId, info[0].SpaceId, info[0].SaveDataId)); + + Assert.Equal(commitId, actualCommitId); + } + + [Fact] + public void GetSaveDataTimeStamp_AfterSetSaveDataTimeStampIsCalled_ReturnsSetTimeStamp() + { + var timeStamp = new PosixTime(12345678); + + var applicationId = new Ncm.ApplicationId(1); + var userId = new UserId(5, 4); + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + // Create the save + Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); + + // Get the created save data's ID + using var iterator = new UniqueRef<SaveDataIterator>(); + Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); + + var info = new SaveDataInfo[2]; + iterator.Get.ReadSaveDataInfo(out long entriesRead, info); + + Assert.Equal(1, entriesRead); + + // Set the new timestamp + Assert.Success(fs.SetSaveDataTimeStamp(info[0].SpaceId, info[0].SaveDataId, timeStamp)); + + Assert.Success(fs.GetSaveDataTimeStamp(out PosixTime actualTimeStamp, info[0].SpaceId, info[0].SaveDataId)); + + Assert.Equal(timeStamp, actualTimeStamp); + } + + private static Result PopulateSaveData(FileSystemClient fs, int count, int seed = -1) + { + if (seed == -1) { - const int count = 20; - const int rngSeed = 359; - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - Assert.Success(PopulateSaveData(fs, count, rngSeed)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo(); - for (int i = 0; i < count; i++) - { - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, SpanHelpers.AsSpan(ref info))); - - Assert.Equal(1, readCount); - Assert.Equal((ulong)i, info.ProgramId.Value); - } - - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCountFinal, SpanHelpers.AsSpan(ref info))); - - Assert.Equal(0, readCountFinal); - } - - [Fact] - public void ReadSaveDataInfo_WhenFilteringSavesByUserId_IteratorReturnsAllMatchingSaves() - { - const int count = 10; - const int countUser1 = 5; - - var user1Id = new UserId(0x1234, 0x5678); - var user2Id = new UserId(0x1122, 0x3344); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - for (int i = 1; i <= countUser1; i++) + for (int i = 1; i <= count; i++) { var applicationId = new Ncm.ApplicationId((uint)i); - Assert.Success(fs.CreateSaveData(applicationId, user1Id, 0, 0x4000, 0x4000, SaveDataFlags.None)); + Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); + if (rc.IsFailure()) return rc; } - - for (int i = countUser1 + 1; i <= count; i++) - { - var applicationId = new Ncm.ApplicationId((uint)i); - Assert.Success(fs.CreateSaveData(applicationId, user2Id, 0, 0x4000, 0x4000, SaveDataFlags.None)); - } - - Assert.Success(SaveDataFilter.Make(out SaveDataFilter filter, default, default, user2Id, default, default)); - - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User, in filter)); - - var info = new SaveDataInfo(); - for (int i = countUser1 + 1; i <= count; i++) - { - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCount, SpanHelpers.AsSpan(ref info))); - - Assert.Equal(1, readCount); - Assert.Equal((ulong)i, info.ProgramId.Value); - Assert.Equal(user2Id, info.UserId); - } - - Assert.Success(iterator.Get.ReadSaveDataInfo(out long readCountFinal, SpanHelpers.AsSpan(ref info))); - - Assert.Equal(0, readCountFinal); } - - [Fact] - public void GetSaveDataCommitId_AfterSetSaveDataCommitIdIsCalled_ReturnsSetCommitId() + else { - long commitId = 46506854; + var rng = new FullCycleRandom(count, seed); - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - // Set the new commit ID - Assert.Success(fs.SetSaveDataCommitId(info[0].SpaceId, info[0].SaveDataId, commitId)); - - Assert.Success(fs.GetSaveDataCommitId(out long actualCommitId, info[0].SpaceId, info[0].SaveDataId)); - - Assert.Equal(commitId, actualCommitId); - } - - [Fact] - public void GetSaveDataTimeStamp_AfterSetSaveDataTimeStampIsCalled_ReturnsSetTimeStamp() - { - var timeStamp = new PosixTime(12345678); - - var applicationId = new Ncm.ApplicationId(1); - var userId = new UserId(5, 4); - - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - - // Create the save - Assert.Success(fs.CreateSaveData(applicationId, userId, 0, 0x1000, 0x1000, SaveDataFlags.None)); - - // Get the created save data's ID - using var iterator = new UniqueRef<SaveDataIterator>(); - Assert.Success(fs.OpenSaveDataIterator(ref iterator.Ref(), SaveDataSpaceId.User)); - - var info = new SaveDataInfo[2]; - iterator.Get.ReadSaveDataInfo(out long entriesRead, info); - - Assert.Equal(1, entriesRead); - - // Set the new timestamp - Assert.Success(fs.SetSaveDataTimeStamp(info[0].SpaceId, info[0].SaveDataId, timeStamp)); - - Assert.Success(fs.GetSaveDataTimeStamp(out PosixTime actualTimeStamp, info[0].SpaceId, info[0].SaveDataId)); - - Assert.Equal(timeStamp, actualTimeStamp); - } - - private static Result PopulateSaveData(FileSystemClient fs, int count, int seed = -1) - { - if (seed == -1) + for (int i = 1; i <= count; i++) { - for (int i = 1; i <= count; i++) - { - var applicationId = new Ncm.ApplicationId((uint)i); - Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); - if (rc.IsFailure()) return rc; - } + var applicationId = new Ncm.ApplicationId((uint)rng.Next()); + Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); + if (rc.IsFailure()) return rc; } - else - { - var rng = new FullCycleRandom(count, seed); - - for (int i = 1; i <= count; i++) - { - var applicationId = new Ncm.ApplicationId((uint)rng.Next()); - Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); - if (rc.IsFailure()) return rc; - } - } - - return Result.Success; } + + return Result.Success; } } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs index dc757357..df352d49 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SdCard.cs @@ -4,78 +4,77 @@ using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using Xunit; -namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class SdCard { - public class SdCard + [Fact] + public void MountSdCard_CardIsInserted_Succeeds() { - [Fact] - public void MountSdCard_CardIsInserted_Succeeds() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.Success(fs.MountSdCard("sdcard".ToU8Span())); - } + Assert.Success(fs.MountSdCard("sdcard".ToU8Span())); + } - [Fact] - public void MountSdCard_CardIsNotInserted_Fails() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + [Fact] + public void MountSdCard_CardIsNotInserted_Fails() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - Assert.Result(ResultFs.PortSdCardNoDevice, fs.MountSdCard("sdcard".ToU8Span())); - } + Assert.Result(ResultFs.PortSdCardNoDevice, fs.MountSdCard("sdcard".ToU8Span())); + } - [Fact] - public void MountSdCard_CanWriteToFsAfterMounted() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void MountSdCard_CanWriteToFsAfterMounted() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - fs.MountSdCard("sdcard".ToU8String()); + fs.MountSdCard("sdcard".ToU8String()); - Assert.Success(fs.CreateFile("sdcard:/file".ToU8Span(), 100, CreateFileOptions.None)); - } + Assert.Success(fs.CreateFile("sdcard:/file".ToU8Span(), 100, CreateFileOptions.None)); + } - [Fact] - public void IsSdCardInserted_CardIsInserted_ReturnsTrue() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void IsSdCardInserted_CardIsInserted_ReturnsTrue() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.True(fs.IsSdCardInserted()); - } + Assert.True(fs.IsSdCardInserted()); + } - [Fact] - public void IsSdCardInserted_CardIsNotInserted_ReturnsFalse() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + [Fact] + public void IsSdCardInserted_CardIsNotInserted_ReturnsFalse() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - Assert.False(fs.IsSdCardInserted()); - } + Assert.False(fs.IsSdCardInserted()); + } - [Fact] - public void IsSdCardAccessible_CardIsInserted_ReturnsTrue() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + [Fact] + public void IsSdCardAccessible_CardIsInserted_ReturnsTrue() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); - Assert.True(fs.IsSdCardAccessible()); - } + Assert.True(fs.IsSdCardAccessible()); + } - [Fact] - public void IsSdCardAccessible_CardIsNotInserted_ReturnsFalse() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + [Fact] + public void IsSdCardAccessible_CardIsNotInserted_ReturnsFalse() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - Assert.False(fs.IsSdCardAccessible()); - } + Assert.False(fs.IsSdCardAccessible()); + } - [Fact] - public void SetSdCardAccessibility_SetAccessibilityPersists() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(false); + [Fact] + public void SetSdCardAccessibility_SetAccessibilityPersists() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(false); - fs.SetSdCardAccessibility(true); - Assert.True(fs.IsSdCardAccessible()); + fs.SetSdCardAccessibility(true); + Assert.True(fs.IsSdCardAccessible()); - fs.SetSdCardAccessibility(false); - Assert.False(fs.IsSdCardAccessible()); - } + fs.SetSdCardAccessibility(false); + Assert.False(fs.IsSdCardAccessible()); } } diff --git a/tests/LibHac.Tests/Fs/FsaExtensions.cs b/tests/LibHac.Tests/Fs/FsaExtensions.cs index e4991c1b..5d405d11 100644 --- a/tests/LibHac.Tests/Fs/FsaExtensions.cs +++ b/tests/LibHac.Tests/Fs/FsaExtensions.cs @@ -4,218 +4,217 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public static class FsaExtensions { - public static class FsaExtensions + private static Result SetUpPath(ref Path path, string value) { - private static Result SetUpPath(ref Path path, string value) - { - if (value is null) - return ResultFs.NullptrArgument.Log(); + if (value is null) + return ResultFs.NullptrArgument.Log(); - Result rc = path.Initialize(StringUtils.StringToUtf8(value)); - if (rc.IsFailure()) return rc; + Result rc = path.Initialize(StringUtils.StringToUtf8(value)); + if (rc.IsFailure()) return rc; - rc = path.Normalize(new PathFlags()); - if (rc.IsFailure()) return rc; + rc = path.Normalize(new PathFlags()); + if (rc.IsFailure()) return rc; - return Result.Success; - } + return Result.Success; + } - public static Result CreateFile(this IFileSystem fs, string path, long size, CreateFileOptions option) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result CreateFile(this IFileSystem fs, string path, long size, CreateFileOptions option) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.CreateFile(in pathNormalized, size, option); - } + return fs.CreateFile(in pathNormalized, size, option); + } - public static Result CreateFile(this IFileSystem fs, string path, long size) - { - return CreateFile(fs, path, size, CreateFileOptions.None); - } + public static Result CreateFile(this IFileSystem fs, string path, long size) + { + return CreateFile(fs, path, size, CreateFileOptions.None); + } - public static Result DeleteFile(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result DeleteFile(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.DeleteFile(in pathNormalized); - } + return fs.DeleteFile(in pathNormalized); + } - public static Result CreateDirectory(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result CreateDirectory(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.CreateDirectory(in pathNormalized); - } + return fs.CreateDirectory(in pathNormalized); + } - public static Result DeleteDirectory(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result DeleteDirectory(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.DeleteDirectory(in pathNormalized); - } + return fs.DeleteDirectory(in pathNormalized); + } - public static Result DeleteDirectoryRecursively(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result DeleteDirectoryRecursively(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.DeleteDirectoryRecursively(in pathNormalized); - } + return fs.DeleteDirectoryRecursively(in pathNormalized); + } - public static Result CleanDirectoryRecursively(this IFileSystem fs, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result CleanDirectoryRecursively(this IFileSystem fs, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.CleanDirectoryRecursively(in pathNormalized); - } + return fs.CleanDirectoryRecursively(in pathNormalized); + } - public static Result RenameFile(this IFileSystem fs, string currentPath, string newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); - if (rc.IsFailure()) return rc; + public static Result RenameFile(this IFileSystem fs, string currentPath, string newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); + if (rc.IsFailure()) return rc; - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), newPath); - if (rc.IsFailure()) return rc; + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), newPath); + if (rc.IsFailure()) return rc; - return fs.RenameFile(in currentPathNormalized, in newPathNormalized); - } + return fs.RenameFile(in currentPathNormalized, in newPathNormalized); + } - public static Result RenameDirectory(this IFileSystem fs, string currentPath, string newPath) - { - using var currentPathNormalized = new Path(); - Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); - if (rc.IsFailure()) return rc; + public static Result RenameDirectory(this IFileSystem fs, string currentPath, string newPath) + { + using var currentPathNormalized = new Path(); + Result rc = SetUpPath(ref currentPathNormalized.Ref(), currentPath); + if (rc.IsFailure()) return rc; - using var newPathNormalized = new Path(); - rc = SetUpPath(ref newPathNormalized.Ref(), newPath); - if (rc.IsFailure()) return rc; + using var newPathNormalized = new Path(); + rc = SetUpPath(ref newPathNormalized.Ref(), newPath); + if (rc.IsFailure()) return rc; - return fs.RenameDirectory(in currentPathNormalized, in newPathNormalized); - } + return fs.RenameDirectory(in currentPathNormalized, in newPathNormalized); + } - public static Result GetEntryType(this IFileSystem fs, out DirectoryEntryType entryType, string path) - { - UnsafeHelpers.SkipParamInit(out entryType); + public static Result GetEntryType(this IFileSystem fs, out DirectoryEntryType entryType, string path) + { + UnsafeHelpers.SkipParamInit(out entryType); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetEntryType(out entryType, in pathNormalized); - } + return fs.GetEntryType(out entryType, in pathNormalized); + } - public static Result GetFreeSpaceSize(this IFileSystem fs, out long freeSpace, string path) - { - UnsafeHelpers.SkipParamInit(out freeSpace); + public static Result GetFreeSpaceSize(this IFileSystem fs, out long freeSpace, string path) + { + UnsafeHelpers.SkipParamInit(out freeSpace); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetFreeSpaceSize(out freeSpace, in pathNormalized); - } + return fs.GetFreeSpaceSize(out freeSpace, in pathNormalized); + } - public static Result GetTotalSpaceSize(this IFileSystem fs, out long totalSpace, string path) - { - UnsafeHelpers.SkipParamInit(out totalSpace); + public static Result GetTotalSpaceSize(this IFileSystem fs, out long totalSpace, string path) + { + UnsafeHelpers.SkipParamInit(out totalSpace); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetTotalSpaceSize(out totalSpace, in pathNormalized); - } + return fs.GetTotalSpaceSize(out totalSpace, in pathNormalized); + } - public static Result OpenFile(this IFileSystem fs, ref UniqueRef<IFile> file, string path, OpenMode mode) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result OpenFile(this IFileSystem fs, ref UniqueRef<IFile> file, string path, OpenMode mode) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.OpenFile(ref file, in pathNormalized, mode); - } + return fs.OpenFile(ref file, in pathNormalized, mode); + } - public static Result OpenDirectory(this IFileSystem fs, ref UniqueRef<IDirectory> directory, string path, OpenDirectoryMode mode) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result OpenDirectory(this IFileSystem fs, ref UniqueRef<IDirectory> directory, string path, OpenDirectoryMode mode) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.OpenDirectory(ref directory, in pathNormalized, mode); - } + return fs.OpenDirectory(ref directory, in pathNormalized, mode); + } - public static Result GetFileTimeStampRaw(this IFileSystem fs, out FileTimeStampRaw timeStamp, string path) - { - UnsafeHelpers.SkipParamInit(out timeStamp); + public static Result GetFileTimeStampRaw(this IFileSystem fs, out FileTimeStampRaw timeStamp, string path) + { + UnsafeHelpers.SkipParamInit(out timeStamp); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetFileTimeStampRaw(out timeStamp, in pathNormalized); - } + return fs.GetFileTimeStampRaw(out timeStamp, in pathNormalized); + } - public static Result QueryEntry(this IFileSystem fs, Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result QueryEntry(this IFileSystem fs, Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized); - } + return fs.QueryEntry(outBuffer, inBuffer, queryId, in pathNormalized); + } - public static Result CreateDirectory(this IAttributeFileSystem fs, string path, NxFileAttributes archiveAttribute) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result CreateDirectory(this IAttributeFileSystem fs, string path, NxFileAttributes archiveAttribute) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.CreateDirectory(in pathNormalized, archiveAttribute); - } + return fs.CreateDirectory(in pathNormalized, archiveAttribute); + } - public static Result GetFileAttributes(this IAttributeFileSystem fs, out NxFileAttributes attributes, string path) - { - UnsafeHelpers.SkipParamInit(out attributes); + public static Result GetFileAttributes(this IAttributeFileSystem fs, out NxFileAttributes attributes, string path) + { + UnsafeHelpers.SkipParamInit(out attributes); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetFileAttributes(out attributes, in pathNormalized); - } + return fs.GetFileAttributes(out attributes, in pathNormalized); + } - public static Result SetFileAttributes(this IAttributeFileSystem fs, string path, NxFileAttributes attributes) - { - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + public static Result SetFileAttributes(this IAttributeFileSystem fs, string path, NxFileAttributes attributes) + { + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.SetFileAttributes(in pathNormalized, attributes); - } + return fs.SetFileAttributes(in pathNormalized, attributes); + } - public static Result GetFileSize(this IAttributeFileSystem fs, out long fileSize, string path) - { - UnsafeHelpers.SkipParamInit(out fileSize); + public static Result GetFileSize(this IAttributeFileSystem fs, out long fileSize, string path) + { + UnsafeHelpers.SkipParamInit(out fileSize); - using var pathNormalized = new Path(); - Result rc = SetUpPath(ref pathNormalized.Ref(), path); - if (rc.IsFailure()) return rc; + using var pathNormalized = new Path(); + Result rc = SetUpPath(ref pathNormalized.Ref(), path); + if (rc.IsFailure()) return rc; - return fs.GetFileSize(out fileSize, in pathNormalized); - } + return fs.GetFileSize(out fileSize, in pathNormalized); } } diff --git a/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs b/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs index 4e843218..840cb7da 100644 --- a/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs +++ b/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs @@ -6,58 +6,57 @@ using LibHac.Fs.Shim; using LibHac.Tests.Fs.FileSystemClientTests; using Xunit; -namespace LibHac.Tests.Fs.FsaTests -{ - public class MultiCommitTests - { - [Fact] - public void Commit_MultipleFileSystems_AllFileSystemsAreCommitted() - { - FileSystemClient fs = FileSystemServerFactory.CreateClient(true); +namespace LibHac.Tests.Fs.FsaTests; - var saveInfo = new List<(int id, string name)> +public class MultiCommitTests +{ + [Fact] + public void Commit_MultipleFileSystems_AllFileSystemsAreCommitted() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + var saveInfo = new List<(int id, string name)> { (1, "Save1"), (3, "Save2"), (2, "Save3") }; - foreach ((int id, string name) info in saveInfo) - { - var applicationId = new Ncm.ApplicationId((uint)info.id); - fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); - fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); - } + foreach ((int id, string name) info in saveInfo) + { + var applicationId = new Ncm.ApplicationId((uint)info.id); + fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); + fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); + } - foreach ((int id, string name) info in saveInfo) - { - fs.CreateFile($"{info.name}:/file{info.id}".ToU8Span(), 0); - } + foreach ((int id, string name) info in saveInfo) + { + fs.CreateFile($"{info.name}:/file{info.id}".ToU8Span(), 0); + } - var names = new List<U8String>(); + var names = new List<U8String>(); - foreach ((int id, string name) info in saveInfo) - { - names.Add(info.name.ToU8String()); - } + foreach ((int id, string name) info in saveInfo) + { + names.Add(info.name.ToU8String()); + } - Assert.Success(fs.Commit(names.ToArray())); + Assert.Success(fs.Commit(names.ToArray())); - foreach ((int id, string name) info in saveInfo) - { - fs.Unmount(info.name.ToU8Span()); - } + foreach ((int id, string name) info in saveInfo) + { + fs.Unmount(info.name.ToU8Span()); + } - foreach ((int id, string name) info in saveInfo) - { - var applicationId = new Ncm.ApplicationId((uint)info.id); - fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); - } + foreach ((int id, string name) info in saveInfo) + { + var applicationId = new Ncm.ApplicationId((uint)info.id); + fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); + } - foreach ((int id, string name) info in saveInfo) - { - Assert.Success(fs.GetEntryType(out _, $"{info.name}:/file{info.id}".ToU8Span())); - } + foreach ((int id, string name) info in saveInfo) + { + Assert.Success(fs.GetEntryType(out _, $"{info.name}:/file{info.id}".ToU8Span())); } } } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs index 6478cea0..4bfc6499 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.Commit.cs @@ -3,138 +3,137 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class CommittableIFileSystemTests { - public abstract partial class CommittableIFileSystemTests + [Fact] + public void Commit_AfterSuccessfulCommit_CanReadCommittedData() { - [Fact] - public void Commit_AfterSuccessfulCommit_CanReadCommittedData() - { - // "Random" test data - byte[] data1 = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; - byte[] data2 = { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 }; + // "Random" test data + byte[] data1 = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; + byte[] data2 = { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 }; - IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); - IFileSystem fs = fsCreator.Create(); + IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); + IFileSystem fs = fsCreator.Create(); - // Make sure to test both directories and files - fs.CreateDirectory("/dir1").ThrowIfFailure(); - fs.CreateDirectory("/dir2").ThrowIfFailure(); + // Make sure to test both directories and files + fs.CreateDirectory("/dir1").ThrowIfFailure(); + fs.CreateDirectory("/dir2").ThrowIfFailure(); - fs.CreateFile("/dir1/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); - fs.CreateFile("/dir2/file", data2.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateFile("/dir1/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateFile("/dir2/file", data2.Length, CreateFileOptions.None).ThrowIfFailure(); - using var file1 = new UniqueRef<IFile>(); - using var file2 = new UniqueRef<IFile>(); + using var file1 = new UniqueRef<IFile>(); + using var file2 = new UniqueRef<IFile>(); - fs.OpenFile(ref file1.Ref(), "/dir1/file", OpenMode.Write).ThrowIfFailure(); - fs.OpenFile(ref file2.Ref(), "/dir2/file", OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(ref file1.Ref(), "/dir1/file", OpenMode.Write).ThrowIfFailure(); + fs.OpenFile(ref file2.Ref(), "/dir2/file", OpenMode.Write).ThrowIfFailure(); - file1.Get.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); - file2.Get.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); + file1.Get.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); + file2.Get.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); - file1.Reset(); - file2.Reset(); + file1.Reset(); + file2.Reset(); - fs.Commit().ThrowIfFailure(); - fs.Dispose(); + fs.Commit().ThrowIfFailure(); + fs.Dispose(); - // Reopen after committing - fs = fsCreator.Create(); + // Reopen after committing + fs = fsCreator.Create(); - byte[] readData1 = new byte[data1.Length]; - byte[] readData2 = new byte[data2.Length]; + byte[] readData1 = new byte[data1.Length]; + byte[] readData2 = new byte[data2.Length]; - Assert.Success(fs.OpenFile(ref file1.Ref(), "/dir1/file", OpenMode.Read)); + Assert.Success(fs.OpenFile(ref file1.Ref(), "/dir1/file", OpenMode.Read)); - Assert.Success(file1.Get.Read(out long bytesReadFile1, 0, readData1, ReadOption.None)); - file1.Reset(); - Assert.Equal(data1.Length, bytesReadFile1); + Assert.Success(file1.Get.Read(out long bytesReadFile1, 0, readData1, ReadOption.None)); + file1.Reset(); + Assert.Equal(data1.Length, bytesReadFile1); - Assert.Equal(data1, readData1); + Assert.Equal(data1, readData1); - Assert.Success(fs.OpenFile(ref file2.Ref(), "/dir2/file", OpenMode.Read)); + Assert.Success(fs.OpenFile(ref file2.Ref(), "/dir2/file", OpenMode.Read)); - Assert.Success(file2.Get.Read(out long bytesReadFile2, 0, readData2, ReadOption.None)); - Assert.Equal(data2.Length, bytesReadFile2); + Assert.Success(file2.Get.Read(out long bytesReadFile2, 0, readData2, ReadOption.None)); + Assert.Equal(data2.Length, bytesReadFile2); - Assert.Equal(data2, readData2); - } + Assert.Equal(data2, readData2); + } - [Fact] - public void Rollback_CreateFileThenRollback_FileDoesNotExist() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void Rollback_CreateFileThenRollback_FileDoesNotExist() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir").ThrowIfFailure(); - fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); - // Rollback should succeed - Assert.Success(fs.Rollback()); + // Rollback should succeed + Assert.Success(fs.Rollback()); - // Make sure the file and directory are gone - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); - } + // Make sure the file and directory are gone + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); + } - [Fact] - public void Rollback_CreateFileThenCloseFs_FileDoesNotExist() - { - IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); - IFileSystem fs = fsCreator.Create(); + [Fact] + public void Rollback_CreateFileThenCloseFs_FileDoesNotExist() + { + IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); + IFileSystem fs = fsCreator.Create(); - fs.CreateDirectory("/dir").ThrowIfFailure(); - fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None).ThrowIfFailure(); - // Close without committing and reopen the file system - fs.Dispose(); - fs = fsCreator.Create(); + // Close without committing and reopen the file system + fs.Dispose(); + fs = fsCreator.Create(); - // Make sure the file and directory are gone - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); - } + // Make sure the file and directory are gone + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir")); + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file")); + } - [Fact] - public void Rollback_AfterChangingExistingFiles_GoesBackToOriginalData() - { - // "Random" test data - byte[] data1 = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; - byte[] data2 = { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 }; + [Fact] + public void Rollback_AfterChangingExistingFiles_GoesBackToOriginalData() + { + // "Random" test data + byte[] data1 = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; + byte[] data2 = { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 }; - IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); - IFileSystem fs = fsCreator.Create(); + IReopenableFileSystemCreator fsCreator = GetFileSystemCreator(); + IFileSystem fs = fsCreator.Create(); - fs.CreateDirectory("/dir").ThrowIfFailure(); - fs.CreateFile("/dir/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); + fs.CreateDirectory("/dir").ThrowIfFailure(); + fs.CreateFile("/dir/file", data1.Length, CreateFileOptions.None).ThrowIfFailure(); - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Write).ThrowIfFailure(); - file.Get.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); - file.Reset(); + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Write).ThrowIfFailure(); + file.Get.Write(0, data1, WriteOption.Flush).ThrowIfFailure(); + file.Reset(); - // Commit and reopen the file system - fs.Commit().ThrowIfFailure(); - fs.Dispose(); + // Commit and reopen the file system + fs.Commit().ThrowIfFailure(); + fs.Dispose(); - fs = fsCreator.Create(); + fs = fsCreator.Create(); - // Make changes to the file - fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Write).ThrowIfFailure(); - file.Get.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); - file.Reset(); + // Make changes to the file + fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Write).ThrowIfFailure(); + file.Get.Write(0, data2, WriteOption.Flush).ThrowIfFailure(); + file.Reset(); - Assert.Success(fs.Rollback()); + Assert.Success(fs.Rollback()); - // The file should contain the original data after the rollback - byte[] readData = new byte[data1.Length]; + // The file should contain the original data after the rollback + byte[] readData = new byte[data1.Length]; - Assert.Success(fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Read)); + Assert.Success(fs.OpenFile(ref file.Ref(), "/dir/file", OpenMode.Read)); - Assert.Success(file.Get.Read(out long bytesRead, 0, readData, ReadOption.None)); - Assert.Equal(data1.Length, bytesRead); + Assert.Success(file.Get.Read(out long bytesRead, 0, readData, ReadOption.None)); + Assert.Equal(data1.Length, bytesRead); - Assert.Equal(data1, readData); - } + Assert.Equal(data1, readData); } } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.cs index 1141f000..59390f32 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/CommittableIFileSystemTests.cs @@ -1,14 +1,13 @@ using LibHac.Fs.Fsa; -namespace LibHac.Tests.Fs.IFileSystemTestBase -{ - public abstract partial class CommittableIFileSystemTests : IFileSystemTests - { - protected interface IReopenableFileSystemCreator - { - IFileSystem Create(); - } +namespace LibHac.Tests.Fs.IFileSystemTestBase; - protected abstract IReopenableFileSystemCreator GetFileSystemCreator(); +public abstract partial class CommittableIFileSystemTests : IFileSystemTests +{ + protected interface IReopenableFileSystemCreator + { + IFileSystem Create(); } + + protected abstract IReopenableFileSystemCreator GetFileSystemCreator(); } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs index ca32f355..b7e28776 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IAttributeFileSystemTests.cs @@ -2,132 +2,131 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract class IAttributeFileSystemTests : IFileSystemTests { - public abstract class IAttributeFileSystemTests : IFileSystemTests + protected abstract IAttributeFileSystem CreateAttributeFileSystem(); + + [Fact] + public void CreateDirectory_WithoutArchiveAttribute_ArchiveFlagIsNotSet() { - protected abstract IAttributeFileSystem CreateAttributeFileSystem(); + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void CreateDirectory_WithoutArchiveAttribute_ArchiveFlagIsNotSet() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.None)); - Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.None)); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); + Assert.Equal(NxFileAttributes.Directory, attributes); + } - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); - Assert.Equal(NxFileAttributes.Directory, attributes); - } + [Fact] + public void CreateDirectory_WithArchiveAttribute_ArchiveFlagIsSet() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void CreateDirectory_WithArchiveAttribute_ArchiveFlagIsSet() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.Archive)); - Assert.Success(fs.CreateDirectory("/dir", NxFileAttributes.Archive)); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); + Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, attributes); + } - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); - Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, attributes); - } + [Fact] + public void GetFileAttributes_AttributesOnNewFileAreEmpty() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateFile("/file", 0, CreateFileOptions.None); - [Fact] - public void GetFileAttributes_AttributesOnNewFileAreEmpty() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/file")); + Assert.Equal(NxFileAttributes.None, attributes); + } - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/file")); - Assert.Equal(NxFileAttributes.None, attributes); - } + [Fact] + public void GetFileAttributes_AttributesOnNewDirHaveOnlyDirFlagSet() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateDirectory("/dir"); - [Fact] - public void GetFileAttributes_AttributesOnNewDirHaveOnlyDirFlagSet() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir"); + Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); + Assert.Equal(NxFileAttributes.Directory, attributes); + } - Assert.Success(fs.GetFileAttributes(out NxFileAttributes attributes, "/dir")); - Assert.Equal(NxFileAttributes.Directory, attributes); - } + [Fact] + public void GetFileAttributes_PathDoesNotExist_ReturnsPathNotFound() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void GetFileAttributes_PathDoesNotExist_ReturnsPathNotFound() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + Result rc = fs.GetFileAttributes(out _, "/path"); - Result rc = fs.GetFileAttributes(out _, "/path"); + Assert.Result(ResultFs.PathNotFound, rc); + } - Assert.Result(ResultFs.PathNotFound, rc); - } + [Fact] + public void SetFileAttributes_PathDoesNotExist_ReturnsPathNotFound() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void SetFileAttributes_PathDoesNotExist_ReturnsPathNotFound() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + Result rc = fs.SetFileAttributes("/path", NxFileAttributes.None); - Result rc = fs.SetFileAttributes("/path", NxFileAttributes.None); + Assert.Result(ResultFs.PathNotFound, rc); + } - Assert.Result(ResultFs.PathNotFound, rc); - } + [Fact] + public void SetFileAttributes_SetAttributeOnFile() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateFile("/file", 0, CreateFileOptions.None); - [Fact] - public void SetFileAttributes_SetAttributeOnFile() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); + Result rcSet = fs.SetFileAttributes("/file", NxFileAttributes.Archive); + Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/file"); - Result rcSet = fs.SetFileAttributes("/file", NxFileAttributes.Archive); - Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/file"); + Assert.Success(rcSet); + Assert.Success(rcGet); + Assert.Equal(NxFileAttributes.Archive, attributes); + } - Assert.Success(rcSet); - Assert.Success(rcGet); - Assert.Equal(NxFileAttributes.Archive, attributes); - } + [Fact] + public void SetFileAttributes_SetAttributeOnDirectory() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateDirectory("/dir"); - [Fact] - public void SetFileAttributes_SetAttributeOnDirectory() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir"); + Result rcSet = fs.SetFileAttributes("/dir", NxFileAttributes.Archive); + Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/dir"); - Result rcSet = fs.SetFileAttributes("/dir", NxFileAttributes.Archive); - Result rcGet = fs.GetFileAttributes(out NxFileAttributes attributes, "/dir"); + Assert.Success(rcSet); + Assert.Success(rcGet); + Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, attributes); + } - Assert.Success(rcSet); - Assert.Success(rcGet); - Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, attributes); - } + [Fact] + public void GetFileSize_ReadNewFileSize() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void GetFileSize_ReadNewFileSize() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateFile("/file", 845, CreateFileOptions.None); - fs.CreateFile("/file", 845, CreateFileOptions.None); + Assert.Success(fs.GetFileSize(out long fileSize, "/file")); + Assert.Equal(845, fileSize); + } - Assert.Success(fs.GetFileSize(out long fileSize, "/file")); - Assert.Equal(845, fileSize); - } + [Fact] + public void GetFileSize_PathDoesNotExist_ReturnsPathNotFound() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); - [Fact] - public void GetFileSize_PathDoesNotExist_ReturnsPathNotFound() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); + Result rc = fs.GetFileSize(out _, "/path"); - Result rc = fs.GetFileSize(out _, "/path"); + Assert.Result(ResultFs.PathNotFound, rc); + } - Assert.Result(ResultFs.PathNotFound, rc); - } + [Fact] + public void GetFileSize_PathIsDirectory_ReturnsPathNotFound() + { + IAttributeFileSystem fs = CreateAttributeFileSystem(); + fs.CreateDirectory("/dir"); - [Fact] - public void GetFileSize_PathIsDirectory_ReturnsPathNotFound() - { - IAttributeFileSystem fs = CreateAttributeFileSystem(); - fs.CreateDirectory("/dir"); + Result rc = fs.GetFileSize(out _, "/dir"); - Result rc = fs.GetFileSize(out _, "/dir"); - - Assert.Result(ResultFs.PathNotFound, rc); - } + Assert.Result(ResultFs.PathNotFound, rc); } } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs index 8543fd81..0056d009 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CleanDirectoryRecursively.cs @@ -2,32 +2,31 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void CleanDirectoryRecursively_DeletesChildren() { - [Fact] - public void CleanDirectoryRecursively_DeletesChildren() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); - fs.CreateDirectory("/dir/dir2"); - fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir2"); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); - Result rcDelete = fs.CleanDirectoryRecursively("/dir"); + Result rcDelete = fs.CleanDirectoryRecursively("/dir"); - Result rcDir1Type = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir"); - Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); - Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); + Result rcDir1Type = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir"); + Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); + Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); - Assert.Success(rcDelete); + Assert.Success(rcDelete); - Assert.Success(rcDir1Type); - Assert.Equal(DirectoryEntryType.Directory, dir1Type); + Assert.Success(rcDir1Type); + Assert.Equal(DirectoryEntryType.Directory, dir1Type); - Assert.Result(ResultFs.PathNotFound, rcDir2Type); - Assert.Result(ResultFs.PathNotFound, rcFileType); - } + Assert.Result(ResultFs.PathNotFound, rcDir2Type); + Assert.Result(ResultFs.PathNotFound, rcFileType); } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs index 91bda4b1..216f9c8d 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateDirectory.cs @@ -2,102 +2,101 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void CreateDirectory_EntryIsAdded() { - [Fact] - public void CreateDirectory_EntryIsAdded() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir"); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir")); - Assert.Equal(DirectoryEntryType.Directory, type); - } - - [Fact] - public void CreateDirectory_DirectoryExists_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir"); - - Result rc = fs.CreateDirectory("/dir"); - - Assert.Result(ResultFs.PathAlreadyExists, rc); - } - - [Fact] - public void CreateDirectory_FileExists_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - Result rc = fs.CreateDirectory("/file"); - - Assert.Result(ResultFs.PathAlreadyExists, rc); - } - - [Fact] - public void CreateDirectory_ParentDoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - Result rc = fs.CreateFile("/dir1/dir2", 0, CreateFileOptions.None); - - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void CreateDirectory_WithTrailingSeparator_EntryIsAdded() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir/"); - Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir/"); - - Assert.Success(rc); - Assert.Equal(DirectoryEntryType.Directory, type); - } - - [Fact] - public void CreateDirectory_MultipleSiblings() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir2"); - - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1"); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2"); - - Assert.Success(rc1); - Assert.Success(rc2); - Assert.Equal(DirectoryEntryType.Directory, type1); - Assert.Equal(DirectoryEntryType.Directory, type2); - } - - [Fact] - public void CreateDirectory_InChildDirectory() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir2"); - - fs.CreateDirectory("/dir1/dir1a"); - fs.CreateDirectory("/dir2/dir2a"); - - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/dir1a"); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/dir2a"); - - Assert.Success(rc1); - Assert.Success(rc2); - Assert.Equal(DirectoryEntryType.Directory, type1); - Assert.Equal(DirectoryEntryType.Directory, type2); - } + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir")); + Assert.Equal(DirectoryEntryType.Directory, type); } -} \ No newline at end of file + + [Fact] + public void CreateDirectory_DirectoryExists_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir"); + + Result rc = fs.CreateDirectory("/dir"); + + Assert.Result(ResultFs.PathAlreadyExists, rc); + } + + [Fact] + public void CreateDirectory_FileExists_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + Result rc = fs.CreateDirectory("/file"); + + Assert.Result(ResultFs.PathAlreadyExists, rc); + } + + [Fact] + public void CreateDirectory_ParentDoesNotExist_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + Result rc = fs.CreateFile("/dir1/dir2", 0, CreateFileOptions.None); + + Assert.Result(ResultFs.PathNotFound, rc); + } + + [Fact] + public void CreateDirectory_WithTrailingSeparator_EntryIsAdded() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir/"); + Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir/"); + + Assert.Success(rc); + Assert.Equal(DirectoryEntryType.Directory, type); + } + + [Fact] + public void CreateDirectory_MultipleSiblings() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); + + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2"); + + Assert.Success(rc1); + Assert.Success(rc2); + Assert.Equal(DirectoryEntryType.Directory, type1); + Assert.Equal(DirectoryEntryType.Directory, type2); + } + + [Fact] + public void CreateDirectory_InChildDirectory() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); + + fs.CreateDirectory("/dir1/dir1a"); + fs.CreateDirectory("/dir2/dir2a"); + + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/dir1a"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/dir2a"); + + Assert.Success(rc1); + Assert.Success(rc2); + Assert.Equal(DirectoryEntryType.Directory, type1); + Assert.Equal(DirectoryEntryType.Directory, type2); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs index 3c47ac8a..66eb0c06 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.CreateFile.cs @@ -3,118 +3,117 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void CreateFile_EntryIsAdded() { - [Fact] - public void CreateFile_EntryIsAdded() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file"); + fs.CreateFile("/file", 0, CreateFileOptions.None); + Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file"); - Assert.Success(rc); - Assert.Equal(DirectoryEntryType.File, type); - } + Assert.Success(rc); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void CreateFile_DirectoryExists_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_DirectoryExists_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir"); - Result rc = fs.CreateFile("/dir", 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/dir", 0, CreateFileOptions.None); - Assert.Result(ResultFs.PathAlreadyExists, rc); - } + Assert.Result(ResultFs.PathAlreadyExists, rc); + } - [Fact] - public void CreateFile_FileExists_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_FileExists_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - Result rc = fs.CreateFile("/file", 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/file", 0, CreateFileOptions.None); - Assert.Result(ResultFs.PathAlreadyExists, rc); - } + Assert.Result(ResultFs.PathAlreadyExists, rc); + } - [Fact] - public void CreateFile_ParentDoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_ParentDoesNotExist_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); - Result rc = fs.CreateFile("/dir/file", 0, CreateFileOptions.None); + Result rc = fs.CreateFile("/dir/file", 0, CreateFileOptions.None); - Assert.Result(ResultFs.PathNotFound, rc); - } + Assert.Result(ResultFs.PathNotFound, rc); + } - [Fact] - public void CreateFile_WithTrailingSeparator_EntryIsAdded() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_WithTrailingSeparator_EntryIsAdded() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file/", 0, CreateFileOptions.None); + fs.CreateFile("/file/", 0, CreateFileOptions.None); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file/")); - Assert.Equal(DirectoryEntryType.File, type); - } + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file/")); + Assert.Equal(DirectoryEntryType.File, type); + } - [Fact] - public void CreateFile_WithSize_SizeIsSet() - { - const long expectedSize = 12345; + [Fact] + public void CreateFile_WithSize_SizeIsSet() + { + const long expectedSize = 12345; - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", expectedSize, CreateFileOptions.None); + fs.CreateFile("/file", expectedSize, CreateFileOptions.None); - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - Assert.Success(file.Get.GetSize(out long fileSize)); - Assert.Equal(expectedSize, fileSize); - } + Assert.Success(file.Get.GetSize(out long fileSize)); + Assert.Equal(expectedSize, fileSize); + } - [Fact] - public void CreateFile_MultipleSiblings() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_MultipleSiblings() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1", 0, CreateFileOptions.None); - fs.CreateFile("/file2", 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/file1"); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/file2"); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/file1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/file2"); - Assert.Success(rc1); - Assert.Success(rc2); - Assert.Equal(DirectoryEntryType.File, type1); - Assert.Equal(DirectoryEntryType.File, type2); - } + Assert.Success(rc1); + Assert.Success(rc2); + Assert.Equal(DirectoryEntryType.File, type1); + Assert.Equal(DirectoryEntryType.File, type2); + } - [Fact] - public void CreateFile_InChildDirectory() - { - IFileSystem fs = CreateFileSystem(); + [Fact] + public void CreateFile_InChildDirectory() + { + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir2"); + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); - fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); - fs.CreateFile("/dir2/file2", 0, CreateFileOptions.None); + fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir2/file2", 0, CreateFileOptions.None); - Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/file1"); - Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/file2"); + Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/file1"); + Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/file2"); - Assert.Success(rc1); - Assert.Success(rc2); - Assert.Equal(DirectoryEntryType.File, type1); - Assert.Equal(DirectoryEntryType.File, type2); - } + Assert.Success(rc1); + Assert.Success(rc2); + Assert.Equal(DirectoryEntryType.File, type1); + Assert.Equal(DirectoryEntryType.File, type2); } } diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs index bc01ecb5..ba37e137 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectory.cs @@ -2,95 +2,94 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void DeleteDirectory_DoesNotExist_ReturnsPathNotFound() { - [Fact] - public void DeleteDirectory_DoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - Result rc = fs.DeleteDirectory("/dir"); + Result rc = fs.DeleteDirectory("/dir"); - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void DeleteDirectory_DirectoryExists_EntryIsRemoved() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir"); - - Result rcDelete = fs.DeleteDirectory("/dir"); - Result rcEntry = fs.GetEntryType(out _, "/dir"); - - Assert.Success(rcDelete); - Assert.Result(ResultFs.PathNotFound, rcEntry); - } - - [Fact] - public void DeleteDirectory_PathIsFile_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - Result rc = fs.DeleteDirectory("/file"); - - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void DeleteDirectory_HasOlderSibling_SiblingNotDeleted() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir2"); - - Result rcDelete = fs.DeleteDirectory("/dir2"); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); - Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); - - Assert.Success(rcDelete); - Assert.Success(rcEntry1); - Assert.Result(ResultFs.PathNotFound, rcEntry2); - - Assert.Equal(DirectoryEntryType.Directory, dir1Type); - } - - [Fact] - public void DeleteDirectory_HasYoungerSibling_SiblingNotDeleted() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir2"); - fs.CreateDirectory("/dir1"); - - Result rcDelete = fs.DeleteDirectory("/dir2"); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); - Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); - - Assert.Success(rcDelete); - Assert.Success(rcEntry1); - Assert.Result(ResultFs.PathNotFound, rcEntry2); - - Assert.Equal(DirectoryEntryType.Directory, dir1Type); - } - - [Fact] - public void DeleteDirectory_NotEmpty_ReturnsDirectoryNotEmpty() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir"); - fs.CreateFile("/dir/file", 0, CreateFileOptions.None); - - Result rc = fs.DeleteDirectory("/dir"); - - Assert.Result(ResultFs.DirectoryNotEmpty, rc); - } + Assert.Result(ResultFs.PathNotFound, rc); } -} \ No newline at end of file + + [Fact] + public void DeleteDirectory_DirectoryExists_EntryIsRemoved() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir"); + + Result rcDelete = fs.DeleteDirectory("/dir"); + Result rcEntry = fs.GetEntryType(out _, "/dir"); + + Assert.Success(rcDelete); + Assert.Result(ResultFs.PathNotFound, rcEntry); + } + + [Fact] + public void DeleteDirectory_PathIsFile_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + Result rc = fs.DeleteDirectory("/file"); + + Assert.Result(ResultFs.PathNotFound, rc); + } + + [Fact] + public void DeleteDirectory_HasOlderSibling_SiblingNotDeleted() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); + + Result rcDelete = fs.DeleteDirectory("/dir2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); + + Assert.Success(rcDelete); + Assert.Success(rcEntry1); + Assert.Result(ResultFs.PathNotFound, rcEntry2); + + Assert.Equal(DirectoryEntryType.Directory, dir1Type); + } + + [Fact] + public void DeleteDirectory_HasYoungerSibling_SiblingNotDeleted() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir2"); + fs.CreateDirectory("/dir1"); + + Result rcDelete = fs.DeleteDirectory("/dir2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcEntry2 = fs.GetEntryType(out _, "/dir2"); + + Assert.Success(rcDelete); + Assert.Success(rcEntry1); + Assert.Result(ResultFs.PathNotFound, rcEntry2); + + Assert.Equal(DirectoryEntryType.Directory, dir1Type); + } + + [Fact] + public void DeleteDirectory_NotEmpty_ReturnsDirectoryNotEmpty() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir"); + fs.CreateFile("/dir/file", 0, CreateFileOptions.None); + + Result rc = fs.DeleteDirectory("/dir"); + + Assert.Result(ResultFs.DirectoryNotEmpty, rc); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs index fde7613e..252ee5ab 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteDirectoryRecursively.cs @@ -2,30 +2,29 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void DeleteDirectoryRecursively_DeletesDirectoryAndChildren() { - [Fact] - public void DeleteDirectoryRecursively_DeletesDirectoryAndChildren() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); - fs.CreateDirectory("/dir/dir2"); - fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir2"); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); - Result rcDelete = fs.DeleteDirectoryRecursively("/dir"); + Result rcDelete = fs.DeleteDirectoryRecursively("/dir"); - Result rcDir1Type = fs.GetEntryType(out _, "/dir"); - Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); - Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); + Result rcDir1Type = fs.GetEntryType(out _, "/dir"); + Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2"); + Result rcFileType = fs.GetEntryType(out _, "/dir/file1"); - Assert.Success(rcDelete); + Assert.Success(rcDelete); - Assert.Result(ResultFs.PathNotFound, rcDir1Type); - Assert.Result(ResultFs.PathNotFound, rcDir2Type); - Assert.Result(ResultFs.PathNotFound, rcFileType); - } + Assert.Result(ResultFs.PathNotFound, rcDir1Type); + Assert.Result(ResultFs.PathNotFound, rcDir2Type); + Assert.Result(ResultFs.PathNotFound, rcFileType); } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs index 06e87839..5a557c57 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.DeleteFile.cs @@ -2,81 +2,80 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void DeleteFile_DoesNotExist_ReturnsPathNotFound() { - [Fact] - public void DeleteFile_DoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - Result rc = fs.DeleteFile("/file"); - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void DeleteFile_FileExists_FileEntryIsRemoved() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - Result rcDelete = fs.DeleteFile("/file"); - Result rcEntry = fs.GetEntryType(out _, "/file"); - - Assert.Success(rcDelete); - Assert.Result(ResultFs.PathNotFound, rcEntry); - } - - [Fact] - public void DeleteFile_PathIsDirectory_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir"); - - Result rc = fs.DeleteFile("/dir"); - - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void DeleteFile_HasOlderSibling_SiblingNotDeleted() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file1", 0, CreateFileOptions.None); - fs.CreateFile("/file2", 0, CreateFileOptions.None); - - Result rcDelete = fs.DeleteFile("/file2"); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); - Result rcEntry2 = fs.GetEntryType(out _, "/file2"); - - Assert.Success(rcDelete); - Assert.Success(rcEntry1); - Assert.Result(ResultFs.PathNotFound, rcEntry2); - - Assert.Equal(DirectoryEntryType.File, dir1Type); - } - - [Fact] - public void DeleteFile_HasYoungerSibling_SiblingNotDeleted() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file2", 0, CreateFileOptions.None); - fs.CreateFile("/file1", 0, CreateFileOptions.None); - - Result rcDelete = fs.DeleteFile("/file2"); - Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); - Result rcEntry2 = fs.GetEntryType(out _, "/file2"); - - Assert.Success(rcDelete); - Assert.Success(rcEntry1); - Assert.Result(ResultFs.PathNotFound, rcEntry2); - - Assert.Equal(DirectoryEntryType.File, dir1Type); - } + Result rc = fs.DeleteFile("/file"); + Assert.Result(ResultFs.PathNotFound, rc); } -} \ No newline at end of file + + [Fact] + public void DeleteFile_FileExists_FileEntryIsRemoved() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + Result rcDelete = fs.DeleteFile("/file"); + Result rcEntry = fs.GetEntryType(out _, "/file"); + + Assert.Success(rcDelete); + Assert.Result(ResultFs.PathNotFound, rcEntry); + } + + [Fact] + public void DeleteFile_PathIsDirectory_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir"); + + Result rc = fs.DeleteFile("/dir"); + + Assert.Result(ResultFs.PathNotFound, rc); + } + + [Fact] + public void DeleteFile_HasOlderSibling_SiblingNotDeleted() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); + + Result rcDelete = fs.DeleteFile("/file2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); + Result rcEntry2 = fs.GetEntryType(out _, "/file2"); + + Assert.Success(rcDelete); + Assert.Success(rcEntry1); + Assert.Result(ResultFs.PathNotFound, rcEntry2); + + Assert.Equal(DirectoryEntryType.File, dir1Type); + } + + [Fact] + public void DeleteFile_HasYoungerSibling_SiblingNotDeleted() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file2", 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); + + Result rcDelete = fs.DeleteFile("/file2"); + Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1"); + Result rcEntry2 = fs.GetEntryType(out _, "/file2"); + + Assert.Success(rcDelete); + Assert.Success(rcEntry1); + Assert.Result(ResultFs.PathNotFound, rcEntry2); + + Assert.Equal(DirectoryEntryType.File, dir1Type); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.GetEntryType.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.GetEntryType.cs index 70c1913a..8bbaa70c 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.GetEntryType.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.GetEntryType.cs @@ -3,26 +3,25 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void GetEntryType_RootIsDirectory() { - [Fact] - public void GetEntryType_RootIsDirectory() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/".ToU8Span())); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/".ToU8Span())); - Assert.Equal(DirectoryEntryType.Directory, type); - } - - [Fact] - public void GetEntryType_PathDoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/path".ToU8Span())); - } + Assert.Equal(DirectoryEntryType.Directory, type); } -} \ No newline at end of file + + [Fact] + public void GetEntryType_PathDoesNotExist_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/path".ToU8Span())); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs index d0810e62..19a5f27c 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs @@ -5,105 +5,104 @@ using LibHac.Fs.Fsa; using LibHac.Util; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void IDirectoryRead_EmptyFs_NoEntriesAreRead() { - [Fact] - public void IDirectoryRead_EmptyFs_NoEntriesAreRead() + IFileSystem fs = CreateFileSystem(); + Span<DirectoryEntry> entries = stackalloc DirectoryEntry[1]; + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.Read(out long entriesRead, entries)); + Assert.Equal(0, entriesRead); + } + + [Fact] + public void IDirectoryGetEntryCount_EmptyFs_EntryCountIsZero() + { + IFileSystem fs = CreateFileSystem(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.GetEntryCount(out long entryCount)); + Assert.Equal(0, entryCount); + } + + [Fact] + public void IDirectoryRead_AllEntriesAreReturned() + { + IFileSystem fs = CreateFileSystem(); + fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir/dir1"); + fs.CreateFile("/dir/dir1/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); + fs.CreateFile("/dir/file2", 0, CreateFileOptions.None); + + using var dir = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref dir.Ref(), "/dir", OpenDirectoryMode.All)); + + var entry1 = new DirectoryEntry(); + var entry2 = new DirectoryEntry(); + var entry3 = new DirectoryEntry(); + var entry4 = new DirectoryEntry(); + + Assert.Success(dir.Get.Read(out long entriesRead1, SpanHelpers.AsSpan(ref entry1))); + Assert.Success(dir.Get.Read(out long entriesRead2, SpanHelpers.AsSpan(ref entry2))); + Assert.Success(dir.Get.Read(out long entriesRead3, SpanHelpers.AsSpan(ref entry3))); + Assert.Success(dir.Get.Read(out long entriesRead4, SpanHelpers.AsSpan(ref entry4))); + + Assert.Equal(1, entriesRead1); + Assert.Equal(1, entriesRead2); + Assert.Equal(1, entriesRead3); + Assert.Equal(0, entriesRead4); + + bool dir1Read = false; + bool file1Read = false; + bool file2Read = false; + + // Entries are not guaranteed to be in any particular order + CheckEntry(ref entry1); + CheckEntry(ref entry2); + CheckEntry(ref entry3); + + Assert.True(dir1Read); + Assert.True(file1Read); + Assert.True(file2Read); + + void CheckEntry(ref DirectoryEntry entry) { - IFileSystem fs = CreateFileSystem(); - Span<DirectoryEntry> entries = stackalloc DirectoryEntry[1]; - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.Read(out long entriesRead, entries)); - Assert.Equal(0, entriesRead); - } - - [Fact] - public void IDirectoryGetEntryCount_EmptyFs_EntryCountIsZero() - { - IFileSystem fs = CreateFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.GetEntryCount(out long entryCount)); - Assert.Equal(0, entryCount); - } - - [Fact] - public void IDirectoryRead_AllEntriesAreReturned() - { - IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); - fs.CreateDirectory("/dir/dir1"); - fs.CreateFile("/dir/dir1/file1", 0, CreateFileOptions.None); - fs.CreateFile("/dir/file1", 0, CreateFileOptions.None); - fs.CreateFile("/dir/file2", 0, CreateFileOptions.None); - - using var dir = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref dir.Ref(), "/dir", OpenDirectoryMode.All)); - - var entry1 = new DirectoryEntry(); - var entry2 = new DirectoryEntry(); - var entry3 = new DirectoryEntry(); - var entry4 = new DirectoryEntry(); - - Assert.Success(dir.Get.Read(out long entriesRead1, SpanHelpers.AsSpan(ref entry1))); - Assert.Success(dir.Get.Read(out long entriesRead2, SpanHelpers.AsSpan(ref entry2))); - Assert.Success(dir.Get.Read(out long entriesRead3, SpanHelpers.AsSpan(ref entry3))); - Assert.Success(dir.Get.Read(out long entriesRead4, SpanHelpers.AsSpan(ref entry4))); - - Assert.Equal(1, entriesRead1); - Assert.Equal(1, entriesRead2); - Assert.Equal(1, entriesRead3); - Assert.Equal(0, entriesRead4); - - bool dir1Read = false; - bool file1Read = false; - bool file2Read = false; - - // Entries are not guaranteed to be in any particular order - CheckEntry(ref entry1); - CheckEntry(ref entry2); - CheckEntry(ref entry3); - - Assert.True(dir1Read); - Assert.True(file1Read); - Assert.True(file2Read); - - void CheckEntry(ref DirectoryEntry entry) + switch (StringUtils.Utf8ZToString(entry.Name)) { - switch (StringUtils.Utf8ZToString(entry.Name)) - { - case "dir1": - Assert.False(dir1Read); - Assert.Equal(DirectoryEntryType.Directory, entry.Type); + case "dir1": + Assert.False(dir1Read); + Assert.Equal(DirectoryEntryType.Directory, entry.Type); - dir1Read = true; - break; + dir1Read = true; + break; - case "file1": - Assert.False(file1Read); - Assert.Equal(DirectoryEntryType.File, entry.Type); + case "file1": + Assert.False(file1Read); + Assert.Equal(DirectoryEntryType.File, entry.Type); - file1Read = true; - break; + file1Read = true; + break; - case "file2": - Assert.False(file2Read); - Assert.Equal(DirectoryEntryType.File, entry.Type); + case "file2": + Assert.False(file2Read); + Assert.Equal(DirectoryEntryType.File, entry.Type); - file2Read = true; - break; + file2Read = true; + break; - default: - throw new ArgumentOutOfRangeException(); - } + default: + throw new ArgumentOutOfRangeException(); } } } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs index a0f8dfc2..4e1b47d5 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Read.cs @@ -4,123 +4,122 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void IFileRead_BytesReadContainsNumberOfBytesRead() { - [Fact] - public void IFileRead_BytesReadContainsNumberOfBytesRead() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", 100, CreateFileOptions.None); + fs.CreateFile("/file", 100, CreateFileOptions.None); - byte[] buffer = new byte[20]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + byte[] buffer = new byte[20]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - Assert.Success(file.Get.Read(out long bytesRead, 50, buffer, ReadOption.None)); - Assert.Equal(20, bytesRead); - } - - [Fact] - public void IFileRead_OffsetPastEndOfFile_ReturnsOutOfRange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Result rc = file.Get.Read(out _, 1, buffer, ReadOption.None); - Assert.Result(ResultFs.OutOfRange, rc); - } - - [Fact] - public void IFileRead_OpenModeNoRead_ReturnsInvalidOpenModeForRead() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - - Result rc = file.Get.Read(out _, 0, buffer, ReadOption.None); - Assert.Result(ResultFs.ReadUnpermitted, rc); - } - - [Fact] - public void IFileRead_NegativeOffset_ReturnsOutOfRange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - - Result rc = file.Get.Read(out _, -5, buffer, ReadOption.None); - Assert.Result(ResultFs.OutOfRange, rc); - } - - [Fact] - public void IFileRead_OffsetPlusSizeOverflows_ReturnsOutOfRange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - - Result rc = file.Get.Read(out _, long.MaxValue - 5, buffer, ReadOption.None); - Assert.Result(ResultFs.OutOfRange, rc); - } - - [Fact] - public void IFileRead_FileTooSmallToFillBuffer_BytesReadContainsAvailableByteCount() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 100, CreateFileOptions.None); - - byte[] buffer = new byte[200]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Assert.Success(file.Get.Read(out long bytesRead, 90, buffer, ReadOption.None)); - Assert.Equal(10, bytesRead); - } - - [Fact] - public void IFileRead_FileTooSmallToFillBuffer_DoesPartialRead() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 100, CreateFileOptions.None); - - // The contents of a created file are undefined, so zero the file - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - file.Get.Write(0, new byte[100], WriteOption.None); - file.Reset(); - - byte[] bufferExpected = new byte[200]; - bufferExpected.AsSpan(10).Fill(0xCC); - - byte[] buffer = new byte[200]; - buffer.AsSpan().Fill(0xCC); - - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Assert.Success(file.Get.Read(out _, 90, buffer, ReadOption.None)); - Assert.Equal(bufferExpected, buffer); - } + Assert.Success(file.Get.Read(out long bytesRead, 50, buffer, ReadOption.None)); + Assert.Equal(20, bytesRead); } -} \ No newline at end of file + + [Fact] + public void IFileRead_OffsetPastEndOfFile_ReturnsOutOfRange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Result rc = file.Get.Read(out _, 1, buffer, ReadOption.None); + Assert.Result(ResultFs.OutOfRange, rc); + } + + [Fact] + public void IFileRead_OpenModeNoRead_ReturnsInvalidOpenModeForRead() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + + Result rc = file.Get.Read(out _, 0, buffer, ReadOption.None); + Assert.Result(ResultFs.ReadUnpermitted, rc); + } + + [Fact] + public void IFileRead_NegativeOffset_ReturnsOutOfRange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + + Result rc = file.Get.Read(out _, -5, buffer, ReadOption.None); + Assert.Result(ResultFs.OutOfRange, rc); + } + + [Fact] + public void IFileRead_OffsetPlusSizeOverflows_ReturnsOutOfRange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + + Result rc = file.Get.Read(out _, long.MaxValue - 5, buffer, ReadOption.None); + Assert.Result(ResultFs.OutOfRange, rc); + } + + [Fact] + public void IFileRead_FileTooSmallToFillBuffer_BytesReadContainsAvailableByteCount() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 100, CreateFileOptions.None); + + byte[] buffer = new byte[200]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Assert.Success(file.Get.Read(out long bytesRead, 90, buffer, ReadOption.None)); + Assert.Equal(10, bytesRead); + } + + [Fact] + public void IFileRead_FileTooSmallToFillBuffer_DoesPartialRead() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 100, CreateFileOptions.None); + + // The contents of a created file are undefined, so zero the file + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + file.Get.Write(0, new byte[100], WriteOption.None); + file.Reset(); + + byte[] bufferExpected = new byte[200]; + bufferExpected.AsSpan(10).Fill(0xCC); + + byte[] buffer = new byte[200]; + buffer.AsSpan().Fill(0xCC); + + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Assert.Success(file.Get.Read(out _, 90, buffer, ReadOption.None)); + Assert.Equal(bufferExpected, buffer); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs index 28dd6f27..89e65813 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Size.cs @@ -3,26 +3,25 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void SetSize_FileSizeModified() { - [Fact] - public void SetSize_FileSizeModified() - { - IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); + IFileSystem fs = CreateFileSystem(); + fs.CreateFile("/file", 0, CreateFileOptions.None); - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); - Result rc = file.Get.SetSize(54321); - file.Reset(); + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); + Result rc = file.Get.SetSize(54321); + file.Reset(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); - file.Get.GetSize(out long fileSize); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); + file.Get.GetSize(out long fileSize); - Assert.Success(rc); - Assert.Equal(54321, fileSize); - } + Assert.Success(rc); + Assert.Equal(54321, fileSize); } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs index 68387f7d..f0825318 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IFile.Write.cs @@ -4,155 +4,154 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void IFileWrite_CanReadBackWrittenData() { - [Fact] - public void IFileWrite_CanReadBackWrittenData() - { - byte[] data = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; + byte[] data = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", data.Length, CreateFileOptions.None); + fs.CreateFile("/file", data.Length, CreateFileOptions.None); - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - file.Get.Write(0, data, WriteOption.None); - file.Reset(); + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + file.Get.Write(0, data, WriteOption.None); + file.Reset(); - byte[] readData = new byte[data.Length]; - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + byte[] readData = new byte[data.Length]; + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - Assert.Success(file.Get.Read(out long bytesRead, 0, readData, ReadOption.None)); - Assert.Equal(data.Length, bytesRead); + Assert.Success(file.Get.Read(out long bytesRead, 0, readData, ReadOption.None)); + Assert.Equal(data.Length, bytesRead); - Assert.Equal(data, readData); - } - - [Fact] - public void IFileWrite_WritePastEndOfFileWithNoAppend_ReturnsFileExtensionWithoutOpenModeAllowAppend() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - - Result rc = file.Get.Write(5, buffer, WriteOption.None); - Assert.Result(ResultFs.FileExtensionWithoutOpenModeAllowAppend, rc); - } - - [Fact] - public void IFileWrite_OpenModeNoWrite_ReturnsInvalidOpenModeForWrite() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Result rc = file.Get.Write(5, buffer, WriteOption.None); - Assert.Result(ResultFs.WriteUnpermitted, rc); - } - - [Fact] - public void IFileWrite_NegativeOffset_ReturnsOutOfRange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Result rc = file.Get.Write(-5, buffer, WriteOption.None); - Assert.Result(ResultFs.OutOfRange, rc); - } - - [Fact] - public void IFileWrite_OffsetPlusSizeOverflows_ReturnsOutOfRange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - Result rc = file.Get.Write(long.MaxValue - 5, buffer, WriteOption.None); - Assert.Result(ResultFs.OutOfRange, rc); - } - - [Fact] - public void IFileWrite_WritePartiallyPastEndOfFileAppendAllowed_FileIsExtended() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); - - Assert.Success(file.Get.Write(5, buffer, WriteOption.None)); - - file.Get.GetSize(out long newSize); - Assert.Equal(15, newSize); - } - - [Fact] - public void IFileWrite_WritePastEndOfFileAppendAllowed_FileIsExtended() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] buffer = new byte[10]; - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); - - Assert.Success(file.Get.Write(15, buffer, WriteOption.None)); - - file.Get.GetSize(out long newSize); - Assert.Equal(25, newSize); - } - - [Fact] - public void IFileWrite_WritePastEndOfFileAppendAllowed_DataIsWritten() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 10, CreateFileOptions.None); - - byte[] bufferExpected = new byte[25]; - bufferExpected.AsSpan(15).Fill(0xCC); - - byte[] writeBuffer = new byte[10]; - writeBuffer.AsSpan().Fill(0xCC); - - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); - - Assert.Success(file.Get.Write(15, writeBuffer, WriteOption.None)); - - // Unwritten portions of new files are undefined, so write to the other portions - file.Get.Write(0, new byte[15], WriteOption.None); - file.Reset(); - - byte[] readBuffer = new byte[25]; - - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); - - file.Get.Read(out _, 0, readBuffer, ReadOption.None); - Assert.Equal(bufferExpected, readBuffer); - } + Assert.Equal(data, readData); } -} \ No newline at end of file + + [Fact] + public void IFileWrite_WritePastEndOfFileWithNoAppend_ReturnsFileExtensionWithoutOpenModeAllowAppend() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + + Result rc = file.Get.Write(5, buffer, WriteOption.None); + Assert.Result(ResultFs.FileExtensionWithoutOpenModeAllowAppend, rc); + } + + [Fact] + public void IFileWrite_OpenModeNoWrite_ReturnsInvalidOpenModeForWrite() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Result rc = file.Get.Write(5, buffer, WriteOption.None); + Assert.Result(ResultFs.WriteUnpermitted, rc); + } + + [Fact] + public void IFileWrite_NegativeOffset_ReturnsOutOfRange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Result rc = file.Get.Write(-5, buffer, WriteOption.None); + Assert.Result(ResultFs.OutOfRange, rc); + } + + [Fact] + public void IFileWrite_OffsetPlusSizeOverflows_ReturnsOutOfRange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + Result rc = file.Get.Write(long.MaxValue - 5, buffer, WriteOption.None); + Assert.Result(ResultFs.OutOfRange, rc); + } + + [Fact] + public void IFileWrite_WritePartiallyPastEndOfFileAppendAllowed_FileIsExtended() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); + + Assert.Success(file.Get.Write(5, buffer, WriteOption.None)); + + file.Get.GetSize(out long newSize); + Assert.Equal(15, newSize); + } + + [Fact] + public void IFileWrite_WritePastEndOfFileAppendAllowed_FileIsExtended() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] buffer = new byte[10]; + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); + + Assert.Success(file.Get.Write(15, buffer, WriteOption.None)); + + file.Get.GetSize(out long newSize); + Assert.Equal(25, newSize); + } + + [Fact] + public void IFileWrite_WritePastEndOfFileAppendAllowed_DataIsWritten() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 10, CreateFileOptions.None); + + byte[] bufferExpected = new byte[25]; + bufferExpected.AsSpan(15).Fill(0xCC); + + byte[] writeBuffer = new byte[10]; + writeBuffer.AsSpan().Fill(0xCC); + + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.All); + + Assert.Success(file.Get.Write(15, writeBuffer, WriteOption.None)); + + // Unwritten portions of new files are undefined, so write to the other portions + file.Get.Write(0, new byte[15], WriteOption.None); + file.Reset(); + + byte[] readBuffer = new byte[25]; + + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Read); + + file.Get.Read(out _, 0, readBuffer, ReadOption.None); + Assert.Equal(bufferExpected, readBuffer); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs index d5f8bc09..8d2aff1c 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenDirectory.cs @@ -3,21 +3,20 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void OpenDirectory_PathIsFile_ReturnsPathNotFound() { - [Fact] - public void OpenDirectory_PathIsFile_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file", 0, CreateFileOptions.None); + fs.CreateFile("/file", 0, CreateFileOptions.None); - using var directory = new UniqueRef<IDirectory>(); - Result rc = fs.OpenDirectory(ref directory.Ref(), "/file", OpenDirectoryMode.All); + using var directory = new UniqueRef<IDirectory>(); + Result rc = fs.OpenDirectory(ref directory.Ref(), "/file", OpenDirectoryMode.All); - Assert.Result(ResultFs.PathNotFound, rc); - } + Assert.Result(ResultFs.PathNotFound, rc); } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs index 7438cda9..35f5f2c5 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.OpenFile.cs @@ -3,21 +3,20 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void OpenFile_PathIsDirectory_ReturnsPathNotFound() { - [Fact] - public void OpenFile_PathIsDirectory_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir"); + fs.CreateDirectory("/dir"); - using var file = new UniqueRef<IFile>(); - Result rc = fs.OpenFile(ref file.Ref(), "/dir", OpenMode.All); + using var file = new UniqueRef<IFile>(); + Result rc = fs.OpenFile(ref file.Ref(), "/dir", OpenMode.All); - Assert.Result(ResultFs.PathNotFound, rc); - } + Assert.Result(ResultFs.PathNotFound, rc); } -} \ No newline at end of file +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs index 0a72ebbe..74a095da 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameDirectory.cs @@ -2,107 +2,106 @@ using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void RenameDirectory_EntriesAreMoved() { - [Fact] - public void RenameDirectory_EntriesAreMoved() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateDirectory("/dir1"); - Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); + fs.CreateDirectory("/dir1"); + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); - Result rcDir1 = fs.GetEntryType(out _, "/dir1"); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); + Result rcDir1 = fs.GetEntryType(out _, "/dir1"); - Assert.Success(rcRename); + Assert.Success(rcRename); - Assert.Success(rcDir2); - Assert.Equal(DirectoryEntryType.Directory, dir2Type); + Assert.Success(rcDir2); + Assert.Equal(DirectoryEntryType.Directory, dir2Type); - Assert.Result(ResultFs.PathNotFound, rcDir1); - } - - [Fact] - public void RenameDirectory_HasChildren_NewChildPathExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir1/dirC"); - fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); - - Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); - - // Check that renamed structure exists - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); - Result rcDirC = fs.GetEntryType(out DirectoryEntryType dir1CType, "/dir2/dirC"); - Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/dir2/file1"); - - // Check that old structure doesn't exist - Result rcDir1 = fs.GetEntryType(out _, "/dir1"); - Result rcDirCOld = fs.GetEntryType(out _, "/dir1/dirC"); - Result rcFile1Old = fs.GetEntryType(out _, "/dir1/file1"); - - Assert.Success(rcRename); - - Assert.Success(rcDir2); - Assert.Success(rcDirC); - Assert.Success(rcFile1); - - Assert.Equal(DirectoryEntryType.Directory, dir2Type); - Assert.Equal(DirectoryEntryType.Directory, dir1CType); - Assert.Equal(DirectoryEntryType.File, file1Type); - - Assert.Result(ResultFs.PathNotFound, rcDir1); - Assert.Result(ResultFs.PathNotFound, rcDirCOld); - Assert.Result(ResultFs.PathNotFound, rcFile1Old); - } - - [Fact] - public void RenameDirectory_DestHasDifferentParentDirectory() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/parent1"); - fs.CreateDirectory("/parent2"); - fs.CreateDirectory("/parent1/dir1"); - - Result rcRename = fs.RenameDirectory("/parent1/dir1", "/parent2/dir2"); - - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/parent2/dir2"); - Result rcDir1 = fs.GetEntryType(out _, "/parent1/dir1"); - - Assert.Success(rcRename); - - Assert.Equal(Result.Success, rcDir2); - Assert.Success(rcDir2); - Assert.Equal(DirectoryEntryType.Directory, dir2Type); - - Assert.Result(ResultFs.PathNotFound, rcDir1); - } - - [Fact] - public void RenameDirectory_DestExists_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateDirectory("/dir1"); - fs.CreateDirectory("/dir2"); - - Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); - - Result rcDir1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); - Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); - - Assert.Result(ResultFs.PathAlreadyExists, rcRename); - - Assert.Success(rcDir1); - Assert.Success(rcDir2); - Assert.Equal(DirectoryEntryType.Directory, dir1Type); - Assert.Equal(DirectoryEntryType.Directory, dir2Type); - } + Assert.Result(ResultFs.PathNotFound, rcDir1); } -} \ No newline at end of file + + [Fact] + public void RenameDirectory_HasChildren_NewChildPathExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir1/dirC"); + fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None); + + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); + + // Check that renamed structure exists + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); + Result rcDirC = fs.GetEntryType(out DirectoryEntryType dir1CType, "/dir2/dirC"); + Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/dir2/file1"); + + // Check that old structure doesn't exist + Result rcDir1 = fs.GetEntryType(out _, "/dir1"); + Result rcDirCOld = fs.GetEntryType(out _, "/dir1/dirC"); + Result rcFile1Old = fs.GetEntryType(out _, "/dir1/file1"); + + Assert.Success(rcRename); + + Assert.Success(rcDir2); + Assert.Success(rcDirC); + Assert.Success(rcFile1); + + Assert.Equal(DirectoryEntryType.Directory, dir2Type); + Assert.Equal(DirectoryEntryType.Directory, dir1CType); + Assert.Equal(DirectoryEntryType.File, file1Type); + + Assert.Result(ResultFs.PathNotFound, rcDir1); + Assert.Result(ResultFs.PathNotFound, rcDirCOld); + Assert.Result(ResultFs.PathNotFound, rcFile1Old); + } + + [Fact] + public void RenameDirectory_DestHasDifferentParentDirectory() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/parent1"); + fs.CreateDirectory("/parent2"); + fs.CreateDirectory("/parent1/dir1"); + + Result rcRename = fs.RenameDirectory("/parent1/dir1", "/parent2/dir2"); + + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/parent2/dir2"); + Result rcDir1 = fs.GetEntryType(out _, "/parent1/dir1"); + + Assert.Success(rcRename); + + Assert.Equal(Result.Success, rcDir2); + Assert.Success(rcDir2); + Assert.Equal(DirectoryEntryType.Directory, dir2Type); + + Assert.Result(ResultFs.PathNotFound, rcDir1); + } + + [Fact] + public void RenameDirectory_DestExists_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateDirectory("/dir1"); + fs.CreateDirectory("/dir2"); + + Result rcRename = fs.RenameDirectory("/dir1", "/dir2"); + + Result rcDir1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1"); + Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2"); + + Assert.Result(ResultFs.PathAlreadyExists, rcRename); + + Assert.Success(rcDir1); + Assert.Success(rcDir2); + Assert.Equal(DirectoryEntryType.Directory, dir1Type); + Assert.Equal(DirectoryEntryType.Directory, dir2Type); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs index d3fe2d81..5165eb0c 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.RenameFile.cs @@ -3,115 +3,114 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using Xunit; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests + [Fact] + public void RenameFile_SameParentDirectory_EntryIsRenamed() { - [Fact] - public void RenameFile_SameParentDirectory_EntryIsRenamed() - { - IFileSystem fs = CreateFileSystem(); + IFileSystem fs = CreateFileSystem(); - fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file1", 0, CreateFileOptions.None); - Assert.Success(fs.RenameFile("/file1", "/file2")); + Assert.Success(fs.RenameFile("/file1", "/file2")); - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file2")); - Result rc = fs.GetEntryType(out _, "/file1"); + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/file2")); + Result rc = fs.GetEntryType(out _, "/file1"); - Assert.Equal(DirectoryEntryType.File, type); - Assert.Result(ResultFs.PathNotFound, rc); - } - [Fact] - public void RenameFile_DifferentParentDirectory_EntryIsRenamed() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file1", 0, CreateFileOptions.None); - fs.CreateDirectory("/dir"); - - Assert.Success(fs.RenameFile("/file1", "/dir/file2")); - - Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir/file2")); - Result rc = fs.GetEntryType(out _, "/file1"); - - Assert.Equal(DirectoryEntryType.File, type); - Assert.Result(ResultFs.PathNotFound, rc); - } - - [Fact] - public void RenameFile_DestExistsAsFile_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file1", 0, CreateFileOptions.None); - fs.CreateFile("/file2", 0, CreateFileOptions.None); - - Result rc = fs.RenameFile("/file1", "/file2"); - - Assert.Result(ResultFs.PathAlreadyExists, rc); - } - - [Fact] - public void RenameFile_DestExistsAsDirectory_ReturnsPathAlreadyExists() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", 0, CreateFileOptions.None); - fs.CreateDirectory("/dir"); - - Result rc = fs.RenameFile("/file", "/dir"); - - Assert.Result(ResultFs.PathAlreadyExists, rc); - } - - [Fact] - public void RenameFile_DestExistsAsFile_FileSizesDoNotChange() - { - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file1", 54321, CreateFileOptions.None); - fs.CreateFile("/file2", 12345, CreateFileOptions.None); - - fs.RenameFile("/file1", "/file2"); - - using var file1 = new UniqueRef<IFile>(); - using var file2 = new UniqueRef<IFile>(); - - Assert.Success(fs.OpenFile(ref file1.Ref(), "/file1", OpenMode.Read)); - Assert.Success(fs.OpenFile(ref file2.Ref(), "/file2", OpenMode.Read)); - - Assert.Success(file1.Get.GetSize(out long file1Size)); - Assert.Success(file2.Get.GetSize(out long file2Size)); - - Assert.Equal(54321, file1Size); - Assert.Equal(12345, file2Size); - } - - [Fact] - public void RenameFile_DataIsUnmodified() - { - byte[] data = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; - - IFileSystem fs = CreateFileSystem(); - - fs.CreateFile("/file", data.Length, CreateFileOptions.None); - - using var file = new UniqueRef<IFile>(); - fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); - file.Get.Write(0, data, WriteOption.None); - file.Reset(); - - fs.RenameFile("/file", "/renamed"); - - byte[] readData = new byte[data.Length]; - - fs.OpenFile(ref file.Ref(), "/renamed", OpenMode.Read); - Result rc = file.Get.Read(out long bytesRead, 0, readData, ReadOption.None); - - Assert.Success(rc); - Assert.Equal(data.Length, bytesRead); - Assert.Equal(data, readData); - } + Assert.Equal(DirectoryEntryType.File, type); + Assert.Result(ResultFs.PathNotFound, rc); } -} \ No newline at end of file + [Fact] + public void RenameFile_DifferentParentDirectory_EntryIsRenamed() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + + Assert.Success(fs.RenameFile("/file1", "/dir/file2")); + + Assert.Success(fs.GetEntryType(out DirectoryEntryType type, "/dir/file2")); + Result rc = fs.GetEntryType(out _, "/file1"); + + Assert.Equal(DirectoryEntryType.File, type); + Assert.Result(ResultFs.PathNotFound, rc); + } + + [Fact] + public void RenameFile_DestExistsAsFile_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file1", 0, CreateFileOptions.None); + fs.CreateFile("/file2", 0, CreateFileOptions.None); + + Result rc = fs.RenameFile("/file1", "/file2"); + + Assert.Result(ResultFs.PathAlreadyExists, rc); + } + + [Fact] + public void RenameFile_DestExistsAsDirectory_ReturnsPathAlreadyExists() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", 0, CreateFileOptions.None); + fs.CreateDirectory("/dir"); + + Result rc = fs.RenameFile("/file", "/dir"); + + Assert.Result(ResultFs.PathAlreadyExists, rc); + } + + [Fact] + public void RenameFile_DestExistsAsFile_FileSizesDoNotChange() + { + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file1", 54321, CreateFileOptions.None); + fs.CreateFile("/file2", 12345, CreateFileOptions.None); + + fs.RenameFile("/file1", "/file2"); + + using var file1 = new UniqueRef<IFile>(); + using var file2 = new UniqueRef<IFile>(); + + Assert.Success(fs.OpenFile(ref file1.Ref(), "/file1", OpenMode.Read)); + Assert.Success(fs.OpenFile(ref file2.Ref(), "/file2", OpenMode.Read)); + + Assert.Success(file1.Get.GetSize(out long file1Size)); + Assert.Success(file2.Get.GetSize(out long file2Size)); + + Assert.Equal(54321, file1Size); + Assert.Equal(12345, file2Size); + } + + [Fact] + public void RenameFile_DataIsUnmodified() + { + byte[] data = { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 }; + + IFileSystem fs = CreateFileSystem(); + + fs.CreateFile("/file", data.Length, CreateFileOptions.None); + + using var file = new UniqueRef<IFile>(); + fs.OpenFile(ref file.Ref(), "/file", OpenMode.Write); + file.Get.Write(0, data, WriteOption.None); + file.Reset(); + + fs.RenameFile("/file", "/renamed"); + + byte[] readData = new byte[data.Length]; + + fs.OpenFile(ref file.Ref(), "/renamed", OpenMode.Read); + Result rc = file.Get.Read(out long bytesRead, 0, readData, ReadOption.None); + + Assert.Success(rc); + Assert.Equal(data.Length, bytesRead); + Assert.Equal(data, readData); + } +} diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.cs index e3ea3ce1..c23cf020 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.cs @@ -1,9 +1,8 @@ using LibHac.Fs.Fsa; -namespace LibHac.Tests.Fs.IFileSystemTestBase +namespace LibHac.Tests.Fs.IFileSystemTestBase; + +public abstract partial class IFileSystemTests { - public abstract partial class IFileSystemTests - { - protected abstract IFileSystem CreateFileSystem(); - } + protected abstract IFileSystem CreateFileSystem(); } diff --git a/tests/LibHac.Tests/Fs/InMemoryFileSystemTests.cs b/tests/LibHac.Tests/Fs/InMemoryFileSystemTests.cs index e99d4da8..ca8d42d7 100644 --- a/tests/LibHac.Tests/Fs/InMemoryFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/InMemoryFileSystemTests.cs @@ -2,18 +2,17 @@ using LibHac.Fs.Fsa; using LibHac.Tests.Fs.IFileSystemTestBase; -namespace LibHac.Tests.Fs -{ - public class InMemoryFileSystemTests : IAttributeFileSystemTests - { - protected override IFileSystem CreateFileSystem() - { - return new InMemoryFileSystem(); - } +namespace LibHac.Tests.Fs; - protected override IAttributeFileSystem CreateAttributeFileSystem() - { - return new InMemoryFileSystem(); - } +public class InMemoryFileSystemTests : IAttributeFileSystemTests +{ + protected override IFileSystem CreateFileSystem() + { + return new InMemoryFileSystem(); + } + + protected override IAttributeFileSystem CreateAttributeFileSystem() + { + return new InMemoryFileSystem(); } } diff --git a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs index fc07e160..ad789031 100644 --- a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs @@ -6,204 +6,203 @@ using LibHac.FsSystem; using LibHac.Util; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class LayeredFileSystemTests { - public class LayeredFileSystemTests + private IFileSystem CreateFileSystem() { - private IFileSystem CreateFileSystem() + var lowerLayerFs = new InMemoryFileSystem(); + var upperLayerFs = new InMemoryFileSystem(); + + var layeredFs = new LayeredFileSystem(lowerLayerFs, upperLayerFs); + + lowerLayerFs.CreateDirectory("/dir").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir").ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); + lowerLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); + + lowerLayerFs.CreateDirectory("/lowerDir").ThrowIfFailure(); + upperLayerFs.CreateDirectory("/upperDir").ThrowIfFailure(); + + lowerLayerFs.CreateFile("/dir/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); + + lowerLayerFs.CreateFile("/dir2/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir2/upperFile", 0, CreateFileOptions.None).ThrowIfFailure(); + + lowerLayerFs.CreateFile("/dir3/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir3/upperFile", 2, CreateFileOptions.None).ThrowIfFailure(); + lowerLayerFs.CreateFile("/dir3/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateFile("/dir3/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); + + lowerLayerFs.CreateFile("/replacedWithDir", 0, CreateFileOptions.None).ThrowIfFailure(); + upperLayerFs.CreateDirectory("/replacedWithDir").ThrowIfFailure(); + upperLayerFs.CreateFile("/replacedWithDir/subFile", 0, CreateFileOptions.None).ThrowIfFailure(); + + return layeredFs; + } + + private IFileSystem CreateEmptyFileSystem() + { + var baseLayerFs = new InMemoryFileSystem(); + var topLayerFs = new InMemoryFileSystem(); + + return new LayeredFileSystem(baseLayerFs, topLayerFs); + } + + [Fact] + public void OpenFile_FileDoesNotExist_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + using var file = new UniqueRef<IFile>(); + Assert.Result(ResultFs.PathNotFound, fs.OpenFile(ref file.Ref(), "/fakefile", OpenMode.All)); + } + + [Fact] + public void OpenFile_FileIsInBothSources_ReturnsFileFromTopSource() + { + IFileSystem fs = CreateFileSystem(); + + using var file = new UniqueRef<IFile>(); + Assert.Success(fs.OpenFile(ref file.Ref(), "/dir/replacedFile", OpenMode.All)); + Assert.Success(file.Get.GetSize(out long fileSize)); + + Assert.Equal(2, fileSize); + } + + [Fact] + public void OpenFile_InsideMergedDirectory_CanOpenFilesFromBothSources() + { + IFileSystem fs = CreateFileSystem(); + + using var file = new UniqueRef<IFile>(); + Assert.Success(fs.OpenFile(ref file.Ref(), "/dir2/lowerFile", OpenMode.All)); + Assert.Success(fs.OpenFile(ref file.Ref(), "/dir2/upperFile", OpenMode.All)); + } + + [Fact] + public void OpenDirectory_DirDoesNotExist_ReturnsPathNotFound() + { + IFileSystem fs = CreateFileSystem(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Result(ResultFs.PathNotFound, fs.OpenDirectory(ref directory.Ref(), "/fakedir", OpenDirectoryMode.All)); + } + + [Fact] + public void OpenDirectory_ExistsInSingleLayer_ReturnsNonMergedDirectory() + { + IFileSystem fs = CreateFileSystem(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/lowerDir", OpenDirectoryMode.All)); + Assert.Equal(typeof(InMemoryFileSystem), directory.Get.GetType().DeclaringType); + } + + [Fact] + public void OpenDirectory_ExistsInMultipleLayers_ReturnsMergedDirectory() + { + IFileSystem fs = CreateFileSystem(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All)); + Assert.Equal(typeof(LayeredFileSystem), directory.Get.GetType().DeclaringType); + } + + [Fact] + public void GetEntryType_InsideMergedDirectory_CanGetEntryTypesFromBothSources() + { + IFileSystem fs = CreateFileSystem(); + + Assert.Success(fs.GetEntryType(out _, "/dir2/lowerFile")); + Assert.Success(fs.GetEntryType(out _, "/dir2/upperFile")); + } + + [Fact] + public void IDirectoryRead_DuplicatedEntriesAreReturnedOnlyOnce() + { + IFileSystem fs = CreateFileSystem(); + Span<DirectoryEntry> entries = stackalloc DirectoryEntry[4]; + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.Read(out long entriesRead, entries)); + Assert.Equal(3, entriesRead); + } + + [Fact] + public void IDirectoryRead_DuplicatedEntryReturnsFromTopLayer() + { + IFileSystem fs = CreateFileSystem(); + var entry = new DirectoryEntry(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.Read(out _, SpanHelpers.AsSpan(ref entry))); + Assert.Equal("replacedFile", StringUtils.Utf8ZToString(entry.Name)); + Assert.Equal(2, entry.Size); + } + + [Fact] + public void IDirectoryRead_EmptyFs_NoEntriesAreRead() + { + IFileSystem fs = CreateEmptyFileSystem(); + var entry = new DirectoryEntry(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref entry))); + Assert.Equal(0, entriesRead); + } + + [Fact] + public void IDirectoryGetEntryCount_DuplicatedEntriesAreCountedOnlyOnce() + { + IFileSystem fs = CreateFileSystem(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); + + Assert.Success(directory.Get.GetEntryCount(out long entryCount)); + Assert.Equal(3, entryCount); + } + + [Fact] + public void IDirectoryGetEntryCount_MergedDirectoryAfterRead_AllEntriesAreCounted() + { + IFileSystem fs = CreateFileSystem(); + var entry = new DirectoryEntry(); + + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); + + // Read all entries + long entriesRead; + do { - var lowerLayerFs = new InMemoryFileSystem(); - var upperLayerFs = new InMemoryFileSystem(); + Assert.Success(directory.Get.Read(out entriesRead, SpanHelpers.AsSpan(ref entry))); + } while (entriesRead != 0); - var layeredFs = new LayeredFileSystem(lowerLayerFs, upperLayerFs); + Assert.Success(directory.Get.GetEntryCount(out long entryCount)); + Assert.Equal(3, entryCount); + } - lowerLayerFs.CreateDirectory("/dir").ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir").ThrowIfFailure(); - lowerLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir2").ThrowIfFailure(); - lowerLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); - upperLayerFs.CreateDirectory("/dir3").ThrowIfFailure(); + [Fact] + public void IDirectoryGetEntryCount_EmptyFs_EntryCountIsZero() + { + IFileSystem fs = CreateEmptyFileSystem(); - lowerLayerFs.CreateDirectory("/lowerDir").ThrowIfFailure(); - upperLayerFs.CreateDirectory("/upperDir").ThrowIfFailure(); + using var directory = new UniqueRef<IDirectory>(); + Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); - lowerLayerFs.CreateFile("/dir/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); - - lowerLayerFs.CreateFile("/dir2/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir2/upperFile", 0, CreateFileOptions.None).ThrowIfFailure(); - - lowerLayerFs.CreateFile("/dir3/lowerFile", 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir3/upperFile", 2, CreateFileOptions.None).ThrowIfFailure(); - lowerLayerFs.CreateFile("/dir3/replacedFile", 1, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateFile("/dir3/replacedFile", 2, CreateFileOptions.None).ThrowIfFailure(); - - lowerLayerFs.CreateFile("/replacedWithDir", 0, CreateFileOptions.None).ThrowIfFailure(); - upperLayerFs.CreateDirectory("/replacedWithDir").ThrowIfFailure(); - upperLayerFs.CreateFile("/replacedWithDir/subFile", 0, CreateFileOptions.None).ThrowIfFailure(); - - return layeredFs; - } - - private IFileSystem CreateEmptyFileSystem() - { - var baseLayerFs = new InMemoryFileSystem(); - var topLayerFs = new InMemoryFileSystem(); - - return new LayeredFileSystem(baseLayerFs, topLayerFs); - } - - [Fact] - public void OpenFile_FileDoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - using var file = new UniqueRef<IFile>(); - Assert.Result(ResultFs.PathNotFound, fs.OpenFile(ref file.Ref(), "/fakefile", OpenMode.All)); - } - - [Fact] - public void OpenFile_FileIsInBothSources_ReturnsFileFromTopSource() - { - IFileSystem fs = CreateFileSystem(); - - using var file = new UniqueRef<IFile>(); - Assert.Success(fs.OpenFile(ref file.Ref(), "/dir/replacedFile", OpenMode.All)); - Assert.Success(file.Get.GetSize(out long fileSize)); - - Assert.Equal(2, fileSize); - } - - [Fact] - public void OpenFile_InsideMergedDirectory_CanOpenFilesFromBothSources() - { - IFileSystem fs = CreateFileSystem(); - - using var file = new UniqueRef<IFile>(); - Assert.Success(fs.OpenFile(ref file.Ref(), "/dir2/lowerFile", OpenMode.All)); - Assert.Success(fs.OpenFile(ref file.Ref(), "/dir2/upperFile", OpenMode.All)); - } - - [Fact] - public void OpenDirectory_DirDoesNotExist_ReturnsPathNotFound() - { - IFileSystem fs = CreateFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Result(ResultFs.PathNotFound, fs.OpenDirectory(ref directory.Ref(), "/fakedir", OpenDirectoryMode.All)); - } - - [Fact] - public void OpenDirectory_ExistsInSingleLayer_ReturnsNonMergedDirectory() - { - IFileSystem fs = CreateFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/lowerDir", OpenDirectoryMode.All)); - Assert.Equal(typeof(InMemoryFileSystem), directory.Get.GetType().DeclaringType); - } - - [Fact] - public void OpenDirectory_ExistsInMultipleLayers_ReturnsMergedDirectory() - { - IFileSystem fs = CreateFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All)); - Assert.Equal(typeof(LayeredFileSystem), directory.Get.GetType().DeclaringType); - } - - [Fact] - public void GetEntryType_InsideMergedDirectory_CanGetEntryTypesFromBothSources() - { - IFileSystem fs = CreateFileSystem(); - - Assert.Success(fs.GetEntryType(out _, "/dir2/lowerFile")); - Assert.Success(fs.GetEntryType(out _, "/dir2/upperFile")); - } - - [Fact] - public void IDirectoryRead_DuplicatedEntriesAreReturnedOnlyOnce() - { - IFileSystem fs = CreateFileSystem(); - Span<DirectoryEntry> entries = stackalloc DirectoryEntry[4]; - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.Read(out long entriesRead, entries)); - Assert.Equal(3, entriesRead); - } - - [Fact] - public void IDirectoryRead_DuplicatedEntryReturnsFromTopLayer() - { - IFileSystem fs = CreateFileSystem(); - var entry = new DirectoryEntry(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.Read(out _, SpanHelpers.AsSpan(ref entry))); - Assert.Equal("replacedFile", StringUtils.Utf8ZToString(entry.Name)); - Assert.Equal(2, entry.Size); - } - - [Fact] - public void IDirectoryRead_EmptyFs_NoEntriesAreRead() - { - IFileSystem fs = CreateEmptyFileSystem(); - var entry = new DirectoryEntry(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.Read(out long entriesRead, SpanHelpers.AsSpan(ref entry))); - Assert.Equal(0, entriesRead); - } - - [Fact] - public void IDirectoryGetEntryCount_DuplicatedEntriesAreCountedOnlyOnce() - { - IFileSystem fs = CreateFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.GetEntryCount(out long entryCount)); - Assert.Equal(3, entryCount); - } - - [Fact] - public void IDirectoryGetEntryCount_MergedDirectoryAfterRead_AllEntriesAreCounted() - { - IFileSystem fs = CreateFileSystem(); - var entry = new DirectoryEntry(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/dir3", OpenDirectoryMode.All)); - - // Read all entries - long entriesRead; - do - { - Assert.Success(directory.Get.Read(out entriesRead, SpanHelpers.AsSpan(ref entry))); - } while (entriesRead != 0); - - Assert.Success(directory.Get.GetEntryCount(out long entryCount)); - Assert.Equal(3, entryCount); - } - - [Fact] - public void IDirectoryGetEntryCount_EmptyFs_EntryCountIsZero() - { - IFileSystem fs = CreateEmptyFileSystem(); - - using var directory = new UniqueRef<IDirectory>(); - Assert.Success(fs.OpenDirectory(ref directory.Ref(), "/", OpenDirectoryMode.All)); - - Assert.Success(directory.Get.GetEntryCount(out long entryCount)); - Assert.Equal(0, entryCount); - } + Assert.Success(directory.Get.GetEntryCount(out long entryCount)); + Assert.Equal(0, entryCount); } } diff --git a/tests/LibHac.Tests/Fs/PathFormatterTests.cs b/tests/LibHac.Tests/Fs/PathFormatterTests.cs index c7d19ad3..0cb382d8 100644 --- a/tests/LibHac.Tests/Fs/PathFormatterTests.cs +++ b/tests/LibHac.Tests/Fs/PathFormatterTests.cs @@ -6,390 +6,389 @@ using LibHac.Fs; using LibHac.Util; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class PathFormatterTests { - public class PathFormatterTests + public static TheoryData<string, string, string, Result> TestData_Normalize_EmptyPath => new() { - public static TheoryData<string, string, string, Result> TestData_Normalize_EmptyPath => new() + { @"", "", @"", ResultFs.InvalidPathFormat.Value }, + { @"", "E", @"", Result.Success }, + { @"/aa/bb/../cc", "E", @"/aa/cc", Result.Success } + }; + + [Theory, MemberData(nameof(TestData_Normalize_EmptyPath))] + public static void Normalize_EmptyPath(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, string, Result> TestData_Normalize_MountName => new() + { + { @"mount:/aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa/bb", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa/bb", "M", @"mount:/aa/bb", Result.Success }, + { @"mount:/aa/./bb", "M", @"mount:/aa/bb", Result.Success }, + { @"mount:\aa\bb", "M", @"mount:", ResultFs.InvalidPathFormat.Value }, + { @"m:/aa/bb", "M", @"", ResultFs.InvalidPathFormat.Value }, + { @"mo>unt:/aa/bb", "M", @"", ResultFs.InvalidCharacter.Value }, + { @"moun?t:/aa/bb", "M", @"", ResultFs.InvalidCharacter.Value }, + { @"mo&unt:/aa/bb", "M", @"mo&unt:/aa/bb", Result.Success }, + { @"/aa/./bb", "M", @"/aa/bb", Result.Success }, + { @"mount/aa/./bb", "M", @"", ResultFs.InvalidPathFormat.Value } + }; + + [Theory, MemberData(nameof(TestData_Normalize_MountName))] + public static void Normalize_MountName(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, string, Result> TestData_Normalize_WindowsPath => new() + { + { @"c:/aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, + { @"c:\aa\bb", "", @"", ResultFs.InvalidCharacter.Value }, + { @"\\host\share", "", @"", ResultFs.InvalidCharacter.Value }, + { @"\\.\c:\", "", @"", ResultFs.InvalidCharacter.Value }, + { @"\\.\c:/aa/bb/.", "", @"", ResultFs.InvalidCharacter.Value }, + { @"\\?\c:\", "", @"", ResultFs.InvalidCharacter.Value }, + { @"mount:\\host\share\aa\bb", "M", @"mount:", ResultFs.InvalidCharacter.Value }, + { @"mount:\\host/share\aa\bb", "M", @"mount:", ResultFs.InvalidCharacter.Value }, + { @"mount:/\\aa\..\bb", "MW", @"mount:", ResultFs.InvalidPathFormat.Value }, + { @"mount:/c:\aa\..\bb", "MW", @"mount:c:/bb", Result.Success }, + { @"mount:/aa/bb", "MW", @"mount:/aa/bb", Result.Success }, + { @"/mount:/aa/bb", "MW", @"/mount:/aa/bb", ResultFs.InvalidCharacter.Value }, + { @"/mount:/aa/bb", "W", @"/mount:/aa/bb", ResultFs.InvalidCharacter.Value }, + { @"a:aa/../bb", "MW", @"a:aa/bb", Result.Success }, + { @"a:aa\..\bb", "MW", @"a:aa/bb", Result.Success }, + { @"/a:aa\..\bb", "W", @"/bb", Result.Success }, + { @"\\?\c:\.\aa", "W", @"\\?\c:/aa", Result.Success }, + { @"\\.\c:\.\aa", "W", @"\\.\c:/aa", Result.Success }, + { @"\\.\mount:\.\aa", "W", @"\\./mount:/aa", ResultFs.InvalidCharacter.Value }, + { @"\\./.\aa", "W", @"\\./aa", Result.Success }, + { @"\\/aa", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\\aa", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\", "W", @"/", Result.Success }, + { @"\\host\share", "W", @"\\host\share/", Result.Success }, + { @"\\host\share\path", "W", @"\\host\share/path", Result.Success }, + { @"\\host\share\path\aa\bb\..\cc\.", "W", @"\\host\share/path/aa/cc", Result.Success }, + { @"\\host\", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\ho$st\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\host:\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\..\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\host\s:hare\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\host\.\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\host\..\path", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @"\\host\sha:re", "W", @"", ResultFs.InvalidPathFormat.Value }, + { @".\\host\share", "RW", @"..\\host\share/", Result.Success } + }; + + [Theory, MemberData(nameof(TestData_Normalize_WindowsPath))] + public static void Normalize_WindowsPath(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, string, Result> TestData_Normalize_RelativePath => new() + { + { @"./aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, + { @"./aa/bb/../cc", "R", @"./aa/cc", Result.Success }, + { @".\aa/bb/../cc", "R", @"..", ResultFs.InvalidCharacter.Value }, + { @".", "R", @".", Result.Success }, + { @"../aa/bb", "R", @"", ResultFs.DirectoryUnobtainable.Value }, + { @"/aa/./bb", "R", @"/aa/bb", Result.Success }, + { @"mount:./aa/bb", "MR", @"mount:./aa/bb", Result.Success }, + { @"mount:./aa/./bb", "MR", @"mount:./aa/bb", Result.Success }, + { @"mount:./aa/bb", "M", @"mount:", ResultFs.InvalidPathFormat.Value } + }; + + [Theory, MemberData(nameof(TestData_Normalize_RelativePath))] + public static void Normalize_RelativePath(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, string, Result> TestData_Normalize_Backslash => new() + { + { @"\aa\bb\..\cc", "", @"", ResultFs.InvalidPathFormat.Value }, + { @"\aa\bb\..\cc", "B", @"", ResultFs.InvalidPathFormat.Value }, + { @"/aa\bb\..\cc", "", @"", ResultFs.InvalidCharacter.Value }, + { @"/aa\bb\..\cc", "B", @"/cc", Result.Success }, + { @"/aa\bb\cc", "", @"", ResultFs.InvalidCharacter.Value }, + { @"/aa\bb\cc", "B", @"/aa\bb\cc", Result.Success }, + { @"\\host\share\path\aa\bb\cc", "W", @"\\host\share/path/aa/bb/cc", Result.Success }, + { @"\\host\share\path\aa\bb\cc", "WB", @"\\host\share/path/aa/bb/cc", Result.Success }, + { @"/aa/bb\../cc/..\dd\..\ee/..", "", @"", ResultFs.InvalidCharacter.Value }, + { @"/aa/bb\../cc/..\dd\..\ee/..", "B", @"/aa", Result.Success } + }; + + [Theory, MemberData(nameof(TestData_Normalize_Backslash))] + public static void Normalize_Backslash(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, string, Result> TestData_Normalize_All => new() + { + { @"mount:./aa/bb", "WRM", @"mount:./aa/bb", Result.Success }, + { @"mount:./aa/bb\cc/dd", "WRM", @"mount:./aa/bb/cc/dd", Result.Success }, + { @"mount:./aa/bb\cc/dd", "WRMB", @"mount:./aa/bb/cc/dd", Result.Success }, + { @"mount:./.c:/aa/bb", "RM", @"mount:./.c:/aa/bb", ResultFs.InvalidCharacter.Value }, + { @"mount:.c:/aa/bb", "WRM", @"mount:./.c:/aa/bb", ResultFs.InvalidCharacter.Value }, + { @"mount:./cc:/aa/bb", "WRM", @"mount:./cc:/aa/bb", ResultFs.InvalidCharacter.Value }, + { @"mount:./\\host\share/aa/bb", "MW", @"mount:", ResultFs.InvalidPathFormat.Value }, + { @"mount:./\\host\share/aa/bb", "WRM", @"mount:.\\host\share/aa/bb", Result.Success }, + { @"mount:.\\host\share/aa/bb", "WRM", @"mount:..\\host\share/aa/bb", Result.Success }, + { @"mount:..\\host\share/aa/bb", "WRM", @"mount:.", ResultFs.DirectoryUnobtainable.Value }, + { @".\\host\share/aa/bb", "WRM", @"..\\host\share/aa/bb", Result.Success }, + { @"..\\host\share/aa/bb", "WRM", @".", ResultFs.DirectoryUnobtainable.Value }, + { @"mount:\\host\share/aa/bb", "MW", @"mount:\\host\share/aa/bb", Result.Success }, + { @"mount:\aa\bb", "BM", @"mount:", ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa\bb", "BM", @"mount:/aa\bb", Result.Success }, + { @".//aa/bb", "RW", @"./aa/bb", Result.Success }, + { @"./aa/bb", "R", @"./aa/bb", Result.Success }, + { @"./c:/aa/bb", "RW", @"./c:/aa/bb", ResultFs.InvalidCharacter.Value } + }; + + [Theory, MemberData(nameof(TestData_Normalize_All))] + public static void Normalize_All(string path, string pathFlags, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); + } + + public static TheoryData<string, string, int, string, Result> TestData_Normalize_SmallBuffer => new() + { + { @"/aa/bb", "M", 1, @"", ResultFs.TooLongPath.Value }, + { @"mount:/aa/bb", "MR", 6, @"", ResultFs.TooLongPath.Value }, + { @"mount:/aa/bb", "MR", 7, @"mount:", ResultFs.TooLongPath.Value }, + { @"aa/bb", "MR", 3, @"./", ResultFs.TooLongPath.Value }, + { @"\\host\share", "W", 13, @"\\host\share", ResultFs.TooLongPath.Value } + }; + + [Theory, MemberData(nameof(TestData_Normalize_SmallBuffer))] + public static void Normalize_SmallBuffer(string path, string pathFlags, int bufferSize, string expectedNormalized, Result expectedResult) + { + NormalizeImpl(path, pathFlags, bufferSize, expectedNormalized, expectedResult); + } + + private static void NormalizeImpl(string path, string pathFlags, int bufferSize, string expectedNormalized, Result expectedResult) + { + byte[] buffer = new byte[bufferSize]; + + Result result = PathFormatter.Normalize(buffer, path.ToU8Span(), GetPathFlags(pathFlags)); + + Assert.Equal(expectedResult, result); + Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_EmptyPath => new() + { + { @"", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"", "E", true, 0, Result.Success }, + { @"/aa/bb/../cc", "E", false, 0, Result.Success } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_EmptyPath))] + public static void IsNormalized_EmptyPath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_MountName => new() + { + { @"mount:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa/bb", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa/bb", "M", true, 12, Result.Success }, + { @"mount:/aa/./bb", "M", false, 6, Result.Success }, + { @"mount:\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"m:/aa/bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mo>unt:/aa/bb", "M", false, 0, ResultFs.InvalidCharacter.Value }, + { @"moun?t:/aa/bb", "M", false, 0, ResultFs.InvalidCharacter.Value }, + { @"mo&unt:/aa/bb", "M", true, 13, Result.Success }, + { @"/aa/./bb", "M", false, 0, Result.Success }, + { @"mount/aa/./bb", "M", false, 0, ResultFs.InvalidPathFormat.Value } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_MountName))] + public static void IsNormalized_MountName(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_WindowsPath => new() + { + { @"c:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"c:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"c:\aa\bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host\share", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\.\c:\", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\.\c:/aa/bb/.", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\?\c:\", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:\\host\share\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:\\host/share\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:/\\aa\..\bb", "MW", false, 0, Result.Success }, + { @"mount:/c:\aa\..\bb", "MW", false, 0, Result.Success }, + { @"mount:/aa/bb", "MW", true, 12, Result.Success }, + { @"/mount:/aa/bb", "MW", false, 0, ResultFs.InvalidCharacter.Value }, + { @"/mount:/aa/bb", "W", false, 0, ResultFs.InvalidCharacter.Value }, + { @"a:aa/../bb", "MW", false, 8, Result.Success }, + { @"a:aa\..\bb", "MW", false, 0, Result.Success }, + { @"/a:aa\..\bb", "W", false, 0, ResultFs.DirectoryUnobtainable.Value }, + { @"\\?\c:\.\aa", "W", false, 0, Result.Success }, + { @"\\.\c:\.\aa", "W", false, 0, Result.Success }, + { @"\\.\mount:\.\aa", "W", false, 0, Result.Success }, + { @"\\./.\aa", "W", false, 0, Result.Success }, + { @"\\/aa", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\\aa", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\", "W", false, 0, Result.Success }, + { @"\\host\share", "W", false, 0, Result.Success }, + { @"\\host\share\path", "W", false, 0, Result.Success }, + { @"\\host\share\path\aa\bb\..\cc\.", "W", false, 0, Result.Success }, + { @"\\host\", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\ho$st\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host:\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\..\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host\s:hare\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host\.\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host\..\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\host\sha:re", "W", false, 0, ResultFs.InvalidPathFormat.Value }, + { @".\\host\share", "RW", false, 0, Result.Success } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_WindowsPath))] + public static void IsNormalized_WindowsPath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_RelativePath => new() + { + { @"./aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"./aa/bb/../cc", "R", false, 1, Result.Success }, + { @".\aa/bb/../cc", "R", false, 0, Result.Success }, + { @".", "R", true, 1, Result.Success }, + { @"../aa/bb", "R", false, 0, ResultFs.DirectoryUnobtainable.Value }, + { @"/aa/./bb", "R", false, 0, Result.Success }, + { @"mount:./aa/bb", "MR", true, 13, Result.Success }, + { @"mount:./aa/./bb", "MR", false, 7, Result.Success }, + { @"mount:./aa/bb", "M", false, 0, ResultFs.InvalidPathFormat.Value } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_RelativePath))] + public static void IsNormalized_RelativePath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_Backslash => new() + { + { @"\aa\bb\..\cc", "", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\aa\bb\..\cc", "B", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"/aa\bb\..\cc", "", false, 0, ResultFs.DirectoryUnobtainable.Value }, + { @"/aa\bb\..\cc", "B", false, 0, ResultFs.DirectoryUnobtainable.Value }, + { @"/aa\bb\cc", "", false, 0, ResultFs.InvalidCharacter.Value }, + { @"/aa\bb\cc", "B", true, 9, Result.Success }, + { @"\\host\share\path\aa\bb\cc", "W", false, 0, Result.Success }, + { @"\\host\share\path\aa\bb\cc", "WB", false, 0, Result.Success }, + { @"/aa/bb\../cc/..\dd\..\ee/..", "", false, 0, ResultFs.DirectoryUnobtainable.Value }, + { @"/aa/bb\../cc/..\dd\..\ee/..", "B", false, 0, ResultFs.DirectoryUnobtainable.Value } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_Backslash))] + public static void IsNormalized_Backslash(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_All => new() + { + { @"mount:./aa/bb", "WRM", true, 13, Result.Success }, + { @"mount:./aa/bb\cc/dd", "WRM", false, 0, Result.Success }, + { @"mount:./aa/bb\cc/dd", "WRMB", true, 19, Result.Success }, + { @"mount:./.c:/aa/bb", "RM", false, 0, ResultFs.InvalidCharacter.Value }, + { @"mount:.c:/aa/bb", "WRM", false, 0, Result.Success }, + { @"mount:./cc:/aa/bb", "WRM", false, 0, ResultFs.InvalidCharacter.Value }, + { @"mount:./\\host\share/aa/bb", "MW", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:./\\host\share/aa/bb", "WRM", false, 0, Result.Success }, + { @"mount:.\\host\share/aa/bb", "WRM", false, 0, Result.Success }, + { @"mount:..\\host\share/aa/bb", "WRM", false, 0, Result.Success }, + { @".\\host\share/aa/bb", "WRM", false, 0, Result.Success }, + { @"..\\host\share/aa/bb", "WRM", false, 0, Result.Success }, + { @"mount:\\host\share/aa/bb", "MW", true, 24, Result.Success }, + { @"mount:\aa\bb", "BM", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:/aa\bb", "BM", true, 12, Result.Success }, + { @".//aa/bb", "RW", false, 1, Result.Success }, + { @"./aa/bb", "R", true, 7, Result.Success }, + { @"./c:/aa/bb", "RW", false, 0, ResultFs.InvalidCharacter.Value } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized_All))] + public static void IsNormalized_All(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); + } + + private static void IsNormalizedImpl(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, + Result expectedResult) + { + Result result = PathFormatter.IsNormalized(out bool isNormalized, out int length, path.ToU8Span(), + GetPathFlags(pathFlags)); + + Assert.Equal(expectedResult, result); + + if (result.IsSuccess()) { - { @"", "", @"", ResultFs.InvalidPathFormat.Value }, - { @"", "E", @"", Result.Success }, - { @"/aa/bb/../cc", "E", @"/aa/cc", Result.Success } - }; + Assert.Equal(expectedIsNormalized, isNormalized); - [Theory, MemberData(nameof(TestData_Normalize_EmptyPath))] - public static void Normalize_EmptyPath(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, string, Result> TestData_Normalize_MountName => new() - { - { @"mount:/aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa/bb", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa/bb", "M", @"mount:/aa/bb", Result.Success }, - { @"mount:/aa/./bb", "M", @"mount:/aa/bb", Result.Success }, - { @"mount:\aa\bb", "M", @"mount:", ResultFs.InvalidPathFormat.Value }, - { @"m:/aa/bb", "M", @"", ResultFs.InvalidPathFormat.Value }, - { @"mo>unt:/aa/bb", "M", @"", ResultFs.InvalidCharacter.Value }, - { @"moun?t:/aa/bb", "M", @"", ResultFs.InvalidCharacter.Value }, - { @"mo&unt:/aa/bb", "M", @"mo&unt:/aa/bb", Result.Success }, - { @"/aa/./bb", "M", @"/aa/bb", Result.Success }, - { @"mount/aa/./bb", "M", @"", ResultFs.InvalidPathFormat.Value } - }; - - [Theory, MemberData(nameof(TestData_Normalize_MountName))] - public static void Normalize_MountName(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, string, Result> TestData_Normalize_WindowsPath => new() - { - { @"c:/aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, - { @"c:\aa\bb", "", @"", ResultFs.InvalidCharacter.Value }, - { @"\\host\share", "", @"", ResultFs.InvalidCharacter.Value }, - { @"\\.\c:\", "", @"", ResultFs.InvalidCharacter.Value }, - { @"\\.\c:/aa/bb/.", "", @"", ResultFs.InvalidCharacter.Value }, - { @"\\?\c:\", "", @"", ResultFs.InvalidCharacter.Value }, - { @"mount:\\host\share\aa\bb", "M", @"mount:", ResultFs.InvalidCharacter.Value }, - { @"mount:\\host/share\aa\bb", "M", @"mount:", ResultFs.InvalidCharacter.Value }, - { @"mount:/\\aa\..\bb", "MW", @"mount:", ResultFs.InvalidPathFormat.Value }, - { @"mount:/c:\aa\..\bb", "MW", @"mount:c:/bb", Result.Success }, - { @"mount:/aa/bb", "MW", @"mount:/aa/bb", Result.Success }, - { @"/mount:/aa/bb", "MW", @"/mount:/aa/bb", ResultFs.InvalidCharacter.Value }, - { @"/mount:/aa/bb", "W", @"/mount:/aa/bb", ResultFs.InvalidCharacter.Value }, - { @"a:aa/../bb", "MW", @"a:aa/bb", Result.Success }, - { @"a:aa\..\bb", "MW", @"a:aa/bb", Result.Success }, - { @"/a:aa\..\bb", "W", @"/bb", Result.Success }, - { @"\\?\c:\.\aa", "W", @"\\?\c:/aa", Result.Success }, - { @"\\.\c:\.\aa", "W", @"\\.\c:/aa", Result.Success }, - { @"\\.\mount:\.\aa", "W", @"\\./mount:/aa", ResultFs.InvalidCharacter.Value }, - { @"\\./.\aa", "W", @"\\./aa", Result.Success }, - { @"\\/aa", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\\aa", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\", "W", @"/", Result.Success }, - { @"\\host\share", "W", @"\\host\share/", Result.Success }, - { @"\\host\share\path", "W", @"\\host\share/path", Result.Success }, - { @"\\host\share\path\aa\bb\..\cc\.", "W", @"\\host\share/path/aa/cc", Result.Success }, - { @"\\host\", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\ho$st\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\host:\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\..\share\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\host\s:hare\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\host\.\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\host\..\path", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @"\\host\sha:re", "W", @"", ResultFs.InvalidPathFormat.Value }, - { @".\\host\share", "RW", @"..\\host\share/", Result.Success } - }; - - [Theory, MemberData(nameof(TestData_Normalize_WindowsPath))] - public static void Normalize_WindowsPath(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, string, Result> TestData_Normalize_RelativePath => new() - { - { @"./aa/bb", "", @"", ResultFs.InvalidPathFormat.Value }, - { @"./aa/bb/../cc", "R", @"./aa/cc", Result.Success }, - { @".\aa/bb/../cc", "R", @"..", ResultFs.InvalidCharacter.Value }, - { @".", "R", @".", Result.Success }, - { @"../aa/bb", "R", @"", ResultFs.DirectoryUnobtainable.Value }, - { @"/aa/./bb", "R", @"/aa/bb", Result.Success }, - { @"mount:./aa/bb", "MR", @"mount:./aa/bb", Result.Success }, - { @"mount:./aa/./bb", "MR", @"mount:./aa/bb", Result.Success }, - { @"mount:./aa/bb", "M", @"mount:", ResultFs.InvalidPathFormat.Value } - }; - - [Theory, MemberData(nameof(TestData_Normalize_RelativePath))] - public static void Normalize_RelativePath(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, string, Result> TestData_Normalize_Backslash => new() - { - { @"\aa\bb\..\cc", "", @"", ResultFs.InvalidPathFormat.Value }, - { @"\aa\bb\..\cc", "B", @"", ResultFs.InvalidPathFormat.Value }, - { @"/aa\bb\..\cc", "", @"", ResultFs.InvalidCharacter.Value }, - { @"/aa\bb\..\cc", "B", @"/cc", Result.Success }, - { @"/aa\bb\cc", "", @"", ResultFs.InvalidCharacter.Value }, - { @"/aa\bb\cc", "B", @"/aa\bb\cc", Result.Success }, - { @"\\host\share\path\aa\bb\cc", "W", @"\\host\share/path/aa/bb/cc", Result.Success }, - { @"\\host\share\path\aa\bb\cc", "WB", @"\\host\share/path/aa/bb/cc", Result.Success }, - { @"/aa/bb\../cc/..\dd\..\ee/..", "", @"", ResultFs.InvalidCharacter.Value }, - { @"/aa/bb\../cc/..\dd\..\ee/..", "B", @"/aa", Result.Success } - }; - - [Theory, MemberData(nameof(TestData_Normalize_Backslash))] - public static void Normalize_Backslash(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, string, Result> TestData_Normalize_All => new() - { - { @"mount:./aa/bb", "WRM", @"mount:./aa/bb", Result.Success }, - { @"mount:./aa/bb\cc/dd", "WRM", @"mount:./aa/bb/cc/dd", Result.Success }, - { @"mount:./aa/bb\cc/dd", "WRMB", @"mount:./aa/bb/cc/dd", Result.Success }, - { @"mount:./.c:/aa/bb", "RM", @"mount:./.c:/aa/bb", ResultFs.InvalidCharacter.Value }, - { @"mount:.c:/aa/bb", "WRM", @"mount:./.c:/aa/bb", ResultFs.InvalidCharacter.Value }, - { @"mount:./cc:/aa/bb", "WRM", @"mount:./cc:/aa/bb", ResultFs.InvalidCharacter.Value }, - { @"mount:./\\host\share/aa/bb", "MW", @"mount:", ResultFs.InvalidPathFormat.Value }, - { @"mount:./\\host\share/aa/bb", "WRM", @"mount:.\\host\share/aa/bb", Result.Success }, - { @"mount:.\\host\share/aa/bb", "WRM", @"mount:..\\host\share/aa/bb", Result.Success }, - { @"mount:..\\host\share/aa/bb", "WRM", @"mount:.", ResultFs.DirectoryUnobtainable.Value }, - { @".\\host\share/aa/bb", "WRM", @"..\\host\share/aa/bb", Result.Success }, - { @"..\\host\share/aa/bb", "WRM", @".", ResultFs.DirectoryUnobtainable.Value }, - { @"mount:\\host\share/aa/bb", "MW", @"mount:\\host\share/aa/bb", Result.Success }, - { @"mount:\aa\bb", "BM", @"mount:", ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa\bb", "BM", @"mount:/aa\bb", Result.Success }, - { @".//aa/bb", "RW", @"./aa/bb", Result.Success }, - { @"./aa/bb", "R", @"./aa/bb", Result.Success }, - { @"./c:/aa/bb", "RW", @"./c:/aa/bb", ResultFs.InvalidCharacter.Value } - }; - - [Theory, MemberData(nameof(TestData_Normalize_All))] - public static void Normalize_All(string path, string pathFlags, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, 0x301, expectedNormalized, expectedResult); - } - - public static TheoryData<string, string, int, string, Result> TestData_Normalize_SmallBuffer => new() - { - { @"/aa/bb", "M", 1, @"", ResultFs.TooLongPath.Value }, - { @"mount:/aa/bb", "MR", 6, @"", ResultFs.TooLongPath.Value }, - { @"mount:/aa/bb", "MR", 7, @"mount:", ResultFs.TooLongPath.Value }, - { @"aa/bb", "MR", 3, @"./", ResultFs.TooLongPath.Value }, - { @"\\host\share", "W", 13, @"\\host\share", ResultFs.TooLongPath.Value } - }; - - [Theory, MemberData(nameof(TestData_Normalize_SmallBuffer))] - public static void Normalize_SmallBuffer(string path, string pathFlags, int bufferSize, string expectedNormalized, Result expectedResult) - { - NormalizeImpl(path, pathFlags, bufferSize, expectedNormalized, expectedResult); - } - - private static void NormalizeImpl(string path, string pathFlags, int bufferSize, string expectedNormalized, Result expectedResult) - { - byte[] buffer = new byte[bufferSize]; - - Result result = PathFormatter.Normalize(buffer, path.ToU8Span(), GetPathFlags(pathFlags)); - - Assert.Equal(expectedResult, result); - Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_EmptyPath => new() - { - { @"", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"", "E", true, 0, Result.Success }, - { @"/aa/bb/../cc", "E", false, 0, Result.Success } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_EmptyPath))] - public static void IsNormalized_EmptyPath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_MountName => new() - { - { @"mount:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa/bb", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa/bb", "M", true, 12, Result.Success }, - { @"mount:/aa/./bb", "M", false, 6, Result.Success }, - { @"mount:\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"m:/aa/bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mo>unt:/aa/bb", "M", false, 0, ResultFs.InvalidCharacter.Value }, - { @"moun?t:/aa/bb", "M", false, 0, ResultFs.InvalidCharacter.Value }, - { @"mo&unt:/aa/bb", "M", true, 13, Result.Success }, - { @"/aa/./bb", "M", false, 0, Result.Success }, - { @"mount/aa/./bb", "M", false, 0, ResultFs.InvalidPathFormat.Value } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_MountName))] - public static void IsNormalized_MountName(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_WindowsPath => new() - { - { @"c:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"c:/aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"c:\aa\bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host\share", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\.\c:\", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\.\c:/aa/bb/.", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\?\c:\", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:\\host\share\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:\\host/share\aa\bb", "M", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:/\\aa\..\bb", "MW", false, 0, Result.Success }, - { @"mount:/c:\aa\..\bb", "MW", false, 0, Result.Success }, - { @"mount:/aa/bb", "MW", true, 12, Result.Success }, - { @"/mount:/aa/bb", "MW", false, 0, ResultFs.InvalidCharacter.Value }, - { @"/mount:/aa/bb", "W", false, 0, ResultFs.InvalidCharacter.Value }, - { @"a:aa/../bb", "MW", false, 8, Result.Success }, - { @"a:aa\..\bb", "MW", false, 0, Result.Success }, - { @"/a:aa\..\bb", "W", false, 0, ResultFs.DirectoryUnobtainable.Value }, - { @"\\?\c:\.\aa", "W", false, 0, Result.Success }, - { @"\\.\c:\.\aa", "W", false, 0, Result.Success }, - { @"\\.\mount:\.\aa", "W", false, 0, Result.Success }, - { @"\\./.\aa", "W", false, 0, Result.Success }, - { @"\\/aa", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\\aa", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\", "W", false, 0, Result.Success }, - { @"\\host\share", "W", false, 0, Result.Success }, - { @"\\host\share\path", "W", false, 0, Result.Success }, - { @"\\host\share\path\aa\bb\..\cc\.", "W", false, 0, Result.Success }, - { @"\\host\", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\ho$st\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host:\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\..\share\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host\s:hare\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host\.\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host\..\path", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\host\sha:re", "W", false, 0, ResultFs.InvalidPathFormat.Value }, - { @".\\host\share", "RW", false, 0, Result.Success } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_WindowsPath))] - public static void IsNormalized_WindowsPath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_RelativePath => new() - { - { @"./aa/bb", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"./aa/bb/../cc", "R", false, 1, Result.Success }, - { @".\aa/bb/../cc", "R", false, 0, Result.Success }, - { @".", "R", true, 1, Result.Success }, - { @"../aa/bb", "R", false, 0, ResultFs.DirectoryUnobtainable.Value }, - { @"/aa/./bb", "R", false, 0, Result.Success }, - { @"mount:./aa/bb", "MR", true, 13, Result.Success }, - { @"mount:./aa/./bb", "MR", false, 7, Result.Success }, - { @"mount:./aa/bb", "M", false, 0, ResultFs.InvalidPathFormat.Value } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_RelativePath))] - public static void IsNormalized_RelativePath(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_Backslash => new() - { - { @"\aa\bb\..\cc", "", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\aa\bb\..\cc", "B", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"/aa\bb\..\cc", "", false, 0, ResultFs.DirectoryUnobtainable.Value }, - { @"/aa\bb\..\cc", "B", false, 0, ResultFs.DirectoryUnobtainable.Value }, - { @"/aa\bb\cc", "", false, 0, ResultFs.InvalidCharacter.Value }, - { @"/aa\bb\cc", "B", true, 9, Result.Success }, - { @"\\host\share\path\aa\bb\cc", "W", false, 0, Result.Success }, - { @"\\host\share\path\aa\bb\cc", "WB", false, 0, Result.Success }, - { @"/aa/bb\../cc/..\dd\..\ee/..", "", false, 0, ResultFs.DirectoryUnobtainable.Value }, - { @"/aa/bb\../cc/..\dd\..\ee/..", "B", false, 0, ResultFs.DirectoryUnobtainable.Value } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_Backslash))] - public static void IsNormalized_Backslash(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - public static TheoryData<string, string, bool, int, Result> TestData_IsNormalized_All => new() - { - { @"mount:./aa/bb", "WRM", true, 13, Result.Success }, - { @"mount:./aa/bb\cc/dd", "WRM", false, 0, Result.Success }, - { @"mount:./aa/bb\cc/dd", "WRMB", true, 19, Result.Success }, - { @"mount:./.c:/aa/bb", "RM", false, 0, ResultFs.InvalidCharacter.Value }, - { @"mount:.c:/aa/bb", "WRM", false, 0, Result.Success }, - { @"mount:./cc:/aa/bb", "WRM", false, 0, ResultFs.InvalidCharacter.Value }, - { @"mount:./\\host\share/aa/bb", "MW", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:./\\host\share/aa/bb", "WRM", false, 0, Result.Success }, - { @"mount:.\\host\share/aa/bb", "WRM", false, 0, Result.Success }, - { @"mount:..\\host\share/aa/bb", "WRM", false, 0, Result.Success }, - { @".\\host\share/aa/bb", "WRM", false, 0, Result.Success }, - { @"..\\host\share/aa/bb", "WRM", false, 0, Result.Success }, - { @"mount:\\host\share/aa/bb", "MW", true, 24, Result.Success }, - { @"mount:\aa\bb", "BM", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:/aa\bb", "BM", true, 12, Result.Success }, - { @".//aa/bb", "RW", false, 1, Result.Success }, - { @"./aa/bb", "R", true, 7, Result.Success }, - { @"./c:/aa/bb", "RW", false, 0, ResultFs.InvalidCharacter.Value } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized_All))] - public static void IsNormalized_All(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - IsNormalizedImpl(path, pathFlags, expectedIsNormalized, expectedLength, expectedResult); - } - - private static void IsNormalizedImpl(string path, string pathFlags, bool expectedIsNormalized, long expectedLength, - Result expectedResult) - { - Result result = PathFormatter.IsNormalized(out bool isNormalized, out int length, path.ToU8Span(), - GetPathFlags(pathFlags)); - - Assert.Equal(expectedResult, result); - - if (result.IsSuccess()) + if (isNormalized) { - Assert.Equal(expectedIsNormalized, isNormalized); - - if (isNormalized) - { - Assert.Equal(expectedLength, length); - } + Assert.Equal(expectedLength, length); } } - - [Fact] - public static void IsNormalized_InvalidUtf8() - { - ReadOnlySpan<byte> invalidUtf8 = new byte[] { 0x44, 0xE3, 0xAA, 0x55, 0x50 }; - - Result result = PathFormatter.IsNormalized(out _, out _, invalidUtf8, new PathFlags()); - - Assert.Result(ResultFs.InvalidPathFormat, result); - } - - private static PathFlags GetPathFlags(string pathFlags) - { - var flags = new PathFlags(); - - foreach (char c in pathFlags) - { - switch (c) - { - case 'B': - flags.AllowBackslash(); - break; - case 'E': - flags.AllowEmptyPath(); - break; - case 'M': - flags.AllowMountName(); - break; - case 'R': - flags.AllowRelativePath(); - break; - case 'W': - flags.AllowWindowsPath(); - break; - } - } - - return flags; - } + } + + [Fact] + public static void IsNormalized_InvalidUtf8() + { + ReadOnlySpan<byte> invalidUtf8 = new byte[] { 0x44, 0xE3, 0xAA, 0x55, 0x50 }; + + Result result = PathFormatter.IsNormalized(out _, out _, invalidUtf8, new PathFlags()); + + Assert.Result(ResultFs.InvalidPathFormat, result); + } + + private static PathFlags GetPathFlags(string pathFlags) + { + var flags = new PathFlags(); + + foreach (char c in pathFlags) + { + switch (c) + { + case 'B': + flags.AllowBackslash(); + break; + case 'E': + flags.AllowEmptyPath(); + break; + case 'M': + flags.AllowMountName(); + break; + case 'R': + flags.AllowRelativePath(); + break; + case 'W': + flags.AllowWindowsPath(); + break; + } + } + + return flags; } } diff --git a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs index 595f6781..064c92e5 100644 --- a/tests/LibHac.Tests/Fs/PathNormalizerTests.cs +++ b/tests/LibHac.Tests/Fs/PathNormalizerTests.cs @@ -5,143 +5,142 @@ using LibHac.Fs; using LibHac.Util; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class PathNormalizerTests { - public class PathNormalizerTests + public static TheoryData<string, bool, bool, string, long, Result> TestData_Normalize => new() { - public static TheoryData<string, bool, bool, string, long, Result> TestData_Normalize => new() + { @"/aa/bb/c/", false, true, @"/aa/bb/c", 8, Result.Success }, + { @"aa/bb/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, + { @"aa/bb/c/", false, true, @"/aa/bb/c", 8, Result.Success }, + { @"mount:a/b", false, true, @"/mount:a/b", 0, ResultFs.InvalidCharacter.Value }, + { @"/aa/bb/../..", true, false, @"/", 1, Result.Success }, + { @"/aa/bb/../../..", true, false, @"/", 1, Result.Success }, + { @"/aa/bb/../../..", false, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value }, + { @"aa/bb/../../..", true, true, @"/", 1, Result.Success }, + { @"aa/bb/../../..", false, true, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value }, + { @"", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, + { @"/", false, false, @"/", 1, Result.Success }, + { @"/.", false, false, @"/", 1, Result.Success }, + { @"/./", false, false, @"/", 1, Result.Success }, + { @"/..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value }, + { @"//.", false, false, @"/", 1, Result.Success }, + { @"/ ..", false, false, @"/ ..", 4, Result.Success }, + { @"/.. /", false, false, @"/.. ", 4, Result.Success }, + { @"/. /.", false, false, @"/. ", 3, Result.Success }, + { @"/aa/bb/cc/dd/./.././../..", false, false, @"/aa", 3, Result.Success }, + { @"/aa/bb/cc/dd/./.././../../..", false, false, @"/", 1, Result.Success }, + { @"/./aa/./bb/./cc/./dd/.", false, false, @"/aa/bb/cc/dd", 12, Result.Success }, + { @"/aa\bb/cc", false, false, @"/aa\bb/cc", 9, Result.Success }, + { @"/aa\bb/cc", false, false, @"/aa\bb/cc", 9, Result.Success }, + { @"/a|/bb/cc", false, false, @"/a|/bb/cc", 0, ResultFs.InvalidCharacter.Value }, + { @"/>a/bb/cc", false, false, @"/>a/bb/cc", 0, ResultFs.InvalidCharacter.Value }, + { @"/aa/.</cc", false, false, @"/aa/.</cc", 0, ResultFs.InvalidCharacter.Value }, + { @"/aa/..</cc", false, false, @"/aa/..</cc", 0, ResultFs.InvalidCharacter.Value }, + { @"\\aa/bb/cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, + { @"\\aa\bb\cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, + { @"/aa/bb/..\cc", false, false, @"/aa/cc", 6, Result.Success }, + { @"/aa/bb\..\cc", false, false, @"/aa/cc", 6, Result.Success }, + { @"/aa/bb\..", false, false, @"/aa", 3, Result.Success }, + { @"/aa\bb/../cc", false, false, @"/cc", 3, Result.Success } + }; + + [Theory, MemberData(nameof(TestData_Normalize))] + public static void Normalize(string path, bool isWindowsPath, bool isDriveRelativePath, string expectedNormalized, + long expectedLength, Result expectedResult) + { + byte[] buffer = new byte[0x301]; + + Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath, + isDriveRelativePath); + + Assert.Equal(expectedResult, result); + Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); + Assert.Equal(expectedLength, normalizedLength); + } + + public static TheoryData<string, int, string, long, Result> TestData_Normalize_SmallBuffer => new() + { + { @"/aa/bb/cc/", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, + { @"/aa/bb/cc/", 8, @"/aa/bb/", 7, ResultFs.TooLongPath.Value }, + { @"/aa/bb/cc/", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, + { @"/aa/bb/cc/", 10, @"/aa/bb/cc", 9, Result.Success }, + { @"/aa/bb/cc", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, + { @"/aa/bb/cc", 10, @"/aa/bb/cc", 9, Result.Success }, + { @"/./aa/./bb/./cc", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, + { @"/./aa/./bb/./cc", 10, @"/aa/bb/cc", 9, Result.Success }, + { @"/aa/bb/cc/../../..", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, + { @"/aa/bb/cc/../../..", 10, @"/aa/bb/cc", 9, ResultFs.TooLongPath.Value }, + { @"/aa/bb/.", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, + { @"/aa/bb/./", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, + { @"/aa/bb/..", 8, @"/aa", 3, Result.Success }, + { @"/aa/bb", 1, @"", 0, ResultFs.TooLongPath.Value }, + { @"/aa/bb", 2, @"/", 1, ResultFs.TooLongPath.Value }, + { @"/aa/bb", 3, @"/a", 2, ResultFs.TooLongPath.Value }, + { @"aa/bb", 1, @"", 0, ResultFs.InvalidPathFormat.Value } + }; + + [Theory, MemberData(nameof(TestData_Normalize_SmallBuffer))] + public static void Normalize_SmallBuffer(string path, int bufferLength, string expectedNormalized, long expectedLength, Result expectedResult) + { + byte[] buffer = new byte[bufferLength]; + + Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false); + + Assert.Equal(expectedResult, result); + Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); + Assert.Equal(expectedLength, normalizedLength); + } + + public static TheoryData<string, bool, long, Result> TestData_IsNormalized => new() + { + { @"/aa/bb/c/", false, 9, Result.Success }, + { @"aa/bb/c/", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"aa/bb/c/", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"mount:a/b", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"/aa/bb/../..", false, 0, Result.Success }, + { @"/aa/bb/../../..", false, 0, Result.Success }, + { @"/aa/bb/../../..", false, 0, Result.Success }, + { @"aa/bb/../../..", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"aa/bb/../../..", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"/", true, 1, Result.Success }, + { @"/.", false, 2, Result.Success }, + { @"/./", false, 0, Result.Success }, + { @"/..", false, 3, Result.Success }, + { @"//.", false, 0, Result.Success }, + { @"/ ..", true, 4, Result.Success }, + { @"/.. /", false, 5, Result.Success }, + { @"/. /.", false, 5, Result.Success }, + { @"/aa/bb/cc/dd/./.././../..", false, 0, Result.Success }, + { @"/aa/bb/cc/dd/./.././../../..", false, 0, Result.Success }, + { @"/./aa/./bb/./cc/./dd/.", false, 0, Result.Success }, + { @"/aa\bb/cc", true, 9, Result.Success }, + { @"/aa\bb/cc", true, 9, Result.Success }, + { @"/a|/bb/cc", false, 0, ResultFs.InvalidCharacter.Value }, + { @"/>a/bb/cc", false, 0, ResultFs.InvalidCharacter.Value }, + { @"/aa/.</cc", false, 0, ResultFs.InvalidCharacter.Value }, + { @"/aa/..</cc", false, 0, ResultFs.InvalidCharacter.Value }, + { @"\\aa/bb/cc", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"\\aa\bb\cc", false, 0, ResultFs.InvalidPathFormat.Value }, + { @"/aa/bb/..\cc", true, 12, Result.Success }, + { @"/aa/bb\..\cc", true, 12, Result.Success }, + { @"/aa/bb\..", true, 9, Result.Success }, + { @"/aa\bb/../cc", false, 0, Result.Success } + }; + + [Theory, MemberData(nameof(TestData_IsNormalized))] + public static void IsNormalized(string path, bool expectedIsNormalized, long expectedLength, Result expectedResult) + { + Result result = PathNormalizer.IsNormalized(out bool isNormalized, out int length, path.ToU8Span()); + + Assert.Equal(expectedResult, result); + Assert.Equal(expectedLength, length); + + if (result.IsSuccess()) { - { @"/aa/bb/c/", false, true, @"/aa/bb/c", 8, Result.Success }, - { @"aa/bb/c/", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, - { @"aa/bb/c/", false, true, @"/aa/bb/c", 8, Result.Success }, - { @"mount:a/b", false, true, @"/mount:a/b", 0, ResultFs.InvalidCharacter.Value }, - { @"/aa/bb/../..", true, false, @"/", 1, Result.Success }, - { @"/aa/bb/../../..", true, false, @"/", 1, Result.Success }, - { @"/aa/bb/../../..", false, false, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value }, - { @"aa/bb/../../..", true, true, @"/", 1, Result.Success }, - { @"aa/bb/../../..", false, true, @"/aa/bb/", 0, ResultFs.DirectoryUnobtainable.Value }, - { @"", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, - { @"/", false, false, @"/", 1, Result.Success }, - { @"/.", false, false, @"/", 1, Result.Success }, - { @"/./", false, false, @"/", 1, Result.Success }, - { @"/..", false, false, @"/", 0, ResultFs.DirectoryUnobtainable.Value }, - { @"//.", false, false, @"/", 1, Result.Success }, - { @"/ ..", false, false, @"/ ..", 4, Result.Success }, - { @"/.. /", false, false, @"/.. ", 4, Result.Success }, - { @"/. /.", false, false, @"/. ", 3, Result.Success }, - { @"/aa/bb/cc/dd/./.././../..", false, false, @"/aa", 3, Result.Success }, - { @"/aa/bb/cc/dd/./.././../../..", false, false, @"/", 1, Result.Success }, - { @"/./aa/./bb/./cc/./dd/.", false, false, @"/aa/bb/cc/dd", 12, Result.Success }, - { @"/aa\bb/cc", false, false, @"/aa\bb/cc", 9, Result.Success }, - { @"/aa\bb/cc", false, false, @"/aa\bb/cc", 9, Result.Success }, - { @"/a|/bb/cc", false, false, @"/a|/bb/cc", 0, ResultFs.InvalidCharacter.Value }, - { @"/>a/bb/cc", false, false, @"/>a/bb/cc", 0, ResultFs.InvalidCharacter.Value }, - { @"/aa/.</cc", false, false, @"/aa/.</cc", 0, ResultFs.InvalidCharacter.Value }, - { @"/aa/..</cc", false, false, @"/aa/..</cc", 0, ResultFs.InvalidCharacter.Value }, - { @"\\aa/bb/cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, - { @"\\aa\bb\cc", false, false, @"", 0, ResultFs.InvalidPathFormat.Value }, - { @"/aa/bb/..\cc", false, false, @"/aa/cc", 6, Result.Success }, - { @"/aa/bb\..\cc", false, false, @"/aa/cc", 6, Result.Success }, - { @"/aa/bb\..", false, false, @"/aa", 3, Result.Success }, - { @"/aa\bb/../cc", false, false, @"/cc", 3, Result.Success } - }; - - [Theory, MemberData(nameof(TestData_Normalize))] - public static void Normalize(string path, bool isWindowsPath, bool isDriveRelativePath, string expectedNormalized, - long expectedLength, Result expectedResult) - { - byte[] buffer = new byte[0x301]; - - Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), isWindowsPath, - isDriveRelativePath); - - Assert.Equal(expectedResult, result); - Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); - Assert.Equal(expectedLength, normalizedLength); - } - - public static TheoryData<string, int, string, long, Result> TestData_Normalize_SmallBuffer => new() - { - { @"/aa/bb/cc/", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, - { @"/aa/bb/cc/", 8, @"/aa/bb/", 7, ResultFs.TooLongPath.Value }, - { @"/aa/bb/cc/", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, - { @"/aa/bb/cc/", 10, @"/aa/bb/cc", 9, Result.Success }, - { @"/aa/bb/cc", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, - { @"/aa/bb/cc", 10, @"/aa/bb/cc", 9, Result.Success }, - { @"/./aa/./bb/./cc", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, - { @"/./aa/./bb/./cc", 10, @"/aa/bb/cc", 9, Result.Success }, - { @"/aa/bb/cc/../../..", 9, @"/aa/bb/c", 8, ResultFs.TooLongPath.Value }, - { @"/aa/bb/cc/../../..", 10, @"/aa/bb/cc", 9, ResultFs.TooLongPath.Value }, - { @"/aa/bb/.", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, - { @"/aa/bb/./", 7, @"/aa/bb", 6, ResultFs.TooLongPath.Value }, - { @"/aa/bb/..", 8, @"/aa", 3, Result.Success }, - { @"/aa/bb", 1, @"", 0, ResultFs.TooLongPath.Value }, - { @"/aa/bb", 2, @"/", 1, ResultFs.TooLongPath.Value }, - { @"/aa/bb", 3, @"/a", 2, ResultFs.TooLongPath.Value }, - { @"aa/bb", 1, @"", 0, ResultFs.InvalidPathFormat.Value } - }; - - [Theory, MemberData(nameof(TestData_Normalize_SmallBuffer))] - public static void Normalize_SmallBuffer(string path, int bufferLength, string expectedNormalized, long expectedLength, Result expectedResult) - { - byte[] buffer = new byte[bufferLength]; - - Result result = PathNormalizer.Normalize(buffer, out int normalizedLength, path.ToU8Span(), false, false); - - Assert.Equal(expectedResult, result); - Assert.Equal(expectedNormalized, StringUtils.Utf8ZToString(buffer)); - Assert.Equal(expectedLength, normalizedLength); - } - - public static TheoryData<string, bool, long, Result> TestData_IsNormalized => new() - { - { @"/aa/bb/c/", false, 9, Result.Success }, - { @"aa/bb/c/", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"aa/bb/c/", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"mount:a/b", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"/aa/bb/../..", false, 0, Result.Success }, - { @"/aa/bb/../../..", false, 0, Result.Success }, - { @"/aa/bb/../../..", false, 0, Result.Success }, - { @"aa/bb/../../..", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"aa/bb/../../..", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"/", true, 1, Result.Success }, - { @"/.", false, 2, Result.Success }, - { @"/./", false, 0, Result.Success }, - { @"/..", false, 3, Result.Success }, - { @"//.", false, 0, Result.Success }, - { @"/ ..", true, 4, Result.Success }, - { @"/.. /", false, 5, Result.Success }, - { @"/. /.", false, 5, Result.Success }, - { @"/aa/bb/cc/dd/./.././../..", false, 0, Result.Success }, - { @"/aa/bb/cc/dd/./.././../../..", false, 0, Result.Success }, - { @"/./aa/./bb/./cc/./dd/.", false, 0, Result.Success }, - { @"/aa\bb/cc", true, 9, Result.Success }, - { @"/aa\bb/cc", true, 9, Result.Success }, - { @"/a|/bb/cc", false, 0, ResultFs.InvalidCharacter.Value }, - { @"/>a/bb/cc", false, 0, ResultFs.InvalidCharacter.Value }, - { @"/aa/.</cc", false, 0, ResultFs.InvalidCharacter.Value }, - { @"/aa/..</cc", false, 0, ResultFs.InvalidCharacter.Value }, - { @"\\aa/bb/cc", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"\\aa\bb\cc", false, 0, ResultFs.InvalidPathFormat.Value }, - { @"/aa/bb/..\cc", true, 12, Result.Success }, - { @"/aa/bb\..\cc", true, 12, Result.Success }, - { @"/aa/bb\..", true, 9, Result.Success }, - { @"/aa\bb/../cc", false, 0, Result.Success } - }; - - [Theory, MemberData(nameof(TestData_IsNormalized))] - public static void IsNormalized(string path, bool expectedIsNormalized, long expectedLength, Result expectedResult) - { - Result result = PathNormalizer.IsNormalized(out bool isNormalized, out int length, path.ToU8Span()); - - Assert.Equal(expectedResult, result); - Assert.Equal(expectedLength, length); - - if (result.IsSuccess()) - { - Assert.Equal(expectedIsNormalized, isNormalized); - } + Assert.Equal(expectedIsNormalized, isNormalized); } } } diff --git a/tests/LibHac.Tests/Fs/PathUtilityTests.cs b/tests/LibHac.Tests/Fs/PathUtilityTests.cs index 3b264acb..051240b1 100644 --- a/tests/LibHac.Tests/Fs/PathUtilityTests.cs +++ b/tests/LibHac.Tests/Fs/PathUtilityTests.cs @@ -4,37 +4,36 @@ using LibHac.Common; using LibHac.Fs; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class PathUtilityTests { - public class PathUtilityTests + public static TheoryData<string, string, bool> TestData_IsSubPath => new() { - public static TheoryData<string, string, bool> TestData_IsSubPath => new() - { - { @"//a/b", @"/a", false }, - { @"/a", @"//a/b", false }, - { @"//a/b", @"\\a", false }, - { @"//a/b", @"//a", true }, - { @"/", @"/a", true }, - { @"/a", @"/", true }, - { @"/", @"/", false }, - { @"", @"", false }, - { @"/", @"", true }, - { @"/", @"mount:/a", false }, - { @"mount:/", @"mount:/", false }, - { @"mount:/a/b", @"mount:/a/b", false }, - { @"mount:/a/b", @"mount:/a/b/c", true }, - { @"/a/b", @"/a/b/c", true }, - { @"/a/b/c", @"/a/b", true }, - { @"/a/b", @"/a/b", false }, - { @"/a/b", @"/a/b\c", false } - }; + { @"//a/b", @"/a", false }, + { @"/a", @"//a/b", false }, + { @"//a/b", @"\\a", false }, + { @"//a/b", @"//a", true }, + { @"/", @"/a", true }, + { @"/a", @"/", true }, + { @"/", @"/", false }, + { @"", @"", false }, + { @"/", @"", true }, + { @"/", @"mount:/a", false }, + { @"mount:/", @"mount:/", false }, + { @"mount:/a/b", @"mount:/a/b", false }, + { @"mount:/a/b", @"mount:/a/b/c", true }, + { @"/a/b", @"/a/b/c", true }, + { @"/a/b/c", @"/a/b", true }, + { @"/a/b", @"/a/b", false }, + { @"/a/b", @"/a/b\c", false } + }; - [Theory, MemberData(nameof(TestData_IsSubPath))] - public static void IsSubPath(string path1, string path2, bool expectedResult) - { - bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span()); + [Theory, MemberData(nameof(TestData_IsSubPath))] + public static void IsSubPath(string path1, string path2, bool expectedResult) + { + bool result = PathUtility.IsSubPath(path1.ToU8Span(), path2.ToU8Span()); - Assert.Equal(expectedResult, result); - } + Assert.Equal(expectedResult, result); } } diff --git a/tests/LibHac.Tests/Fs/StorageTester.cs b/tests/LibHac.Tests/Fs/StorageTester.cs index 4f0c171c..2ff882eb 100644 --- a/tests/LibHac.Tests/Fs/StorageTester.cs +++ b/tests/LibHac.Tests/Fs/StorageTester.cs @@ -3,263 +3,262 @@ using System.IO; using System.Linq; using LibHac.Fs; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class StorageTester { - public class StorageTester + private Random _random; + private byte[][] _backingArrays; + private byte[][] _buffers; + private int _size; + + private int[] _frequentAccessOffsets; + private int _lastAccessEnd; + private int _totalAccessCount; + private Configuration _config; + + public class Configuration { - private Random _random; - private byte[][] _backingArrays; - private byte[][] _buffers; - private int _size; + public Entry[] Entries { get; set; } + public int[] SizeClassProbs { get; set; } + public int[] SizeClassMaxSizes { get; set; } + public int[] TaskProbs { get; set; } + public int[] AccessTypeProbs { get; set; } + public ulong RngSeed { get; set; } + public int FrequentAccessBlockCount { get; set; } + } - private int[] _frequentAccessOffsets; - private int _lastAccessEnd; - private int _totalAccessCount; - private Configuration _config; + public StorageTester(Configuration config) + { + Entry[] entries = config.Entries; - public class Configuration + if (entries.Length < 2) { - public Entry[] Entries { get; set; } - public int[] SizeClassProbs { get; set; } - public int[] SizeClassMaxSizes { get; set; } - public int[] TaskProbs { get; set; } - public int[] AccessTypeProbs { get; set; } - public ulong RngSeed { get; set; } - public int FrequentAccessBlockCount { get; set; } + throw new ArgumentException("At least 2 storage entries must be provided", nameof(config.Entries)); } - public StorageTester(Configuration config) + if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) { - Entry[] entries = config.Entries; - - if (entries.Length < 2) - { - throw new ArgumentException("At least 2 storage entries must be provided", nameof(config.Entries)); - } - - if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) - { - throw new ArgumentException("All storages must have the same size.", nameof(config.Entries)); - } - - if (entries[0].BackingArray.Length == 0) - { - throw new ArgumentException("The storage size must be greater than 0.", nameof(config.Entries)); - } - - _config = config; - _random = new Random(config.RngSeed); - - _backingArrays = entries.Select(x => x.BackingArray).ToArray(); - - _buffers = new byte[entries.Length][]; - for (int i = 0; i < entries.Length; i++) - { - _buffers[i] = new byte[config.SizeClassMaxSizes[^1]]; - } - - _size = entries[0].BackingArray.Length; - _lastAccessEnd = 0; - - _frequentAccessOffsets = new int[config.FrequentAccessBlockCount]; - for (int i = 0; i < _frequentAccessOffsets.Length; i++) - { - _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); - } + throw new ArgumentException("All storages must have the same size.", nameof(config.Entries)); } - //public StorageTester(ulong rngSeed, int frequentAccessBlockCount, params Entry[] entries) - //{ - // if (entries.Length < 2) - // { - // throw new ArgumentException("At least 2 storage entries must be provided", nameof(entries)); - // } - - // if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) - // { - // throw new ArgumentException("All storages must have the same size.", nameof(entries)); - // } - - // if (entries[0].BackingArray.Length == 0) - // { - // throw new ArgumentException("The storage size must be greater than 0.", nameof(entries)); - // } - - // _random = new Random(rngSeed); - - // _entries = entries; - // _backingArrays = entries.Select(x => x.BackingArray).ToArray(); - - // _buffers = new byte[entries.Length][]; - // for (int i = 0; i < entries.Length; i++) - // { - // _buffers[i] = new byte[SizeClassMaxSizes[^1]]; - // } - - // _size = _entries[0].BackingArray.Length; - // _lastAccessEnd = 0; - - // _frequentAccessOffsets = new int[frequentAccessBlockCount]; - // for (int i = 0; i < _frequentAccessOffsets.Length; i++) - // { - // _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); - // } - //} - - public void Run(long accessCount) + if (entries[0].BackingArray.Length == 0) { - long endCount = _totalAccessCount + accessCount; - - while (_totalAccessCount < endCount) - { - Task task = ChooseTask(); - switch (task) - { - case Task.Read: - RunRead(); - break; - case Task.Write: - RunWrite(); - break; - case Task.Flush: - RunFlush(); - break; - } - - _totalAccessCount++; - } + throw new ArgumentException("The storage size must be greater than 0.", nameof(config.Entries)); } - private void RunRead() + _config = config; + _random = new Random(config.RngSeed); + + _backingArrays = entries.Select(x => x.BackingArray).ToArray(); + + _buffers = new byte[entries.Length][]; + for (int i = 0; i < entries.Length; i++) { - int sizeClass = ChooseSizeClass(); - AccessType accessType = ChooseAccessType(); - int offset = ChooseOffset(accessType); - int size = ChooseSize(offset, sizeClass); - - for (int i = 0; i < _config.Entries.Length; i++) - { - Entry entry = _config.Entries[i]; - entry.Storage.Read(offset, _buffers[i].AsSpan(0, size)).ThrowIfFailure(); - } - - if (!CompareBuffers(_buffers, size)) - { - throw new InvalidDataException($"Read: Offset {offset}; Size {size}"); - } + _buffers[i] = new byte[config.SizeClassMaxSizes[^1]]; } - private void RunWrite() + _size = entries[0].BackingArray.Length; + _lastAccessEnd = 0; + + _frequentAccessOffsets = new int[config.FrequentAccessBlockCount]; + for (int i = 0; i < _frequentAccessOffsets.Length; i++) { - int sizeClass = ChooseSizeClass(); - AccessType accessType = ChooseAccessType(); - int offset = ChooseOffset(accessType); - int size = ChooseSize(offset, sizeClass); - - Span<byte> buffer = _buffers[0].AsSpan(0, size); - _random.NextBytes(buffer); - - for (int i = 0; i < _config.Entries.Length; i++) - { - Entry entry = _config.Entries[i]; - entry.Storage.Write(offset, buffer).ThrowIfFailure(); - } - } - - private void RunFlush() - { - foreach (Entry entry in _config.Entries) - { - entry.Storage.Flush().ThrowIfFailure(); - } - - if (!CompareBuffers(_backingArrays, _size)) - { - throw new InvalidDataException("Flush"); - } - } - - private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs); - private int ChooseSizeClass() => ChooseProb(_config.SizeClassProbs); - private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs); - - private int ChooseOffset(AccessType type) => type switch - { - AccessType.Random => _random.Next(0, _size), - AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd, - AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)], - _ => 0 - }; - - private int ChooseSize(int offset, int sizeClass) - { - int availableSize = Math.Max(0, _size - offset); - int randSize = _random.Next(0, _config.SizeClassMaxSizes[sizeClass]); - return Math.Min(availableSize, randSize); - } - - private int ChooseProb(int[] weights) - { - int total = 0; - foreach (int weight in weights) - { - total += weight; - } - - int rand = _random.Next(0, total); - int currentThreshold = 0; - - for (int i = 0; i < weights.Length; i++) - { - currentThreshold += weights[i]; - - if (rand < currentThreshold) - return i; - } - - return 0; - } - - private bool CompareBuffers(byte[][] buffers, int size) - { - Span<byte> baseBuffer = buffers[0].AsSpan(0, size); - - for (int i = 1; i < buffers.Length; i++) - { - Span<byte> testBuffer = buffers[i].AsSpan(0, size); - if (!baseBuffer.SequenceEqual(testBuffer)) - { - return false; - } - } - - return true; - } - - public readonly struct Entry - { - public readonly IStorage Storage; - public readonly byte[] BackingArray; - - public Entry(IStorage storage, byte[] backingArray) - { - Storage = storage; - BackingArray = backingArray; - } - } - - private enum Task - { - Read = 0, - Write = 1, - Flush = 2 - } - - private enum AccessType - { - Random = 0, - Sequential = 1, - FrequentBlock = 2 + _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); } } + + //public StorageTester(ulong rngSeed, int frequentAccessBlockCount, params Entry[] entries) + //{ + // if (entries.Length < 2) + // { + // throw new ArgumentException("At least 2 storage entries must be provided", nameof(entries)); + // } + + // if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) + // { + // throw new ArgumentException("All storages must have the same size.", nameof(entries)); + // } + + // if (entries[0].BackingArray.Length == 0) + // { + // throw new ArgumentException("The storage size must be greater than 0.", nameof(entries)); + // } + + // _random = new Random(rngSeed); + + // _entries = entries; + // _backingArrays = entries.Select(x => x.BackingArray).ToArray(); + + // _buffers = new byte[entries.Length][]; + // for (int i = 0; i < entries.Length; i++) + // { + // _buffers[i] = new byte[SizeClassMaxSizes[^1]]; + // } + + // _size = _entries[0].BackingArray.Length; + // _lastAccessEnd = 0; + + // _frequentAccessOffsets = new int[frequentAccessBlockCount]; + // for (int i = 0; i < _frequentAccessOffsets.Length; i++) + // { + // _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); + // } + //} + + public void Run(long accessCount) + { + long endCount = _totalAccessCount + accessCount; + + while (_totalAccessCount < endCount) + { + Task task = ChooseTask(); + switch (task) + { + case Task.Read: + RunRead(); + break; + case Task.Write: + RunWrite(); + break; + case Task.Flush: + RunFlush(); + break; + } + + _totalAccessCount++; + } + } + + private void RunRead() + { + int sizeClass = ChooseSizeClass(); + AccessType accessType = ChooseAccessType(); + int offset = ChooseOffset(accessType); + int size = ChooseSize(offset, sizeClass); + + for (int i = 0; i < _config.Entries.Length; i++) + { + Entry entry = _config.Entries[i]; + entry.Storage.Read(offset, _buffers[i].AsSpan(0, size)).ThrowIfFailure(); + } + + if (!CompareBuffers(_buffers, size)) + { + throw new InvalidDataException($"Read: Offset {offset}; Size {size}"); + } + } + + private void RunWrite() + { + int sizeClass = ChooseSizeClass(); + AccessType accessType = ChooseAccessType(); + int offset = ChooseOffset(accessType); + int size = ChooseSize(offset, sizeClass); + + Span<byte> buffer = _buffers[0].AsSpan(0, size); + _random.NextBytes(buffer); + + for (int i = 0; i < _config.Entries.Length; i++) + { + Entry entry = _config.Entries[i]; + entry.Storage.Write(offset, buffer).ThrowIfFailure(); + } + } + + private void RunFlush() + { + foreach (Entry entry in _config.Entries) + { + entry.Storage.Flush().ThrowIfFailure(); + } + + if (!CompareBuffers(_backingArrays, _size)) + { + throw new InvalidDataException("Flush"); + } + } + + private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs); + private int ChooseSizeClass() => ChooseProb(_config.SizeClassProbs); + private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs); + + private int ChooseOffset(AccessType type) => type switch + { + AccessType.Random => _random.Next(0, _size), + AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd, + AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)], + _ => 0 + }; + + private int ChooseSize(int offset, int sizeClass) + { + int availableSize = Math.Max(0, _size - offset); + int randSize = _random.Next(0, _config.SizeClassMaxSizes[sizeClass]); + return Math.Min(availableSize, randSize); + } + + private int ChooseProb(int[] weights) + { + int total = 0; + foreach (int weight in weights) + { + total += weight; + } + + int rand = _random.Next(0, total); + int currentThreshold = 0; + + for (int i = 0; i < weights.Length; i++) + { + currentThreshold += weights[i]; + + if (rand < currentThreshold) + return i; + } + + return 0; + } + + private bool CompareBuffers(byte[][] buffers, int size) + { + Span<byte> baseBuffer = buffers[0].AsSpan(0, size); + + for (int i = 1; i < buffers.Length; i++) + { + Span<byte> testBuffer = buffers[i].AsSpan(0, size); + if (!baseBuffer.SequenceEqual(testBuffer)) + { + return false; + } + } + + return true; + } + + public readonly struct Entry + { + public readonly IStorage Storage; + public readonly byte[] BackingArray; + + public Entry(IStorage storage, byte[] backingArray) + { + Storage = storage; + BackingArray = backingArray; + } + } + + private enum Task + { + Read = 0, + Write = 1, + Flush = 2 + } + + private enum AccessType + { + Random = 0, + Sequential = 1, + FrequentBlock = 2 + } } diff --git a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs index 20960d66..3e48aeee 100644 --- a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs @@ -5,65 +5,64 @@ using LibHac.FsSystem; using LibHac.Tests.Fs.IFileSystemTestBase; using Xunit; -namespace LibHac.Tests.Fs +namespace LibHac.Tests.Fs; + +public class SubdirectoryFileSystemTests : IFileSystemTests { - public class SubdirectoryFileSystemTests : IFileSystemTests + protected override IFileSystem CreateFileSystem() { - protected override IFileSystem CreateFileSystem() - { - return CreateFileSystemInternal().subDirFs; - } - - private (IFileSystem baseFs, IFileSystem subDirFs) CreateFileSystemInternal() - { - var baseFs = new InMemoryFileSystem(); - baseFs.CreateDirectory("/sub"); - baseFs.CreateDirectory("/sub/path"); - - using var rootPath = new Path(); - PathFunctions.SetUpFixedPath(ref rootPath.Ref(), "/sub/path".ToU8String()); - - var subFs = new SubdirectoryFileSystem(baseFs); - subFs.Initialize(in rootPath).ThrowIfFailure(); - - return (baseFs, subFs); - } - - [Fact] - public void CreateFile_CreatedInBaseFileSystem() - { - (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); - - subDirFs.CreateFile("/file", 0, CreateFileOptions.None); - - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/file")); - Assert.Equal(DirectoryEntryType.File, type); - } - - [Fact] - public void CreateDirectory_CreatedInBaseFileSystem() - { - (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); - - subDirFs.CreateDirectory("/dir"); - - Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/dir")); - Assert.Equal(DirectoryEntryType.Directory, type); - } + return CreateFileSystemInternal().subDirFs; } - public class SubdirectoryFileSystemTestsRoot : IFileSystemTests + private (IFileSystem baseFs, IFileSystem subDirFs) CreateFileSystemInternal() { - protected override IFileSystem CreateFileSystem() - { - var baseFs = new InMemoryFileSystem(); + var baseFs = new InMemoryFileSystem(); + baseFs.CreateDirectory("/sub"); + baseFs.CreateDirectory("/sub/path"); - using var rootPath = new Path(); - PathFunctions.SetUpFixedPath(ref rootPath.Ref(), "/".ToU8String()); + using var rootPath = new Path(); + PathFunctions.SetUpFixedPath(ref rootPath.Ref(), "/sub/path".ToU8String()); - var subFs = new SubdirectoryFileSystem(baseFs); - subFs.Initialize(in rootPath).ThrowIfFailure(); - return subFs; - } + var subFs = new SubdirectoryFileSystem(baseFs); + subFs.Initialize(in rootPath).ThrowIfFailure(); + + return (baseFs, subFs); + } + + [Fact] + public void CreateFile_CreatedInBaseFileSystem() + { + (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); + + subDirFs.CreateFile("/file", 0, CreateFileOptions.None); + + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/file")); + Assert.Equal(DirectoryEntryType.File, type); + } + + [Fact] + public void CreateDirectory_CreatedInBaseFileSystem() + { + (IFileSystem baseFs, IFileSystem subDirFs) = CreateFileSystemInternal(); + + subDirFs.CreateDirectory("/dir"); + + Assert.Success(baseFs.GetEntryType(out DirectoryEntryType type, "/sub/path/dir")); + Assert.Equal(DirectoryEntryType.Directory, type); + } +} + +public class SubdirectoryFileSystemTestsRoot : IFileSystemTests +{ + protected override IFileSystem CreateFileSystem() + { + var baseFs = new InMemoryFileSystem(); + + using var rootPath = new Path(); + PathFunctions.SetUpFixedPath(ref rootPath.Ref(), "/".ToU8String()); + + var subFs = new SubdirectoryFileSystem(baseFs); + subFs.Initialize(in rootPath).ThrowIfFailure(); + return subFs; } } diff --git a/tests/LibHac.Tests/FsSrv/AccessControlTests.cs b/tests/LibHac.Tests/FsSrv/AccessControlTests.cs index f898f8e2..ade5e02b 100644 --- a/tests/LibHac.Tests/FsSrv/AccessControlTests.cs +++ b/tests/LibHac.Tests/FsSrv/AccessControlTests.cs @@ -6,59 +6,58 @@ using LibHac.Ncm; using Xunit; using ContentType = LibHac.Fs.ContentType; -namespace LibHac.Tests.FsSrv +namespace LibHac.Tests.FsSrv; + +public class AccessControlTests { - public class AccessControlTests + [Fact] + public void OpenFileSystemWithNoPermissions_ReturnsPermissionDenied() { - [Fact] - public void OpenFileSystemWithNoPermissions_ReturnsPermissionDenied() - { - Horizon hos = HorizonFactory.CreateBasicHorizon(); + Horizon hos = HorizonFactory.CreateBasicHorizon(); - HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); - HorizonClient client = hos.CreateHorizonClient(); + HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); + HorizonClient client = hos.CreateHorizonClient(); - var dataHeader = new AccessControlDataHeader(); - var descriptor = new AccessControlDescriptor(); + var dataHeader = new AccessControlDataHeader(); + var descriptor = new AccessControlDescriptor(); - descriptor.Version = 123; - dataHeader.Version = 123; + descriptor.Version = 123; + dataHeader.Version = 123; - descriptor.AccessFlags = (ulong)AccessControlBits.Bits.None; - dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.None; + descriptor.AccessFlags = (ulong)AccessControlBits.Bits.None; + dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.None; - Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), - StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), - SpanHelpers.AsReadOnlyByteSpan(in descriptor))); + Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), + StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), + SpanHelpers.AsReadOnlyByteSpan(in descriptor))); - Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); - Assert.Result(ResultFs.PermissionDenied, rc); - } + Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); + Assert.Result(ResultFs.PermissionDenied, rc); + } - [Fact] - public void OpenFileSystemWithPermissions_ReturnsInvalidNcaMountPoint() - { - Horizon hos = HorizonFactory.CreateBasicHorizon(); + [Fact] + public void OpenFileSystemWithPermissions_ReturnsInvalidNcaMountPoint() + { + Horizon hos = HorizonFactory.CreateBasicHorizon(); - HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); - HorizonClient client = hos.CreateHorizonClient(); + HorizonClient regClient = hos.CreatePrivilegedHorizonClient(); + HorizonClient client = hos.CreateHorizonClient(); - var dataHeader = new AccessControlDataHeader(); - var descriptor = new AccessControlDescriptor(); + var dataHeader = new AccessControlDataHeader(); + var descriptor = new AccessControlDescriptor(); - descriptor.Version = 123; - dataHeader.Version = 123; + descriptor.Version = 123; + dataHeader.Version = 123; - descriptor.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; - dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; + descriptor.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; + dataHeader.AccessFlags = (ulong)AccessControlBits.Bits.ApplicationInfo; - Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), - StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), - SpanHelpers.AsReadOnlyByteSpan(in descriptor))); + Assert.Success(regClient.Fs.RegisterProgram(client.ProcessId.Value, new ProgramId(123), + StorageId.BuiltInUser, SpanHelpers.AsReadOnlyByteSpan(in dataHeader), + SpanHelpers.AsReadOnlyByteSpan(in descriptor))); - // We should get UnexpectedInNcaFileSystemServiceImplA because mounting NCAs from @System isn't allowed - Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); - Assert.Result(ResultFs.UnexpectedInNcaFileSystemServiceImplA, rc); - } + // We should get UnexpectedInNcaFileSystemServiceImplA because mounting NCAs from @System isn't allowed + Result rc = client.Fs.MountContent("test".ToU8Span(), "@System:/fake.nca".ToU8Span(), ContentType.Control); + Assert.Result(ResultFs.UnexpectedInNcaFileSystemServiceImplA, rc); } } diff --git a/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs b/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs index 7d8469e8..55790e32 100644 --- a/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs +++ b/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs @@ -8,191 +8,190 @@ using LibHac.Ncm; using LibHac.Util; using Xunit; -namespace LibHac.Tests.FsSrv +namespace LibHac.Tests.FsSrv; + +public class ProgramIndexMapInfoTests { - public class ProgramIndexMapInfoTests + [Fact] + public void GetProgramIndexForAccessLog_IsMultiProgram_ReturnsCorrectIndex() { - [Fact] - public void GetProgramIndexForAccessLog_IsMultiProgram_ReturnsCorrectIndex() + const int count = 7; + + Horizon hos = HorizonFactory.CreateBasicHorizon(); + + var programs = new HorizonClient[count]; + + programs[0] = hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), + AccessControlBits.Bits.RegisterProgramIndexMapInfo); + + for (int i = 1; i < programs.Length; i++) { - const int count = 7; - - Horizon hos = HorizonFactory.CreateBasicHorizon(); - - var programs = new HorizonClient[count]; - - programs[0] = hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), - AccessControlBits.Bits.RegisterProgramIndexMapInfo); - - for (int i = 1; i < programs.Length; i++) - { - programs[i] = - hos.CreateHorizonClient(new ProgramLocation(new ProgramId((ulong)(i + 1)), StorageId.BuiltInSystem), - AccessControlBits.Bits.None); - } - - var map = new ProgramIndexMapInfo[count]; - - for (int i = 0; i < map.Length; i++) - { - map[i].MainProgramId = new ProgramId(1); - map[i].ProgramId = new ProgramId((ulong)(i + 1)); - map[i].ProgramIndex = (byte)i; - } - - Assert.Success(programs[0].Fs.RegisterProgramIndexMapInfo(map)); - - for (int i = 0; i < programs.Length; i++) - { - using SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> fsProxy = - programs[i].Fs.Impl.GetFileSystemProxyServiceObject(); - Assert.Success(fsProxy.Get.GetProgramIndexForAccessLog(out int programIndex, out int programCount)); - - Assert.Equal(i, programIndex); - Assert.Equal(count, programCount); - } + programs[i] = + hos.CreateHorizonClient(new ProgramLocation(new ProgramId((ulong)(i + 1)), StorageId.BuiltInSystem), + AccessControlBits.Bits.None); } - private ProgramIndexMapInfoManager CreatePopulatedManager(int count, Func<int, long> idCreator) + var map = new ProgramIndexMapInfo[count]; + + for (int i = 0; i < map.Length; i++) { - var manager = new ProgramIndexMapInfoManager(); - - var map = new ProgramIndexMapInfo[count]; - - for (int i = 0; i < map.Length; i++) - { - map[i].MainProgramId = new ProgramId((ulong)idCreator(0)); - map[i].ProgramId = new ProgramId((ulong)idCreator(i)); - map[i].ProgramIndex = (byte)i; - } - - Assert.Success(manager.Reset(map)); - - return manager; + map[i].MainProgramId = new ProgramId(1); + map[i].ProgramId = new ProgramId((ulong)(i + 1)); + map[i].ProgramIndex = (byte)i; } - [Fact] - public void Get_IdDoesNotExist_ReturnsNull() + Assert.Success(programs[0].Fs.RegisterProgramIndexMapInfo(map)); + + for (int i = 0; i < programs.Length; i++) { - const int count = 5; + using SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> fsProxy = + programs[i].Fs.Impl.GetFileSystemProxyServiceObject(); + Assert.Success(fsProxy.Get.GetProgramIndexForAccessLog(out int programIndex, out int programCount)); - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 3); - - Assert.False(manager.Get(new ProgramId(0)).HasValue); - Assert.False(manager.Get(new ProgramId(2)).HasValue); - Assert.False(manager.Get(new ProgramId(8)).HasValue); - Assert.False(manager.Get(new ProgramId(9001)).HasValue); - } - - [Fact] - public void Get_IdExists_ReturnsCorrectMapInfo() - { - const int count = 5; - - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); - - // ReSharper disable PossibleInvalidOperationException - Optional<ProgramIndexMapInfo> map = manager.Get(new ProgramId(1)); - Assert.True(map.HasValue); - Assert.Equal(new ProgramId(1), map.Value.MainProgramId); - Assert.Equal(new ProgramId(1), map.Value.ProgramId); - Assert.Equal(0, map.Value.ProgramIndex); - - map = manager.Get(new ProgramId(4)); - Assert.True(map.HasValue); - Assert.Equal(new ProgramId(1), map.Value.MainProgramId); - Assert.Equal(new ProgramId(4), map.Value.ProgramId); - Assert.Equal(3, map.Value.ProgramIndex); - // ReSharper restore PossibleInvalidOperationException - } - - [Fact] - public void GetProgramId_WithIndex_ReturnsProgramIdOfSpecifiedIndex() - { - const int count = 5; - - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => (x + 1) * 5); - - // Check that the program ID can be retrieved using any program in the set - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(5), 3)); - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(10), 3)); - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(15), 3)); - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(20), 3)); - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(25), 3)); - - // Check that any index can be used - Assert.Equal(new ProgramId(5), manager.GetProgramId(new ProgramId(10), 0)); - Assert.Equal(new ProgramId(10), manager.GetProgramId(new ProgramId(10), 1)); - Assert.Equal(new ProgramId(15), manager.GetProgramId(new ProgramId(10), 2)); - Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(10), 3)); - Assert.Equal(new ProgramId(25), manager.GetProgramId(new ProgramId(10), 4)); - } - - [Fact] - public void GetProgramId_InputIdDoesNotExist_ReturnsInvalidId() - { - const int count = 5; - - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); - - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(0), 3)); - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(666), 3)); - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(777), 3)); - } - - [Fact] - public void GetProgramId_InputIndexDoesNotExist_ReturnsInvalidId() - { - const int count = 5; - - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); - - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 5)); - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 12)); - Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 255)); - } - - [Fact] - public void GetProgramCount_MapIsEmpty_ReturnsZero() - { - var manager = new ProgramIndexMapInfoManager(); - - Assert.Equal(0, manager.GetProgramCount()); - } - - [Fact] - public void GetProgramCount_MapHasEntries_ReturnsCorrectCount() - { - ProgramIndexMapInfoManager manager = CreatePopulatedManager(1, x => x + 1); - Assert.Equal(1, manager.GetProgramCount()); - - manager = CreatePopulatedManager(10, x => x + 1); - Assert.Equal(10, manager.GetProgramCount()); - - manager = CreatePopulatedManager(255, x => x + 1); - Assert.Equal(255, manager.GetProgramCount()); - } - - [Fact] - public void Clear_MapHasEntries_CountIsZero() - { - const int count = 5; - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); - - manager.Clear(); - - Assert.Equal(0, manager.GetProgramCount()); - } - - [Fact] - public void Clear_MapHasEntries_CannotGetOldEntry() - { - const int count = 5; - ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); - - manager.Clear(); - - Assert.False(manager.Get(new ProgramId(2)).HasValue); + Assert.Equal(i, programIndex); + Assert.Equal(count, programCount); } } -} \ No newline at end of file + + private ProgramIndexMapInfoManager CreatePopulatedManager(int count, Func<int, long> idCreator) + { + var manager = new ProgramIndexMapInfoManager(); + + var map = new ProgramIndexMapInfo[count]; + + for (int i = 0; i < map.Length; i++) + { + map[i].MainProgramId = new ProgramId((ulong)idCreator(0)); + map[i].ProgramId = new ProgramId((ulong)idCreator(i)); + map[i].ProgramIndex = (byte)i; + } + + Assert.Success(manager.Reset(map)); + + return manager; + } + + [Fact] + public void Get_IdDoesNotExist_ReturnsNull() + { + const int count = 5; + + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 3); + + Assert.False(manager.Get(new ProgramId(0)).HasValue); + Assert.False(manager.Get(new ProgramId(2)).HasValue); + Assert.False(manager.Get(new ProgramId(8)).HasValue); + Assert.False(manager.Get(new ProgramId(9001)).HasValue); + } + + [Fact] + public void Get_IdExists_ReturnsCorrectMapInfo() + { + const int count = 5; + + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); + + // ReSharper disable PossibleInvalidOperationException + Optional<ProgramIndexMapInfo> map = manager.Get(new ProgramId(1)); + Assert.True(map.HasValue); + Assert.Equal(new ProgramId(1), map.Value.MainProgramId); + Assert.Equal(new ProgramId(1), map.Value.ProgramId); + Assert.Equal(0, map.Value.ProgramIndex); + + map = manager.Get(new ProgramId(4)); + Assert.True(map.HasValue); + Assert.Equal(new ProgramId(1), map.Value.MainProgramId); + Assert.Equal(new ProgramId(4), map.Value.ProgramId); + Assert.Equal(3, map.Value.ProgramIndex); + // ReSharper restore PossibleInvalidOperationException + } + + [Fact] + public void GetProgramId_WithIndex_ReturnsProgramIdOfSpecifiedIndex() + { + const int count = 5; + + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => (x + 1) * 5); + + // Check that the program ID can be retrieved using any program in the set + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(5), 3)); + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(10), 3)); + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(15), 3)); + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(20), 3)); + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(25), 3)); + + // Check that any index can be used + Assert.Equal(new ProgramId(5), manager.GetProgramId(new ProgramId(10), 0)); + Assert.Equal(new ProgramId(10), manager.GetProgramId(new ProgramId(10), 1)); + Assert.Equal(new ProgramId(15), manager.GetProgramId(new ProgramId(10), 2)); + Assert.Equal(new ProgramId(20), manager.GetProgramId(new ProgramId(10), 3)); + Assert.Equal(new ProgramId(25), manager.GetProgramId(new ProgramId(10), 4)); + } + + [Fact] + public void GetProgramId_InputIdDoesNotExist_ReturnsInvalidId() + { + const int count = 5; + + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); + + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(0), 3)); + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(666), 3)); + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(777), 3)); + } + + [Fact] + public void GetProgramId_InputIndexDoesNotExist_ReturnsInvalidId() + { + const int count = 5; + + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); + + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 5)); + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 12)); + Assert.Equal(ProgramId.InvalidId, manager.GetProgramId(new ProgramId(2), 255)); + } + + [Fact] + public void GetProgramCount_MapIsEmpty_ReturnsZero() + { + var manager = new ProgramIndexMapInfoManager(); + + Assert.Equal(0, manager.GetProgramCount()); + } + + [Fact] + public void GetProgramCount_MapHasEntries_ReturnsCorrectCount() + { + ProgramIndexMapInfoManager manager = CreatePopulatedManager(1, x => x + 1); + Assert.Equal(1, manager.GetProgramCount()); + + manager = CreatePopulatedManager(10, x => x + 1); + Assert.Equal(10, manager.GetProgramCount()); + + manager = CreatePopulatedManager(255, x => x + 1); + Assert.Equal(255, manager.GetProgramCount()); + } + + [Fact] + public void Clear_MapHasEntries_CountIsZero() + { + const int count = 5; + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); + + manager.Clear(); + + Assert.Equal(0, manager.GetProgramCount()); + } + + [Fact] + public void Clear_MapHasEntries_CannotGetOldEntry() + { + const int count = 5; + ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); + + manager.Clear(); + + Assert.False(manager.Get(new ProgramId(2)).HasValue); + } +} diff --git a/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs b/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs index 8cc94ded..ece69197 100644 --- a/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs +++ b/tests/LibHac.Tests/FsSystem/BufferedStorageTests.cs @@ -6,54 +6,54 @@ using LibHac.FsSystem.Save; using LibHac.Tests.Fs; using Xunit; -namespace LibHac.Tests.FsSystem +namespace LibHac.Tests.FsSystem; + +public class BufferedStorageTests { - public class BufferedStorageTests + [Fact] + public void Write_SingleBlock_CanReadBack() { - [Fact] - public void Write_SingleBlock_CanReadBack() - { - byte[] buffer = new byte[0x18000]; - byte[] workBuffer = new byte[0x18000]; - var bufferManager = new FileSystemBufferManager(); - Assert.Success(bufferManager.Initialize(5, buffer, 0x4000, workBuffer)); + byte[] buffer = new byte[0x18000]; + byte[] workBuffer = new byte[0x18000]; + var bufferManager = new FileSystemBufferManager(); + Assert.Success(bufferManager.Initialize(5, buffer, 0x4000, workBuffer)); - byte[] storageBuffer = new byte[0x80000]; - var baseStorage = new SubStorage(new MemoryStorage(storageBuffer), 0, storageBuffer.Length); + byte[] storageBuffer = new byte[0x80000]; + var baseStorage = new SubStorage(new MemoryStorage(storageBuffer), 0, storageBuffer.Length); - var bufferedStorage = new BufferedStorage(); - Assert.Success(bufferedStorage.Initialize(baseStorage, bufferManager, 0x4000, 4)); + var bufferedStorage = new BufferedStorage(); + Assert.Success(bufferedStorage.Initialize(baseStorage, bufferManager, 0x4000, 4)); - byte[] writeBuffer = new byte[0x400]; - byte[] readBuffer = new byte[0x400]; + byte[] writeBuffer = new byte[0x400]; + byte[] readBuffer = new byte[0x400]; - writeBuffer.AsSpan().Fill(0xAA); - Assert.Success(bufferedStorage.Write(0x10000, writeBuffer)); - Assert.Success(bufferedStorage.Read(0x10000, readBuffer)); + writeBuffer.AsSpan().Fill(0xAA); + Assert.Success(bufferedStorage.Write(0x10000, writeBuffer)); + Assert.Success(bufferedStorage.Read(0x10000, readBuffer)); - Assert.Equal(writeBuffer, readBuffer); - } + Assert.Equal(writeBuffer, readBuffer); + } - public class AccessTestConfig - { - public int[] SizeClassProbs { get; set; } - public int[] SizeClassMaxSizes { get; set; } - public int[] TaskProbs { get; set; } - public int[] AccessTypeProbs { get; set; } - public ulong RngSeed { get; set; } - public int FrequentAccessBlockCount { get; set; } - public int BlockSize { get; set; } - public int StorageCacheCount { get; set; } - public bool EnableBulkRead { get; set; } - public int StorageSize { get; set; } - public int HeapSize { get; set; } - public int HeapBlockSize { get; set; } - public int BufferManagerCacheCount { get; set; } - } + public class AccessTestConfig + { + public int[] SizeClassProbs { get; set; } + public int[] SizeClassMaxSizes { get; set; } + public int[] TaskProbs { get; set; } + public int[] AccessTypeProbs { get; set; } + public ulong RngSeed { get; set; } + public int FrequentAccessBlockCount { get; set; } + public int BlockSize { get; set; } + public int StorageCacheCount { get; set; } + public bool EnableBulkRead { get; set; } + public int StorageSize { get; set; } + public int HeapSize { get; set; } + public int HeapBlockSize { get; set; } + public int BufferManagerCacheCount { get; set; } + } - public static AccessTestConfig[] AccessTestConfigs = - { + public static AccessTestConfig[] AccessTestConfigs = + { new() { SizeClassProbs = new[] {50, 50, 5}, @@ -168,62 +168,61 @@ namespace LibHac.Tests.FsSystem } }; - private static TheoryData<T> CreateTheoryData<T>(IEnumerable<T> items) + private static TheoryData<T> CreateTheoryData<T>(IEnumerable<T> items) + { + var output = new TheoryData<T>(); + + foreach (T item in items) { - var output = new TheoryData<T>(); - - foreach (T item in items) - { - output.Add(item); - } - - return output; + output.Add(item); } - public static TheoryData<AccessTestConfig> AccessTestTheoryData = CreateTheoryData(AccessTestConfigs); + return output; + } - [Theory] - [MemberData(nameof(AccessTestTheoryData))] - public void ReadWrite_AccessCorrectnessTestAgainstMemoryStorage(AccessTestConfig config) + public static TheoryData<AccessTestConfig> AccessTestTheoryData = CreateTheoryData(AccessTestConfigs); + + [Theory] + [MemberData(nameof(AccessTestTheoryData))] + public void ReadWrite_AccessCorrectnessTestAgainstMemoryStorage(AccessTestConfig config) + { + int orderMax = FileSystemBuddyHeap.QueryOrderMax((nuint)config.HeapSize, (nuint)config.HeapBlockSize); + int workBufferSize = (int)FileSystemBuddyHeap.QueryWorkBufferSize(orderMax); + byte[] workBuffer = GC.AllocateArray<byte>(workBufferSize, true); + byte[] heapBuffer = new byte[config.HeapSize]; + + var bufferManager = new FileSystemBufferManager(); + Assert.Success(bufferManager.Initialize(config.BufferManagerCacheCount, heapBuffer, config.HeapBlockSize, workBuffer)); + + byte[] memoryStorageArray = new byte[config.StorageSize]; + byte[] bufferedStorageArray = new byte[config.StorageSize]; + + var memoryStorage = new MemoryStorage(memoryStorageArray); + var baseBufferedStorage = new SubStorage(new MemoryStorage(bufferedStorageArray), 0, bufferedStorageArray.Length); + + var bufferedStorage = new BufferedStorage(); + Assert.Success(bufferedStorage.Initialize(baseBufferedStorage, bufferManager, config.BlockSize, config.StorageCacheCount)); + + if (config.EnableBulkRead) { - int orderMax = FileSystemBuddyHeap.QueryOrderMax((nuint)config.HeapSize, (nuint)config.HeapBlockSize); - int workBufferSize = (int)FileSystemBuddyHeap.QueryWorkBufferSize(orderMax); - byte[] workBuffer = GC.AllocateArray<byte>(workBufferSize, true); - byte[] heapBuffer = new byte[config.HeapSize]; - - var bufferManager = new FileSystemBufferManager(); - Assert.Success(bufferManager.Initialize(config.BufferManagerCacheCount, heapBuffer, config.HeapBlockSize, workBuffer)); - - byte[] memoryStorageArray = new byte[config.StorageSize]; - byte[] bufferedStorageArray = new byte[config.StorageSize]; - - var memoryStorage = new MemoryStorage(memoryStorageArray); - var baseBufferedStorage = new SubStorage(new MemoryStorage(bufferedStorageArray), 0, bufferedStorageArray.Length); - - var bufferedStorage = new BufferedStorage(); - Assert.Success(bufferedStorage.Initialize(baseBufferedStorage, bufferManager, config.BlockSize, config.StorageCacheCount)); - - if (config.EnableBulkRead) - { - bufferedStorage.EnableBulkRead(); - } - - var memoryStorageEntry = new StorageTester.Entry(memoryStorage, memoryStorageArray); - var bufferedStorageEntry = new StorageTester.Entry(bufferedStorage, bufferedStorageArray); - - var testerConfig = new StorageTester.Configuration() - { - Entries = new[] { memoryStorageEntry, bufferedStorageEntry }, - SizeClassProbs = config.SizeClassProbs, - SizeClassMaxSizes = config.SizeClassMaxSizes, - TaskProbs = config.TaskProbs, - AccessTypeProbs = config.AccessTypeProbs, - RngSeed = config.RngSeed, - FrequentAccessBlockCount = config.FrequentAccessBlockCount - }; - - var tester = new StorageTester(testerConfig); - tester.Run(0x100); + bufferedStorage.EnableBulkRead(); } + + var memoryStorageEntry = new StorageTester.Entry(memoryStorage, memoryStorageArray); + var bufferedStorageEntry = new StorageTester.Entry(bufferedStorage, bufferedStorageArray); + + var testerConfig = new StorageTester.Configuration() + { + Entries = new[] { memoryStorageEntry, bufferedStorageEntry }, + SizeClassProbs = config.SizeClassProbs, + SizeClassMaxSizes = config.SizeClassMaxSizes, + TaskProbs = config.TaskProbs, + AccessTypeProbs = config.AccessTypeProbs, + RngSeed = config.RngSeed, + FrequentAccessBlockCount = config.FrequentAccessBlockCount + }; + + var tester = new StorageTester(testerConfig); + tester.Run(0x100); } } diff --git a/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs index d60d302a..0222ef6d 100644 --- a/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs +++ b/tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs @@ -1,7 +1,6 @@ -namespace LibHac.Tests.FsSystem +namespace LibHac.Tests.FsSystem; + +public class ConcatenationFileSystemTests { - public class ConcatenationFileSystemTests - { - //asdf - } + //asdf } diff --git a/tests/LibHac.Tests/FsSystem/FileSystemBufferManagerTests.cs b/tests/LibHac.Tests/FsSystem/FileSystemBufferManagerTests.cs index 220bad7a..4860bd93 100644 --- a/tests/LibHac.Tests/FsSystem/FileSystemBufferManagerTests.cs +++ b/tests/LibHac.Tests/FsSystem/FileSystemBufferManagerTests.cs @@ -2,88 +2,87 @@ using LibHac.FsSystem; using Xunit; -namespace LibHac.Tests.FsSystem +namespace LibHac.Tests.FsSystem; + +public class FileSystemBufferManagerTests { - public class FileSystemBufferManagerTests + private FileSystemBufferManager CreateManager(int size, int blockSize = 0x4000, int maxCacheCount = 16) { - private FileSystemBufferManager CreateManager(int size, int blockSize = 0x4000, int maxCacheCount = 16) - { - int orderMax = FileSystemBuddyHeap.QueryOrderMax((nuint)size, (nuint)blockSize); - nuint workBufferSize = FileSystemBuddyHeap.QueryWorkBufferSize(orderMax); - byte[] workBuffer = new byte[workBufferSize]; - byte[] heapBuffer = new byte[size]; + int orderMax = FileSystemBuddyHeap.QueryOrderMax((nuint)size, (nuint)blockSize); + nuint workBufferSize = FileSystemBuddyHeap.QueryWorkBufferSize(orderMax); + byte[] workBuffer = new byte[workBufferSize]; + byte[] heapBuffer = new byte[size]; - var bufferManager = new FileSystemBufferManager(); - Assert.Success(bufferManager.Initialize(maxCacheCount, heapBuffer, blockSize, workBuffer)); - return bufferManager; - } + var bufferManager = new FileSystemBufferManager(); + Assert.Success(bufferManager.Initialize(maxCacheCount, heapBuffer, blockSize, workBuffer)); + return bufferManager; + } - [Fact] - public void AllocateBuffer_NoFreeSpace_ReturnsNull() - { - FileSystemBufferManager manager = CreateManager(0x20000); - Buffer buffer1 = manager.AllocateBuffer(0x10000); - Buffer buffer2 = manager.AllocateBuffer(0x10000); - Buffer buffer3 = manager.AllocateBuffer(0x4000); + [Fact] + public void AllocateBuffer_NoFreeSpace_ReturnsNull() + { + FileSystemBufferManager manager = CreateManager(0x20000); + Buffer buffer1 = manager.AllocateBuffer(0x10000); + Buffer buffer2 = manager.AllocateBuffer(0x10000); + Buffer buffer3 = manager.AllocateBuffer(0x4000); - Assert.True(!buffer1.IsNull); - Assert.True(!buffer2.IsNull); - Assert.True(buffer3.IsNull); - } + Assert.True(!buffer1.IsNull); + Assert.True(!buffer2.IsNull); + Assert.True(buffer3.IsNull); + } - [Fact] - public void AcquireCache_EntryNotEvicted_ReturnsEntry() - { - FileSystemBufferManager manager = CreateManager(0x20000); - Buffer buffer1 = manager.AllocateBuffer(0x10000); + [Fact] + public void AcquireCache_EntryNotEvicted_ReturnsEntry() + { + FileSystemBufferManager manager = CreateManager(0x20000); + Buffer buffer1 = manager.AllocateBuffer(0x10000); - long handle = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); + long handle = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); - manager.AllocateBuffer(0x10000); - Buffer buffer3 = manager.AcquireCache(handle); + manager.AllocateBuffer(0x10000); + Buffer buffer3 = manager.AcquireCache(handle); - Assert.Equal(buffer1, buffer3); - } + Assert.Equal(buffer1, buffer3); + } - [Fact] - public void AcquireCache_EntryEvicted_ReturnsNull() - { - FileSystemBufferManager manager = CreateManager(0x20000); - Buffer buffer1 = manager.AllocateBuffer(0x10000); + [Fact] + public void AcquireCache_EntryEvicted_ReturnsNull() + { + FileSystemBufferManager manager = CreateManager(0x20000); + Buffer buffer1 = manager.AllocateBuffer(0x10000); - long handle = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); + long handle = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); - manager.AllocateBuffer(0x20000); - Buffer buffer3 = manager.AcquireCache(handle); + manager.AllocateBuffer(0x20000); + Buffer buffer3 = manager.AcquireCache(handle); - Assert.True(buffer3.IsNull); - } + Assert.True(buffer3.IsNull); + } - [Fact] - public void AcquireCache_MultipleEntriesEvicted_OldestAreEvicted() - { - FileSystemBufferManager manager = CreateManager(0x20000); - Buffer buffer1 = manager.AllocateBuffer(0x8000); - Buffer buffer2 = manager.AllocateBuffer(0x8000); - Buffer buffer3 = manager.AllocateBuffer(0x8000); - Buffer buffer4 = manager.AllocateBuffer(0x8000); + [Fact] + public void AcquireCache_MultipleEntriesEvicted_OldestAreEvicted() + { + FileSystemBufferManager manager = CreateManager(0x20000); + Buffer buffer1 = manager.AllocateBuffer(0x8000); + Buffer buffer2 = manager.AllocateBuffer(0x8000); + Buffer buffer3 = manager.AllocateBuffer(0x8000); + Buffer buffer4 = manager.AllocateBuffer(0x8000); - long handle1 = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); - long handle2 = manager.RegisterCache(buffer2, new IBufferManager.BufferAttribute()); - long handle3 = manager.RegisterCache(buffer3, new IBufferManager.BufferAttribute()); - long handle4 = manager.RegisterCache(buffer4, new IBufferManager.BufferAttribute()); + long handle1 = manager.RegisterCache(buffer1, new IBufferManager.BufferAttribute()); + long handle2 = manager.RegisterCache(buffer2, new IBufferManager.BufferAttribute()); + long handle3 = manager.RegisterCache(buffer3, new IBufferManager.BufferAttribute()); + long handle4 = manager.RegisterCache(buffer4, new IBufferManager.BufferAttribute()); - manager.AllocateBuffer(0x10000); + manager.AllocateBuffer(0x10000); - Buffer buffer1B = manager.AcquireCache(handle1); - Buffer buffer2B = manager.AcquireCache(handle2); - Buffer buffer3B = manager.AcquireCache(handle3); - Buffer buffer4B = manager.AcquireCache(handle4); + Buffer buffer1B = manager.AcquireCache(handle1); + Buffer buffer2B = manager.AcquireCache(handle2); + Buffer buffer3B = manager.AcquireCache(handle3); + Buffer buffer4B = manager.AcquireCache(handle4); - Assert.True(buffer1B.IsNull); - Assert.True(buffer2B.IsNull); - Assert.Equal(buffer3, buffer3B); - Assert.Equal(buffer4, buffer4B); - } + Assert.True(buffer1B.IsNull); + Assert.True(buffer2B.IsNull); + Assert.Equal(buffer3, buffer3B); + Assert.Equal(buffer4, buffer4B); } } diff --git a/tests/LibHac.Tests/FullCycleRandom.cs b/tests/LibHac.Tests/FullCycleRandom.cs index bd5866ad..e98d7173 100644 --- a/tests/LibHac.Tests/FullCycleRandom.cs +++ b/tests/LibHac.Tests/FullCycleRandom.cs @@ -1,54 +1,53 @@ using System; using System.Numerics; -namespace LibHac.Tests +namespace LibHac.Tests; + +/// <summary> +/// Simple, full-cycle PRNG for use in tests. +/// </summary> +public class FullCycleRandom { - /// <summary> - /// Simple, full-cycle PRNG for use in tests. - /// </summary> - public class FullCycleRandom + private int _state; + private int _mult; + private int _inc; + private int _and; + private int _max; + + public FullCycleRandom(int period, int seed) { - private int _state; - private int _mult; - private int _inc; - private int _and; - private int _max; + // Avoid exponential growth pattern when initializing with a 0 seed + seed ^= 0x55555555; + _max = period - 1; + int order = BitOperations.Log2((uint)period - 1) + 1; - public FullCycleRandom(int period, int seed) + // There isn't any deep reasoning behind the choice of the number of bits + // in the seed used for initializing each parameter + int multSeedBits = Math.Max(order >> 1, 2); + int multSeedMask = (1 << multSeedBits) - 1; + int multSeed = seed & multSeedMask; + _mult = (multSeed << 2) | 5; + + int incSeedBits = Math.Max(order >> 2, 2); + int incSeedMask = (1 << incSeedBits) - 1; + int incSeed = (seed >> multSeedBits) & incSeedMask; + _inc = incSeed | 1; + + int stateSeedBits = order; + int stateSeedMask = (1 << stateSeedBits) - 1; + int stateSeed = (seed >> multSeedBits + incSeedBits) & stateSeedMask; + _state = stateSeed; + + _and = (1 << order) - 1; + } + + public int Next() + { + do { - // Avoid exponential growth pattern when initializing with a 0 seed - seed ^= 0x55555555; - _max = period - 1; - int order = BitOperations.Log2((uint)period - 1) + 1; + _state = (_state * _mult + _inc) & _and; + } while (_state > _max); - // There isn't any deep reasoning behind the choice of the number of bits - // in the seed used for initializing each parameter - int multSeedBits = Math.Max(order >> 1, 2); - int multSeedMask = (1 << multSeedBits) - 1; - int multSeed = seed & multSeedMask; - _mult = (multSeed << 2) | 5; - - int incSeedBits = Math.Max(order >> 2, 2); - int incSeedMask = (1 << incSeedBits) - 1; - int incSeed = (seed >> multSeedBits) & incSeedMask; - _inc = incSeed | 1; - - int stateSeedBits = order; - int stateSeedMask = (1 << stateSeedBits) - 1; - int stateSeed = (seed >> multSeedBits + incSeedBits) & stateSeedMask; - _state = stateSeed; - - _and = (1 << order) - 1; - } - - public int Next() - { - do - { - _state = (_state * _mult + _inc) & _and; - } while (_state > _max); - - return _state; - } + return _state; } } diff --git a/tests/LibHac.Tests/HorizonFactory.cs b/tests/LibHac.Tests/HorizonFactory.cs index 75579bac..f333a050 100644 --- a/tests/LibHac.Tests/HorizonFactory.cs +++ b/tests/LibHac.Tests/HorizonFactory.cs @@ -4,33 +4,32 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; -namespace LibHac.Tests +namespace LibHac.Tests; + +public static class HorizonFactory { - public static class HorizonFactory + public static Horizon CreateBasicHorizon() { - public static Horizon CreateBasicHorizon() - { - IFileSystem rootFs = new InMemoryFileSystem(); - var keySet = new KeySet(); + IFileSystem rootFs = new InMemoryFileSystem(); + var keySet = new KeySet(); - var horizon = new Horizon(new HorizonConfiguration()); + var horizon = new Horizon(new HorizonConfiguration()); - HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); - var fsServer = new FileSystemServer(fsServerClient); + HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); + var fsServer = new FileSystemServer(fsServerClient); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); - var config = new FileSystemServerConfig(); - config.FsCreators = defaultObjects.FsCreators; - config.DeviceOperator = defaultObjects.DeviceOperator; - config.ExternalKeySet = new ExternalKeySet(); + var config = new FileSystemServerConfig(); + config.FsCreators = defaultObjects.FsCreators; + config.DeviceOperator = defaultObjects.DeviceOperator; + config.ExternalKeySet = new ExternalKeySet(); - FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); + FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); - HorizonClient bcatServerClient = horizon.CreateHorizonClient(); - _ = new BcatServer(bcatServerClient); + HorizonClient bcatServerClient = horizon.CreateHorizonClient(); + _ = new BcatServer(bcatServerClient); - return horizon; - } + return horizon; } } diff --git a/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs b/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs index 21556110..f3832948 100644 --- a/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs +++ b/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs @@ -8,578 +8,577 @@ using Xunit; using TTest = System.Int32; -namespace LibHac.Tests.Kvdb +namespace LibHac.Tests.Kvdb; + +public class FlatMapKeyValueStoreTests { - public class FlatMapKeyValueStoreTests + private static readonly U8String MountName = new U8String("mount"); + private static readonly U8String RootPath = new U8String("mount:/"); + private static readonly U8String ArchiveFilePath = new U8String("mount:/imkvdb.arc"); + + private static (FlatMapKeyValueStore<T> kvStore, FileSystemClient fsClient) Create<T>(int capacity) + where T : unmanaged, IEquatable<T>, IComparable<T> { - private static readonly U8String MountName = new U8String("mount"); - private static readonly U8String RootPath = new U8String("mount:/"); - private static readonly U8String ArchiveFilePath = new U8String("mount:/imkvdb.arc"); + FileSystemClient fsClient = FileSystemServerFactory.CreateClient(false); - private static (FlatMapKeyValueStore<T> kvStore, FileSystemClient fsClient) Create<T>(int capacity) - where T : unmanaged, IEquatable<T>, IComparable<T> + using var mountedFs = new UniqueRef<IFileSystem>(new InMemoryFileSystem()); + fsClient.Register(MountName, ref mountedFs.Ref()).ThrowIfFailure(); + + FlatMapKeyValueStore<T> kvStore = Create<T>(fsClient, capacity); + + return (kvStore, fsClient); + } + + private static FlatMapKeyValueStore<T> Create<T>(FileSystemClient fsClient, int capacity) + where T : unmanaged, IEquatable<T>, IComparable<T> + { + var memoryResource = new ArrayPoolMemoryResource(); + + var kvStore = new FlatMapKeyValueStore<T>(); + kvStore.Initialize(fsClient, RootPath, capacity, memoryResource, memoryResource).ThrowIfFailure(); + + return kvStore; + } + + private static byte[][] GenerateValues(int count, int startingSize) + { + byte[][] values = new byte[count][]; + + for (int i = 0; i < count; i++) { - FileSystemClient fsClient = FileSystemServerFactory.CreateClient(false); - - using var mountedFs = new UniqueRef<IFileSystem>(new InMemoryFileSystem()); - fsClient.Register(MountName, ref mountedFs.Ref()).ThrowIfFailure(); - - FlatMapKeyValueStore<T> kvStore = Create<T>(fsClient, capacity); - - return (kvStore, fsClient); + byte[] value = new byte[startingSize + i]; + value.AsSpan().Fill((byte)count); + values[i] = value; } - private static FlatMapKeyValueStore<T> Create<T>(FileSystemClient fsClient, int capacity) - where T : unmanaged, IEquatable<T>, IComparable<T> + return values; + } + + private static Result PopulateKvStore(FlatMapKeyValueStore<TTest> kvStore, out byte[][] addedValues, int count, + int startingValueSize = 20, int seed = -1) + { + addedValues = null; + byte[][] values = GenerateValues(count, startingValueSize); + + if (seed == -1) { - var memoryResource = new ArrayPoolMemoryResource(); - - var kvStore = new FlatMapKeyValueStore<T>(); - kvStore.Initialize(fsClient, RootPath, capacity, memoryResource, memoryResource).ThrowIfFailure(); - - return kvStore; - } - - private static byte[][] GenerateValues(int count, int startingSize) - { - byte[][] values = new byte[count][]; - - for (int i = 0; i < count; i++) - { - byte[] value = new byte[startingSize + i]; - value.AsSpan().Fill((byte)count); - values[i] = value; - } - - return values; - } - - private static Result PopulateKvStore(FlatMapKeyValueStore<TTest> kvStore, out byte[][] addedValues, int count, - int startingValueSize = 20, int seed = -1) - { - addedValues = null; - byte[][] values = GenerateValues(count, startingValueSize); - - if (seed == -1) - { - for (TTest i = 0; i < count; i++) - { - Result rc = kvStore.Set(in i, values[i]); - if (rc.IsFailure()) return rc; - } - } - else - { - var rng = new FullCycleRandom(count, seed); - - for (int i = 0; i < count; i++) - { - TTest index = rng.Next(); - Result rc = kvStore.Set(in index, values[index]); - if (rc.IsFailure()) return rc; - } - } - - addedValues = values; - return Result.Success; - } - - [Fact] - public void Count_EmptyStore_ReturnsZero() - { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(10); - - Assert.Equal(0, kvStore.Count); - } - - [Theory] - [InlineData(1)] - [InlineData(5)] - [InlineData(10)] - public void Count_PopulatedStore_ReturnsCorrectCount(int count) - { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(10); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - Assert.Equal(count, kvStore.Count); - } - - [Fact] - public void Load_FileDoesNotExist_ExistingEntriesAreCleared() - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - Assert.Success(kvStore.Load()); - Assert.Equal(0, kvStore.Count); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(10)] - public void Load_AfterArchiveHasBeenSaved_AllEntriesAreLoaded(int count) - { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count + 5); - Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); - - Assert.Success(kvStore.Save()); - kvStore.Dispose(); - - kvStore = Create<TTest>(fsClient, count + 5); - Assert.Success(kvStore.Load()); - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - - // Check if each key-value pair matches - for (int i = 0; i < count; i++) - { - TTest expectedKey = i; - byte[] expectedValue = values[i]; - - ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); - - Assert.Equal(expectedKey, kv.Key); - Assert.Equal(expectedValue, kv.Value.Get().ToArray()); - - iterator.Next(); - } - - Assert.True(iterator.IsEnd()); - } - - [Fact] - public void Load_CapacityIsTooSmall_ReturnsOutOfKeyResource() - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - Assert.Success(kvStore.Save()); - kvStore.Dispose(); - - - kvStore = Create<TTest>(fsClient, count - 5); - Assert.Result(ResultKvdb.OutOfKeyResource, kvStore.Load()); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(10)] - public void Save_ArchiveFileIsWrittenToDisk(int count) - { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count + 5); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - Assert.Success(kvStore.Save()); - - Assert.Success(fsClient.GetEntryType(out DirectoryEntryType entryType, ArchiveFilePath)); - Assert.Equal(DirectoryEntryType.File, entryType); - } - - [Fact] - public void Get_PopulatedStoreAndEntryDoesNotExist_ReturnsKeyNotFound() - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - TTest key = 20; - byte[] value = new byte[20]; - - Result rc = kvStore.Get(out int _, in key, value); - Assert.Result(ResultKvdb.KeyNotFound, rc); - } - - [Fact] - public void Get_PopulatedStore_GetsCorrectValueSizes() - { - const int count = 10; - const int startingValueSize = 20; - const int rngSeed = 220; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count, startingValueSize, rngSeed)); - - // Check the size of each entry - byte[] value = new byte[100]; - for (TTest i = 0; i < count; i++) { - Assert.Success(kvStore.Get(out int valueSize, in i, value)); - Assert.Equal(startingValueSize + i, valueSize); + Result rc = kvStore.Set(in i, values[i]); + if (rc.IsFailure()) return rc; } } - - [Fact] - public void Get_PopulatedStoreAndEntryExists_GetsCorrectValue() + else { - const int count = 10; - const int startingValueSize = 20; - const int rngSeed = 188; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count, startingValueSize, rngSeed)); - - // Check if each value matches - byte[] value = new byte[100]; + var rng = new FullCycleRandom(count, seed); for (int i = 0; i < count; i++) { - TTest key = i; - Assert.Success(kvStore.Get(out int _, in key, value)); - Assert.Equal(values[i], value.AsSpan(0, startingValueSize + i).ToArray()); + TTest index = rng.Next(); + Result rc = kvStore.Set(in index, values[index]); + if (rc.IsFailure()) return rc; } } - [Theory] - [InlineData(1)] - [InlineData(5)] - [InlineData(10)] - public void Set_StoreIsFullAndEntryDoesNotExist_ReturnsOutOfKeyResource(int count) + addedValues = values; + return Result.Success; + } + + [Fact] + public void Count_EmptyStore_ReturnsZero() + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(10); + + Assert.Equal(0, kvStore.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + public void Count_PopulatedStore_ReturnsCorrectCount(int count) + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(10); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + Assert.Equal(count, kvStore.Count); + } + + [Fact] + public void Load_FileDoesNotExist_ExistingEntriesAreCleared() + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + Assert.Success(kvStore.Load()); + Assert.Equal(0, kvStore.Count); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] + public void Load_AfterArchiveHasBeenSaved_AllEntriesAreLoaded(int count) + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count + 5); + Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); + + Assert.Success(kvStore.Save()); + kvStore.Dispose(); + + kvStore = Create<TTest>(fsClient, count + 5); + Assert.Success(kvStore.Load()); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + // Check if each key-value pair matches + for (int i = 0; i < count; i++) { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); + TTest expectedKey = i; + byte[] expectedValue = values[i]; - TTest key = count; - Result rc = kvStore.Set(in key, values[0]); + ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); - Assert.Result(ResultKvdb.OutOfKeyResource, rc); + Assert.Equal(expectedKey, kv.Key); + Assert.Equal(expectedValue, kv.Value.Get().ToArray()); + + iterator.Next(); } - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(9)] - public void Set_StoreIsFullAndEntryAlreadyExists_ReplacesOriginalValue(int entryToReplace) + Assert.True(iterator.IsEnd()); + } + + [Fact] + public void Load_CapacityIsTooSmall_ReturnsOutOfKeyResource() + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + Assert.Success(kvStore.Save()); + kvStore.Dispose(); + + + kvStore = Create<TTest>(fsClient, count - 5); + Assert.Result(ResultKvdb.OutOfKeyResource, kvStore.Load()); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] + public void Save_ArchiveFileIsWrittenToDisk(int count) + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient fsClient) = Create<TTest>(count + 5); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + Assert.Success(kvStore.Save()); + + Assert.Success(fsClient.GetEntryType(out DirectoryEntryType entryType, ArchiveFilePath)); + Assert.Equal(DirectoryEntryType.File, entryType); + } + + [Fact] + public void Get_PopulatedStoreAndEntryDoesNotExist_ReturnsKeyNotFound() + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest key = 20; + byte[] value = new byte[20]; + + Result rc = kvStore.Get(out int _, in key, value); + Assert.Result(ResultKvdb.KeyNotFound, rc); + } + + [Fact] + public void Get_PopulatedStore_GetsCorrectValueSizes() + { + const int count = 10; + const int startingValueSize = 20; + const int rngSeed = 220; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count, startingValueSize, rngSeed)); + + // Check the size of each entry + byte[] value = new byte[100]; + + for (TTest i = 0; i < count; i++) { - const int count = 10; + Assert.Success(kvStore.Get(out int valueSize, in i, value)); + Assert.Equal(startingValueSize + i, valueSize); + } + } - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); + [Fact] + public void Get_PopulatedStoreAndEntryExists_GetsCorrectValue() + { + const int count = 10; + const int startingValueSize = 20; + const int rngSeed = 188; - TTest key = entryToReplace; - byte[] value = new byte[15]; - value.AsSpan().Fill(0xFF); + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count, startingValueSize, rngSeed)); - Assert.Success(kvStore.Set(in key, value)); + // Check if each value matches + byte[] value = new byte[100]; - // Read back the value - byte[] readValue = new byte[20]; - Assert.Success(kvStore.Get(out int valueSize, in key, readValue)); + for (int i = 0; i < count; i++) + { + TTest key = i; + Assert.Success(kvStore.Get(out int _, in key, value)); + Assert.Equal(values[i], value.AsSpan(0, startingValueSize + i).ToArray()); + } + } - // Check the value contents and size - Assert.Equal(value.Length, valueSize); - Assert.Equal(value, readValue.AsSpan(0, valueSize).ToArray()); + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + public void Set_StoreIsFullAndEntryDoesNotExist_ReturnsOutOfKeyResource(int count) + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); + + TTest key = count; + Result rc = kvStore.Set(in key, values[0]); + + Assert.Result(ResultKvdb.OutOfKeyResource, rc); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(9)] + public void Set_StoreIsFullAndEntryAlreadyExists_ReplacesOriginalValue(int entryToReplace) + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest key = entryToReplace; + byte[] value = new byte[15]; + value.AsSpan().Fill(0xFF); + + Assert.Success(kvStore.Set(in key, value)); + + // Read back the value + byte[] readValue = new byte[20]; + Assert.Success(kvStore.Get(out int valueSize, in key, readValue)); + + // Check the value contents and size + Assert.Equal(value.Length, valueSize); + Assert.Equal(value, readValue.AsSpan(0, valueSize).ToArray()); + } + + [Theory] + [InlineData(10, 89)] + [InlineData(10, 50)] + [InlineData(1000, 75367)] + [InlineData(1000, 117331)] + public void Set_StoreIsFilledInRandomOrder_EntriesAreSorted(int entryCount, int rngSeed) + { + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(entryCount + 10); + Assert.Success(PopulateKvStore(kvStore, out byte[][] values, entryCount, 20, rngSeed)); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + // Check if each key-value pair matches + for (int i = 0; i < entryCount; i++) + { + TTest expectedKey = i; + byte[] expectedValue = values[i]; + + ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); + + Assert.Equal(expectedKey, kv.Key); + Assert.Equal(expectedValue, kv.Value.Get().ToArray()); + + iterator.Next(); } - [Theory] - [InlineData(10, 89)] - [InlineData(10, 50)] - [InlineData(1000, 75367)] - [InlineData(1000, 117331)] - public void Set_StoreIsFilledInRandomOrder_EntriesAreSorted(int entryCount, int rngSeed) + Assert.True(iterator.IsEnd()); + } + + [Fact] + public void Delete_EmptyStore_ReturnsKeyNotFound() + { + const int count = 10; + TTest keyToDelete = 4; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + + Result rc = kvStore.Delete(in keyToDelete); + Assert.Result(ResultKvdb.KeyNotFound, rc); + } + + [Fact] + public void Delete_PopulatedStoreAndEntryDoesNotExist_ReturnsKeyNotFound() + { + const int count = 10; + TTest keyToDelete = 44; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + Result rc = kvStore.Delete(in keyToDelete); + Assert.Result(ResultKvdb.KeyNotFound, rc); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(9)] + public void Delete_PopulatedStoreAndEntryExists_CannotGetAfterDeletion(int entryToDelete) + { + const int count = 10; + const int startingValueSize = 20; + const int rngSeed = 114; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count, startingValueSize, rngSeed)); + + TTest keyToDelete = entryToDelete; + Assert.Success(kvStore.Delete(in keyToDelete)); + + byte[] value = new byte[20]; + + Result rc = kvStore.Get(out int _, in keyToDelete, value); + Assert.Result(ResultKvdb.KeyNotFound, rc); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(9)] + public void Delete_PopulatedStoreAndEntryExists_CountIsDecremented(int entryToDelete) + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest keyToDelete = entryToDelete; + Assert.Success(kvStore.Delete(in keyToDelete)); + + Assert.Equal(count - 1, kvStore.Count); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(9)] + public void Delete_PopulatedStoreAndEntryExists_RemainingEntriesAreSorted(int entryToDelete) + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest keyToDelete = entryToDelete; + Assert.Success(kvStore.Delete(in keyToDelete)); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + // Check if the remaining keys exist in order + for (int i = 0; i < count; i++) { - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(entryCount + 10); - Assert.Success(PopulateKvStore(kvStore, out byte[][] values, entryCount, 20, rngSeed)); + if (i == entryToDelete) + continue; - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + TTest expectedKey = i; - // Check if each key-value pair matches - for (int i = 0; i < entryCount; i++) - { - TTest expectedKey = i; - byte[] expectedValue = values[i]; - - ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); - - Assert.Equal(expectedKey, kv.Key); - Assert.Equal(expectedValue, kv.Value.Get().ToArray()); - - iterator.Next(); - } - - Assert.True(iterator.IsEnd()); - } - - [Fact] - public void Delete_EmptyStore_ReturnsKeyNotFound() - { - const int count = 10; - TTest keyToDelete = 4; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - - Result rc = kvStore.Delete(in keyToDelete); - Assert.Result(ResultKvdb.KeyNotFound, rc); - } - - [Fact] - public void Delete_PopulatedStoreAndEntryDoesNotExist_ReturnsKeyNotFound() - { - const int count = 10; - TTest keyToDelete = 44; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - Result rc = kvStore.Delete(in keyToDelete); - Assert.Result(ResultKvdb.KeyNotFound, rc); - } - - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(9)] - public void Delete_PopulatedStoreAndEntryExists_CannotGetAfterDeletion(int entryToDelete) - { - const int count = 10; - const int startingValueSize = 20; - const int rngSeed = 114; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count, startingValueSize, rngSeed)); - - TTest keyToDelete = entryToDelete; - Assert.Success(kvStore.Delete(in keyToDelete)); - - byte[] value = new byte[20]; - - Result rc = kvStore.Get(out int _, in keyToDelete, value); - Assert.Result(ResultKvdb.KeyNotFound, rc); - } - - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(9)] - public void Delete_PopulatedStoreAndEntryExists_CountIsDecremented(int entryToDelete) - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - TTest keyToDelete = entryToDelete; - Assert.Success(kvStore.Delete(in keyToDelete)); - - Assert.Equal(count - 1, kvStore.Count); - } - - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(9)] - public void Delete_PopulatedStoreAndEntryExists_RemainingEntriesAreSorted(int entryToDelete) - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - TTest keyToDelete = entryToDelete; - Assert.Success(kvStore.Delete(in keyToDelete)); - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - - // Check if the remaining keys exist in order - for (int i = 0; i < count; i++) - { - if (i == entryToDelete) - continue; - - TTest expectedKey = i; - - Assert.Equal(expectedKey, iterator.Get().Key); - - iterator.Next(); - } - - Assert.True(iterator.IsEnd()); - } - - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(9)] - public void GetLowerBoundIterator_EntryExists_StartsIterationAtSpecifiedKey(int startEntry) - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - TTest startingKey = startEntry; - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in startingKey); - - Assert.False(iterator.IsEnd()); - Assert.Equal(startingKey, iterator.Get().Key); - } - - [Theory] - [InlineData(1)] - [InlineData(5)] - [InlineData(9)] - public void GetLowerBoundIterator_EntryDoesNotExist_StartsIterationAtNextLargestKey(int startIndex) - { - const int count = 10; - const int startingValueSize = 20; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - - byte[][] values = GenerateValues(count, startingValueSize); - - for (int i = 0; i < count; i++) - { - TTest key = i * 2; - Assert.Success(kvStore.Set(in key, values[i])); - } - - TTest startingKey = startIndex; - TTest nextLargestKey = startIndex + 1; - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in startingKey); - - Assert.False(iterator.IsEnd()); - Assert.Equal(nextLargestKey, iterator.Get().Key); - } - - [Fact] - public void GetLowerBoundIterator_LargerThanAllKeysInStore_IteratorIsAtEnd() - { - const int count = 10; - const int startIndex = 20; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - TTest key = startIndex; - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in key); - - Assert.True(iterator.IsEnd()); - } - - [Theory] - [InlineData(2, 3, 2)] - [InlineData(3, 3, 4)] - [InlineData(5, 3, 5)] - public void FixIterator_RemoveEntry_IteratorPointsToSameEntry(int positionWhenRemoving, int entryToRemove, int expectedNewPosition) - { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - - while (iterator.Get().Key != positionWhenRemoving) - { - iterator.Next(); - } - - TTest keyToRemove = entryToRemove; - Assert.Success(kvStore.Delete(in keyToRemove)); - - kvStore.FixIterator(ref iterator, in keyToRemove); - - TTest expectedKey = expectedNewPosition; Assert.Equal(expectedKey, iterator.Get().Key); + + iterator.Next(); } - [Theory] - [InlineData(6, 7, 6)] - [InlineData(8, 7, 8)] - public void FixIterator_AddEntry_IteratorPointsToSameEntry(int positionWhenAdding, int entryToAdd, int expectedNewPosition) + Assert.True(iterator.IsEnd()); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(9)] + public void GetLowerBoundIterator_EntryExists_StartsIterationAtSpecifiedKey(int startEntry) + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest startingKey = startEntry; + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in startingKey); + + Assert.False(iterator.IsEnd()); + Assert.Equal(startingKey, iterator.Get().Key); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(9)] + public void GetLowerBoundIterator_EntryDoesNotExist_StartsIterationAtNextLargestKey(int startIndex) + { + const int count = 10; + const int startingValueSize = 20; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + + byte[][] values = GenerateValues(count, startingValueSize); + + for (int i = 0; i < count; i++) { - const int count = 10; - const int startingValueSize = 20; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count + 5); - - byte[][] values = GenerateValues(count, startingValueSize); - - for (int i = 0; i < count; i++) - { - TTest key = i * 2; - Assert.Success(kvStore.Set(in key, values[i])); - } - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - - while (iterator.Get().Key != positionWhenAdding) - { - iterator.Next(); - } - - TTest keyToAdd = entryToAdd; - byte[] valueToAdd = new byte[10]; - - Assert.Success(kvStore.Set(in keyToAdd, valueToAdd)); - - kvStore.FixIterator(ref iterator, in keyToAdd); - - TTest expectedKey = expectedNewPosition; - Assert.Equal(expectedKey, iterator.Get().Key); + TTest key = i * 2; + Assert.Success(kvStore.Set(in key, values[i])); } - [Fact] - public void IteratorIsEnd_EmptyStore_ReturnsTrue() + TTest startingKey = startIndex; + TTest nextLargestKey = startIndex + 1; + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in startingKey); + + Assert.False(iterator.IsEnd()); + Assert.Equal(nextLargestKey, iterator.Get().Key); + } + + [Fact] + public void GetLowerBoundIterator_LargerThanAllKeysInStore_IteratorIsAtEnd() + { + const int count = 10; + const int startIndex = 20; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + TTest key = startIndex; + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetLowerBoundIterator(in key); + + Assert.True(iterator.IsEnd()); + } + + [Theory] + [InlineData(2, 3, 2)] + [InlineData(3, 3, 4)] + [InlineData(5, 3, 5)] + public void FixIterator_RemoveEntry_IteratorPointsToSameEntry(int positionWhenRemoving, int entryToRemove, int expectedNewPosition) + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + while (iterator.Get().Key != positionWhenRemoving) { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - Assert.True(iterator.IsEnd()); + iterator.Next(); } - [Fact] - public void IteratorIsEnd_PopulatedStore_ReturnsFalseUntilFinishedIterating() + TTest keyToRemove = entryToRemove; + Assert.Success(kvStore.Delete(in keyToRemove)); + + kvStore.FixIterator(ref iterator, in keyToRemove); + + TTest expectedKey = expectedNewPosition; + Assert.Equal(expectedKey, iterator.Get().Key); + } + + [Theory] + [InlineData(6, 7, 6)] + [InlineData(8, 7, 8)] + public void FixIterator_AddEntry_IteratorPointsToSameEntry(int positionWhenAdding, int entryToAdd, int expectedNewPosition) + { + const int count = 10; + const int startingValueSize = 20; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count + 5); + + byte[][] values = GenerateValues(count, startingValueSize); + + for (int i = 0; i < count; i++) { - const int count = 10; - - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out _, count)); - - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); - - for (int i = 0; i < count; i++) - { - Assert.False(iterator.IsEnd()); - iterator.Next(); - } - - // Iterated all entries. Should return true now - Assert.True(iterator.IsEnd()); + TTest key = i * 2; + Assert.Success(kvStore.Set(in key, values[i])); } - [Fact] - public void IteratorGet_PopulatedStore_ReturnsEntriesInOrder() + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + while (iterator.Get().Key != positionWhenAdding) { - const int count = 10; + iterator.Next(); + } - (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); - Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); + TTest keyToAdd = entryToAdd; + byte[] valueToAdd = new byte[10]; - FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + Assert.Success(kvStore.Set(in keyToAdd, valueToAdd)); - // Check if each key-value pair matches - for (int i = 0; i < count; i++) - { - TTest expectedKey = i; - byte[] expectedValue = values[i]; + kvStore.FixIterator(ref iterator, in keyToAdd); - ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); + TTest expectedKey = expectedNewPosition; + Assert.Equal(expectedKey, iterator.Get().Key); + } - Assert.Equal(expectedKey, kv.Key); - Assert.Equal(expectedValue, kv.Value.Get().ToArray()); + [Fact] + public void IteratorIsEnd_EmptyStore_ReturnsTrue() + { + const int count = 10; - iterator.Next(); - } + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + Assert.True(iterator.IsEnd()); + } + + [Fact] + public void IteratorIsEnd_PopulatedStore_ReturnsFalseUntilFinishedIterating() + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out _, count)); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + for (int i = 0; i < count; i++) + { + Assert.False(iterator.IsEnd()); + iterator.Next(); + } + + // Iterated all entries. Should return true now + Assert.True(iterator.IsEnd()); + } + + [Fact] + public void IteratorGet_PopulatedStore_ReturnsEntriesInOrder() + { + const int count = 10; + + (FlatMapKeyValueStore<TTest> kvStore, FileSystemClient _) = Create<TTest>(count); + Assert.Success(PopulateKvStore(kvStore, out byte[][] values, count)); + + FlatMapKeyValueStore<TTest>.Iterator iterator = kvStore.GetBeginIterator(); + + // Check if each key-value pair matches + for (int i = 0; i < count; i++) + { + TTest expectedKey = i; + byte[] expectedValue = values[i]; + + ref FlatMapKeyValueStore<TTest>.KeyValue kv = ref iterator.Get(); + + Assert.Equal(expectedKey, kv.Key); + Assert.Equal(expectedValue, kv.Value.Get().ToArray()); + + iterator.Next(); } } } diff --git a/tests/LibHac.Tests/LibHac.Tests.csproj b/tests/LibHac.Tests/LibHac.Tests.csproj index 0c35d458..7d81d314 100644 --- a/tests/LibHac.Tests/LibHac.Tests.csproj +++ b/tests/LibHac.Tests/LibHac.Tests.csproj @@ -2,6 +2,7 @@ <PropertyGroup> <TargetFramework>net5.0</TargetFramework> + <LangVersion>10.0</LangVersion> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <IsPackable>false</IsPackable> diff --git a/tests/LibHac.Tests/LibHacTestFramework.cs b/tests/LibHac.Tests/LibHacTestFramework.cs index d7864490..45f76eee 100644 --- a/tests/LibHac.Tests/LibHacTestFramework.cs +++ b/tests/LibHac.Tests/LibHacTestFramework.cs @@ -7,40 +7,39 @@ using Xunit.Sdk; [assembly: Xunit.TestFramework("LibHac.Tests." + nameof(LibHacTestFramework), "LibHac.Tests")] -namespace LibHac.Tests +namespace LibHac.Tests; + +public class LibHacTestFramework : XunitTestFramework { - public class LibHacTestFramework : XunitTestFramework + public LibHacTestFramework(IMessageSink messageSink) + : base(messageSink) { - public LibHacTestFramework(IMessageSink messageSink) - : base(messageSink) - { - SetDebugHandler(); - SetResultNames(); - } + SetDebugHandler(); + SetResultNames(); + } - // Todo: Catch assertions in PathToolTestGenerator.cpp - private static readonly string[] SkipAbortFunctions = { "Normalize" }; + // Todo: Catch assertions in PathToolTestGenerator.cpp + private static readonly string[] SkipAbortFunctions = { "Normalize" }; - private static void SetDebugHandler() + private static void SetDebugHandler() + { + AssertionFailureHandler handler = (in AssertionInfo info) => { - AssertionFailureHandler handler = (in AssertionInfo info) => + if (SkipAbortFunctions.Contains(info.FunctionName)) { - if (SkipAbortFunctions.Contains(info.FunctionName)) - { - return AssertionFailureOperation.Continue; - } + return AssertionFailureOperation.Continue; + } - return AssertionFailureOperation.Abort; - }; + return AssertionFailureOperation.Abort; + }; - Assert.SetAssertionFailureHandler(handler); - Trace.Listeners.Clear(); - Trace.Listeners.Add(new DebugAssertHandler()); - } + Assert.SetAssertionFailureHandler(handler); + Trace.Listeners.Clear(); + Trace.Listeners.Add(new DebugAssertHandler()); + } - private static void SetResultNames() - { - Result.SetNameResolver(new ResultNameResolver()); - } + private static void SetResultNames() + { + Result.SetNameResolver(new ResultNameResolver()); } } diff --git a/tests/LibHac.Tests/Loader/TypeSizeTests.cs b/tests/LibHac.Tests/Loader/TypeSizeTests.cs index 56ef42aa..27f4f2e3 100644 --- a/tests/LibHac.Tests/Loader/TypeSizeTests.cs +++ b/tests/LibHac.Tests/Loader/TypeSizeTests.cs @@ -2,26 +2,25 @@ using LibHac.Loader; using Xunit; -namespace LibHac.Tests.Loader +namespace LibHac.Tests.Loader; + +public class TypeSizeTests { - public class TypeSizeTests + [Fact] + public static void MetaSizeIsCorrect() { - [Fact] - public static void MetaSizeIsCorrect() - { - Assert.Equal(0x80, Unsafe.SizeOf<Meta>()); - } + Assert.Equal(0x80, Unsafe.SizeOf<Meta>()); + } - [Fact] - public static void AciSizeIsCorrect() - { - Assert.Equal(0x40, Unsafe.SizeOf<AciHeader>()); - } + [Fact] + public static void AciSizeIsCorrect() + { + Assert.Equal(0x40, Unsafe.SizeOf<AciHeader>()); + } - [Fact] - public static void AcidSizeIsCorrect() - { - Assert.Equal(0x240, Unsafe.SizeOf<AcidHeaderData>()); - } + [Fact] + public static void AcidSizeIsCorrect() + { + Assert.Equal(0x240, Unsafe.SizeOf<AcidHeaderData>()); } } diff --git a/tests/LibHac.Tests/PathToolsTests.cs b/tests/LibHac.Tests/PathToolsTests.cs index d6284594..77d95b45 100644 --- a/tests/LibHac.Tests/PathToolsTests.cs +++ b/tests/LibHac.Tests/PathToolsTests.cs @@ -6,12 +6,12 @@ using LibHac.FsSystem; using LibHac.Util; using Xunit; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class PathToolsTests { - public class PathToolsTests + public static object[][] NormalizedPathTestItems = { - public static object[][] NormalizedPathTestItems = - { new object[] {"", "/"}, new object[] {"/", "/"}, new object[] {"/.", "/"}, @@ -54,8 +54,8 @@ namespace LibHac.Tests new object[] {"mount:/d./aa", "mount:/d./aa"}, }; - public static object[][] SubPathTestItems = - { + public static object[][] SubPathTestItems = + { new object[] {"/", "/", false}, new object[] {"/", "/a", true}, new object[] {"/", "/a/", true}, @@ -89,8 +89,8 @@ namespace LibHac.Tests new object[] {"mount:/a/b/c/", "mount:/a/b/cd", false}, }; - public static object[][] ParentDirectoryTestItems = - { + public static object[][] ParentDirectoryTestItems = + { new object[] {"/", ""}, new object[] {"/a", "/"}, new object[] {"/aa/aabc/f", "/aa/aabc"}, @@ -99,81 +99,81 @@ namespace LibHac.Tests new object[] {"mount:/aa/aabc/f", "mount:/aa/aabc"} }; - public static object[][] IsNormalizedTestItems = GetNormalizedPaths(true); + public static object[][] IsNormalizedTestItems = GetNormalizedPaths(true); - public static object[][] IsNotNormalizedTestItems = GetNormalizedPaths(false); + public static object[][] IsNotNormalizedTestItems = GetNormalizedPaths(false); - [Theory] - [MemberData(nameof(NormalizedPathTestItems))] - public static void NormalizePath(string path, string expected) + [Theory] + [MemberData(nameof(NormalizedPathTestItems))] + public static void NormalizePath(string path, string expected) + { + string actual = PathTools.Normalize(path); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(IsNormalizedTestItems))] + public static void IsNormalized(string path) + { + Assert.True(PathTools.IsNormalized(path.AsSpan())); + } + + [Theory] + [MemberData(nameof(IsNotNormalizedTestItems))] + public static void IsNotNormalized(string path) + { + Assert.False(PathTools.IsNormalized(path.AsSpan())); + } + + [Theory] + [MemberData(nameof(SubPathTestItems))] + public static void TestSubPath(string rootPath, string path, bool expected) + { + bool actual = PathTools.IsSubPath(rootPath.AsSpan(), path.AsSpan()); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(SubPathTestItems))] + public static void TestSubPathReverse(string rootPath, string path, bool expected) + { + bool actual = PathTools.IsSubPath(path.AsSpan(), rootPath.AsSpan()); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(ParentDirectoryTestItems))] + public static void TestParentDirectory(string path, string expected) + { + string actual = PathTools.GetParentDirectory(path); + + Assert.Equal(expected, actual); + } + + private static object[][] GetNormalizedPaths(bool getNormalized) + { + var normalizedPaths = new HashSet<string>(); + var notNormalizedPaths = new HashSet<string>(); + + foreach (object[] pair in NormalizedPathTestItems) { - string actual = PathTools.Normalize(path); + string pathNotNorm = (string)pair[0]; + string pathNorm = (string)pair[1]; - Assert.Equal(expected, actual); + if (pathNorm != pathNotNorm) notNormalizedPaths.Add(pathNotNorm); + normalizedPaths.Add(pathNorm); } - [Theory] - [MemberData(nameof(IsNormalizedTestItems))] - public static void IsNormalized(string path) - { - Assert.True(PathTools.IsNormalized(path.AsSpan())); - } + HashSet<string> paths = getNormalized ? normalizedPaths : notNormalizedPaths; - [Theory] - [MemberData(nameof(IsNotNormalizedTestItems))] - public static void IsNotNormalized(string path) - { - Assert.False(PathTools.IsNormalized(path.AsSpan())); - } + return paths.Select(x => new object[] { x }).ToArray(); + } - [Theory] - [MemberData(nameof(SubPathTestItems))] - public static void TestSubPath(string rootPath, string path, bool expected) - { - bool actual = PathTools.IsSubPath(rootPath.AsSpan(), path.AsSpan()); - - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(SubPathTestItems))] - public static void TestSubPathReverse(string rootPath, string path, bool expected) - { - bool actual = PathTools.IsSubPath(path.AsSpan(), rootPath.AsSpan()); - - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(ParentDirectoryTestItems))] - public static void TestParentDirectory(string path, string expected) - { - string actual = PathTools.GetParentDirectory(path); - - Assert.Equal(expected, actual); - } - - private static object[][] GetNormalizedPaths(bool getNormalized) - { - var normalizedPaths = new HashSet<string>(); - var notNormalizedPaths = new HashSet<string>(); - - foreach (object[] pair in NormalizedPathTestItems) - { - string pathNotNorm = (string)pair[0]; - string pathNorm = (string)pair[1]; - - if (pathNorm != pathNotNorm) notNormalizedPaths.Add(pathNotNorm); - normalizedPaths.Add(pathNorm); - } - - HashSet<string> paths = getNormalized ? normalizedPaths : notNormalizedPaths; - - return paths.Select(x => new object[] { x }).ToArray(); - } - - public static object[][] GetFileNameTestItems = - { + public static object[][] GetFileNameTestItems = + { new object[] {"/a/bb/ccc", "ccc"}, new object[] {"/a/bb/ccc/", ""}, new object[] {"/a/bb", "bb"}, @@ -183,21 +183,21 @@ namespace LibHac.Tests new object[] {"/", ""}, }; - [Theory] - [MemberData(nameof(GetFileNameTestItems))] - public static void GetFileNameTest(string path, string expected) - { - var u8Path = path.ToU8String(); + [Theory] + [MemberData(nameof(GetFileNameTestItems))] + public static void GetFileNameTest(string path, string expected) + { + var u8Path = path.ToU8String(); - ReadOnlySpan<byte> fileName = PathTools.GetFileName(u8Path); + ReadOnlySpan<byte> fileName = PathTools.GetFileName(u8Path); - string actual = StringUtils.Utf8ZToString(fileName); + string actual = StringUtils.Utf8ZToString(fileName); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - public static object[][] GetLastSegmentTestItems = - { + public static object[][] GetLastSegmentTestItems = + { new object[] {"/a/bb/ccc", "ccc"}, new object[] {"/a/bb/ccc/", "ccc"}, new object[] {"/a/bb", "bb"}, @@ -207,17 +207,16 @@ namespace LibHac.Tests new object[] {"/", ""}, }; - [Theory] - [MemberData(nameof(GetLastSegmentTestItems))] - public static void GetLastSegmentTest(string path, string expected) - { - var u8Path = path.ToU8String(); + [Theory] + [MemberData(nameof(GetLastSegmentTestItems))] + public static void GetLastSegmentTest(string path, string expected) + { + var u8Path = path.ToU8String(); - ReadOnlySpan<byte> fileName = PathTools.GetLastSegment(u8Path); + ReadOnlySpan<byte> fileName = PathTools.GetLastSegment(u8Path); - string actual = StringUtils.Utf8ZToString(fileName); + string actual = StringUtils.Utf8ZToString(fileName); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } } diff --git a/tests/LibHac.Tests/Random.cs b/tests/LibHac.Tests/Random.cs index 32e66999..3e4867e1 100644 --- a/tests/LibHac.Tests/Random.cs +++ b/tests/LibHac.Tests/Random.cs @@ -2,64 +2,63 @@ using System.Numerics; using System.Runtime.InteropServices; -namespace LibHac.Tests +namespace LibHac.Tests; + +public struct Random { - public struct Random + private ulong _state1; + private ulong _state2; + + public Random(ulong seed) { - private ulong _state1; - private ulong _state2; + ulong x = seed; + ulong z = x + 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + x = z ^ (z >> 31); + z = (x += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + _state1 = z ^ (z >> 31); + _state2 = x; + } - public Random(ulong seed) + ulong Next() + { + ulong s0 = _state1; + ulong s1 = _state2; + ulong result = BitOperations.RotateLeft(s0 + s1, 17) + s0; + + s1 ^= s0; + _state1 = BitOperations.RotateLeft(s0, 49) ^ s1 ^ (s1 << 21); + _state2 = BitOperations.RotateLeft(s1, 28); + + return result; + } + + public int Next(int minValue, int maxValue) + { + if (minValue > maxValue) { - ulong x = seed; - ulong z = x + 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - x = z ^ (z >> 31); - z = (x += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - _state1 = z ^ (z >> 31); - _state2 = x; + throw new ArgumentOutOfRangeException(nameof(minValue)); } - ulong Next() + long range = (long)maxValue - minValue; + return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue; + } + + public void NextBytes(Span<byte> buffer) + { + Span<ulong> bufferUlong = MemoryMarshal.Cast<byte, ulong>(buffer); + + for (int i = 0; i < bufferUlong.Length; i++) { - ulong s0 = _state1; - ulong s1 = _state2; - ulong result = BitOperations.RotateLeft(s0 + s1, 17) + s0; - - s1 ^= s0; - _state1 = BitOperations.RotateLeft(s0, 49) ^ s1 ^ (s1 << 21); - _state2 = BitOperations.RotateLeft(s1, 28); - - return result; + bufferUlong[i] = Next(); } - public int Next(int minValue, int maxValue) + for (int i = bufferUlong.Length * sizeof(ulong); i < buffer.Length; i++) { - if (minValue > maxValue) - { - throw new ArgumentOutOfRangeException(nameof(minValue)); - } - - long range = (long)maxValue - minValue; - return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue; - } - - public void NextBytes(Span<byte> buffer) - { - Span<ulong> bufferUlong = MemoryMarshal.Cast<byte, ulong>(buffer); - - for (int i = 0; i < bufferUlong.Length; i++) - { - bufferUlong[i] = Next(); - } - - for (int i = bufferUlong.Length * sizeof(ulong); i < buffer.Length; i++) - { - buffer[i] = (byte)Next(); - } + buffer[i] = (byte)Next(); } } } diff --git a/tests/LibHac.Tests/ResultAsserts.cs b/tests/LibHac.Tests/ResultAsserts.cs index c158bae6..335f72e0 100644 --- a/tests/LibHac.Tests/ResultAsserts.cs +++ b/tests/LibHac.Tests/ResultAsserts.cs @@ -2,24 +2,23 @@ using Xunit.Sdk; // ReSharper disable once CheckNamespace -namespace Xunit +namespace Xunit; + +public partial class Assert { - public partial class Assert + public static void Success(Result result) { - public static void Success(Result result) - { - Equal(LibHac.Result.Success, result); - } + Equal(LibHac.Result.Success, result); + } - public static void Failure(Result result) - { - NotEqual(LibHac.Result.Success, result); - } + public static void Failure(Result result) + { + NotEqual(LibHac.Result.Success, result); + } - public static void Result(Result.Base expected, Result actual) - { - if (!expected.Includes(actual)) - throw new EqualException(expected.Value, actual); - } + public static void Result(Result.Base expected, Result actual) + { + if (!expected.Includes(actual)) + throw new EqualException(expected.Value, actual); } } diff --git a/tests/LibHac.Tests/ResultNameResolver.cs b/tests/LibHac.Tests/ResultNameResolver.cs index f133ddd6..cfef672c 100644 --- a/tests/LibHac.Tests/ResultNameResolver.cs +++ b/tests/LibHac.Tests/ResultNameResolver.cs @@ -3,37 +3,36 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace LibHac.Tests +namespace LibHac.Tests; + +internal class ResultNameResolver : Result.IResultNameResolver { - internal class ResultNameResolver : Result.IResultNameResolver + private Lazy<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames); + + public bool TryResolveName(Result result, out string name) { - private Lazy<Dictionary<Result, string>> ResultNames { get; } = new Lazy<Dictionary<Result, string>>(GetResultNames); + return ResultNames.Value.TryGetValue(result, out name); + } - public bool TryResolveName(Result result, out string name) - { - return ResultNames.Value.TryGetValue(result, out name); - } + private static Dictionary<Result, string> GetResultNames() + { + var dict = new Dictionary<Result, string>(); - private static Dictionary<Result, string> GetResultNames() - { - var dict = new Dictionary<Result, string>(); + Assembly assembly = typeof(Result).Assembly; - Assembly assembly = typeof(Result).Assembly; + foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) + foreach (PropertyInfo property in type.DeclaredProperties + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) + { + object value = property.GetValue(null, null); + if (value is null) continue; - foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) - foreach (PropertyInfo property in type.DeclaredProperties - .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) - { - object value = property.GetValue(null, null); - if (value is null) continue; + Result resultValue = ((Result.Base)value).Value; + string name = $"{type.Name}{property.Name}"; - Result resultValue = ((Result.Base)value).Value; - string name = $"{type.Name}{property.Name}"; + dict[resultValue] = name; + } - dict[resultValue] = name; - } - - return dict; - } + return dict; } } diff --git a/tests/LibHac.Tests/RomFsTests.cs b/tests/LibHac.Tests/RomFsTests.cs index 5163e8fa..15e1d26c 100644 --- a/tests/LibHac.Tests/RomFsTests.cs +++ b/tests/LibHac.Tests/RomFsTests.cs @@ -2,203 +2,202 @@ using LibHac.FsSystem.RomFs; using Xunit; -namespace LibHac.Tests +namespace LibHac.Tests; + +public class RomFsTests { - public class RomFsTests + [Fact] + public void SimpleAddAndRead() { - [Fact] - public void SimpleAddAndRead() + const string path = "/a/b"; + + var table = new HierarchicalRomFileTable<RomFileInfo>(); + 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<RomFileInfo>(); + 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<RomFileInfo>(); + 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<RomFileInfo>(); + var item = new RomFileInfo(); + + Assert.Throws<ArgumentException>(() => table.AddFile("", ref item)); + } + + [Fact] + public void OpeningNonexistentFileFails() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); + + bool success = table.TryOpenFile("/foo", out _); + Assert.False(success); + } + + [Fact] + public void OpeningNonexistentDirectoryFails() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); + + bool success = table.TryOpenDirectory("/foo", out _); + Assert.False(success); + } + + [Fact] + public void OpeningFileAsDirectoryFails() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); + 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<RomFileInfo>(); + table.AddDirectory("/dir"); + + bool success = table.TryOpenFile("/dir", out _); + Assert.False(success); + } + + [Fact] + public void ChildFileIteration() + { + const int fileCount = 10; + var table = new HierarchicalRomFileTable<RomFileInfo>(); + + for (int i = 0; i < fileCount; i++) { - const string path = "/a/b"; - - var table = new HierarchicalRomFileTable<RomFileInfo>(); - 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); + var item = new RomFileInfo { Length = i, Offset = i }; + table.AddFile($"/a/{i}", ref item); } - [Fact] - public void UpdateExistingFile() + bool openDirSuccess = table.TryOpenDirectory("/a", out FindPosition position); + Assert.True(openDirSuccess, "Error opening directory"); + + for (int i = 0; i < fileCount; i++) { - const string path = "/a/b"; + var expectedItem = new RomFileInfo { Length = i, Offset = i }; + string expectedName = i.ToString(); - var table = new HierarchicalRomFileTable<RomFileInfo>(); - var originalItem = new RomFileInfo { Length = 1, Offset = 1 }; - var newItem = new RomFileInfo { Length = 1, Offset = 1 }; + bool success = table.FindNextFile(ref position, out RomFileInfo actualItem, out string actualName); - 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); + Assert.True(success, $"Failed reading file {i}"); + Assert.Equal(expectedItem, actualItem); + Assert.Equal(expectedName, actualName); } - [Fact] - public void AddingDirectory() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); - var expectedPosition = new FindPosition { NextDirectory = -1, NextFile = -1 }; + bool endOfFilesSuccess = table.FindNextFile(ref position, out _, out _); + Assert.False(endOfFilesSuccess, "Table returned more files than it should"); + } - table.AddDirectory("/dir"); - bool success = table.TryOpenDirectory("/dir", out FindPosition position); + [Fact] + public void ChildFileIterationPeek() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); - Assert.True(success, "Opening directory failed"); - Assert.Equal(expectedPosition, position); - } + var itemA = new RomFileInfo { Length = 1, Offset = 1 }; + var itemB = new RomFileInfo { Length = 2, Offset = 2 }; - [Fact] - public void AddingEmptyPathThrows() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); - var item = new RomFileInfo(); + table.AddFile("/a/a", ref itemA); + table.AddFile("/a/b", ref itemB); - Assert.Throws<ArgumentException>(() => table.AddFile("", ref item)); - } + table.TryOpenDirectory("/a", out FindPosition position); - [Fact] - public void OpeningNonexistentFileFails() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); + table.TryOpenFile(position.NextFile, out RomFileInfo peekItemA); + Assert.Equal(itemA, peekItemA); - bool success = table.TryOpenFile("/foo", out _); - Assert.False(success); - } + table.FindNextFile(ref position, out RomFileInfo iterateItemA, out _); + Assert.Equal(itemA, iterateItemA); - [Fact] - public void OpeningNonexistentDirectoryFails() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); + table.TryOpenFile(position.NextFile, out RomFileInfo peekItemB); + Assert.Equal(itemB, peekItemB); - bool success = table.TryOpenDirectory("/foo", out _); - Assert.False(success); - } + table.FindNextFile(ref position, out RomFileInfo iterateItemB, out _); + Assert.Equal(itemB, iterateItemB); + } - [Fact] - public void OpeningFileAsDirectoryFails() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); - var fileInfo = new RomFileInfo(); - table.AddFile("/file", ref fileInfo); + [Fact] + public void AddingCousinFiles() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); - bool success = table.TryOpenDirectory("/file", out _); - Assert.False(success); - } + var itemB1 = new RomFileInfo { Length = 1, Offset = 1 }; + var itemB2 = new RomFileInfo { Length = 2, Offset = 2 }; + var itemB3 = new RomFileInfo { Length = 3, Offset = 3 }; - [Fact] - public void OpeningDirectoryAsFileFails() - { - var table = new HierarchicalRomFileTable<RomFileInfo>(); - table.AddDirectory("/dir"); + table.AddFile("/a/b1/c", ref itemB1); + table.AddFile("/a/b2/c", ref itemB2); + table.AddFile("/a/b3/c", ref itemB3); - bool success = table.TryOpenFile("/dir", out _); - Assert.False(success); - } + table.TryOpenFile("/a/b1/c", out RomFileInfo actualItemB1); + table.TryOpenFile("/a/b2/c", out RomFileInfo actualItemB2); + table.TryOpenFile("/a/b3/c", out RomFileInfo actualItemB3); - [Fact] - public void ChildFileIteration() - { - const int fileCount = 10; - var table = new HierarchicalRomFileTable<RomFileInfo>(); + Assert.Equal(itemB1, actualItemB1); + Assert.Equal(itemB2, actualItemB2); + Assert.Equal(itemB3, actualItemB3); + } - for (int i = 0; i < fileCount; i++) - { - var item = new RomFileInfo { Length = i, Offset = i }; - table.AddFile($"/a/{i}", ref item); - } + [Fact] + public void AddingSiblingFiles() + { + var table = new HierarchicalRomFileTable<RomFileInfo>(); - bool openDirSuccess = table.TryOpenDirectory("/a", out FindPosition position); - Assert.True(openDirSuccess, "Error opening directory"); + var itemC1 = new RomFileInfo { Length = 1, Offset = 1 }; + var itemC2 = new RomFileInfo { Length = 2, Offset = 2 }; + var itemC3 = new RomFileInfo { Length = 3, Offset = 3 }; - for (int i = 0; i < fileCount; i++) - { - var expectedItem = new RomFileInfo { Length = i, Offset = i }; - string expectedName = i.ToString(); + table.AddFile("/a/b/c1", ref itemC1); + table.AddFile("/a/b/c2", ref itemC2); + table.AddFile("/a/b/c3", ref itemC3); - bool success = table.FindNextFile(ref position, out RomFileInfo actualItem, out string actualName); + 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.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<RomFileInfo>(); - - 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<RomFileInfo>(); - - 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<RomFileInfo>(); - - 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); - } + Assert.Equal(itemC1, actualItemC1); + Assert.Equal(itemC2, actualItemC2); + Assert.Equal(itemC3, actualItemC3); } } diff --git a/tests/LibHac.Tests/SpanEqualAsserts.cs b/tests/LibHac.Tests/SpanEqualAsserts.cs index 3cd04c66..038b39d5 100644 --- a/tests/LibHac.Tests/SpanEqualAsserts.cs +++ b/tests/LibHac.Tests/SpanEqualAsserts.cs @@ -2,21 +2,20 @@ using Xunit.Sdk; // ReSharper disable once CheckNamespace -namespace Xunit +namespace Xunit; + +public partial class Assert { - public partial class Assert + /// <summary> + /// Verifies that two spans are equal, using a default comparer. + /// </summary> + /// <typeparam name="T">The type of the objects to be compared</typeparam> + /// <param name="expected">The expected value</param> + /// <param name="actual">The value to be compared against</param> + /// <exception cref="EqualException">Thrown when the objects are not equal</exception> + public static void Equal<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual) where T : unmanaged, IEquatable<T> { - /// <summary> - /// Verifies that two spans are equal, using a default comparer. - /// </summary> - /// <typeparam name="T">The type of the objects to be compared</typeparam> - /// <param name="expected">The expected value</param> - /// <param name="actual">The value to be compared against</param> - /// <exception cref="EqualException">Thrown when the objects are not equal</exception> - public static void Equal<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual) where T : unmanaged, IEquatable<T> - { - if(!expected.SequenceEqual(actual)) - throw new EqualException(expected.ToArray(), actual.ToArray()); - } + if (!expected.SequenceEqual(actual)) + throw new EqualException(expected.ToArray(), actual.ToArray()); } } diff --git a/tests/LibHac.Tests/Spl/TypeSizeTests.cs b/tests/LibHac.Tests/Spl/TypeSizeTests.cs index eaacd097..dcbe5463 100644 --- a/tests/LibHac.Tests/Spl/TypeSizeTests.cs +++ b/tests/LibHac.Tests/Spl/TypeSizeTests.cs @@ -3,14 +3,13 @@ using System.Runtime.CompilerServices; using LibHac.Spl; using Xunit; -namespace LibHac.Tests.Spl +namespace LibHac.Tests.Spl; + +public class TypeSizeTests { - public class TypeSizeTests + [Fact] + public static void AccessKeySizeIs0x10() { - [Fact] - public static void AccessKeySizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf<AccessKey>()); - } + Assert.Equal(0x10, Unsafe.SizeOf<AccessKey>()); } } diff --git a/tests/LibHac.Tests/Util/CharacterEncodingTests.cs b/tests/LibHac.Tests/Util/CharacterEncodingTests.cs index c01f949e..d4ecebc1 100644 --- a/tests/LibHac.Tests/Util/CharacterEncodingTests.cs +++ b/tests/LibHac.Tests/Util/CharacterEncodingTests.cs @@ -9,656 +9,655 @@ using System.Text.RegularExpressions; using LibHac.Util; using Xunit; -namespace LibHac.Tests.Util +namespace LibHac.Tests.Util; + +public class CharacterEncodingTests { - public class CharacterEncodingTests + // Most of these tests are stolen from .NET's UTF-8 tests. Some of the comments in this file may + // mention code paths and functions being tested in the .NET runtime that don't apply here as a result. + + // ReSharper disable InconsistentNaming UnusedMember.Local + private const string X_UTF8 = "58"; // U+0058 LATIN CAPITAL LETTER X, 1 byte + private const string X_UTF16 = "X"; + + private const string Y_UTF8 = "59"; // U+0058 LATIN CAPITAL LETTER Y, 1 byte + private const string Y_UTF16 = "Y"; + + private const string Z_UTF8 = "5A"; // U+0058 LATIN CAPITAL LETTER Z, 1 byte + private const string Z_UTF16 = "Z"; + + private const string E_ACUTE_UTF8 = "C3A9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE, 2 bytes + private const string E_ACUTE_UTF16 = "\u00E9"; + + private const string EURO_SYMBOL_UTF8 = "E282AC"; // U+20AC EURO SIGN, 3 bytes + private const string EURO_SYMBOL_UTF16 = "\u20AC"; + + private const string REPLACEMENT_CHAR_UTF8 = "EFBFBD"; // U+FFFD REPLACEMENT CHAR, 3 bytes + private const string REPLACEMENT_CHAR_UTF16 = "\uFFFD"; + + private const string GRINNING_FACE_UTF8 = "F09F9880"; // U+1F600 GRINNING FACE, 4 bytes + private const string GRINNING_FACE_UTF16 = "\U0001F600"; + + private const string WOMAN_CARTWHEELING_MEDSKIN_UTF16 = "\U0001F938\U0001F3FD\u200D\u2640\uFE0F"; // U+1F938 U+1F3FD U+200D U+2640 U+FE0F WOMAN CARTWHEELING: MEDIUM SKIN TONE + + // All valid scalars [ U+0000 .. U+D7FF ] and [ U+E000 .. U+10FFFF ]. + private static readonly IEnumerable<Rune> s_allValidScalars = Enumerable.Range(0x0000, 0xD800).Concat(Enumerable.Range(0xE000, 0x110000 - 0xE000)).Select(value => new Rune(value)); + + private static readonly ReadOnlyMemory<uint> s_allScalarsAsUtf32; + private static readonly ReadOnlyMemory<char> s_allScalarsAsUtf16; + private static readonly ReadOnlyMemory<byte> s_allScalarsAsUtf8; + // ReSharper restore InconsistentNaming UnusedMember.Local + + static CharacterEncodingTests() { - // Most of these tests are stolen from .NET's UTF-8 tests. Some of the comments in this file may - // mention code paths and functions being tested in the .NET runtime that don't apply here as a result. + var allScalarsAsUtf32 = new List<uint>(); + var allScalarsAsUtf16 = new List<char>(); + var allScalarsAsUtf8 = new List<byte>(); - // ReSharper disable InconsistentNaming UnusedMember.Local - private const string X_UTF8 = "58"; // U+0058 LATIN CAPITAL LETTER X, 1 byte - private const string X_UTF16 = "X"; - - private const string Y_UTF8 = "59"; // U+0058 LATIN CAPITAL LETTER Y, 1 byte - private const string Y_UTF16 = "Y"; + Span<byte> utf8 = stackalloc byte[4]; + Span<char> utf16 = stackalloc char[2]; - private const string Z_UTF8 = "5A"; // U+0058 LATIN CAPITAL LETTER Z, 1 byte - private const string Z_UTF16 = "Z"; - - private const string E_ACUTE_UTF8 = "C3A9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE, 2 bytes - private const string E_ACUTE_UTF16 = "\u00E9"; - - private const string EURO_SYMBOL_UTF8 = "E282AC"; // U+20AC EURO SIGN, 3 bytes - private const string EURO_SYMBOL_UTF16 = "\u20AC"; - - private const string REPLACEMENT_CHAR_UTF8 = "EFBFBD"; // U+FFFD REPLACEMENT CHAR, 3 bytes - private const string REPLACEMENT_CHAR_UTF16 = "\uFFFD"; - - private const string GRINNING_FACE_UTF8 = "F09F9880"; // U+1F600 GRINNING FACE, 4 bytes - private const string GRINNING_FACE_UTF16 = "\U0001F600"; - - private const string WOMAN_CARTWHEELING_MEDSKIN_UTF16 = "\U0001F938\U0001F3FD\u200D\u2640\uFE0F"; // U+1F938 U+1F3FD U+200D U+2640 U+FE0F WOMAN CARTWHEELING: MEDIUM SKIN TONE - - // All valid scalars [ U+0000 .. U+D7FF ] and [ U+E000 .. U+10FFFF ]. - private static readonly IEnumerable<Rune> s_allValidScalars = Enumerable.Range(0x0000, 0xD800).Concat(Enumerable.Range(0xE000, 0x110000 - 0xE000)).Select(value => new Rune(value)); - - private static readonly ReadOnlyMemory<uint> s_allScalarsAsUtf32; - private static readonly ReadOnlyMemory<char> s_allScalarsAsUtf16; - private static readonly ReadOnlyMemory<byte> s_allScalarsAsUtf8; - // ReSharper restore InconsistentNaming UnusedMember.Local - - static CharacterEncodingTests() + foreach (Rune rune in s_allValidScalars) { - var allScalarsAsUtf32 = new List<uint>(); - var allScalarsAsUtf16 = new List<char>(); - var allScalarsAsUtf8 = new List<byte>(); + int utf8Length = ToUtf8(rune, utf8); + int utf16Length = ToUtf16(rune, utf16); - Span<byte> utf8 = stackalloc byte[4]; - Span<char> utf16 = stackalloc char[2]; + allScalarsAsUtf32.Add((uint)rune.Value); - foreach (Rune rune in s_allValidScalars) - { - int utf8Length = ToUtf8(rune, utf8); - int utf16Length = ToUtf16(rune, utf16); + for (int i = 0; i < utf16Length; i++) + allScalarsAsUtf16.Add(utf16[i]); - allScalarsAsUtf32.Add((uint)rune.Value); - - for (int i = 0; i < utf16Length; i++) - allScalarsAsUtf16.Add(utf16[i]); - - for (int i = 0; i < utf8Length; i++) - allScalarsAsUtf8.Add(utf8[i]); - } - - s_allScalarsAsUtf32 = allScalarsAsUtf32.ToArray().AsMemory(); - s_allScalarsAsUtf16 = allScalarsAsUtf16.ToArray().AsMemory(); - s_allScalarsAsUtf8 = allScalarsAsUtf8.ToArray().AsMemory(); + for (int i = 0; i < utf8Length; i++) + allScalarsAsUtf8.Add(utf8[i]); } - /* - * COMMON UTILITIES FOR UNIT TESTS - */ + s_allScalarsAsUtf32 = allScalarsAsUtf32.ToArray().AsMemory(); + s_allScalarsAsUtf16 = allScalarsAsUtf16.ToArray().AsMemory(); + s_allScalarsAsUtf8 = allScalarsAsUtf8.ToArray().AsMemory(); + } - public static byte[] DecodeHex(ReadOnlySpan<char> inputHex) + /* + * COMMON UTILITIES FOR UNIT TESTS + */ + + public static byte[] DecodeHex(ReadOnlySpan<char> inputHex) + { + Assert.True(Regex.IsMatch(inputHex.ToString(), "^([0-9a-fA-F]{2})*$"), "Input must be an even number of hex characters."); + + return Convert.FromHexString(inputHex); + } + + public static byte[] ToUtf8(Rune rune) + { + Span<byte> utf8 = stackalloc byte[4]; + + int length = ToUtf8(rune, utf8); + return utf8.Slice(0, length).ToArray(); + } + + private static char[] ToUtf16(Rune rune) + { + Span<char> utf16 = stackalloc char[2]; + + int length = ToUtf16(rune, utf16); + return utf16.Slice(0, length).ToArray(); + } + + // !! IMPORTANT !! + // Don't delete this implementation, as we use it as a reference to make sure the framework's + // transcoding logic is correct. + public static int ToUtf8(Rune rune, Span<byte> destination) + { + if (!Rune.IsValid(rune.Value)) { - Assert.True(Regex.IsMatch(inputHex.ToString(), "^([0-9a-fA-F]{2})*$"), "Input must be an even number of hex characters."); - - return Convert.FromHexString(inputHex); + Assert.True(Rune.IsValid(rune.Value), $"Rune with value U+{(uint)rune.Value:X4} is not well-formed."); } - public static byte[] ToUtf8(Rune rune) - { - Span<byte> utf8 = stackalloc byte[4]; + Assert.True(destination.Length == 4); - int length = ToUtf8(rune, utf8); - return utf8.Slice(0, length).ToArray(); + destination[0] = 0; + destination[1] = 0; + destination[2] = 0; + destination[3] = 0; + + if (rune.Value < 0x80) + { + destination[0] = (byte)rune.Value; + return 1; + } + else if (rune.Value < 0x0800) + { + destination[0] = (byte)((rune.Value >> 6) | 0xC0); + destination[1] = (byte)((rune.Value & 0x3F) | 0x80); + return 2; + } + else if (rune.Value < 0x10000) + { + destination[0] = (byte)((rune.Value >> 12) | 0xE0); + destination[1] = (byte)(((rune.Value >> 6) & 0x3F) | 0x80); + destination[2] = (byte)((rune.Value & 0x3F) | 0x80); + return 3; + } + else + { + destination[0] = (byte)((rune.Value >> 18) | 0xF0); + destination[1] = (byte)(((rune.Value >> 12) & 0x3F) | 0x80); + destination[2] = (byte)(((rune.Value >> 6) & 0x3F) | 0x80); + destination[3] = (byte)((rune.Value & 0x3F) | 0x80); + return 4; + } + } + + // !! IMPORTANT !! + // Don't delete this implementation, as we use it as a reference to make sure the framework's + // transcoding logic is correct. + private static int ToUtf16(Rune rune, Span<char> destination) + { + if (!Rune.IsValid(rune.Value)) + { + Assert.True(Rune.IsValid(rune.Value), $"Rune with value U+{(uint)rune.Value:X4} is not well-formed."); } - private static char[] ToUtf16(Rune rune) - { - Span<char> utf16 = stackalloc char[2]; + Assert.True(destination.Length == 2); - int length = ToUtf16(rune, utf16); - return utf16.Slice(0, length).ToArray(); + destination[0] = '\0'; + destination[1] = '\0'; + + if (rune.IsBmp) + { + destination[0] = (char)rune.Value; + return 1; } - - // !! IMPORTANT !! - // Don't delete this implementation, as we use it as a reference to make sure the framework's - // transcoding logic is correct. - public static int ToUtf8(Rune rune, Span<byte> destination) + else { - if (!Rune.IsValid(rune.Value)) - { - Assert.True(Rune.IsValid(rune.Value), $"Rune with value U+{(uint)rune.Value:X4} is not well-formed."); - } - - Assert.True(destination.Length == 4); - - destination[0] = 0; - destination[1] = 0; - destination[2] = 0; - destination[3] = 0; - - if (rune.Value < 0x80) - { - destination[0] = (byte)rune.Value; - return 1; - } - else if (rune.Value < 0x0800) - { - destination[0] = (byte)((rune.Value >> 6) | 0xC0); - destination[1] = (byte)((rune.Value & 0x3F) | 0x80); - return 2; - } - else if (rune.Value < 0x10000) - { - destination[0] = (byte)((rune.Value >> 12) | 0xE0); - destination[1] = (byte)(((rune.Value >> 6) & 0x3F) | 0x80); - destination[2] = (byte)((rune.Value & 0x3F) | 0x80); - return 3; - } - else - { - destination[0] = (byte)((rune.Value >> 18) | 0xF0); - destination[1] = (byte)(((rune.Value >> 12) & 0x3F) | 0x80); - destination[2] = (byte)(((rune.Value >> 6) & 0x3F) | 0x80); - destination[3] = (byte)((rune.Value & 0x3F) | 0x80); - return 4; - } + destination[0] = (char)((rune.Value >> 10) + 0xD800 - 0x40); + destination[1] = (char)((rune.Value & 0x03FF) + 0xDC00); + return 2; } + } - // !! IMPORTANT !! - // Don't delete this implementation, as we use it as a reference to make sure the framework's - // transcoding logic is correct. - private static int ToUtf16(Rune rune, Span<char> destination) + [Theory] + [InlineData("", "")] // empty string is OK + [InlineData(X_UTF16, X_UTF8)] + [InlineData(E_ACUTE_UTF16, E_ACUTE_UTF8)] + [InlineData(EURO_SYMBOL_UTF16, EURO_SYMBOL_UTF8)] + public void Utf16ToUtf8_WithSmallValidBuffers(string utf16Input, string expectedUtf8TranscodingHex) + { + Assert.InRange(utf16Input.Length, 0, 1); + + Utf16ToUtf8_String_Test_Core( + utf16Input: utf16Input, + destinationSize: expectedUtf8TranscodingHex.Length / 2, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); + } + + [Theory] + [InlineData('\uD800', CharacterEncodingResult.InsufficientLength)] // standalone high surrogate + [InlineData('\uDFFF', CharacterEncodingResult.InvalidFormat)] // standalone low surrogate + public void Utf16ToUtf8_WithOnlyStandaloneSurrogates(char charValue, CharacterEncodingResult expectedEncodingResult) + { + Utf16ToUtf8_String_Test_Core( + utf16Input: new[] { charValue }, + destinationSize: 0, + expectedEncodingResult: expectedEncodingResult, + expectedUtf8Transcoding: Span<byte>.Empty); + } + + [Theory] + [InlineData("<LOW><HIGH>", "")] // swapped surrogate pair characters + [InlineData("A<LOW><HIGH>", "41")] // consume standalone ASCII char, then swapped surrogate pair characters + [InlineData("A<HIGH>B", "41F0")] // consume standalone ASCII char, then standalone high surrogate char + [InlineData("A<LOW>B", "41")] // consume standalone ASCII char, then standalone low surrogate char + [InlineData("AB<HIGH><HIGH>", "4142F0")] // consume two ASCII chars, then standalone high surrogate char + [InlineData("AB<LOW><LOW>", "4142")] // consume two ASCII chars, then standalone low surrogate char + public void Utf16ToUtf8_WithInvalidSurrogates(string utf16Input, string expectedUtf8TranscodingHex) + { + // xUnit can't handle ill-formed strings in [InlineData], so we replace here. + + utf16Input = utf16Input.Replace("<HIGH>", "\uD800").Replace("<LOW>", "\uDFFF"); + + // These test cases are for the "fast processing" code which is the main loop of TranscodeToUtf8, + // so inputs should be at least 2 chars. + + Assert.True(utf16Input.Length >= 2); + + Utf16ToUtf8_String_Test_Core( + utf16Input: utf16Input, + destinationSize: expectedUtf8TranscodingHex.Length / 2, + expectedEncodingResult: CharacterEncodingResult.InvalidFormat, + expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); + + // Now try the tests again with a larger buffer. + // This ensures that running out of destination space wasn't the reason we failed. + + Utf16ToUtf8_String_Test_Core( + utf16Input: utf16Input, + destinationSize: (expectedUtf8TranscodingHex.Length) / 2 + 16, + expectedEncodingResult: CharacterEncodingResult.InvalidFormat, + expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); + } + + [Theory] + [InlineData("80", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character + [InlineData("8182", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character + [InlineData("838485", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character + [InlineData(X_UTF8 + "80", CharacterEncodingResult.InsufficientLength, X_UTF16)] // sequence cannot begin with continuation character + [InlineData(X_UTF8 + "8182", CharacterEncodingResult.InsufficientLength, X_UTF16)] // sequence cannot begin with continuation character + [InlineData("C0", CharacterEncodingResult.InvalidFormat, "")] // [ C0 ] is always invalid + [InlineData("C080", CharacterEncodingResult.InsufficientLength, "")] // [ C0 ] is always invalid + [InlineData("C08081", CharacterEncodingResult.InsufficientLength, "")] // [ C0 ] is always invalid + [InlineData(X_UTF8 + "C1", CharacterEncodingResult.InvalidFormat, X_UTF16)] // [ C1 ] is always invalid + [InlineData(X_UTF8 + "C180", CharacterEncodingResult.InsufficientLength, X_UTF16)] // [ C1 ] is always invalid + [InlineData(X_UTF8 + "C27F", CharacterEncodingResult.InsufficientLength, X_UTF16)] // [ C2 ] is improperly terminated + [InlineData("E2827F", CharacterEncodingResult.InsufficientLength, "")] // [ E2 82 ] is improperly terminated + [InlineData("E09F80", CharacterEncodingResult.InsufficientLength, "")] // [ E0 9F ... ] is overlong + [InlineData("E0C080", CharacterEncodingResult.InsufficientLength, "")] // [ E0 ] is improperly terminated + [InlineData("ED7F80", CharacterEncodingResult.InsufficientLength, "")] // [ ED ] is improperly terminated + [InlineData("EDA080", CharacterEncodingResult.InsufficientLength, "")] // [ ED A0 ... ] is surrogate + public void Utf8ToUtf16_WithSmallInvalidBuffers(string utf8HexInput, CharacterEncodingResult expectedEncodingResult, string expectedUtf16Transcoding) + { + Utf8ToUtf16_String_Test_Core( + utf8Input: DecodeHex(utf8HexInput), + destinationSize: expectedUtf16Transcoding.Length, + expectedEncodingResult: expectedEncodingResult, + expectedUtf16Transcoding: expectedUtf16Transcoding); + + // Now try the tests again with a larger buffer. + // This ensures that the sequence is seen as invalid when not hitting the destination length check. + + Utf8ToUtf16_String_Test_Core( + utf8Input: DecodeHex(utf8HexInput), + destinationSize: expectedUtf16Transcoding.Length + 16, + expectedEncodingResult: CharacterEncodingResult.InvalidFormat, + expectedUtf16Transcoding: expectedUtf16Transcoding); + } + + [Theory] + /* SMALL VALID BUFFERS - tests drain loop at end of method */ + [InlineData("")] // empty string is OK + [InlineData("X")] + [InlineData("XY")] + [InlineData("XYZ")] + [InlineData(E_ACUTE_UTF16)] + [InlineData(X_UTF16 + E_ACUTE_UTF16)] + [InlineData(E_ACUTE_UTF16 + X_UTF16)] + [InlineData(EURO_SYMBOL_UTF16)] + /* LARGE VALID BUFFERS - test main loop at beginning of method */ + [InlineData(E_ACUTE_UTF16 + "ABCD" + "0123456789:;<=>?")] // Loop unrolling at end of buffer + [InlineData(E_ACUTE_UTF16 + "ABCD" + "0123456789:;<=>?" + "01234567" + E_ACUTE_UTF16 + "89:;<=>?")] // Loop unrolling interrupted by non-ASCII + [InlineData("ABC" + E_ACUTE_UTF16 + "0123")] // 3 ASCII bytes followed by non-ASCII + [InlineData("AB" + E_ACUTE_UTF16 + "0123")] // 2 ASCII bytes followed by non-ASCII + [InlineData("A" + E_ACUTE_UTF16 + "0123")] // 1 ASCII byte followed by non-ASCII + [InlineData(E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16)] // 4x 2-byte sequences, exercises optimization code path in 2-byte sequence processing + [InlineData(E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + "PQ")] // 3x 2-byte sequences + 2 ASCII bytes, exercises optimization code path in 2-byte sequence processing + [InlineData(E_ACUTE_UTF16 + "PQ")] // single 2-byte sequence + 2 trailing ASCII bytes, exercises draining logic in 2-byte sequence processing + [InlineData(E_ACUTE_UTF16 + "P" + E_ACUTE_UTF16 + "0@P")] // single 2-byte sequences + 1 trailing ASCII byte + 2-byte sequence, exercises draining logic in 2-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + "@")] // single 3-byte sequence + 1 trailing ASCII byte, exercises draining logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + "@P`")] // single 3-byte sequence + 3 trailing ASCII byte, exercises draining logic and "running out of data" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16)] // 3x 3-byte sequences, exercises "stay within 3-byte loop" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16)] // 4x 3-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + E_ACUTE_UTF16)] // 3x 3-byte sequences + single 2-byte sequence, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16)] // 2x 3-byte sequences + 4x 2-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(GRINNING_FACE_UTF16 + GRINNING_FACE_UTF16)] // 2x 4-byte sequences, exercises 4-byte sequence processing + [InlineData(GRINNING_FACE_UTF16 + "@AB")] // single 4-byte sequence + 3 ASCII bytes, exercises 4-byte sequence processing and draining logic + [InlineData(WOMAN_CARTWHEELING_MEDSKIN_UTF16)] // exercises switching between multiple sequence lengths + public void Utf8ToUtf16_ValidBuffers(string utf16Input) + { + // We're going to run the tests with destination buffer lengths ranging from 0 all the way + // to buffers large enough to hold the full output. This allows us to test logic that + // detects whether we're about to overrun our destination buffer and instead returns DestinationTooSmall. + + Rune[] enumeratedScalars = utf16Input.EnumerateRunes().ToArray(); + + // Convert entire input to UTF-8 using our unit test reference logic. + + byte[] utf8Input = enumeratedScalars.SelectMany(ToUtf8).ToArray(); + + // 0-length buffer test + Utf8ToUtf16_String_Test_Core( + utf8Input: utf8Input, + destinationSize: 0, + expectedEncodingResult: (utf8Input.Length == 0) ? CharacterEncodingResult.Success : CharacterEncodingResult.InsufficientLength, + expectedUtf16Transcoding: ReadOnlySpan<char>.Empty); + + char[] concatenatedUtf16 = Array.Empty<char>(); + + for (int i = 0; i < enumeratedScalars.Length; i++) { - if (!Rune.IsValid(rune.Value)) + Rune thisScalar = enumeratedScalars[i]; + + // if this is an astral scalar value, quickly test a buffer that's not large enough to contain the entire UTF-16 encoding + + if (!thisScalar.IsBmp) { - Assert.True(Rune.IsValid(rune.Value), $"Rune with value U+{(uint)rune.Value:X4} is not well-formed."); - } - - Assert.True(destination.Length == 2); - - destination[0] = '\0'; - destination[1] = '\0'; - - if (rune.IsBmp) - { - destination[0] = (char)rune.Value; - return 1; - } - else - { - destination[0] = (char)((rune.Value >> 10) + 0xD800 - 0x40); - destination[1] = (char)((rune.Value & 0x03FF) + 0xDC00); - return 2; - } - } - - [Theory] - [InlineData("", "")] // empty string is OK - [InlineData(X_UTF16, X_UTF8)] - [InlineData(E_ACUTE_UTF16, E_ACUTE_UTF8)] - [InlineData(EURO_SYMBOL_UTF16, EURO_SYMBOL_UTF8)] - public void Utf16ToUtf8_WithSmallValidBuffers(string utf16Input, string expectedUtf8TranscodingHex) - { - Assert.InRange(utf16Input.Length, 0, 1); - - Utf16ToUtf8_String_Test_Core( - utf16Input: utf16Input, - destinationSize: expectedUtf8TranscodingHex.Length / 2, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); - } - - [Theory] - [InlineData('\uD800', CharacterEncodingResult.InsufficientLength)] // standalone high surrogate - [InlineData('\uDFFF', CharacterEncodingResult.InvalidFormat)] // standalone low surrogate - public void Utf16ToUtf8_WithOnlyStandaloneSurrogates(char charValue, CharacterEncodingResult expectedEncodingResult) - { - Utf16ToUtf8_String_Test_Core( - utf16Input: new[] { charValue }, - destinationSize: 0, - expectedEncodingResult: expectedEncodingResult, - expectedUtf8Transcoding: Span<byte>.Empty); - } - - [Theory] - [InlineData("<LOW><HIGH>", "")] // swapped surrogate pair characters - [InlineData("A<LOW><HIGH>", "41")] // consume standalone ASCII char, then swapped surrogate pair characters - [InlineData("A<HIGH>B", "41F0")] // consume standalone ASCII char, then standalone high surrogate char - [InlineData("A<LOW>B", "41")] // consume standalone ASCII char, then standalone low surrogate char - [InlineData("AB<HIGH><HIGH>", "4142F0")] // consume two ASCII chars, then standalone high surrogate char - [InlineData("AB<LOW><LOW>", "4142")] // consume two ASCII chars, then standalone low surrogate char - public void Utf16ToUtf8_WithInvalidSurrogates(string utf16Input, string expectedUtf8TranscodingHex) - { - // xUnit can't handle ill-formed strings in [InlineData], so we replace here. - - utf16Input = utf16Input.Replace("<HIGH>", "\uD800").Replace("<LOW>", "\uDFFF"); - - // These test cases are for the "fast processing" code which is the main loop of TranscodeToUtf8, - // so inputs should be at least 2 chars. - - Assert.True(utf16Input.Length >= 2); - - Utf16ToUtf8_String_Test_Core( - utf16Input: utf16Input, - destinationSize: expectedUtf8TranscodingHex.Length / 2, - expectedEncodingResult: CharacterEncodingResult.InvalidFormat, - expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); - - // Now try the tests again with a larger buffer. - // This ensures that running out of destination space wasn't the reason we failed. - - Utf16ToUtf8_String_Test_Core( - utf16Input: utf16Input, - destinationSize: (expectedUtf8TranscodingHex.Length) / 2 + 16, - expectedEncodingResult: CharacterEncodingResult.InvalidFormat, - expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); - } - - [Theory] - [InlineData("80", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character - [InlineData("8182", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character - [InlineData("838485", CharacterEncodingResult.InsufficientLength, "")] // sequence cannot begin with continuation character - [InlineData(X_UTF8 + "80", CharacterEncodingResult.InsufficientLength, X_UTF16)] // sequence cannot begin with continuation character - [InlineData(X_UTF8 + "8182", CharacterEncodingResult.InsufficientLength, X_UTF16)] // sequence cannot begin with continuation character - [InlineData("C0", CharacterEncodingResult.InvalidFormat, "")] // [ C0 ] is always invalid - [InlineData("C080", CharacterEncodingResult.InsufficientLength, "")] // [ C0 ] is always invalid - [InlineData("C08081", CharacterEncodingResult.InsufficientLength, "")] // [ C0 ] is always invalid - [InlineData(X_UTF8 + "C1", CharacterEncodingResult.InvalidFormat, X_UTF16)] // [ C1 ] is always invalid - [InlineData(X_UTF8 + "C180", CharacterEncodingResult.InsufficientLength, X_UTF16)] // [ C1 ] is always invalid - [InlineData(X_UTF8 + "C27F", CharacterEncodingResult.InsufficientLength, X_UTF16)] // [ C2 ] is improperly terminated - [InlineData("E2827F", CharacterEncodingResult.InsufficientLength, "")] // [ E2 82 ] is improperly terminated - [InlineData("E09F80", CharacterEncodingResult.InsufficientLength, "")] // [ E0 9F ... ] is overlong - [InlineData("E0C080", CharacterEncodingResult.InsufficientLength, "")] // [ E0 ] is improperly terminated - [InlineData("ED7F80", CharacterEncodingResult.InsufficientLength, "")] // [ ED ] is improperly terminated - [InlineData("EDA080", CharacterEncodingResult.InsufficientLength, "")] // [ ED A0 ... ] is surrogate - public void Utf8ToUtf16_WithSmallInvalidBuffers(string utf8HexInput, CharacterEncodingResult expectedEncodingResult, string expectedUtf16Transcoding) - { - Utf8ToUtf16_String_Test_Core( - utf8Input: DecodeHex(utf8HexInput), - destinationSize: expectedUtf16Transcoding.Length, - expectedEncodingResult: expectedEncodingResult, - expectedUtf16Transcoding: expectedUtf16Transcoding); - - // Now try the tests again with a larger buffer. - // This ensures that the sequence is seen as invalid when not hitting the destination length check. - - Utf8ToUtf16_String_Test_Core( - utf8Input: DecodeHex(utf8HexInput), - destinationSize: expectedUtf16Transcoding.Length + 16, - expectedEncodingResult: CharacterEncodingResult.InvalidFormat, - expectedUtf16Transcoding: expectedUtf16Transcoding); - } - - [Theory] - /* SMALL VALID BUFFERS - tests drain loop at end of method */ - [InlineData("")] // empty string is OK - [InlineData("X")] - [InlineData("XY")] - [InlineData("XYZ")] - [InlineData(E_ACUTE_UTF16)] - [InlineData(X_UTF16 + E_ACUTE_UTF16)] - [InlineData(E_ACUTE_UTF16 + X_UTF16)] - [InlineData(EURO_SYMBOL_UTF16)] - /* LARGE VALID BUFFERS - test main loop at beginning of method */ - [InlineData(E_ACUTE_UTF16 + "ABCD" + "0123456789:;<=>?")] // Loop unrolling at end of buffer - [InlineData(E_ACUTE_UTF16 + "ABCD" + "0123456789:;<=>?" + "01234567" + E_ACUTE_UTF16 + "89:;<=>?")] // Loop unrolling interrupted by non-ASCII - [InlineData("ABC" + E_ACUTE_UTF16 + "0123")] // 3 ASCII bytes followed by non-ASCII - [InlineData("AB" + E_ACUTE_UTF16 + "0123")] // 2 ASCII bytes followed by non-ASCII - [InlineData("A" + E_ACUTE_UTF16 + "0123")] // 1 ASCII byte followed by non-ASCII - [InlineData(E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16)] // 4x 2-byte sequences, exercises optimization code path in 2-byte sequence processing - [InlineData(E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + "PQ")] // 3x 2-byte sequences + 2 ASCII bytes, exercises optimization code path in 2-byte sequence processing - [InlineData(E_ACUTE_UTF16 + "PQ")] // single 2-byte sequence + 2 trailing ASCII bytes, exercises draining logic in 2-byte sequence processing - [InlineData(E_ACUTE_UTF16 + "P" + E_ACUTE_UTF16 + "0@P")] // single 2-byte sequences + 1 trailing ASCII byte + 2-byte sequence, exercises draining logic in 2-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + "@")] // single 3-byte sequence + 1 trailing ASCII byte, exercises draining logic in 3-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + "@P`")] // single 3-byte sequence + 3 trailing ASCII byte, exercises draining logic and "running out of data" logic in 3-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16)] // 3x 3-byte sequences, exercises "stay within 3-byte loop" logic in 3-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16)] // 4x 3-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + E_ACUTE_UTF16)] // 3x 3-byte sequences + single 2-byte sequence, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing - [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16)] // 2x 3-byte sequences + 4x 2-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing - [InlineData(GRINNING_FACE_UTF16 + GRINNING_FACE_UTF16)] // 2x 4-byte sequences, exercises 4-byte sequence processing - [InlineData(GRINNING_FACE_UTF16 + "@AB")] // single 4-byte sequence + 3 ASCII bytes, exercises 4-byte sequence processing and draining logic - [InlineData(WOMAN_CARTWHEELING_MEDSKIN_UTF16)] // exercises switching between multiple sequence lengths - public void Utf8ToUtf16_ValidBuffers(string utf16Input) - { - // We're going to run the tests with destination buffer lengths ranging from 0 all the way - // to buffers large enough to hold the full output. This allows us to test logic that - // detects whether we're about to overrun our destination buffer and instead returns DestinationTooSmall. - - Rune[] enumeratedScalars = utf16Input.EnumerateRunes().ToArray(); - - // Convert entire input to UTF-8 using our unit test reference logic. - - byte[] utf8Input = enumeratedScalars.SelectMany(ToUtf8).ToArray(); - - // 0-length buffer test - Utf8ToUtf16_String_Test_Core( - utf8Input: utf8Input, - destinationSize: 0, - expectedEncodingResult: (utf8Input.Length == 0) ? CharacterEncodingResult.Success : CharacterEncodingResult.InsufficientLength, - expectedUtf16Transcoding: ReadOnlySpan<char>.Empty); - - char[] concatenatedUtf16 = Array.Empty<char>(); - - for (int i = 0; i < enumeratedScalars.Length; i++) - { - Rune thisScalar = enumeratedScalars[i]; - - // if this is an astral scalar value, quickly test a buffer that's not large enough to contain the entire UTF-16 encoding - - if (!thisScalar.IsBmp) - { - Utf8ToUtf16_String_Test_Core( - utf8Input: utf8Input, - destinationSize: concatenatedUtf16.Length + 1, - expectedEncodingResult: CharacterEncodingResult.InsufficientLength, - expectedUtf16Transcoding: concatenatedUtf16); - } - - // now provide a destination buffer large enough to hold the next full scalar encoding - - concatenatedUtf16 = concatenatedUtf16.Concat(ToUtf16(thisScalar)).ToArray(); - Utf8ToUtf16_String_Test_Core( utf8Input: utf8Input, - destinationSize: concatenatedUtf16.Length, - expectedEncodingResult: (i == enumeratedScalars.Length - 1) ? CharacterEncodingResult.Success : CharacterEncodingResult.InsufficientLength, + destinationSize: concatenatedUtf16.Length + 1, + expectedEncodingResult: CharacterEncodingResult.InsufficientLength, expectedUtf16Transcoding: concatenatedUtf16); } - // now throw lots of ASCII data at the beginning so that we exercise the vectorized code paths + // now provide a destination buffer large enough to hold the next full scalar encoding - utf16Input = new string('x', 64) + utf16Input; - utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + concatenatedUtf16 = concatenatedUtf16.Concat(ToUtf16(thisScalar)).ToArray(); Utf8ToUtf16_String_Test_Core( utf8Input: utf8Input, - destinationSize: utf16Input.Length, + destinationSize: concatenatedUtf16.Length, + expectedEncodingResult: (i == enumeratedScalars.Length - 1) ? CharacterEncodingResult.Success : CharacterEncodingResult.InsufficientLength, + expectedUtf16Transcoding: concatenatedUtf16); + } + + // now throw lots of ASCII data at the beginning so that we exercise the vectorized code paths + + utf16Input = new string('x', 64) + utf16Input; + utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + Utf8ToUtf16_String_Test_Core( + utf8Input: utf8Input, + destinationSize: utf16Input.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf16Transcoding: utf16Input); + + // now throw some non-ASCII data at the beginning so that we *don't* exercise the vectorized code paths + + utf16Input = WOMAN_CARTWHEELING_MEDSKIN_UTF16 + utf16Input[64..]; + utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + Utf8ToUtf16_String_Test_Core( + utf8Input: utf8Input, + destinationSize: utf16Input.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf16Transcoding: utf16Input); + } + + [Fact] + public void Utf8ToUtf16_String_AllPossibleScalarValues() + { + Utf8ToUtf16_String_Test_Core( + utf8Input: s_allScalarsAsUtf8.Span, + destinationSize: s_allScalarsAsUtf16.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf16Transcoding: s_allScalarsAsUtf16.Span); + } + + [Fact] + public void Utf16ToUtf8_String_AllPossibleScalarValues() + { + Utf16ToUtf8_String_Test_Core( + utf16Input: s_allScalarsAsUtf16.Span, + destinationSize: s_allScalarsAsUtf8.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf8Transcoding: s_allScalarsAsUtf8.Span); + } + + [Fact] + public void Utf8ToUtf32_String_AllPossibleScalarValues() + { + Utf8ToUtf32_String_Test_Core( + utf8Input: s_allScalarsAsUtf8.Span, + destinationSize: s_allScalarsAsUtf32.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf32Transcoding: s_allScalarsAsUtf32.Span); + } + + [Fact] + public void Utf32ToUtf8_String_AllPossibleScalarValues() + { + Utf32ToUtf8_String_Test_Core( + utf32Input: s_allScalarsAsUtf32.Span, + destinationSize: s_allScalarsAsUtf8.Length, + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf8Transcoding: s_allScalarsAsUtf8.Span); + } + + [Fact] + public void Utf8ToUtf16_Length_AllPossibleScalarValues() + { + Utf8ToUtf16_Length_Test_Core( + // Skip the first code point because it's 0 + utf8Input: s_allScalarsAsUtf8.Span.Slice(1), + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf16Length: s_allScalarsAsUtf16.Length - 1); + } + + [Fact] + public void Utf16ToUtf8_Length_AllPossibleScalarValues() + { + Utf16ToUtf8_Length_Test_Core( + utf16Input: s_allScalarsAsUtf16.Span.Slice(1), + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf8Length: s_allScalarsAsUtf8.Length - 1); + } + + [Fact] + public void Utf8ToUtf32_Length_AllPossibleScalarValues() + { + Utf8ToUtf32_Length_Test_Core( + utf8Input: s_allScalarsAsUtf8.Span.Slice(1), + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf32Length: s_allScalarsAsUtf32.Length - 1); + } + + [Fact] + public void Utf32ToUtf8_Length_AllPossibleScalarValues() + { + Utf32ToUtf8_Length_Test_Core( + utf32Input: s_allScalarsAsUtf32.Span.Slice(1), + expectedEncodingResult: CharacterEncodingResult.Success, + expectedUtf8Length: s_allScalarsAsUtf8.Length - 1); + } + + [Fact] + public void Utf8ToUtf16_Character_AllPossibleScalarValues() + { + Span<byte> utf8 = stackalloc byte[4]; + Span<char> utf16 = stackalloc char[2]; + + foreach (Rune rune in s_allValidScalars) + { + ToUtf8(rune, utf8); + ToUtf16(rune, utf16); + + Utf8ToUtf16_Character_Test_Core( + utf8Input: utf8, expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf16Transcoding: utf16Input); + expectedUtf16Transcoding: utf16); + } + } - // now throw some non-ASCII data at the beginning so that we *don't* exercise the vectorized code paths + [Fact] + public void Utf16ToUtf8_Character_AllPossibleScalarValues() + { + Span<byte> utf8 = stackalloc byte[4]; + Span<char> utf16 = stackalloc char[2]; - utf16Input = WOMAN_CARTWHEELING_MEDSKIN_UTF16 + utf16Input[64..]; - utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + foreach (Rune rune in s_allValidScalars) + { + ToUtf8(rune, utf8); + ToUtf16(rune, utf16); - Utf8ToUtf16_String_Test_Core( - utf8Input: utf8Input, - destinationSize: utf16Input.Length, + Utf16ToUtf8_Character_Test_Core( + utf16Input: utf16, expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf16Transcoding: utf16Input); + expectedUtf8Transcoding: utf8); } + } - [Fact] - public void Utf8ToUtf16_String_AllPossibleScalarValues() + [Fact] + public void Utf8ToUtf32_Character_AllPossibleScalarValues() + { + Span<byte> utf8 = stackalloc byte[4]; + + foreach (Rune rune in s_allValidScalars) { - Utf8ToUtf16_String_Test_Core( - utf8Input: s_allScalarsAsUtf8.Span, - destinationSize: s_allScalarsAsUtf16.Length, + ToUtf8(rune, utf8); + + Utf8ToUtf32_Character_Test_Core( + utf8Input: utf8, expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf16Transcoding: s_allScalarsAsUtf16.Span); + expectedUtf32Transcoding: (uint)rune.Value); } + } - [Fact] - public void Utf16ToUtf8_String_AllPossibleScalarValues() + [Fact] + public void Utf32ToUtf8_Character_AllPossibleScalarValues() + { + Span<byte> utf8 = stackalloc byte[4]; + + foreach (Rune rune in s_allValidScalars) { - Utf16ToUtf8_String_Test_Core( - utf16Input: s_allScalarsAsUtf16.Span, - destinationSize: s_allScalarsAsUtf8.Length, + ToUtf8(rune, utf8); + + Utf32ToUtf8_Character_Test_Core( + utf32Input: (uint)rune.Value, expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Transcoding: s_allScalarsAsUtf8.Span); + expectedUtf8Transcoding: utf8); } + } - [Fact] - public void Utf8ToUtf32_String_AllPossibleScalarValues() + [Fact] + public void PickOutCharacterFromUtf8String_AllPossibleScalarValues() + { + byte[] expectedUtf8 = new byte[4]; + byte[] actualUtf8 = new byte[4]; + + ReadOnlySpan<byte> utf8Values = s_allScalarsAsUtf8.Span.Slice(1); + + foreach (Rune rune in s_allValidScalars.Skip(1)) { - Utf8ToUtf32_String_Test_Core( - utf8Input: s_allScalarsAsUtf8.Span, - destinationSize: s_allScalarsAsUtf32.Length, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf32Transcoding: s_allScalarsAsUtf32.Span); + ToUtf8(rune, expectedUtf8); + + CharacterEncodingResult result = CharacterEncoding.PickOutCharacterFromUtf8String(actualUtf8, ref utf8Values); + + Assert.Equal(CharacterEncodingResult.Success, result); + Assert.Equal((ReadOnlySpan<byte>)expectedUtf8, actualUtf8); } + } - [Fact] - public void Utf32ToUtf8_String_AllPossibleScalarValues() + private static void Utf8ToUtf16_String_Test_Core(ReadOnlySpan<byte> utf8Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<char> expectedUtf16Transcoding) + { + char[] destination = new char[destinationSize]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf8ToUtf16Native(destination, utf8Input, utf8Input.Length); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf16Transcoding, destination.AsSpan(0, expectedUtf16Transcoding.Length)); + } + + private static void Utf16ToUtf8_String_Test_Core(ReadOnlySpan<char> utf16Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) + { + byte[] destination = new byte[destinationSize]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf16NativeToUtf8(destination, utf16Input, utf16Input.Length); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Transcoding, destination.AsSpan(0, expectedUtf8Transcoding.Length)); + } + + private static void Utf8ToUtf32_String_Test_Core(ReadOnlySpan<byte> utf8Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<uint> expectedUtf32Transcoding) + { + uint[] destination = new uint[destinationSize]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf8ToUtf32(destination, utf8Input, utf8Input.Length); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf32Transcoding, destination.AsSpan(0, expectedUtf32Transcoding.Length)); + } + + private static void Utf32ToUtf8_String_Test_Core(ReadOnlySpan<uint> utf32Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) + { + byte[] destination = new byte[destinationSize]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf32ToUtf8(destination, utf32Input, utf32Input.Length); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Transcoding, destination.AsSpan(0, expectedUtf8Transcoding.Length)); + } + + private static void Utf8ToUtf16_Length_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf16Length) + { + CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf8ToUtf16Native(out int actualLength, utf8Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf16Length, actualLength); + } + + private static void Utf16ToUtf8_Length_Test_Core(ReadOnlySpan<char> utf16Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf8Length) + { + CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf16NativeToUtf8(out int actualLength, utf16Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Length, actualLength); + } + + private static void Utf8ToUtf32_Length_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf32Length) + { + CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf8ToUtf32(out int actualLength, utf8Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf32Length, actualLength); + } + + private static void Utf32ToUtf8_Length_Test_Core(ReadOnlySpan<uint> utf32Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf8Length) + { + CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf32ToUtf8(out int actualLength, utf32Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Length, actualLength); + } + + private static void Utf8ToUtf16_Character_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<char> expectedUtf16Transcoding) + { + Span<char> destination = stackalloc char[2]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf8ToUtf16Native(destination, utf8Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf16Transcoding, destination.Slice(0, expectedUtf16Transcoding.Length)); + + for (int i = expectedUtf16Transcoding.Length; i < destination.Length; i++) { - Utf32ToUtf8_String_Test_Core( - utf32Input: s_allScalarsAsUtf32.Span, - destinationSize: s_allScalarsAsUtf8.Length, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Transcoding: s_allScalarsAsUtf8.Span); + Assert.Equal(0, destination[i]); } + } - [Fact] - public void Utf8ToUtf16_Length_AllPossibleScalarValues() + private static void Utf16ToUtf8_Character_Test_Core(ReadOnlySpan<char> utf16Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) + { + Span<byte> destination = stackalloc byte[4]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf16NativeToUtf8(destination, utf16Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Transcoding, destination.Slice(0, expectedUtf8Transcoding.Length)); + + for (int i = expectedUtf8Transcoding.Length; i < destination.Length; i++) { - Utf8ToUtf16_Length_Test_Core( - // Skip the first code point because it's 0 - utf8Input: s_allScalarsAsUtf8.Span.Slice(1), - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf16Length: s_allScalarsAsUtf16.Length - 1); + Assert.Equal(0, destination[i]); } + } - [Fact] - public void Utf16ToUtf8_Length_AllPossibleScalarValues() + private static void Utf8ToUtf32_Character_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, uint expectedUtf32Transcoding) + { + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out uint destination, utf8Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf32Transcoding, destination); + } + + private static void Utf32ToUtf8_Character_Test_Core(uint utf32Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) + { + Span<byte> destination = stackalloc byte[4]; + + CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf32ToUtf8(destination, utf32Input); + + Assert.Equal(expectedEncodingResult, actualEncodingResult); + Assert.Equal(expectedUtf8Transcoding, destination.Slice(0, expectedUtf8Transcoding.Length)); + + for (int i = expectedUtf8Transcoding.Length; i < destination.Length; i++) { - Utf16ToUtf8_Length_Test_Core( - utf16Input: s_allScalarsAsUtf16.Span.Slice(1), - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Length: s_allScalarsAsUtf8.Length - 1); - } - - [Fact] - public void Utf8ToUtf32_Length_AllPossibleScalarValues() - { - Utf8ToUtf32_Length_Test_Core( - utf8Input: s_allScalarsAsUtf8.Span.Slice(1), - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf32Length: s_allScalarsAsUtf32.Length - 1); - } - - [Fact] - public void Utf32ToUtf8_Length_AllPossibleScalarValues() - { - Utf32ToUtf8_Length_Test_Core( - utf32Input: s_allScalarsAsUtf32.Span.Slice(1), - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Length: s_allScalarsAsUtf8.Length - 1); - } - - [Fact] - public void Utf8ToUtf16_Character_AllPossibleScalarValues() - { - Span<byte> utf8 = stackalloc byte[4]; - Span<char> utf16 = stackalloc char[2]; - - foreach (Rune rune in s_allValidScalars) - { - ToUtf8(rune, utf8); - ToUtf16(rune, utf16); - - Utf8ToUtf16_Character_Test_Core( - utf8Input: utf8, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf16Transcoding: utf16); - } - } - - [Fact] - public void Utf16ToUtf8_Character_AllPossibleScalarValues() - { - Span<byte> utf8 = stackalloc byte[4]; - Span<char> utf16 = stackalloc char[2]; - - foreach (Rune rune in s_allValidScalars) - { - ToUtf8(rune, utf8); - ToUtf16(rune, utf16); - - Utf16ToUtf8_Character_Test_Core( - utf16Input: utf16, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Transcoding: utf8); - } - } - - [Fact] - public void Utf8ToUtf32_Character_AllPossibleScalarValues() - { - Span<byte> utf8 = stackalloc byte[4]; - - foreach (Rune rune in s_allValidScalars) - { - ToUtf8(rune, utf8); - - Utf8ToUtf32_Character_Test_Core( - utf8Input: utf8, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf32Transcoding: (uint)rune.Value); - } - } - - [Fact] - public void Utf32ToUtf8_Character_AllPossibleScalarValues() - { - Span<byte> utf8 = stackalloc byte[4]; - - foreach (Rune rune in s_allValidScalars) - { - ToUtf8(rune, utf8); - - Utf32ToUtf8_Character_Test_Core( - utf32Input: (uint)rune.Value, - expectedEncodingResult: CharacterEncodingResult.Success, - expectedUtf8Transcoding: utf8); - } - } - - [Fact] - public void PickOutCharacterFromUtf8String_AllPossibleScalarValues() - { - byte[] expectedUtf8 = new byte[4]; - byte[] actualUtf8 = new byte[4]; - - ReadOnlySpan<byte> utf8Values = s_allScalarsAsUtf8.Span.Slice(1); - - foreach (Rune rune in s_allValidScalars.Skip(1)) - { - ToUtf8(rune, expectedUtf8); - - CharacterEncodingResult result = CharacterEncoding.PickOutCharacterFromUtf8String(actualUtf8, ref utf8Values); - - Assert.Equal(CharacterEncodingResult.Success, result); - Assert.Equal((ReadOnlySpan<byte>)expectedUtf8, actualUtf8); - } - } - - private static void Utf8ToUtf16_String_Test_Core(ReadOnlySpan<byte> utf8Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<char> expectedUtf16Transcoding) - { - char[] destination = new char[destinationSize]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf8ToUtf16Native(destination, utf8Input, utf8Input.Length); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf16Transcoding, destination.AsSpan(0, expectedUtf16Transcoding.Length)); - } - - private static void Utf16ToUtf8_String_Test_Core(ReadOnlySpan<char> utf16Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) - { - byte[] destination = new byte[destinationSize]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf16NativeToUtf8(destination, utf16Input, utf16Input.Length); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Transcoding, destination.AsSpan(0, expectedUtf8Transcoding.Length)); - } - - private static void Utf8ToUtf32_String_Test_Core(ReadOnlySpan<byte> utf8Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<uint> expectedUtf32Transcoding) - { - uint[] destination = new uint[destinationSize]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf8ToUtf32(destination, utf8Input, utf8Input.Length); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf32Transcoding, destination.AsSpan(0, expectedUtf32Transcoding.Length)); - } - - private static void Utf32ToUtf8_String_Test_Core(ReadOnlySpan<uint> utf32Input, int destinationSize, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) - { - byte[] destination = new byte[destinationSize]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertStringUtf32ToUtf8(destination, utf32Input, utf32Input.Length); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Transcoding, destination.AsSpan(0, expectedUtf8Transcoding.Length)); - } - - private static void Utf8ToUtf16_Length_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf16Length) - { - CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf8ToUtf16Native(out int actualLength, utf8Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf16Length, actualLength); - } - - private static void Utf16ToUtf8_Length_Test_Core(ReadOnlySpan<char> utf16Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf8Length) - { - CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf16NativeToUtf8(out int actualLength, utf16Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Length, actualLength); - } - - private static void Utf8ToUtf32_Length_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf32Length) - { - CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf8ToUtf32(out int actualLength, utf8Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf32Length, actualLength); - } - - private static void Utf32ToUtf8_Length_Test_Core(ReadOnlySpan<uint> utf32Input, CharacterEncodingResult expectedEncodingResult, int expectedUtf8Length) - { - CharacterEncodingResult actualEncodingResult = CharacterEncoding.GetLengthOfConvertedStringUtf32ToUtf8(out int actualLength, utf32Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Length, actualLength); - } - - private static void Utf8ToUtf16_Character_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<char> expectedUtf16Transcoding) - { - Span<char> destination = stackalloc char[2]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf8ToUtf16Native(destination, utf8Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf16Transcoding, destination.Slice(0, expectedUtf16Transcoding.Length)); - - for (int i = expectedUtf16Transcoding.Length; i < destination.Length; i++) - { - Assert.Equal(0, destination[i]); - } - } - - private static void Utf16ToUtf8_Character_Test_Core(ReadOnlySpan<char> utf16Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) - { - Span<byte> destination = stackalloc byte[4]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf16NativeToUtf8(destination, utf16Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Transcoding, destination.Slice(0, expectedUtf8Transcoding.Length)); - - for (int i = expectedUtf8Transcoding.Length; i < destination.Length; i++) - { - Assert.Equal(0, destination[i]); - } - } - - private static void Utf8ToUtf32_Character_Test_Core(ReadOnlySpan<byte> utf8Input, CharacterEncodingResult expectedEncodingResult, uint expectedUtf32Transcoding) - { - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf8ToUtf32(out uint destination, utf8Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf32Transcoding, destination); - } - - private static void Utf32ToUtf8_Character_Test_Core(uint utf32Input, CharacterEncodingResult expectedEncodingResult, ReadOnlySpan<byte> expectedUtf8Transcoding) - { - Span<byte> destination = stackalloc byte[4]; - - CharacterEncodingResult actualEncodingResult = CharacterEncoding.ConvertCharacterUtf32ToUtf8(destination, utf32Input); - - Assert.Equal(expectedEncodingResult, actualEncodingResult); - Assert.Equal(expectedUtf8Transcoding, destination.Slice(0, expectedUtf8Transcoding.Length)); - - for (int i = expectedUtf8Transcoding.Length; i < destination.Length; i++) - { - Assert.Equal(0, destination[i]); - } + Assert.Equal(0, destination[i]); } } }