SaveImporter.cs 6.4 KB

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