SaveImporter.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using LibHac;
  2. using LibHac.Common;
  3. using LibHac.Fs;
  4. using LibHac.Fs.Shim;
  5. using LibHac.FsSystem;
  6. using LibHac.Ncm;
  7. using Ryujinx.HLE.Utilities;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Runtime.CompilerServices;
  13. namespace Ryujinx.Ui
  14. {
  15. internal class SaveImporter
  16. {
  17. private FileSystemClient FsClient { get; }
  18. private string ImportPath { get; }
  19. public SaveImporter(string importPath, FileSystemClient destFsClient)
  20. {
  21. ImportPath = importPath;
  22. FsClient = destFsClient;
  23. }
  24. // Returns the number of saves imported
  25. public int Import()
  26. {
  27. return ImportSaves(FsClient, ImportPath);
  28. }
  29. private static int ImportSaves(FileSystemClient fsClient, string rootSaveDir)
  30. {
  31. if (!Directory.Exists(rootSaveDir))
  32. {
  33. return 0;
  34. }
  35. SaveFinder finder = new SaveFinder();
  36. finder.FindSaves(rootSaveDir);
  37. foreach (SaveToImport save in finder.Saves)
  38. {
  39. Result importResult = ImportSave(fsClient, save);
  40. if (importResult.IsFailure())
  41. {
  42. throw new HorizonResultException(importResult, $"Error importing save {save.Path}");
  43. }
  44. }
  45. return finder.Saves.Count;
  46. }
  47. private static Result ImportSave(FileSystemClient fs, SaveToImport save)
  48. {
  49. SaveDataAttribute key = save.Attribute;
  50. Result result = fs.CreateSaveData(key.TitleId, key.UserId, key.TitleId, 0, 0, 0);
  51. if (result.IsFailure()) return result;
  52. bool isOldMounted = false;
  53. bool isNewMounted = false;
  54. try
  55. {
  56. result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path));
  57. if (result.IsFailure()) return result;
  58. isOldMounted = true;
  59. result = fs.MountSaveData("NewSave".ToU8Span(), key.TitleId, key.UserId);
  60. if (result.IsFailure()) return result;
  61. isNewMounted = true;
  62. result = fs.CopyDirectory("OldSave:/", "NewSave:/");
  63. if (result.IsFailure()) return result;
  64. result = fs.Commit("NewSave".ToU8Span());
  65. }
  66. finally
  67. {
  68. if (isOldMounted)
  69. {
  70. fs.Unmount("OldSave".ToU8Span());
  71. }
  72. if (isNewMounted)
  73. {
  74. fs.Unmount("NewSave".ToU8Span());
  75. }
  76. }
  77. return result;
  78. }
  79. private class SaveFinder
  80. {
  81. public List<SaveToImport> Saves { get; } = new List<SaveToImport>();
  82. public void FindSaves(string rootPath)
  83. {
  84. foreach (string subDir in Directory.EnumerateDirectories(rootPath))
  85. {
  86. if (TryGetUInt64(subDir, out ulong saveDataId))
  87. {
  88. SearchSaveId(subDir, saveDataId);
  89. }
  90. }
  91. }
  92. private void SearchSaveId(string path, ulong saveDataId)
  93. {
  94. foreach (string subDir in Directory.EnumerateDirectories(path))
  95. {
  96. if (TryGetUserId(subDir, out UserId userId))
  97. {
  98. SearchUser(subDir, saveDataId, userId);
  99. }
  100. }
  101. }
  102. private void SearchUser(string path, ulong saveDataId, UserId userId)
  103. {
  104. foreach (string subDir in Directory.EnumerateDirectories(path))
  105. {
  106. if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath))
  107. {
  108. SaveDataAttribute attribute = new SaveDataAttribute
  109. {
  110. Type = SaveDataType.Account,
  111. UserId = userId,
  112. TitleId = new TitleId(titleId)
  113. };
  114. SaveToImport save = new SaveToImport(dataPath, attribute);
  115. Saves.Add(save);
  116. }
  117. }
  118. }
  119. private static bool TryGetDataPath(string path, out string dataPath)
  120. {
  121. string committedPath = Path.Combine(path, "0");
  122. string workingPath = Path.Combine(path, "1");
  123. if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any())
  124. {
  125. dataPath = committedPath;
  126. return true;
  127. }
  128. if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any())
  129. {
  130. dataPath = workingPath;
  131. return true;
  132. }
  133. dataPath = default;
  134. return false;
  135. }
  136. private static bool TryGetUInt64(string path, out ulong converted)
  137. {
  138. string name = Path.GetFileName(path);
  139. if (name.Length == 16)
  140. {
  141. try
  142. {
  143. converted = Convert.ToUInt64(name, 16);
  144. return true;
  145. }
  146. catch { }
  147. }
  148. converted = default;
  149. return false;
  150. }
  151. private static bool TryGetUserId(string path, out UserId userId)
  152. {
  153. string name = Path.GetFileName(path);
  154. if (name.Length == 32)
  155. {
  156. try
  157. {
  158. UInt128 id = new UInt128(name);
  159. userId = Unsafe.As<UInt128, UserId>(ref id);
  160. return true;
  161. }
  162. catch { }
  163. }
  164. userId = default;
  165. return false;
  166. }
  167. }
  168. private class SaveToImport
  169. {
  170. public string Path { get; }
  171. public SaveDataAttribute Attribute { get; }
  172. public SaveToImport(string path, SaveDataAttribute attribute)
  173. {
  174. Path = path;
  175. Attribute = attribute;
  176. }
  177. }
  178. }
  179. }