Rename RyuFs directory to Ryujinx
This commit is contained in:
parent
c965acaa2d
commit
788eca55de
4 changed files with 305 additions and 236 deletions
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
public class VirtualFileSystem : IDisposable
|
||||
{
|
||||
public const string BasePath = "RyuFs";
|
||||
public const string BasePath = "Ryujinx";
|
||||
public const string NandPath = "bis";
|
||||
public const string SdCardPath = "sdcard";
|
||||
public const string SystemPath = "system";
|
||||
|
|
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Gal.OpenGL;
|
|||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.Ui;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
@ -88,16 +87,26 @@ namespace Ryujinx.Ui
|
|||
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
|
||||
bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded);
|
||||
if (!continueWithStartup)
|
||||
{
|
||||
End();
|
||||
}
|
||||
|
||||
_renderer = new OglRenderer();
|
||||
|
||||
_audioOut = InitializeAudioEngine();
|
||||
|
||||
_device = new HLE.Switch(_renderer, _audioOut);
|
||||
|
||||
bool continueAfterMigration = Migration.TryMigrateForStartup(this, _device);
|
||||
if (!continueAfterMigration)
|
||||
if (migrationNeeded)
|
||||
{
|
||||
End();
|
||||
bool migrationSuccessful = Migration.DoMigrationForStartup(this, _device);
|
||||
|
||||
if (!migrationSuccessful)
|
||||
{
|
||||
End();
|
||||
}
|
||||
}
|
||||
|
||||
_treeView = _gameTable;
|
||||
|
@ -449,8 +458,8 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
|
||||
Profile.FinishProfiling();
|
||||
_device.Dispose();
|
||||
_audioOut.Dispose();
|
||||
_device?.Dispose();
|
||||
_audioOut?.Dispose();
|
||||
DiscordClient?.Dispose();
|
||||
Logger.Shutdown();
|
||||
Environment.Exit(0);
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
using Gtk;
|
||||
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.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
|
||||
|
@ -27,15 +18,18 @@ namespace Ryujinx.Ui
|
|||
Device = device;
|
||||
}
|
||||
|
||||
public static bool TryMigrateForStartup(Window parentWindow, Switch device)
|
||||
public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded)
|
||||
{
|
||||
const int responseYes = -8;
|
||||
|
||||
if (!IsMigrationNeeded(device.FileSystem.GetBasePath()))
|
||||
if (!IsMigrationNeeded())
|
||||
{
|
||||
isMigrationNeeded = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
isMigrationNeeded = true;
|
||||
|
||||
int dialogResponse;
|
||||
|
||||
using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question,
|
||||
|
@ -51,11 +45,11 @@ namespace Ryujinx.Ui
|
|||
dialogResponse = dialog.Run();
|
||||
}
|
||||
|
||||
if (dialogResponse != responseYes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return dialogResponse == responseYes;
|
||||
}
|
||||
|
||||
public static bool DoMigrationForStartup(Window parentWindow, Switch device)
|
||||
{
|
||||
try
|
||||
{
|
||||
Migration migration = new Migration(device);
|
||||
|
@ -83,259 +77,107 @@ namespace Ryujinx.Ui
|
|||
// Returns the number of saves migrated
|
||||
public int Migrate()
|
||||
{
|
||||
string basePath = Device.FileSystem.GetBasePath();
|
||||
string backupPath = Path.Combine(basePath, "Migration backup (Can delete if successful)");
|
||||
string backupUserSavePath = Path.Combine(backupPath, "nand/user/save");
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
||||
if (!IsMigrationNeeded(basePath))
|
||||
return 0;
|
||||
string oldBasePath = Path.Combine(appDataPath, "RyuFs");
|
||||
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.Exists(Path.Combine(basePath, "sdcard"));
|
||||
Directory.CreateDirectory(newPath);
|
||||
|
||||
bool hasOldDirs = Directory.Exists(Path.Combine(basePath, "nand")) ||
|
||||
Directory.Exists(Path.Combine(basePath, "sdmc"));
|
||||
CopyExcept(oldPath, newPath, "nand", "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"));
|
||||
RenameDirectory(Path.Combine(basePath, "sdmc"), Path.Combine(basePath, "sdcard"));
|
||||
}
|
||||
exclude = exclude.Select(x => x.ToLowerInvariant()).ToArray();
|
||||
|
||||
private static bool RenameDirectory(string oldDir, string newDir)
|
||||
{
|
||||
if (Directory.Exists(newDir))
|
||||
return false;
|
||||
DirectoryInfo srcDir = new DirectoryInfo(srcPath);
|
||||
|
||||
if (!Directory.Exists(oldDir))
|
||||
if (!srcDir.Exists)
|
||||
{
|
||||
Directory.CreateDirectory(newDir);
|
||||
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(newDir));
|
||||
Directory.Move(oldDir, newDir);
|
||||
Directory.CreateDirectory(dstPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void BackupSaves(string basePath, string backupPath)
|
||||
{
|
||||
Directory.CreateDirectory(backupPath);
|
||||
|
||||
string userSaveDir = Path.Combine(basePath, "nand/user/save");
|
||||
string backupUserSaveDir = Path.Combine(backupPath, "nand/user/save");
|
||||
|
||||
if (Directory.Exists(userSaveDir))
|
||||
foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
|
||||
{
|
||||
RenameDirectory(userSaveDir, backupUserSaveDir);
|
||||
if (exclude.Contains(subDir.Name.ToLowerInvariant()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
|
||||
}
|
||||
|
||||
string systemSaveDir = Path.Combine(basePath, "nand/system/save");
|
||||
string backupSystemSaveDir = Path.Combine(backupPath, "nand/system/save");
|
||||
|
||||
if (Directory.Exists(systemSaveDir))
|
||||
foreach (FileInfo file in srcDir.EnumerateFiles())
|
||||
{
|
||||
RenameDirectory(systemSaveDir, backupSystemSaveDir);
|
||||
file.CopyTo(Path.Combine(dstPath, file.Name));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the number of saves migrated
|
||||
private static int MigrateSaves(FileSystemClient fsClient, string rootSaveDir)
|
||||
private static void CopyDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
if (!Directory.Exists(rootSaveDir))
|
||||
Directory.CreateDirectory(dstPath);
|
||||
|
||||
DirectoryInfo srcDir = new DirectoryInfo(srcPath);
|
||||
|
||||
if (!srcDir.Exists)
|
||||
{
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
SaveFinder finder = new SaveFinder();
|
||||
finder.FindSaves(rootSaveDir);
|
||||
Directory.CreateDirectory(dstPath);
|
||||
|
||||
foreach (SaveToMigrate save in finder.Saves)
|
||||
foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
|
||||
{
|
||||
Result migrateResult = MigrateSave(fsClient, save);
|
||||
|
||||
if (migrateResult.IsFailure())
|
||||
{
|
||||
throw new HorizonResultException(migrateResult, $"Error migrating save {save.Path}");
|
||||
}
|
||||
CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
|
||||
}
|
||||
|
||||
return finder.Saves.Count;
|
||||
}
|
||||
|
||||
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
|
||||
foreach (FileInfo file in srcDir.EnumerateFiles())
|
||||
{
|
||||
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;
|
||||
file.CopyTo(Path.Combine(dstPath, file.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private class SaveToMigrate
|
||||
private static bool IsMigrationNeeded()
|
||||
{
|
||||
public string Path { get; }
|
||||
public SaveDataAttribute Attribute { get; }
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
||||
public SaveToMigrate(string path, SaveDataAttribute attribute)
|
||||
{
|
||||
Path = path;
|
||||
Attribute = attribute;
|
||||
}
|
||||
string oldBasePath = Path.Combine(appDataPath, "RyuFs");
|
||||
string newBasePath = Path.Combine(appDataPath, "Ryujinx");
|
||||
|
||||
return Directory.Exists(oldBasePath) && !Directory.Exists(newBasePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
218
Ryujinx/Ui/SaveImporter.cs
Normal file
218
Ryujinx/Ui/SaveImporter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue