using UnityEngine; using ExIni; using System; using System.Collections.Generic; using System.Text; using System.IO; using Util; namespace CM3D2.MultipleMaids.Plugin { public partial class MultipleMaids { internal static readonly byte[] pngEnd = Encoding.ASCII.GetBytes("IEND"); internal static readonly byte[] kankyoHeader = Encoding.ASCII.GetBytes("KANKYO"); internal const string sceneDirectoryName = "< Scene Root >"; internal const string kankyoDirectoryName = "< Kankyo Root >"; internal readonly string saveScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsScene"); private static string kankyoScenePath = Path.Combine(Path.GetFullPath(".\\"), "Mod\\MultipleMaidsKankyo"); private static string sceneData; private static List scenes = new List(50); private GUIStyle selectedButtonStyle; private GUIStyle sceneSortComboStyle; private GUIContent infoString; private string[] directoryList; private Texture2D frame; private Texture2D infoHighlight; private Rect sceneManagerRect; private Rect sceneModalRect; private Rect resizeManagerRect; private Vector2 sceneManagerScrollPos = Vector2.zero; private Vector2 dirListScrollPos = Vector2.zero; private bool sceneManagerInitialize = false; private bool loadSceneFlag = false; private bool overwriteFlag = false; private bool createSceneFlag = false; private bool quickSaveFlag = false; private bool manageSceneFlag = false; private bool kankyoModeFlag = false; private bool kankyoToggle = false; private bool kankyoScene = false; private bool createDirectoryFlag = false; private bool deleteDirectoryFlag = false; private bool deleteSceneFlag = false; private bool resizeManager = false; private int selectedScene = 0; private int selectedDirectory = 0; private string currentDirectory = sceneDirectoryName; private string textFieldValue = ""; private readonly ComboBox2 sceneSortCombo = new ComboBox2(); private static GUIContent[] sceneSortComboList; private static int sceneSortModeOld = 0; private static bool sceneSortAscendingOld = true; private static int selectedSceneSortMode = 0; private static bool sceneSortAscending = true; private static readonly string[] ConfigSortMode = new[] { "name", "date_created", "date_modified" }; private enum SortMode { Name, DateCreated, DateModified } public void InitializeSceneManager() { frame = MakeTex(2, 2, Color.white); infoHighlight = MakeTex(2, 2, new Color(0f, 0f, 0f, 0.8f)); sceneSortCombo.selectedItemIndex = selectedSceneSortMode; sceneSortComboList = new GUIContent[] { new GUIContent("Name"), new GUIContent("Date Created"), new GUIContent("Date Modified") }; selectedButtonStyle = new GUIStyle("button"); selectedButtonStyle.normal.background = MakeTex(1, 1, new Color(0.5f, 0.5f, 0.5f, 0.4f)); selectedButtonStyle.normal.textColor = Color.white; sceneSortComboStyle = new GUIStyle(); sceneSortComboStyle.normal.textColor = Color.white; sceneSortComboStyle.normal.background = MakeTex(2, 2, new Color(0.0f, 0.0f, 0.0f, 0.5f)); sceneSortComboStyle.onHover.background = sceneSortComboStyle.hover.background = new Texture2D(2, 2); sceneSortComboStyle.padding.left = sceneSortComboStyle.padding.right = sceneSortComboStyle.padding.top = sceneSortComboStyle.padding.bottom = GetPix(0); sceneSortComboStyle.fontSize = GetPix(13); currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName; GetSceneDirectories(); if (Preferences["scenemanager"]["converted"].Value != "true") { ConvertIni(); Preferences["scenemanager"]["converted"].Value = "true"; SaveConfig(); } GetScenes(currentDirectory); sceneManagerInitialize = true; } private void SaveSceneSortPreferences() { if (sceneSortAscending != sceneSortAscendingOld || sceneSortModeOld != selectedSceneSortMode) { Preferences["scenemanager"]["scene_sort_ascending"].Value = sceneSortAscending ? "true" : "false"; Preferences["scenemanager"]["scene_sort_mode"].Value = ConfigSortMode[selectedSceneSortMode]; SaveConfig(); sceneSortAscendingOld = sceneSortAscending; sceneSortModeOld = selectedSceneSortMode; } } private void RefreshSceneManager() { SwitchDirectory(currentDirectory); GetSceneDirectories(); GetScenes(currentDirectory); } private void SwitchDirectory(string target) { string root = kankyoModeFlag ? kankyoScenePath : saveScenePath; string path = target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName) ? "" : target; string targetDirectory = Path.Combine(root, path); if (!Directory.Exists(targetDirectory)) { currentDirectory = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName; selectedDirectory = 0; GetSceneDirectories(); } else { selectedDirectory = Array.FindIndex(directoryList, d => d.Equals(target, StringComparison.OrdinalIgnoreCase)); selectedDirectory = selectedDirectory == -1 ? 0 : selectedDirectory; } if (target == currentDirectory) return; currentDirectory = target; GetScenes(target); } private void GetSceneDirectories() { if (!Directory.Exists(saveScenePath)) { Directory.CreateDirectory(saveScenePath); } if (!Directory.Exists(kankyoScenePath)) { Directory.CreateDirectory(kankyoScenePath); } string root = kankyoModeFlag ? kankyoScenePath : saveScenePath; DirectoryInfo[] directoryInfo = new DirectoryInfo(root).GetDirectories(); directoryList = new string[directoryInfo.Length + 1]; directoryList[0] = kankyoModeFlag ? kankyoDirectoryName : sceneDirectoryName; for (int i = 0; i < directoryInfo.Length; i++) { directoryList[i + 1] = directoryInfo[i].Name; } } private void GetScenes(string target) { string root = kankyoModeFlag ? kankyoScenePath : saveScenePath; scenes.Clear(); if (target.Equals(sceneDirectoryName) || target.Equals(kankyoDirectoryName)) { target = ""; } string workingPath = Path.Combine(root, target); DirectoryInfo info = new DirectoryInfo(workingPath); foreach (var scene in info.GetFiles("*.png")) { Texture2D screenshot = new Texture2D(2, 2, TextureFormat.ARGB32, false); screenshot.LoadImage(File.ReadAllBytes(scene.FullName)); scenes.Add(new ScenePng(scene, screenshot)); } selectedScene = scenes.Count == 0 ? 0 : scenes.Count - 1; SortScenes(); sceneManagerScrollPos.y = 0; } private void SortScenes() { Comparison sortMode = null; switch ((SortMode)selectedSceneSortMode) { case SortMode.Name: sortMode = SortByName; break; case SortMode.DateCreated: sortMode = SortByDateCreated; break; case SortMode.DateModified: sortMode = SortByDateModified; break; } scenes.Sort(sortMode); } private void SetSceneSortAscending(bool ascending) { sceneSortAscending = ascending; SortScenes(); } private void SetSceneSortMode(int sortMode) { selectedSceneSortMode = Mathf.Clamp(sortMode, 0, 2); SortScenes(); } private static int SortByName(ScenePng a, ScenePng b) { int direction = sceneSortAscending ? -1 : 1; return direction * a.info.Name.CompareTo(b.info.Name); } private static int SortByDateCreated(ScenePng a, ScenePng b) { int direction = sceneSortAscending ? -1 : 1; return direction * a.info.CreationTime.CompareTo(b.info.CreationTime); } private static int SortByDateModified(ScenePng a, ScenePng b) { int direction = sceneSortAscending ? -1 : 1; return direction * a.info.LastWriteTime.CompareTo(b.info.LastWriteTime); } private void CreateDirectory(string directoryName) { string root = kankyoModeFlag ? kankyoScenePath : saveScenePath; directoryName = string.Join("", directoryName.Split(Path.GetInvalidFileNameChars())); string newDirectory = Path.Combine(root, directoryName); if (!Directory.Exists(newDirectory)) { Directory.CreateDirectory(newDirectory); } GetSceneDirectories(); SwitchDirectory(directoryName); } private void DeleteDirectory() { string root = kankyoModeFlag ? kankyoScenePath : saveScenePath; string directory = Path.Combine(root, directoryList[selectedDirectory]); if (Directory.Exists(directory)) { DirectoryInfo dirInfo = new DirectoryInfo(directory); foreach (var finfo in dirInfo.GetFiles()) { finfo.Delete(); } dirInfo.Delete(); currentDirectory = sceneDirectoryName; } RefreshSceneManager(); } private void DeleteScene() { string file = scenes[selectedScene].info.FullName; if (File.Exists(file)) { scenes[selectedScene].info.Delete(); scenes.RemoveAt(selectedScene); } RefreshSceneManager(); } private void OverwriteScene() { string file = scenes[selectedScene].info.FullName; if (!File.Exists(file)) { RefreshSceneManager(); } } private void SaveScene() { string target = currentDirectory; bool scene = target.Equals(sceneDirectoryName); bool kankyo = target.Equals(kankyoDirectoryName); if (scene || kankyo) target = ""; string saveDirectory; saveDirectory = kankyoModeFlag ? Path.Combine(kankyoScenePath, target) : Path.Combine(saveScenePath, target); if (!Directory.Exists(saveDirectory)) { thum_byte_to_base64_ = ""; thum_file_path_ = ""; RefreshSceneManager(); return; } string sceneString = SerializeScene(); #region MM screenshot processing stuff Texture2D screenshot = new Texture2D(1, 1, TextureFormat.ARGB32, false); screenshot.LoadImage(File.ReadAllBytes(thum_file_path_)); float num2 = screenshot.width / (float)screenshot.height; Vector2 vector2 = new Vector2(480f, 270f); int newWidth = screenshot.width; int newHeight = screenshot.height; if (vector2.x < (double)screenshot.width && vector2.y < (double)screenshot.height) { newWidth = (int)vector2.x; newHeight = Mathf.RoundToInt(newWidth / num2); if (vector2.y < (double)newHeight) { newHeight = (int)vector2.y; newWidth = Mathf.RoundToInt(newHeight * num2); } } else if (vector2.x < (double)screenshot.width) { newWidth = (int)vector2.x; newHeight = Mathf.RoundToInt(newWidth / num2); } else if (vector2.y < (double)screenshot.height) { newHeight = (int)vector2.y; newWidth = Mathf.RoundToInt(newHeight * num2); } TextureScale.Bilinear(screenshot, newWidth, newHeight); #endregion thum_byte_to_base64_ = ""; thum_file_path_ = ""; string sceneType = kankyoModeFlag ? "mmkankyo" : "mmscene"; string filePath; string fileName = $"{sceneType}{DateTime.Now:yyyyMMddHHmmss}.png"; FileInfo oldFileInfo = null; if (overwriteFlag) { oldFileInfo = scenes[selectedScene].info; fileName = oldFileInfo.Name; scenes.RemoveAt(selectedScene); } filePath = Path.Combine(saveDirectory, fileName); using (FileStream fileStream = File.Create(filePath)) using (MemoryStream sceneStream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString))) { byte[] screenshotBuffer = screenshot.EncodeToPNG(); byte[] sceneBuffer = LZMA.Compress(sceneStream); fileStream.Write(screenshotBuffer, 0, screenshotBuffer.Length); if (kankyoModeFlag) fileStream.Write(kankyoHeader, 0, kankyoHeader.Length); fileStream.Write(sceneBuffer, 0, sceneBuffer.Length); } ScenePng newScenePng = new ScenePng(new FileInfo(filePath), screenshot); if (oldFileInfo != null) { File.SetCreationTime(filePath, oldFileInfo.CreationTime); newScenePng.info.CreationTime = oldFileInfo.CreationTime; } scenes.Add(newScenePng); selectedScene = scenes.Count - 1; SortScenes(); } private void ReadScene() { string filePath = scenes[selectedScene].info.FullName; sceneData = null; if (!File.Exists(filePath)) { RefreshSceneManager(); return; } using (FileStream fileStream = File.OpenRead(filePath)) { long pos = fileStream.Position = fileStream.Length - pngEnd.Length; byte[] buf = new byte[pngEnd.Length]; while (true) { if (pos < 0) { Util.Logger.Log(LogLevel.Error, $"Could not read '{Path.GetFileName(filePath)}'"); return; } fileStream.Position = pos; fileStream.Read(buf, 0, pngEnd.Length); if (BytesEqual(buf, pngEnd)) break; --pos; } fileStream.Position += 4; byte[] kankyo = new byte[kankyoHeader.Length]; fileStream.Read(kankyo, 0, kankyo.Length); if (BytesEqual(kankyo, kankyoHeader)) { kankyoScene = true; } else { kankyoScene = false; fileStream.Position -= kankyoHeader.Length; } try { using (var sceneStream = LZMA.Decompress(fileStream)) { sceneData = Encoding.Unicode.GetString(sceneStream.ToArray()); } } catch (Exception e) { Util.Logger.Log(LogLevel.Error, $"Failed to decompress scene data because '{e}'\n"); } } } private static bool BytesEqual(byte[] a, byte[] b) { if (a.Length != b.Length) return false; for (long i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; } private void ConvertIni() { HashSet saveSceneEntries = new HashSet(); IniSection MMScene = Preferences["scene"]; foreach (IniKey iniKey in MMScene.Keys) { string keyName = iniKey.Key; if (keyName[0] == 's') { int index = Int32.Parse(keyName.Substring(1 + (keyName[1] == 's' ? 1 : 0))); if (!saveSceneEntries.Contains(index)) { saveSceneEntries.Add(index); ConvertSceneToPng(index, MMScene); } } } } private void ConvertSceneToPng(int index, IniSection MMScene) { byte[] sceneBuffer; byte[] screenshotBuffer = frame.EncodeToPNG(); string sceneString = MMScene.GetKey($"s{index}")?.RawValue; string screenshotString = MMScene.GetKey($"ss{index}")?.RawValue; bool kankyo = index >= 10000; if (index == 9999 || String.IsNullOrEmpty(sceneString)) return; using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(sceneString))) { sceneBuffer = LZMA.Compress(stream); } DateTime dateSaved = DateTime.Parse(sceneString.Split(',')[0]); if (!String.IsNullOrEmpty(screenshotString)) screenshotBuffer = Convert.FromBase64String(screenshotString); string sceneType = kankyo ? "mmkankyo" : "mmscene"; string savePngFilename = $"{sceneType}{index}_{dateSaved.ToString("yyyyMMddHHmm")}.png"; string outPath = Path.Combine(kankyo ? kankyoScenePath : saveScenePath, savePngFilename); using (BinaryWriter stream = new BinaryWriter(File.Create(outPath))) { stream.Write(screenshotBuffer); if (kankyo) stream.Write(kankyoHeader); stream.Write(sceneBuffer); } File.SetCreationTime(outPath, dateSaved); File.SetLastWriteTime(outPath, dateSaved); } private class ScenePng { public FileInfo info { get; } public Texture2D screenshot { get; } public ScenePng(FileInfo info, Texture2D screenshot) { this.info = info; this.screenshot = screenshot; } } } }