|
|
@@ -21,6 +21,7 @@ using System.IO;
|
|
|
using System.IO.Compression;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
|
+using System.Text.RegularExpressions;
|
|
|
using Path = System.IO.Path;
|
|
|
|
|
|
namespace Ryujinx.HLE.FileSystem
|
|
|
@@ -474,6 +475,74 @@ namespace Ryujinx.HLE.FileSystem
|
|
|
FinishInstallation(temporaryDirectory, registeredDirectory);
|
|
|
}
|
|
|
|
|
|
+ public void InstallKeys(string keysSource, string installDirectory)
|
|
|
+ {
|
|
|
+ if (Directory.Exists(keysSource))
|
|
|
+ {
|
|
|
+ foreach (var filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
|
|
|
+ {
|
|
|
+ VerifyKeysFile(filePath);
|
|
|
+ File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!File.Exists(keysSource))
|
|
|
+ {
|
|
|
+ throw new FileNotFoundException("Keys file does not exist.");
|
|
|
+ }
|
|
|
+
|
|
|
+ FileInfo info = new(keysSource);
|
|
|
+
|
|
|
+ using FileStream file = File.OpenRead(keysSource);
|
|
|
+
|
|
|
+ switch (info.Extension)
|
|
|
+ {
|
|
|
+ case ".zip":
|
|
|
+ using (ZipArchive archive = ZipFile.OpenRead(keysSource))
|
|
|
+ {
|
|
|
+ InstallKeysFromZip(archive, installDirectory);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ".keys":
|
|
|
+ VerifyKeysFile(keysSource);
|
|
|
+ File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void InstallKeysFromZip(ZipArchive archive, string installDirectory)
|
|
|
+ {
|
|
|
+ string temporaryDirectory = Path.Combine(installDirectory, "temp");
|
|
|
+ if (Directory.Exists(temporaryDirectory))
|
|
|
+ {
|
|
|
+ Directory.Delete(temporaryDirectory, true);
|
|
|
+ }
|
|
|
+ Directory.CreateDirectory(temporaryDirectory);
|
|
|
+ foreach (var entry in archive.Entries)
|
|
|
+ {
|
|
|
+ if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ string extractDestination = Path.Combine(temporaryDirectory, entry.Name);
|
|
|
+ entry.ExtractToFile(extractDestination, overwrite: true);
|
|
|
+ try
|
|
|
+ {
|
|
|
+ VerifyKeysFile(extractDestination);
|
|
|
+ File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ Directory.Delete(temporaryDirectory, true);
|
|
|
+ throw;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Directory.Delete(temporaryDirectory, true);
|
|
|
+ }
|
|
|
+
|
|
|
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
|
|
{
|
|
|
if (Directory.Exists(registeredDirectory))
|
|
|
@@ -947,5 +1016,70 @@ namespace Ryujinx.HLE.FileSystem
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
+
|
|
|
+ public void VerifyKeysFile(string filePath)
|
|
|
+ {
|
|
|
+ // Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
|
|
|
+ string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
|
|
|
+ string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
|
|
|
+
|
|
|
+ if (File.Exists(filePath))
|
|
|
+ {
|
|
|
+ // Read all lines from the file
|
|
|
+ string fileName = Path.GetFileName(filePath);
|
|
|
+ string[] lines = File.ReadAllLines(filePath);
|
|
|
+
|
|
|
+ bool verified = false;
|
|
|
+ switch (fileName)
|
|
|
+ {
|
|
|
+ case "prod.keys":
|
|
|
+ verified = verifyKeys(lines, genericPattern);
|
|
|
+ break;
|
|
|
+ case "title.keys":
|
|
|
+ verified = verifyKeys(lines, titlePattern);
|
|
|
+ break;
|
|
|
+ case "console.keys":
|
|
|
+ verified = verifyKeys(lines, genericPattern);
|
|
|
+ break;
|
|
|
+ case "dev.keys":
|
|
|
+ verified = verifyKeys(lines, genericPattern);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
|
|
+ }
|
|
|
+ if (!verified)
|
|
|
+ {
|
|
|
+ throw new FormatException($"Invalid \"{filePath}\" file format.");
|
|
|
+ }
|
|
|
+ } else
|
|
|
+ {
|
|
|
+ throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool verifyKeys(string[] lines, string regex)
|
|
|
+ {
|
|
|
+ foreach (string line in lines)
|
|
|
+ {
|
|
|
+ if (!Regex.IsMatch(line, regex))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool AreKeysAlredyPresent(string pathToCheck)
|
|
|
+ {
|
|
|
+ string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
|
|
+ foreach (var file in fileNames)
|
|
|
+ {
|
|
|
+ if (File.Exists(Path.Combine(pathToCheck, file)))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|
|
|
}
|