Add a function to migrate to the new directory layout

This commit is contained in:
Alex Barney 2019-10-30 14:39:02 -05:00
parent 6cb52a5909
commit 60af09611e
6 changed files with 301 additions and 44 deletions

View file

@ -120,7 +120,7 @@ namespace Ryujinx.HLE.HOS
internal long HidBaseAddress { get; private set; }
internal FileSystemServer FsServer { get; private set; }
internal FileSystemClient FsClient { get; private set; }
public FileSystemClient FsClient { get; private set; }
internal EmulatedGameCard GameCard { get; private set; }

View file

@ -3,54 +3,12 @@ using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Spl;
using Ryujinx.Common;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Utilities;
using System.IO;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{
static class FileSystemProxyHelper
{
public static ResultCode LoadSaveDataFileSystem(ServiceCtx context, bool readOnly, out IFileSystem loadedFileSystem)
{
loadedFileSystem = null;
SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64();
ulong titleId = context.RequestData.ReadUInt64();
UInt128 userId = context.RequestData.ReadStruct<UInt128>();
long saveId = context.RequestData.ReadInt64();
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte();
SaveInfo saveInfo = new SaveInfo(titleId, saveId, saveDataType, saveSpaceId, userId);
string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo);
try
{
LocalFileSystem fileSystem = new LocalFileSystem(savePath);
Result result = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem dirFileSystem, fileSystem);
if (result.IsFailure())
{
return (ResultCode)result.Value;
}
LibHac.Fs.IFileSystem saveFileSystem = dirFileSystem;
if (readOnly)
{
saveFileSystem = new ReadOnlyFileSystem(saveFileSystem);
}
loadedFileSystem = new IFileSystem(saveFileSystem);
}
catch (HorizonResultException ex)
{
return (ResultCode)ex.ResultValue.Value;
}
return ResultCode.Success;
}
public static ResultCode OpenNsp(ServiceCtx context, string pfsPath, out IFileSystem openedFileSystem)
{
openedFileSystem = null;

View file

@ -17,7 +17,7 @@ namespace Ryujinx.HLE
internal NvGpu Gpu { get; private set; }
internal VirtualFileSystem FileSystem { get; private set; }
public VirtualFileSystem FileSystem { get; private set; }
public Horizon System { get; private set; }

View file

@ -6,6 +6,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Gal.OpenGL;
using Ryujinx.Profiler;
using Ryujinx.Ui;
using System;
using System.Diagnostics;
using System.IO;
@ -524,6 +525,13 @@ namespace Ryujinx.UI
}
}
private void Migration_Pressed(object o, EventArgs args)
{
Migration migration = new Migration(_device);
migration.Migrate();
}
private void About_Pressed(object o, EventArgs args)
{
AboutWindow AboutWin = new AboutWindow();

View file

@ -253,6 +253,22 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes">Tools</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="DoMigration">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Migrate files from an old layout</property>
<property name="label" translatable="yes">Migrate Files</property>
<property name="use_underline">True</property>
<signal name="activate" handler="Migration_Pressed" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>

275
Ryujinx/Ui/Migration.cs Normal file
View file

@ -0,0 +1,275 @@
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;
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 Migration
{
private Switch Device { get; }
public Migration(Switch device)
{
Device = device;
}
public void Migrate()
{
string basePath = Device.FileSystem.GetBasePath();
string backupPath = Path.Combine(basePath, "Migration backup (Can delete if successful)");
string backupUserSavePath = Path.Combine(backupPath, "userSaves");
if (!IsMigrationNeeded(basePath))
return;
MigrateDirectories(basePath);
BackupSaves(basePath, backupPath);
MigrateSaves(Device.System.FsClient, backupUserSavePath);
}
private static bool IsMigrationNeeded(string basePath)
{
return !Directory.Exists(Path.Combine(basePath, "bis")) &&
!Directory.Exists(Path.Combine(basePath, "sdcard"));
}
private static void MigrateDirectories(string basePath)
{
RenameDirectory(Path.Combine(basePath, "nand"), Path.Combine(basePath, "bis"));
RenameDirectory(Path.Combine(basePath, "sdmc"), Path.Combine(basePath, "sdcard"));
}
private static bool RenameDirectory(string oldDir, string newDir)
{
if (Directory.Exists(newDir))
return false;
if (!Directory.Exists(oldDir))
{
Directory.CreateDirectory(newDir);
return true;
}
Directory.Move(oldDir, newDir);
return true;
}
private static void BackupSaves(string basePath, string backupPath)
{
Directory.CreateDirectory(backupPath);
string userSaveDir = Path.Combine(basePath, "bis/user/save");
string backupUserSaveDir = Path.Combine(backupPath, "userSaves");
if (Directory.Exists(userSaveDir))
{
RenameDirectory(userSaveDir, backupUserSaveDir);
}
string systemSaveDir = Path.Combine(basePath, "bis/system/save");
string backupSystemSaveDir = Path.Combine(backupPath, "systemSaves");
if (Directory.Exists(systemSaveDir))
{
RenameDirectory(systemSaveDir, backupSystemSaveDir);
}
}
private static void MigrateSaves(FileSystemClient fsClient, string rootSaveDir)
{
if (!Directory.Exists(rootSaveDir))
{
return;
}
SaveFinder finder = new SaveFinder();
finder.FindSaves(rootSaveDir);
foreach (SaveToMigrate save in finder.Saves)
{
Result migrateResult = MigrateSave(fsClient, save);
if (migrateResult.IsFailure())
{
throw new HorizonResultException(migrateResult, $"Error migrating save {save.Path}");
}
}
}
private static Result MigrateSave(FileSystemClient fs, SaveToMigrate 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<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;
}
}
}
}