Add a function to migrate to the new directory layout
This commit is contained in:
parent
6cb52a5909
commit
60af09611e
6 changed files with 301 additions and 44 deletions
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
275
Ryujinx/Ui/Migration.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue