Rename RyuFs directory to Ryujinx

This commit is contained in:
Alex Barney 2019-11-30 15:24:24 -06:00
commit 788eca55de
4 changed files with 305 additions and 236 deletions

View file

@ -7,7 +7,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
public class VirtualFileSystem : IDisposable public class VirtualFileSystem : IDisposable
{ {
public const string BasePath = "RyuFs"; public const string BasePath = "Ryujinx";
public const string NandPath = "bis"; public const string NandPath = "bis";
public const string SdCardPath = "sdcard"; public const string SdCardPath = "sdcard";
public const string SystemPath = "system"; public const string SystemPath = "system";

View file

@ -7,7 +7,6 @@ using Ryujinx.Graphics.Gal.OpenGL;
using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Profiler; using Ryujinx.Profiler;
using Ryujinx.Ui;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -88,17 +87,27 @@ namespace Ryujinx.Ui
ApplicationLibrary.ApplicationAdded += Application_Added; ApplicationLibrary.ApplicationAdded += Application_Added;
bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded);
if (!continueWithStartup)
{
End();
}
_renderer = new OglRenderer(); _renderer = new OglRenderer();
_audioOut = InitializeAudioEngine(); _audioOut = InitializeAudioEngine();
_device = new HLE.Switch(_renderer, _audioOut); _device = new HLE.Switch(_renderer, _audioOut);
bool continueAfterMigration = Migration.TryMigrateForStartup(this, _device); if (migrationNeeded)
if (!continueAfterMigration) {
bool migrationSuccessful = Migration.DoMigrationForStartup(this, _device);
if (!migrationSuccessful)
{ {
End(); End();
} }
}
_treeView = _gameTable; _treeView = _gameTable;
@ -449,8 +458,8 @@ namespace Ryujinx.Ui
} }
Profile.FinishProfiling(); Profile.FinishProfiling();
_device.Dispose(); _device?.Dispose();
_audioOut.Dispose(); _audioOut?.Dispose();
DiscordClient?.Dispose(); DiscordClient?.Dispose();
Logger.Shutdown(); Logger.Shutdown();
Environment.Exit(0); Environment.Exit(0);

View file

@ -1,18 +1,9 @@
using Gtk; using Gtk;
using LibHac; using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
using Ryujinx.HLE.Utilities;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using Switch = Ryujinx.HLE.Switch; using Switch = Ryujinx.HLE.Switch;
@ -27,15 +18,18 @@ namespace Ryujinx.Ui
Device = device; Device = device;
} }
public static bool TryMigrateForStartup(Window parentWindow, Switch device) public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded)
{ {
const int responseYes = -8; const int responseYes = -8;
if (!IsMigrationNeeded(device.FileSystem.GetBasePath())) if (!IsMigrationNeeded())
{ {
isMigrationNeeded = false;
return true; return true;
} }
isMigrationNeeded = true;
int dialogResponse; int dialogResponse;
using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question, using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question,
@ -51,11 +45,11 @@ namespace Ryujinx.Ui
dialogResponse = dialog.Run(); dialogResponse = dialog.Run();
} }
if (dialogResponse != responseYes) return dialogResponse == responseYes;
{
return false;
} }
public static bool DoMigrationForStartup(Window parentWindow, Switch device)
{
try try
{ {
Migration migration = new Migration(device); Migration migration = new Migration(device);
@ -83,259 +77,107 @@ namespace Ryujinx.Ui
// Returns the number of saves migrated // Returns the number of saves migrated
public int Migrate() public int Migrate()
{ {
string basePath = Device.FileSystem.GetBasePath(); string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string backupPath = Path.Combine(basePath, "Migration backup (Can delete if successful)");
string backupUserSavePath = Path.Combine(backupPath, "nand/user/save");
if (!IsMigrationNeeded(basePath)) string oldBasePath = Path.Combine(appDataPath, "RyuFs");
return 0; string newBasePath = Path.Combine(appDataPath, "Ryujinx");
BackupSaves(basePath, backupPath); string oldSaveDir = Path.Combine(oldBasePath, "nand/user/save");
MigrateDirectories(basePath); CopyRyuFs(oldBasePath, newBasePath);
return MigrateSaves(Device.System.FsClient, backupUserSavePath); var importer = new SaveImporter(oldSaveDir, Device.System.FsClient);
return importer.Import();
} }
private static bool IsMigrationNeeded(string basePath) private static void CopyRyuFs(string oldPath, string newPath)
{ {
bool missingNewDirs = !Directory.Exists(Path.Combine(basePath, "bis")) && Directory.CreateDirectory(newPath);
!Directory.Exists(Path.Combine(basePath, "sdcard"));
bool hasOldDirs = Directory.Exists(Path.Combine(basePath, "nand")) || CopyExcept(oldPath, newPath, "nand", "sdmc");
Directory.Exists(Path.Combine(basePath, "sdmc"));
return missingNewDirs && hasOldDirs; string oldNandPath = Path.Combine(oldPath, "nand");
string newNandPath = Path.Combine(newPath, "bis");
CopyExcept(oldNandPath, newNandPath, "system", "user");
string oldSdPath = Path.Combine(oldPath, "sdmc");
string newSdPath = Path.Combine(newPath, "sdcard");
CopyDirectory(oldSdPath, newSdPath);
string oldSystemPath = Path.Combine(oldNandPath, "system");
string newSystemPath = Path.Combine(newNandPath, "system");
CopyExcept(oldSystemPath, newSystemPath, "save");
string oldUserPath = Path.Combine(oldNandPath, "user");
string newUserPath = Path.Combine(newNandPath, "user");
CopyExcept(oldUserPath, newUserPath, "save");
} }
private static void MigrateDirectories(string basePath) private static void CopyExcept(string srcPath, string dstPath, params string[] exclude)
{ {
RenameDirectory(Path.Combine(basePath, "nand"), Path.Combine(basePath, "bis")); exclude = exclude.Select(x => x.ToLowerInvariant()).ToArray();
RenameDirectory(Path.Combine(basePath, "sdmc"), Path.Combine(basePath, "sdcard"));
DirectoryInfo srcDir = new DirectoryInfo(srcPath);
if (!srcDir.Exists)
{
return;
} }
private static bool RenameDirectory(string oldDir, string newDir) Directory.CreateDirectory(dstPath);
{
if (Directory.Exists(newDir))
return false;
if (!Directory.Exists(oldDir)) foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
{ {
Directory.CreateDirectory(newDir); if (exclude.Contains(subDir.Name.ToLowerInvariant()))
{
return true; continue;
} }
Directory.CreateDirectory(Path.GetDirectoryName(newDir)); CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
Directory.Move(oldDir, newDir);
return true;
} }
private static void BackupSaves(string basePath, string backupPath) foreach (FileInfo file in srcDir.EnumerateFiles())
{ {
Directory.CreateDirectory(backupPath); file.CopyTo(Path.Combine(dstPath, file.Name));
string userSaveDir = Path.Combine(basePath, "nand/user/save");
string backupUserSaveDir = Path.Combine(backupPath, "nand/user/save");
if (Directory.Exists(userSaveDir))
{
RenameDirectory(userSaveDir, backupUserSaveDir);
}
string systemSaveDir = Path.Combine(basePath, "nand/system/save");
string backupSystemSaveDir = Path.Combine(backupPath, "nand/system/save");
if (Directory.Exists(systemSaveDir))
{
RenameDirectory(systemSaveDir, backupSystemSaveDir);
} }
} }
// Returns the number of saves migrated private static void CopyDirectory(string srcPath, string dstPath)
private static int MigrateSaves(FileSystemClient fsClient, string rootSaveDir)
{ {
if (!Directory.Exists(rootSaveDir)) Directory.CreateDirectory(dstPath);
DirectoryInfo srcDir = new DirectoryInfo(srcPath);
if (!srcDir.Exists)
{ {
return 0; return;
} }
SaveFinder finder = new SaveFinder(); Directory.CreateDirectory(dstPath);
finder.FindSaves(rootSaveDir);
foreach (SaveToMigrate save in finder.Saves) foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
{ {
Result migrateResult = MigrateSave(fsClient, save); CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
}
if (migrateResult.IsFailure()) foreach (FileInfo file in srcDir.EnumerateFiles())
{ {
throw new HorizonResultException(migrateResult, $"Error migrating save {save.Path}"); file.CopyTo(Path.Combine(dstPath, file.Name));
} }
} }
return finder.Saves.Count; private static bool IsMigrationNeeded()
}
private static Result MigrateSave(FileSystemClient fs, SaveToMigrate save)
{ {
SaveDataAttribute key = save.Attribute; string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
Result result = fs.CreateSaveData(key.TitleId, key.UserId, key.TitleId, 0, 0, 0); string oldBasePath = Path.Combine(appDataPath, "RyuFs");
if (result.IsFailure()) return result; string newBasePath = Path.Combine(appDataPath, "Ryujinx");
bool isOldMounted = false; return Directory.Exists(oldBasePath) && !Directory.Exists(newBasePath);
bool isNewMounted = false;
try
{
result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path));
if (result.IsFailure()) return result;
isOldMounted = true;
result = fs.MountSaveData("NewSave".ToU8Span(), key.TitleId, key.UserId);
if (result.IsFailure()) return result;
isNewMounted = true;
result = fs.CopyDirectory("OldSave:/", "NewSave:/");
if (result.IsFailure()) return result;
result = fs.Commit("NewSave");
}
finally
{
if (isOldMounted)
{
fs.Unmount("OldSave");
}
if (isNewMounted)
{
fs.Unmount("NewSave");
}
}
return result;
}
private class SaveFinder
{
public List<SaveToMigrate> Saves { get; } = new List<SaveToMigrate>();
public void FindSaves(string rootPath)
{
foreach (string subDir in Directory.EnumerateDirectories(rootPath))
{
if (TryGetUInt64(subDir, out ulong saveDataId))
{
SearchSaveId(subDir, saveDataId);
}
}
}
private void SearchSaveId(string path, ulong saveDataId)
{
foreach (string subDir in Directory.EnumerateDirectories(path))
{
if (TryGetUserId(subDir, out UserId userId))
{
SearchUser(subDir, saveDataId, userId);
}
}
}
private void SearchUser(string path, ulong saveDataId, UserId userId)
{
foreach (string subDir in Directory.EnumerateDirectories(path))
{
if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath))
{
SaveDataAttribute attribute = new SaveDataAttribute
{
Type = SaveDataType.SaveData,
UserId = userId,
TitleId = new TitleId(titleId)
};
SaveToMigrate save = new SaveToMigrate(dataPath, attribute);
Saves.Add(save);
}
}
}
private static bool TryGetDataPath(string path, out string dataPath)
{
string committedPath = Path.Combine(path, "0");
string workingPath = Path.Combine(path, "1");
if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any())
{
dataPath = committedPath;
return true;
}
if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any())
{
dataPath = workingPath;
return true;
}
dataPath = default;
return false;
}
private static bool TryGetUInt64(string path, out ulong converted)
{
string name = Path.GetFileName(path);
if (name.Length == 16)
{
try
{
converted = Convert.ToUInt64(name, 16);
return true;
}
catch { }
}
converted = default;
return false;
}
private static bool TryGetUserId(string path, out UserId userId)
{
string name = Path.GetFileName(path);
if (name.Length == 32)
{
try
{
UInt128 id = new UInt128(name);
userId = Unsafe.As<UInt128, UserId>(ref id);
return true;
}
catch { }
}
userId = default;
return false;
}
}
private class SaveToMigrate
{
public string Path { get; }
public SaveDataAttribute Attribute { get; }
public SaveToMigrate(string path, SaveDataAttribute attribute)
{
Path = path;
Attribute = attribute;
}
} }
} }
} }

218
Ryujinx/Ui/SaveImporter.cs Normal file
View file

@ -0,0 +1,218 @@
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Ryujinx.Ui
{
internal class SaveImporter
{
private FileSystemClient FsClient { get; }
private string ImportPath { get; }
public SaveImporter(string importPath, FileSystemClient destFsClient)
{
ImportPath = importPath;
FsClient = destFsClient;
}
// Returns the number of saves imported
public int Import()
{
return ImportSaves(FsClient, ImportPath);
}
private static int ImportSaves(FileSystemClient fsClient, string rootSaveDir)
{
if (!Directory.Exists(rootSaveDir))
{
return 0;
}
SaveFinder finder = new SaveFinder();
finder.FindSaves(rootSaveDir);
foreach (SaveToImport save in finder.Saves)
{
Result importResult = ImportSave(fsClient, save);
if (importResult.IsFailure())
{
throw new HorizonResultException(importResult, $"Error importing save {save.Path}");
}
}
return finder.Saves.Count;
}
private static Result ImportSave(FileSystemClient fs, SaveToImport save)
{
SaveDataAttribute key = save.Attribute;
Result result = fs.CreateSaveData(key.TitleId, key.UserId, key.TitleId, 0, 0, 0);
if (result.IsFailure()) return result;
bool isOldMounted = false;
bool isNewMounted = false;
try
{
result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path));
if (result.IsFailure()) return result;
isOldMounted = true;
result = fs.MountSaveData("NewSave".ToU8Span(), key.TitleId, key.UserId);
if (result.IsFailure()) return result;
isNewMounted = true;
result = fs.CopyDirectory("OldSave:/", "NewSave:/");
if (result.IsFailure()) return result;
result = fs.Commit("NewSave");
}
finally
{
if (isOldMounted)
{
fs.Unmount("OldSave");
}
if (isNewMounted)
{
fs.Unmount("NewSave");
}
}
return result;
}
private class SaveFinder
{
public List<SaveToImport> Saves { get; } = new List<SaveToImport>();
public void FindSaves(string rootPath)
{
foreach (string subDir in Directory.EnumerateDirectories(rootPath))
{
if (TryGetUInt64(subDir, out ulong saveDataId))
{
SearchSaveId(subDir, saveDataId);
}
}
}
private void SearchSaveId(string path, ulong saveDataId)
{
foreach (string subDir in Directory.EnumerateDirectories(path))
{
if (TryGetUserId(subDir, out UserId userId))
{
SearchUser(subDir, saveDataId, userId);
}
}
}
private void SearchUser(string path, ulong saveDataId, UserId userId)
{
foreach (string subDir in Directory.EnumerateDirectories(path))
{
if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath))
{
SaveDataAttribute attribute = new SaveDataAttribute
{
Type = SaveDataType.SaveData,
UserId = userId,
TitleId = new TitleId(titleId)
};
SaveToImport save = new SaveToImport(dataPath, attribute);
Saves.Add(save);
}
}
}
private static bool TryGetDataPath(string path, out string dataPath)
{
string committedPath = Path.Combine(path, "0");
string workingPath = Path.Combine(path, "1");
if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any())
{
dataPath = committedPath;
return true;
}
if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any())
{
dataPath = workingPath;
return true;
}
dataPath = default;
return false;
}
private static bool TryGetUInt64(string path, out ulong converted)
{
string name = Path.GetFileName(path);
if (name.Length == 16)
{
try
{
converted = Convert.ToUInt64(name, 16);
return true;
}
catch { }
}
converted = default;
return false;
}
private static bool TryGetUserId(string path, out UserId userId)
{
string name = Path.GetFileName(path);
if (name.Length == 32)
{
try
{
UInt128 id = new UInt128(name);
userId = Unsafe.As<UInt128, UserId>(ref id);
return true;
}
catch { }
}
userId = default;
return false;
}
}
private class SaveToImport
{
public string Path { get; }
public SaveDataAttribute Attribute { get; }
public SaveToImport(string path, SaveDataAttribute attribute)
{
Path = path;
Attribute = attribute;
}
}
}
}