SaveManager.cs 15 KB

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