SaveManager.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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 GUIStyle sceneSortComboStyle;
  22. private GUIContent infoString;
  23. private string[] directoryList;
  24. private Texture2D frame;
  25. private Texture2D infoHighlight;
  26. private Rect sceneManagerRect;
  27. private Rect sceneModalRect;
  28. private Rect resizeManagerRect;
  29. private Vector2 sceneManagerScrollPos = Vector2.zero;
  30. private Vector2 dirListScrollPos = Vector2.zero;
  31. private bool sceneManagerInitialize = false;
  32. private bool loadSceneFlag = false;
  33. private bool overwriteFlag = false;
  34. private bool createSceneFlag = false;
  35. private bool quickSaveFlag = false;
  36. private bool manageSceneFlag = false;
  37. private bool kankyoModeFlag = false;
  38. private bool kankyoToggle = false;
  39. private bool kankyoScene = false;
  40. private bool createDirectoryFlag = false;
  41. private bool deleteDirectoryFlag = false;
  42. private bool deleteSceneFlag = false;
  43. private bool resizeManager = false;
  44. private int selectedScene = 0;
  45. private int selectedDirectory = 0;
  46. private string currentDirectory = sceneDirectoryName;
  47. private string textFieldValue = "";
  48. private readonly ComboBox2 sceneSortCombo = new ComboBox2();
  49. private static GUIContent[] sceneSortComboList;
  50. private static int sceneSortModeOld = 0;
  51. private static bool sceneSortAscendingOld = true;
  52. private static int selectedSceneSortMode = 0;
  53. private static bool sceneSortAscending = true;
  54. private static readonly string[] ConfigSortMode = new[] { "name", "date_created", "date_modified" };
  55. private enum SortMode
  56. {
  57. Name, DateCreated, DateModified
  58. }
  59. public void InitializeSceneManager()
  60. {
  61. frame = MakeTex(2, 2, Color.white);
  62. infoHighlight = MakeTex(2, 2, new Color(0f, 0f, 0f, 0.8f));
  63. sceneSortCombo.selectedItemIndex = selectedSceneSortMode;
  64. sceneSortComboList = new GUIContent[] {
  65. new GUIContent("Name"),
  66. new GUIContent("Date Created"),
  67. new GUIContent("Date Modified")
  68. };
  69. selectedButtonStyle = new GUIStyle("button");
  70. selectedButtonStyle.normal.background = MakeTex(1, 1, new Color(0.5f, 0.5f, 0.5f, 0.4f));
  71. selectedButtonStyle.normal.textColor = Color.white;
  72. sceneSortComboStyle = new GUIStyle();
  73. sceneSortComboStyle.normal.textColor = Color.white;
  74. sceneSortComboStyle.normal.background = MakeTex(2, 2, new Color(0.0f, 0.0f, 0.0f, 0.5f));
  75. sceneSortComboStyle.onHover.background = sceneSortComboStyle.hover.background = new Texture2D(2, 2);
  76. sceneSortComboStyle.padding.left = sceneSortComboStyle.padding.right = sceneSortComboStyle.padding.top = sceneSortComboStyle.padding.bottom = GetPix(0);
  77. sceneSortComboStyle.fontSize = GetPix(13);
  78. currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  79. GetSceneDirectories();
  80. if (Preferences["scenemanager"]["converted"].Value != "true")
  81. {
  82. ConvertIni();
  83. Preferences["scenemanager"]["converted"].Value = "true";
  84. SaveConfig();
  85. }
  86. GetScenes(currentDirectory);
  87. sceneManagerInitialize = true;
  88. }
  89. private void SaveSceneSortPreferences()
  90. {
  91. if (sceneSortAscending != sceneSortAscendingOld || sceneSortModeOld != selectedSceneSortMode)
  92. {
  93. Preferences["scenemanager"]["scene_sort_ascending"].Value = sceneSortAscending ? "true" : "false";
  94. Preferences["scenemanager"]["scene_sort_mode"].Value = ConfigSortMode[selectedSceneSortMode];
  95. SaveConfig();
  96. sceneSortAscendingOld = sceneSortAscending;
  97. sceneSortModeOld = selectedSceneSortMode;
  98. }
  99. }
  100. private void RefreshSceneManager()
  101. {
  102. SwitchDirectory(currentDirectory);
  103. GetSceneDirectories();
  104. GetScenes(currentDirectory);
  105. }
  106. private void SwitchDirectory(string target)
  107. {
  108. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  109. string path = target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName)
  110. ? ""
  111. : target;
  112. string targetDirectory = Path.Combine(root, path);
  113. if (!Directory.Exists(targetDirectory))
  114. {
  115. currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  116. selectedDirectory = 0;
  117. GetSceneDirectories();
  118. }
  119. else
  120. {
  121. selectedDirectory = Array.FindIndex(directoryList, d => d.Equals(target, StringComparison.OrdinalIgnoreCase));
  122. selectedDirectory = selectedDirectory == -1 ? 0 : selectedDirectory;
  123. }
  124. if (target == currentDirectory) return;
  125. currentDirectory = target;
  126. GetScenes(target);
  127. }
  128. private void GetSceneDirectories()
  129. {
  130. if (!Directory.Exists(saveScenePath))
  131. {
  132. Directory.CreateDirectory(saveScenePath);
  133. }
  134. if (!Directory.Exists(kankyoScenePath))
  135. {
  136. Directory.CreateDirectory(kankyoScenePath);
  137. }
  138. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  139. DirectoryInfo[] directoryInfo = new DirectoryInfo(root).GetDirectories();
  140. directoryList = new string[directoryInfo.Length + 1];
  141. directoryList[0] = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName;
  142. for (int i = 0; i < directoryInfo.Length; i++)
  143. {
  144. directoryList[i + 1] = directoryInfo[i].Name;
  145. }
  146. }
  147. private void GetScenes(string target)
  148. {
  149. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  150. scenes.Clear();
  151. if (target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName))
  152. {
  153. target = "";
  154. }
  155. string workingPath = Path.Combine(root, target);
  156. DirectoryInfo info = new DirectoryInfo(workingPath);
  157. foreach (var scene in info.GetFiles("*.png"))
  158. {
  159. Texture2D screenshot = new Texture2D(2, 2, TextureFormat.ARGB32, false);
  160. screenshot.LoadImage(File.ReadAllBytes(scene.FullName));
  161. scenes.Add(new ScenePng(scene, screenshot));
  162. }
  163. selectedScene = scenes.Count == 0 ? 0 : scenes.Count - 1;
  164. SortScenes();
  165. sceneManagerScrollPos.y = 0;
  166. }
  167. private void SortScenes()
  168. {
  169. Comparison<ScenePng> sortMode = null;
  170. switch ((SortMode)selectedSceneSortMode)
  171. {
  172. case SortMode.Name:
  173. sortMode = SortByName;
  174. break;
  175. case SortMode.DateCreated:
  176. sortMode = SortByDateCreated;
  177. break;
  178. case SortMode.DateModified:
  179. sortMode = SortByDateModified;
  180. break;
  181. }
  182. scenes.Sort(sortMode);
  183. }
  184. private void SetSceneSortAscending(bool ascending)
  185. {
  186. sceneSortAscending = ascending;
  187. SortScenes();
  188. }
  189. private void SetSceneSortMode(int sortMode)
  190. {
  191. selectedSceneSortMode = Mathf.Clamp(sortMode, 0, 2);
  192. SortScenes();
  193. }
  194. private static int SortByName(ScenePng a, ScenePng b)
  195. {
  196. int direction = sceneSortAscending ? -1 : 1;
  197. return direction * a.info.Name.CompareTo(b.info.Name);
  198. }
  199. private static int SortByDateCreated(ScenePng a, ScenePng b)
  200. {
  201. int direction = sceneSortAscending ? -1 : 1;
  202. return direction * a.info.CreationTime.CompareTo(b.info.CreationTime);
  203. }
  204. private static int SortByDateModified(ScenePng a, ScenePng b)
  205. {
  206. int direction = sceneSortAscending ? -1 : 1;
  207. return direction * a.info.LastWriteTime.CompareTo(b.info.LastWriteTime);
  208. }
  209. private void CreateDirectory(string directoryName)
  210. {
  211. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  212. directoryName = string.Join("", directoryName.Split(Path.GetInvalidFileNameChars()));
  213. string newDirectory = Path.Combine(root, directoryName);
  214. if (!Directory.Exists(newDirectory))
  215. {
  216. Directory.CreateDirectory(newDirectory);
  217. }
  218. GetSceneDirectories();
  219. SwitchDirectory(directoryName);
  220. }
  221. private void DeleteDirectory()
  222. {
  223. string root = kankyoModeFlag ? kankyoScenePath : saveScenePath;
  224. string directory = Path.Combine(root, directoryList[selectedDirectory]);
  225. if (Directory.Exists(directory))
  226. {
  227. DirectoryInfo dirInfo = new DirectoryInfo(directory);
  228. foreach (var finfo in dirInfo.GetFiles())
  229. {
  230. finfo.Delete();
  231. }
  232. dirInfo.Delete();
  233. currentDirectory = sceneDirectoryName;
  234. }
  235. RefreshSceneManager();
  236. }
  237. private void DeleteScene()
  238. {
  239. string file = scenes[selectedScene].info.FullName;
  240. if (File.Exists(file))
  241. {
  242. scenes[selectedScene].info.Delete();
  243. scenes.RemoveAt(selectedScene);
  244. }
  245. RefreshSceneManager();
  246. }
  247. private void OverwriteScene()
  248. {
  249. string file = scenes[selectedScene].info.FullName;
  250. if (!File.Exists(file))
  251. {
  252. RefreshSceneManager();
  253. }
  254. }
  255. private void SaveScene()
  256. {
  257. string target = currentDirectory;
  258. bool scene = target.Equals(sceneDirectoryName);
  259. bool kankyo = target.Equals(kankyoDirectoryName);
  260. if (scene || kankyo)
  261. target = "";
  262. string saveDirectory;
  263. saveDirectory = kankyoModeFlag
  264. ? Path.Combine(kankyoScenePath, target)
  265. : Path.Combine(saveScenePath, target);
  266. if (!Directory.Exists(saveDirectory))
  267. {
  268. thum_byte_to_base64_ = "";
  269. thum_file_path_ = "";
  270. RefreshSceneManager();
  271. return;
  272. }
  273. string sceneString = SerializeScene();
  274. #region MM screenshot processing stuff
  275. Texture2D screenshot = new Texture2D(1, 1, TextureFormat.ARGB32, false);
  276. screenshot.LoadImage(File.ReadAllBytes(thum_file_path_));
  277. float num2 = screenshot.width / (float)screenshot.height;
  278. Vector2 vector2 = new Vector2(480f, 270f);
  279. int newWidth = screenshot.width;
  280. int newHeight = screenshot.height;
  281. if (vector2.x < (double)screenshot.width && vector2.y < (double)screenshot.height)
  282. {
  283. newWidth = (int)vector2.x;
  284. newHeight = Mathf.RoundToInt(newWidth / num2);
  285. if (vector2.y < (double)newHeight)
  286. {
  287. newHeight = (int)vector2.y;
  288. newWidth = Mathf.RoundToInt(newHeight * num2);
  289. }
  290. }
  291. else if (vector2.x < (double)screenshot.width)
  292. {
  293. newWidth = (int)vector2.x;
  294. newHeight = Mathf.RoundToInt(newWidth / num2);
  295. }
  296. else if (vector2.y < (double)screenshot.height)
  297. {
  298. newHeight = (int)vector2.y;
  299. newWidth = Mathf.RoundToInt(newHeight * num2);
  300. }
  301. TextureScale.Bilinear(screenshot, newWidth, newHeight);
  302. #endregion
  303. thum_byte_to_base64_ = "";
  304. thum_file_path_ = "";
  305. string sceneType = kankyoModeFlag ? "mmkankyo" : "mmscene";
  306. string filePath;
  307. string fileName = $"{sceneType}{DateTime.Now:yyyyMMddHHmmss}.png";
  308. FileInfo oldFileInfo = null;
  309. if (overwriteFlag)
  310. {
  311. oldFileInfo = scenes[selectedScene].info;
  312. fileName = oldFileInfo.Name;
  313. scenes.RemoveAt(selectedScene);
  314. }
  315. filePath = Path.Combine(saveDirectory, fileName);
  316. using (FileStream fileStream = File.Create(filePath))
  317. using (MemoryStream sceneStream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
  318. {
  319. byte[] screenshotBuffer = screenshot.EncodeToPNG();
  320. byte[] sceneBuffer = LZMA.Compress(sceneStream);
  321. fileStream.Write(screenshotBuffer, 0, screenshotBuffer.Length);
  322. if (kankyoModeFlag) fileStream.Write(kankyoHeader, 0, kankyoHeader.Length);
  323. fileStream.Write(sceneBuffer, 0, sceneBuffer.Length);
  324. }
  325. ScenePng newScenePng = new ScenePng(new FileInfo(filePath), screenshot);
  326. if (oldFileInfo != null)
  327. {
  328. File.SetCreationTime(filePath, oldFileInfo.CreationTime);
  329. newScenePng.info.CreationTime = oldFileInfo.CreationTime;
  330. }
  331. scenes.Add(newScenePng);
  332. selectedScene = scenes.Count - 1;
  333. SortScenes();
  334. }
  335. private void ReadScene()
  336. {
  337. string filePath = scenes[selectedScene].info.FullName;
  338. sceneData = null;
  339. if (!File.Exists(filePath))
  340. {
  341. RefreshSceneManager();
  342. return;
  343. }
  344. using (FileStream fileStream = File.OpenRead(filePath))
  345. {
  346. long pos = fileStream.Position = fileStream.Length - pngEnd.Length;
  347. byte[] buf = new byte[pngEnd.Length];
  348. while (true)
  349. {
  350. if (pos < 0)
  351. {
  352. Util.Logger.Log(LogLevel.Error, $"Could not read '{Path.GetFileName(filePath)}'");
  353. return;
  354. }
  355. fileStream.Position = pos;
  356. fileStream.Read(buf, 0, pngEnd.Length);
  357. if (BytesEqual(buf, pngEnd)) break;
  358. --pos;
  359. }
  360. fileStream.Position += 4;
  361. byte[] kankyo = new byte[kankyoHeader.Length];
  362. fileStream.Read(kankyo, 0, kankyo.Length);
  363. if (BytesEqual(kankyo, kankyoHeader))
  364. {
  365. kankyoScene = true;
  366. }
  367. else
  368. {
  369. kankyoScene = false;
  370. fileStream.Position -= kankyoHeader.Length;
  371. }
  372. try
  373. {
  374. using (var sceneStream = LZMA.Decompress(fileStream))
  375. {
  376. sceneData = Encoding.Unicode.GetString(sceneStream.ToArray());
  377. }
  378. }
  379. catch (Exception e)
  380. {
  381. Util.Logger.Log(LogLevel.Error, $"Failed to decompress scene data because '{e}'\n");
  382. }
  383. }
  384. }
  385. private static bool BytesEqual(byte[] a, byte[] b)
  386. {
  387. if (a.Length != b.Length) return false;
  388. for (long i = 0; i < a.Length; i++)
  389. {
  390. if (a[i] != b[i]) return false;
  391. }
  392. return true;
  393. }
  394. private void ConvertIni()
  395. {
  396. HashSet<int> saveSceneEntries = new HashSet<int>();
  397. IniSection MMScene = Preferences["scene"];
  398. foreach (IniKey iniKey in MMScene.Keys)
  399. {
  400. string keyName = iniKey.Key;
  401. if (keyName[0] == 's')
  402. {
  403. int index = Int32.Parse(keyName.Substring(1 + (keyName[1] == 's' ? 1 : 0)));
  404. if (!saveSceneEntries.Contains(index))
  405. {
  406. saveSceneEntries.Add(index);
  407. ConvertSceneToPng(index, MMScene);
  408. }
  409. }
  410. }
  411. }
  412. private void ConvertSceneToPng(int index, IniSection MMScene)
  413. {
  414. byte[] sceneBuffer;
  415. byte[] screenshotBuffer = frame.EncodeToPNG();
  416. string sceneString = MMScene.GetKey($"s{index}")?.RawValue;
  417. string screenshotString = MMScene.GetKey($"ss{index}")?.RawValue;
  418. bool kankyo = index >= 10000;
  419. if (index == 9999 || String.IsNullOrEmpty(sceneString)) return;
  420. using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString)))
  421. {
  422. sceneBuffer = LZMA.Compress(stream);
  423. }
  424. DateTime dateSaved = DateTime.Parse(sceneString.Split(',')[0]);
  425. if (!String.IsNullOrEmpty(screenshotString))
  426. screenshotBuffer = Convert.FromBase64String(screenshotString);
  427. string sceneType = kankyo ? "mmkankyo" : "mmscene";
  428. string savePngFilename = $"{sceneType}{index}_{dateSaved.ToString("yyyyMMddHHmm")}.png";
  429. string outPath = Path.Combine(kankyo ? kankyoScenePath : saveScenePath, savePngFilename);
  430. using (BinaryWriter stream = new BinaryWriter(File.Create(outPath)))
  431. {
  432. stream.Write(screenshotBuffer);
  433. if (kankyo) stream.Write(kankyoHeader);
  434. stream.Write(sceneBuffer);
  435. }
  436. File.SetCreationTime(outPath, dateSaved);
  437. File.SetLastWriteTime(outPath, dateSaved);
  438. }
  439. private class ScenePng
  440. {
  441. public FileInfo info { get; }
  442. public Texture2D screenshot { get; }
  443. public ScenePng(FileInfo info, Texture2D screenshot)
  444. {
  445. this.info = info;
  446. this.screenshot = screenshot;
  447. }
  448. }
  449. }
  450. }