SaveManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. using UnityEngine;
  2. using ExIni;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using System.IO;
  7. using Util;
  8. namespace CM3D2.MultipleMaids.Plugin
  9. {
  10. public partial class MultipleMaids
  11. {
  12. internal static readonly byte[] pngEnd = Encoding.ASCII.GetBytes("IEND");
  13. internal static readonly byte[] kankyoHeader = Encoding.ASCII.GetBytes("KANKYO");
  14. internal const string sceneDirectoryName = "< Scene Root >";
  15. internal const string kankyoDirectoryName = "< Kankyo Root >";
  16. internal readonly string saveScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsScene");
  17. private static string kankyoScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsKankyo");
  18. private static string sceneData;
  19. private static List<ScenePng> scenes = new List<ScenePng>(50);
  20. private GUIStyle selectedButtonStyle;
  21. private GUIContent infoString;
  22. private string[] directoryList;
  23. private Texture2D frame;
  24. private Texture2D infoHighlight;
  25. private Rect sceneManagerRect;
  26. private Rect sceneModalRect;
  27. private Rect resizeManagerRect;
  28. private Vector2 sceneManagerScrollPos = Vector2.zero;
  29. private Vector2 dirListScrollPos = Vector2.zero;
  30. private bool sceneManagerInitialize = false;
  31. private bool loadSceneFlag = false;
  32. private bool overwriteFlag = false;
  33. private bool createSceneFlag = false;
  34. private bool quickSaveFlag = false;
  35. private bool manageSceneFlag = false;
  36. private bool kankyoModeFlag = false;
  37. private bool kankyoToggle = false;
  38. private bool kankyoScene = false;
  39. private bool createDirectoryFlag = false;
  40. private bool deleteDirectoryFlag = false;
  41. private bool deleteSceneFlag = false;
  42. private bool resizeManager = false;
  43. private int selectedScene = 0;
  44. private int selectedDirectory = 0;
  45. private string currentDirectory = sceneDirectoryName;
  46. private string textFieldValue = "";
  47. public void InitializeSceneManager()
  48. {
  49. frame = MakeTex(2, 2, Color.white);
  50. infoHighlight = MakeTex(2, 2, new Color(0f, 0f, 0f, 0.8f));
  51. selectedButtonStyle = new GUIStyle("button");
  52. selectedButtonStyle.normal.background = MakeTex(1, 1, new Color(0.5f, 0.5f, 0.5f, 0.4f));
  53. selectedButtonStyle.normal.textColor = Color.white;
  54. currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  55. GetSceneDirectories();
  56. if (Preferences["scenemanager"]["converted"].Value != "true")
  57. {
  58. ConvertIni();
  59. Preferences["scenemanager"]["converted"].Value = "true";
  60. SaveConfig();
  61. }
  62. GetScenes(currentDirectory);
  63. sceneManagerInitialize = true;
  64. }
  65. private void RefreshSceneManager()
  66. {
  67. SwitchDirectory(currentDirectory);
  68. GetSceneDirectories();
  69. GetScenes(currentDirectory);
  70. }
  71. private void SwitchDirectory(string target)
  72. {
  73. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  74. string path = target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName)
  75. ? ""
  76. : target;
  77. string targetDirectory = Path.Combine(root, path);
  78. if (!Directory.Exists(targetDirectory))
  79. {
  80. currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  81. selectedDirectory = 0;
  82. GetSceneDirectories();
  83. }
  84. else
  85. {
  86. selectedDirectory = Array.FindIndex(directoryList, d => d.Equals(target, StringComparison.OrdinalIgnoreCase));
  87. selectedDirectory = selectedDirectory == -1 ? 0 : selectedDirectory;
  88. }
  89. if (target == currentDirectory) return;
  90. currentDirectory = target;
  91. GetScenes(target);
  92. }
  93. private void GetSceneDirectories()
  94. {
  95. if (!Directory.Exists(saveScenePath))
  96. {
  97. Directory.CreateDirectory(saveScenePath);
  98. }
  99. if (!Directory.Exists(kankyoScenePath))
  100. {
  101. Directory.CreateDirectory(kankyoScenePath);
  102. }
  103. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  104. DirectoryInfo[] directoryInfo = new DirectoryInfo(root).GetDirectories();
  105. directoryList = new string[directoryInfo.Length + 1];
  106. directoryList[0] = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  107. for (int i = 0; i < directoryInfo.Length; i++)
  108. {
  109. directoryList[i + 1] = directoryInfo[i].Name;
  110. }
  111. }
  112. private void GetScenes(string target)
  113. {
  114. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  115. scenes.Clear();
  116. if (target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName))
  117. {
  118. target = "";
  119. }
  120. string workingPath = Path.Combine(root, target);
  121. DirectoryInfo info = new DirectoryInfo(workingPath);
  122. foreach (var scene in info.GetFiles("*.png"))
  123. {
  124. Texture2D screenshot = new Texture2D(2, 2, TextureFormat.ARGB32, false);
  125. screenshot.LoadImage(File.ReadAllBytes(scene.FullName));
  126. scenes.Add(new ScenePng(scene, screenshot));
  127. }
  128. selectedScene = scenes.Count == 0 ? 0 : scenes.Count - 1;
  129. scenes.Sort((a, b) => a.info.LastWriteTime.CompareTo(b.info.LastWriteTime));
  130. sceneManagerScrollPos.y = 0;
  131. }
  132. private void CreateDirectory(string directoryName)
  133. {
  134. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  135. directoryName = string.Join("", directoryName.Split(Path.GetInvalidFileNameChars()));
  136. string newDirectory = Path.Combine(root, directoryName);
  137. if (!Directory.Exists(newDirectory))
  138. {
  139. Directory.CreateDirectory(newDirectory);
  140. }
  141. GetSceneDirectories();
  142. SwitchDirectory(directoryName);
  143. }
  144. private void DeleteDirectory()
  145. {
  146. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  147. string directory = Path.Combine(root, directoryList[selectedDirectory]);
  148. if (Directory.Exists(directory))
  149. {
  150. DirectoryInfo dirInfo = new DirectoryInfo(directory);
  151. foreach (var finfo in dirInfo.GetFiles())
  152. {
  153. finfo.Delete();
  154. }
  155. dirInfo.Delete();
  156. currentDirectory = sceneDirectoryName;
  157. }
  158. RefreshSceneManager();
  159. }
  160. private void DeleteScene()
  161. {
  162. string file = scenes[selectedScene].info.FullName;
  163. if (File.Exists(file))
  164. {
  165. scenes[selectedScene].info.Delete();
  166. scenes.RemoveAt(selectedScene);
  167. }
  168. RefreshSceneManager();
  169. }
  170. private void OverwriteScene()
  171. {
  172. string file = scenes[selectedScene].info.FullName;
  173. if (!File.Exists(file))
  174. {
  175. RefreshSceneManager();
  176. }
  177. else
  178. {
  179. File.Delete(file);
  180. scenes.RemoveAt(selectedScene);
  181. sceneManagerScrollPos.y = 0;
  182. }
  183. }
  184. private void SaveScene()
  185. {
  186. string target = currentDirectory;
  187. bool scene = target.Equals(sceneDirectoryName);
  188. bool kankyo = target.Equals(kankyoDirectoryName);
  189. if (scene || kankyo)
  190. target = "";
  191. string saveDirectory;
  192. saveDirectory = kankyoModeFlag
  193. ? Path.Combine(kankyoScenePath, target)
  194. : Path.Combine(saveScenePath, target);
  195. if (!Directory.Exists(saveDirectory))
  196. {
  197. thum_byte_to_base64_ = "";
  198. thum_file_path_ = "";
  199. RefreshSceneManager();
  200. return;
  201. }
  202. string sceneString = SerializeScene();
  203. #region MM screenshot processing stuff
  204. Texture2D screenshot = new Texture2D(1, 1, TextureFormat.ARGB32, false);
  205. screenshot.LoadImage(File.ReadAllBytes(thum_file_path_));
  206. float num2 = screenshot.width / (float)screenshot.height;
  207. Vector2 vector2 = new Vector2(480f, 270f);
  208. int newWidth = screenshot.width;
  209. int newHeight = screenshot.height;
  210. if (vector2.x < (double)screenshot.width && vector2.y < (double)screenshot.height)
  211. {
  212. newWidth = (int)vector2.x;
  213. newHeight = Mathf.RoundToInt(newWidth / num2);
  214. if (vector2.y < (double)newHeight)
  215. {
  216. newHeight = (int)vector2.y;
  217. newWidth = Mathf.RoundToInt(newHeight * num2);
  218. }
  219. }
  220. else if (vector2.x < (double)screenshot.width)
  221. {
  222. newWidth = (int)vector2.x;
  223. newHeight = Mathf.RoundToInt(newWidth / num2);
  224. }
  225. else if (vector2.y < (double)screenshot.height)
  226. {
  227. newHeight = (int)vector2.y;
  228. newWidth = Mathf.RoundToInt(newHeight * num2);
  229. }
  230. TextureScale.Bilinear(screenshot, newWidth, newHeight);
  231. #endregion
  232. thum_byte_to_base64_ = "";
  233. thum_file_path_ = "";
  234. string sceneType = kankyoModeFlag ? "mmkankyo" : "mmscene";
  235. string filePath;
  236. filePath = Path.Combine(saveDirectory, $"{sceneType}{DateTime.Now:yyyyMMddHHmmss}.png");
  237. using (FileStream fileStream = File.Create(filePath))
  238. using (MemoryStream sceneStream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
  239. {
  240. byte[] screenshotBuffer = screenshot.EncodeToPNG();
  241. byte[] sceneBuffer = LZMA.Compress(sceneStream);
  242. fileStream.Write(screenshotBuffer, 0, screenshotBuffer.Length);
  243. if (kankyoModeFlag) fileStream.Write(kankyoHeader, 0, kankyoHeader.Length);
  244. fileStream.Write(sceneBuffer, 0, sceneBuffer.Length);
  245. }
  246. scenes.Add(new ScenePng(new FileInfo(filePath), screenshot));
  247. selectedScene = scenes.Count - 1;
  248. }
  249. private void ReadScene()
  250. {
  251. string filePath = scenes[selectedScene].info.FullName;
  252. sceneData = null;
  253. if (!File.Exists(filePath))
  254. {
  255. RefreshSceneManager();
  256. return;
  257. }
  258. using (FileStream fileStream = File.OpenRead(filePath))
  259. {
  260. long pos = fileStream.Position = fileStream.Length - pngEnd.Length;
  261. byte[] buf = new byte[pngEnd.Length];
  262. while (true)
  263. {
  264. if (pos < 0)
  265. {
  266. Util.Logger.Log(LogLevel.Error, $"Could not read '{Path.GetFileName(filePath)}'");
  267. return;
  268. }
  269. fileStream.Position = pos;
  270. fileStream.Read(buf, 0, pngEnd.Length);
  271. if (BytesEqual(buf, pngEnd)) break;
  272. --pos;
  273. }
  274. fileStream.Position += 4;
  275. byte[] kankyo = new byte[kankyoHeader.Length];
  276. fileStream.Read(kankyo, 0, kankyo.Length);
  277. if (BytesEqual(kankyo, kankyoHeader))
  278. {
  279. kankyoScene = true;
  280. }
  281. else
  282. {
  283. kankyoScene = false;
  284. fileStream.Position -= kankyoHeader.Length;
  285. }
  286. try
  287. {
  288. using (var sceneStream = LZMA.Decompress(fileStream))
  289. {
  290. sceneData = Encoding.Unicode.GetString(sceneStream.ToArray());
  291. }
  292. }
  293. catch (Exception e)
  294. {
  295. Util.Logger.Log(LogLevel.Error, $"Failed to decompress scene data because '{e}'\n");
  296. }
  297. }
  298. }
  299. private static bool BytesEqual(byte[] a, byte[] b)
  300. {
  301. if (a.Length != b.Length) return false;
  302. for (long i = 0; i < a.Length; i++)
  303. {
  304. if (a[i] != b[i]) return false;
  305. }
  306. return true;
  307. }
  308. private void ConvertIni()
  309. {
  310. HashSet<int> saveSceneEntries = new HashSet<int>();
  311. IniSection MMScene = Preferences["scene"];
  312. foreach (IniKey iniKey in MMScene.Keys)
  313. {
  314. string keyName = iniKey.Key;
  315. if (keyName[0] == 's')
  316. {
  317. int index = Int32.Parse(keyName.Substring(1 + (keyName[1] == 's' ? 1 : 0)));
  318. if (!saveSceneEntries.Contains(index))
  319. {
  320. saveSceneEntries.Add(index);
  321. ConvertSceneToPng(index, MMScene);
  322. }
  323. }
  324. }
  325. }
  326. private void ConvertSceneToPng(int index, IniSection MMScene)
  327. {
  328. byte[] sceneBuffer;
  329. byte[] screenshotBuffer = frame.EncodeToPNG();
  330. string sceneString = MMScene.GetKey($"s{index}")?.RawValue;
  331. string screenshotString = MMScene.GetKey($"ss{index}")?.RawValue;
  332. bool kankyo = index >= 10000;
  333. if (index == 9999 || String.IsNullOrEmpty(sceneString)) return;
  334. using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
  335. {
  336. sceneBuffer = LZMA.Compress(stream);
  337. }
  338. DateTime dateSaved = DateTime.Parse(sceneString.Split(',')[0]);
  339. if (!String.IsNullOrEmpty(screenshotString))
  340. screenshotBuffer = Convert.FromBase64String(screenshotString);
  341. string sceneType = kankyo ? "mmkankyo" : "mmscene";
  342. string savePngFilename = $"{sceneType}{index}_{dateSaved.ToString("yyyyMMddHHmm")}.png";
  343. string outPath = Path.Combine(kankyo ? kankyoScenePath : saveScenePath, savePngFilename);
  344. using (BinaryWriter stream = new BinaryWriter(File.Create(outPath)))
  345. {
  346. stream.Write(screenshotBuffer);
  347. if (kankyo) stream.Write(kankyoHeader);
  348. stream.Write(sceneBuffer);
  349. }
  350. File.SetCreationTime(outPath, dateSaved);
  351. File.SetLastWriteTime(outPath, dateSaved);
  352. }
  353. private class ScenePng
  354. {
  355. public FileInfo info { get; }
  356. public Texture2D screenshot { get; }
  357. public ScenePng(FileInfo info, Texture2D screenshot)
  358. {
  359. this.info = info;
  360. this.screenshot = screenshot;
  361. }
  362. }
  363. }
  364. }