using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using Newtonsoft.Json; using MyRoomCustom; using UnityEngine; using wf; namespace COM3D2.MeidoPhotoStudio.Plugin { using static MenuFileUtility; public static class Constants { private static bool beginHandItemInit; private static bool beginMpnAttachInit; public const string customPoseDirectory = "Custom Poses"; public const string customHandDirectory = "Hand Presets"; public const string customFaceDirectory = "Face Presets"; public const string sceneDirectory = "Scenes"; public const string kankyoDirectory = "Environments"; public const string configDirectory = "MeidoPhotoStudio"; public const string translationDirectory = "Translations"; public const string databaseDirectory = "Database"; public static readonly string customPosePath; public static readonly string customHandPath; public static readonly string customFacePath; public static readonly string scenesPath; public static readonly string kankyoPath; public static readonly string configPath; public static readonly string databasePath; public static readonly int mainWindowID = 765; public static readonly int messageWindowID = 961; public static readonly int sceneManagerWindowID = 876; public static readonly int sceneManagerModalID = 283; public static readonly int dropdownWindowID = 777; public enum Window { Call, Pose, Face, BG, BG2, Main, Message, Save, SaveModal, Settings } public enum Scene { Daily = 3, Edit = 5 } public static readonly List PoseGroupList = new List(); public static readonly Dictionary> PoseDict = new Dictionary>(); public static readonly List CustomPoseGroupList = new List(); public static readonly Dictionary> CustomPoseDict = new Dictionary>(); public static readonly List CustomHandGroupList = new List(); public static readonly Dictionary> CustomHandDict = new Dictionary>(); public static readonly List FaceGroupList = new List(); public static readonly Dictionary> FaceDict = new Dictionary>(); public static readonly List CustomFaceGroupList = new List(); public static readonly Dictionary> CustomFaceDict = new Dictionary>(); public static readonly List BGList = new List(); public static readonly List> MyRoomCustomBGList = new List>(); public static readonly List DoguCategories = new List(); public static readonly Dictionary> DoguDict = new Dictionary>(); public static readonly List MyRoomPropCategories = new List(); public static readonly Dictionary> MyRoomPropDict = new Dictionary>(); public static readonly Dictionary> ModPropDict = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); public static readonly List SceneDirectoryList = new List(); public static readonly List KankyoDirectoryList = new List(); public static readonly List MpnAttachPropList = new List(); public static int MyRoomCustomBGIndex { get; private set; } = -1; public static bool HandItemsInitialized { get; private set; } public static bool MpnAttachInitialized { get; private set; } public static bool MenuFilesInitialized { get; private set; } public static event EventHandler MenuFilesChange; public static event EventHandler CustomPoseChange; public static event EventHandler CustomHandChange; public static event EventHandler CustomFaceChange; public enum DoguCategory { Other, Mob, Desk, HandItem, BGSmall } public static readonly Dictionary customDoguCategories = new Dictionary() { [DoguCategory.Other] = "other", [DoguCategory.Mob] = "mob", [DoguCategory.Desk] = "desk", [DoguCategory.HandItem] = "handItem", [DoguCategory.BGSmall] = "bgSmall" }; static Constants() { configPath = Path.Combine(BepInEx.Paths.ConfigPath, configDirectory); string presetPath = Path.Combine(configPath, "Presets"); customPosePath = Path.Combine(presetPath, customPoseDirectory); customHandPath = Path.Combine(presetPath, customHandDirectory); customFacePath = Path.Combine(presetPath, customFaceDirectory); scenesPath = Path.Combine(configPath, sceneDirectory); kankyoPath = Path.Combine(configPath, kankyoDirectory); databasePath = Path.Combine(configPath, databaseDirectory); string[] directories = new[] { customPosePath, customHandPath, scenesPath, kankyoPath, configPath, customFacePath, databasePath }; foreach (string directory in directories) { if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); } } public static void Initialize() { InitializeSceneDirectories(); InitializeKankyoDirectories(); InitializePoses(); InitializeHandPresets(); InitializeFaceBlends(); InitializeBGs(); InitializeDogu(); InitializeMyRoomProps(); InitializeMpnAttachProps(); } public static void AddFacePreset(Dictionary faceData, string filename, string directory) { filename = Utility.SanitizePathPortion(filename); directory = Utility.SanitizePathPortion(directory); if (string.IsNullOrEmpty(filename)) filename = "face_preset"; if (directory.Equals(customFaceDirectory, StringComparison.InvariantCultureIgnoreCase)) { directory = string.Empty; } directory = Path.Combine(customFacePath, directory); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); string fullPath = Path.Combine(directory, filename); if (File.Exists($"{fullPath}.xml")) fullPath += $"_{Utility.Timestamp}"; fullPath = Path.GetFullPath($"{fullPath}.xml"); if (!fullPath.StartsWith(customFacePath)) { Utility.LogError($"Could not save face preset! Path is invalid: '{fullPath}'"); return; } XElement rootElement = new XElement("FaceData"); foreach (KeyValuePair kvp in faceData) { rootElement.Add(new XElement("elm", kvp.Value.ToString("G9"), new XAttribute("name", kvp.Key))); } XDocument fullDocument = new XDocument( new XDeclaration("1.0", "utf-8", "true"), new XComment("MeidoPhotoStudio Face Preset"), rootElement ); fullDocument.Save(fullPath); FileInfo fileInfo = new FileInfo(fullPath); string category = fileInfo.Directory.Name; string faceGroup = CustomFaceGroupList.Find( group => string.Equals(category, group, StringComparison.InvariantCultureIgnoreCase) ); if (string.IsNullOrEmpty(faceGroup)) { CustomFaceGroupList.Add(category); CustomFaceDict[category] = new List(); CustomFaceGroupList.Sort((a, b) => KeepAtTop(a, b, customFaceDirectory)); } else category = faceGroup; CustomFaceDict[category].Add(fullPath); CustomFaceDict[category].Sort(LexicographicStringComparer.Comparison); CustomFaceChange?.Invoke(null, new PresetChangeEventArgs(fullPath, category)); } public static void AddPose(byte[] anmBinary, string filename, string directory) { // TODO: Consider writing a file system monitor filename = Utility.SanitizePathPortion(filename); directory = Utility.SanitizePathPortion(directory); if (string.IsNullOrEmpty(filename)) filename = "custom_pose"; if (directory.Equals(customPoseDirectory, StringComparison.InvariantCultureIgnoreCase)) { directory = string.Empty; } directory = Path.Combine(customPosePath, directory); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); string fullPath = Path.Combine(directory, filename); if (File.Exists($"{fullPath}.anm")) fullPath += $"_{Utility.Timestamp}"; fullPath = Path.GetFullPath($"{fullPath}.anm"); if (!fullPath.StartsWith(customPosePath)) { Utility.LogError($"Could not save pose! Path is invalid: '{fullPath}'"); return; } File.WriteAllBytes(fullPath, anmBinary); FileInfo fileInfo = new FileInfo(fullPath); string category = fileInfo.Directory.Name; string poseGroup = CustomPoseGroupList.Find( group => string.Equals(category, group, StringComparison.InvariantCultureIgnoreCase) ); if (string.IsNullOrEmpty(poseGroup)) { CustomPoseGroupList.Add(category); CustomPoseDict[category] = new List(); CustomPoseGroupList.Sort((a, b) => KeepAtTop(a, b, customPoseDirectory)); } else category = poseGroup; CustomPoseDict[category].Add(fullPath); CustomPoseDict[category].Sort(LexicographicStringComparer.Comparison); CustomPoseChange?.Invoke(null, new PresetChangeEventArgs(fullPath, category)); } public static void AddHand(byte[] handBinary, bool right, string filename, string directory) { filename = Utility.SanitizePathPortion(filename); directory = Utility.SanitizePathPortion(directory); if (string.IsNullOrEmpty(filename)) filename = "custom_hand"; if (directory.Equals(customHandDirectory, StringComparison.InvariantCultureIgnoreCase)) { directory = string.Empty; } directory = Path.Combine(customHandPath, directory); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); string fullPath = Path.Combine(directory, filename); if (File.Exists($"{fullPath}.xml")) fullPath += $"_{Utility.Timestamp}"; fullPath = Path.GetFullPath($"{fullPath}.xml"); if (!fullPath.StartsWith(customHandPath)) { Utility.LogError($"Could not save hand! Path is invalid: '{fullPath}'"); return; } int gameVersion = Misc.GAME_VERSION; // get game version from user's Assembly-CSharp XDocument finalXml = new XDocument(new XDeclaration("1.0", "utf-8", "true"), new XComment("CM3D2 FingerData"), new XElement("FingerData", new XElement("GameVersion", gameVersion), new XElement("RightData", right), new XElement("BinaryData", Convert.ToBase64String(handBinary)) ) ); finalXml.Save(fullPath); FileInfo fileInfo = new FileInfo(fullPath); string category = fileInfo.Directory.Name; string handGroup = CustomHandGroupList.Find( group => string.Equals(category, group, StringComparison.InvariantCultureIgnoreCase) ); if (string.IsNullOrEmpty(handGroup)) { CustomHandGroupList.Add(category); CustomHandDict[category] = new List(); CustomHandGroupList.Sort((a, b) => KeepAtTop(a, b, customHandDirectory)); } else category = handGroup; CustomHandDict[category].Add(fullPath); CustomHandDict[category].Sort(LexicographicStringComparer.Comparison); CustomHandChange?.Invoke(null, new PresetChangeEventArgs(fullPath, category)); } public static void InitializeSceneDirectories() { SceneDirectoryList.Clear(); SceneDirectoryList.Add(sceneDirectory); foreach (string directory in Directory.GetDirectories(scenesPath)) { SceneDirectoryList.Add(new DirectoryInfo(directory).Name); } SceneDirectoryList.Sort((a, b) => KeepAtTop(a, b, sceneDirectory)); } public static void InitializeKankyoDirectories() { KankyoDirectoryList.Clear(); KankyoDirectoryList.Add(kankyoDirectory); foreach (string directory in Directory.GetDirectories(kankyoPath)) { KankyoDirectoryList.Add(new DirectoryInfo(directory).Name); } KankyoDirectoryList.Sort((a, b) => KeepAtTop(a, b, kankyoDirectory)); } private static int KeepAtTop(string a, string b, string topItem) { if (a == b) return 0; if (a == topItem) return -1; if (b == topItem) return 1; else return LexicographicStringComparer.Comparison(a, b); } public static void InitializePoses() { static void AddDefaultPose() { if (!PoseDict.ContainsKey("normal")) PoseDict["normal"] = new List() { "maid_stand01" }; if (!PoseGroupList.Contains("normal")) PoseGroupList.Insert(0, "normal"); } // Load Poses string poseListPath = Path.Combine(databasePath, "mm_pose_list.json"); try { string poseListJson = File.ReadAllText(poseListPath); foreach (SerializePoseList poseList in JsonConvert.DeserializeObject>(poseListJson)) { PoseDict[poseList.UIName] = poseList.PoseList; PoseGroupList.Add(poseList.UIName); } } catch (IOException e) { Utility.LogError($"Could not open pose database because {e.Message}"); Utility.LogMessage("Pose list will be serverely limited."); AddDefaultPose(); } catch (Exception e) { Utility.LogError($"Could not parse pose database because {e.Message}"); AddDefaultPose(); } // Get Other poses that'll go into Normal, Normal 2 and Ero 2 string[] com3d2MotionList = GameUty.FileSystem.GetList("motion", AFileSystemBase.ListType.AllFile); if (com3d2MotionList?.Length > 0) { HashSet poseSet = new HashSet(); foreach (List poses in PoseDict.Values) poseSet.UnionWith(poses); string[] newCategories = new[] { "normal2", "ero2" }; foreach (string category in newCategories) { if (!PoseDict.ContainsKey(category)) PoseDict[category] = new List(); } // TODO: Try to group these poses into more than "normal2" and "ero2" foreach (string path in com3d2MotionList) { if (Path.GetExtension(path) == ".anm") { string file = Path.GetFileNameWithoutExtension(path); if (!poseSet.Contains(file)) { if (file.StartsWith("edit_")) PoseDict["normal"].Add(file); else if (file != "dance_cm3d2_001_zoukin" && file != "dance_cm3d2_001_mop" && file != "aruki_1_idougo_f" && file != "sleep2" && file != "stand_akire2" && !file.EndsWith("_3_") && !file.EndsWith("_5_") && !file.StartsWith("vr_") && !file.StartsWith("dance_mc") && !file.Contains("_kubi_") && !file.Contains("a01_") && !file.Contains("b01_") && !file.Contains("b02_") && !file.EndsWith("_m2") && !file.EndsWith("_m2_once_") && !file.StartsWith("h_") && !file.StartsWith("event_") && !file.StartsWith("man_") && !file.EndsWith("_m") && !file.Contains("_m_") && !file.Contains("_man_") ) { if (path.Contains(@"\sex\")) PoseDict["ero2"].Add(file); else PoseDict["normal2"].Add(file); } } } } foreach (string category in newCategories) { if (PoseDict[category].Count > 0) { if (!PoseGroupList.Contains(category)) PoseGroupList.Add(category); } else PoseDict.Remove(category); } } InitializeCustomPoses(); } public static void InitializeCustomPoses() { static void GetPoses(string directory) { IEnumerable poseList = Directory.GetFiles(directory) .Where(file => Path.GetExtension(file) == ".anm"); if (poseList.Any()) { string poseGroupName = new DirectoryInfo(directory).Name; if (poseGroupName != customPoseDirectory) CustomPoseGroupList.Add(poseGroupName); CustomPoseDict[poseGroupName] = poseList.ToList(); CustomPoseDict[poseGroupName].Sort(LexicographicStringComparer.Comparison); } } CustomPoseGroupList.Clear(); CustomPoseDict.Clear(); CustomPoseGroupList.Add(customPoseDirectory); CustomPoseDict[customPoseDirectory] = new List(); GetPoses(customPosePath); foreach (string directory in Directory.GetDirectories(customPosePath)) GetPoses(directory); CustomPoseGroupList.Sort((a, b) => KeepAtTop(a, b, customPoseDirectory)); CustomPoseChange?.Invoke(null, PresetChangeEventArgs.Empty); } public static void InitializeHandPresets() { static void GetPresets(string directory) { IEnumerable presetList = Directory.GetFiles(directory) .Where(file => Path.GetExtension(file) == ".xml"); if (presetList.Any()) { string presetCategory = new DirectoryInfo(directory).Name; if (presetCategory != customHandDirectory) CustomHandGroupList.Add(presetCategory); CustomHandDict[presetCategory] = presetList.ToList(); CustomHandDict[presetCategory].Sort(LexicographicStringComparer.Comparison); } } CustomHandGroupList.Clear(); CustomHandDict.Clear(); CustomHandGroupList.Add(customHandDirectory); CustomHandDict[customHandDirectory] = new List(); GetPresets(customHandPath); foreach (string directory in Directory.GetDirectories(customHandPath)) GetPresets(directory); CustomHandGroupList.Sort((a, b) => KeepAtTop(a, b, customHandDirectory)); CustomHandChange?.Invoke(null, PresetChangeEventArgs.Empty); } public static void InitializeFaceBlends() { PhotoFaceData.Create(); FaceGroupList.AddRange(PhotoFaceData.popup_category_list.Select(kvp => kvp.Key)); foreach (KeyValuePair> kvp in PhotoFaceData.category_list) { FaceDict[kvp.Key] = kvp.Value.ConvertAll(data => data.setting_name); } InitializeCustomFaceBlends(); } public static void InitializeCustomFaceBlends() { static void GetFacePresets(string directory) { IEnumerable presetList = Directory.GetFiles(directory) .Where(file => Path.GetExtension(file) == ".xml").ToList(); if (presetList.Any()) { string faceGroupName = new DirectoryInfo(directory).Name; if (faceGroupName != customFaceDirectory) CustomFaceGroupList.Add(faceGroupName); CustomFaceDict[faceGroupName] = presetList.ToList(); CustomFaceDict[faceGroupName].Sort(LexicographicStringComparer.Comparison); } } CustomFaceGroupList.Clear(); CustomFaceDict.Clear(); CustomFaceGroupList.Add(customFaceDirectory); CustomFaceDict[customFaceDirectory] = new List(); GetFacePresets(customFacePath); foreach (string directory in Directory.GetDirectories(customFacePath)) GetFacePresets(directory); CustomFaceGroupList.Sort((a, b) => KeepAtTop(a, b, customFaceDirectory)); CustomFaceChange?.Invoke(null, PresetChangeEventArgs.Empty); } public static void InitializeBGs() { // Load BGs PhotoBGData.Create(); // COM3D2 BGs foreach (PhotoBGData bgData in PhotoBGData.data) { if (!string.IsNullOrEmpty(bgData.create_prefab_name)) { string bg = bgData.create_prefab_name; BGList.Add(bg); } } // CM3D2 BGs if (GameUty.IsEnabledCompatibilityMode) { using CsvParser csvParser = OpenCsvParser("phot_bg_list.nei", GameUty.FileSystemOld); for (int cell_y = 1; cell_y < csvParser.max_cell_y; cell_y++) { if (csvParser.IsCellToExistData(3, cell_y)) { string bg = csvParser.GetCellAsString(3, cell_y); BGList.Add(bg); } } } Dictionary saveDataDict = CreativeRoomManager.GetSaveDataDic(); if (saveDataDict != null) { MyRoomCustomBGIndex = BGList.Count; MyRoomCustomBGList.AddRange(saveDataDict); } } public static void InitializeDogu() { foreach (string customCategory in customDoguCategories.Values) { DoguDict[customCategory] = new List(); } InitializeDeskItems(); InitializePhotoBGItems(); InitializeOtherDogu(); InitializeHandItems(); foreach (string category in PhotoBGObjectData.popup_category_list.Select(kvp => kvp.Key)) { if (category == "マイオブジェクト") continue; DoguCategories.Add(category); } foreach (DoguCategory category in Enum.GetValues(typeof(DoguCategory))) { DoguCategories.Add(customDoguCategories[category]); } } private static void InitializeOtherDogu() { DoguDict[customDoguCategories[DoguCategory.BGSmall]] = BGList; DoguDict[customDoguCategories[DoguCategory.Mob]].AddRange(new[] { "Mob_Man_Stand001", "Mob_Man_Stand002", "Mob_Man_Stand003", "Mob_Man_Sit001", "Mob_Man_Sit002", "Mob_Man_Sit003", "Mob_Girl_Stand001", "Mob_Girl_Stand002", "Mob_Girl_Stand003", "Mob_Girl_Sit001", "Mob_Girl_Sit002", "Mob_Girl_Sit003" }); List DoguList = DoguDict[customDoguCategories[DoguCategory.Other]]; // bg object extend HashSet doguHashSet = new HashSet(StringComparer.InvariantCultureIgnoreCase); doguHashSet.UnionWith(BGList); try { string ignoreListPath = Path.Combine(databasePath, "bg_ignore_list.json"); string ignoreListJson = File.ReadAllText(ignoreListPath); string[] ignoreList = JsonConvert.DeserializeObject(ignoreListJson); doguHashSet.UnionWith(ignoreList); } catch (IOException e) { Utility.LogWarning($"Could not open ignored BG database because {e.Message}"); } catch { } foreach (List doguList in DoguDict.Values) { doguHashSet.UnionWith(doguList); } foreach (string path in GameUty.FileSystem.GetList("bg", AFileSystemBase.ListType.AllFile)) { if (Path.GetExtension(path) == ".asset_bg" && !path.Contains("myroomcustomize")) { string file = Path.GetFileNameWithoutExtension(path); if (!doguHashSet.Contains(file) && !file.EndsWith("_hit")) { DoguList.Add(file); doguHashSet.Add(file); } } } // Get cherry picked dogu that I can't find in the game files try { string doguExtendPath = Path.Combine(databasePath, "extra_dogu.json"); string doguExtendJson = File.ReadAllText(doguExtendPath); DoguList.AddRange(JsonConvert.DeserializeObject>(doguExtendJson)); } catch (IOException e) { Utility.LogError($"Could not open extra prop database because {e.Message}"); } catch (Exception e) { Utility.LogError($"Could not parse extra prop database because {e.Message}"); } foreach (string path in GameUty.FileSystemOld.GetList("bg", AFileSystemBase.ListType.AllFile)) { if (Path.GetExtension(path) == ".asset_bg") { string file = Path.GetFileNameWithoutExtension(path); if (!doguHashSet.Contains(file) && !file.EndsWith("_not_optimisation")) { DoguList.Add(file); } } } } private static void InitializeDeskItems() { HashSet enabledIDs = new HashSet(); CsvCommonIdManager.ReadEnabledIdList( CsvCommonIdManager.FileSystemType.Normal, true, "desk_item_enabled_id", ref enabledIDs ); CsvCommonIdManager.ReadEnabledIdList( CsvCommonIdManager.FileSystemType.Old, true, "desk_item_enabled_id", ref enabledIDs ); List com3d2DeskDogu = DoguDict[customDoguCategories[DoguCategory.Desk]]; void GetDeskItems(AFileSystemBase fs) { using CsvParser csvParser = OpenCsvParser("desk_item_detail.nei", fs); for (int cell_y = 1; cell_y < csvParser.max_cell_y; cell_y++) { if (csvParser.IsCellToExistData(0, cell_y)) { int cell = csvParser.GetCellAsInteger(0, cell_y); if (enabledIDs.Contains(cell)) { string dogu = string.Empty; if (csvParser.IsCellToExistData(3, cell_y)) { dogu = csvParser.GetCellAsString(3, cell_y); } else if (csvParser.IsCellToExistData(4, cell_y)) { dogu = csvParser.GetCellAsString(4, cell_y); } if (!string.IsNullOrEmpty(dogu)) { com3d2DeskDogu.Add(dogu); } } } } } GetDeskItems(GameUty.FileSystem); } private static void InitializePhotoBGItems() { PhotoBGObjectData.Create(); List photoBGObjectList = PhotoBGObjectData.data; List doguCategories = new List(); HashSet addedCategories = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach (PhotoBGObjectData photoBGObject in photoBGObjectList) { string category = photoBGObject.category; if (!addedCategories.Contains(category)) { addedCategories.Add(category); doguCategories.Add(category); } if (!DoguDict.ContainsKey(category)) { DoguDict[category] = new List(); } string dogu = string.Empty; if (!string.IsNullOrEmpty(photoBGObject.create_prefab_name)) { dogu = photoBGObject.create_prefab_name; } else if (!string.IsNullOrEmpty(photoBGObject.create_asset_bundle_name)) { dogu = photoBGObject.create_asset_bundle_name; } else if (!string.IsNullOrEmpty(photoBGObject.direct_file)) { dogu = photoBGObject.direct_file; } if (!string.IsNullOrEmpty(dogu)) { DoguDict[category].Add(dogu); } } DoguDict["パーティクル"].AddRange(new[] { "Particle/pLineY", "Particle/pLineP02", "Particle/pHeart01", "Particle/pLine_act2", "Particle/pstarY_act2" }); } private static void InitializeHandItems() { if (HandItemsInitialized) return; if (!MenuFilesReady) { if (!beginHandItemInit) MenuFilesReadyChange += (s, a) => InitializeHandItems(); beginHandItemInit = true; return; } MenuDataBase menuDataBase = GameMain.Instance.MenuDataBase; HashSet doguHashSet = new HashSet(StringComparer.InvariantCultureIgnoreCase); doguHashSet.UnionWith(BGList); try { string ignoreListPath = Path.Combine(databasePath, "bg_ignore_list.json"); string ignoreListJson = File.ReadAllText(ignoreListPath); string[] ignoreList = JsonConvert.DeserializeObject(ignoreListJson); doguHashSet.UnionWith(ignoreList); } catch (IOException e) { Utility.LogWarning($"Could not open ignored BG database because {e.Message}"); } catch (Exception e) { Utility.LogError($"Could not parse ignored BG database because {e.Message}"); } foreach (List doguList in DoguDict.Values) { doguHashSet.UnionWith(doguList); } string category = customDoguCategories[DoguCategory.HandItem]; for (int i = 0; i < menuDataBase.GetDataSize(); i++) { menuDataBase.SetIndex(i); if ((MPN)menuDataBase.GetCategoryMpn() == MPN.handitem) { string menuFileName = menuDataBase.GetMenuFileName(); if (menuDataBase.GetBoDelOnly() || menuFileName.EndsWith("_del.menu")) continue; string handItemAsOdogu = Utility.HandItemToOdogu(menuFileName); string isolatedHandItem = menuFileName.Substring(menuFileName.IndexOf('_') + 1); if (!doguHashSet.Contains(handItemAsOdogu) && !doguHashSet.Contains(isolatedHandItem)) { doguHashSet.Add(isolatedHandItem); DoguDict[category].Add(menuFileName); // Check for a half deck of cards to add the full deck as well if (menuFileName == "handitemd_cards_i_.menu") { DoguDict[category].Add("handiteml_cards_i_.menu"); } } } } HandItemsInitialized = true; OnMenuFilesChange(MenuFilesEventArgs.EventType.HandItems); } private static void InitializeMpnAttachProps() { if (MpnAttachInitialized) return; if (!MenuFilesReady) { if (!beginMpnAttachInit) MenuFilesReadyChange += (s, a) => InitializeMpnAttachProps(); beginMpnAttachInit = true; return; } MenuDataBase menuDataBase = GameMain.Instance.MenuDataBase; MPN[] attachMpn = { MPN.kousoku_lower, MPN.kousoku_upper }; for (int i = 0; i < menuDataBase.GetDataSize(); i++) { menuDataBase.SetIndex(i); MPN itemMpn = (MPN)menuDataBase.GetCategoryMpn(); if (attachMpn.Any(mpn => mpn == itemMpn)) { string menuFileName = menuDataBase.GetMenuFileName(); string mpnTag = menuDataBase.GetCategoryMpnText(); if (menuDataBase.GetBoDelOnly() || menuFileName.EndsWith("_del.menu")) continue; MpnAttachPropList.Add(new MpnAttachProp(itemMpn, menuFileName)); } } MpnAttachInitialized = true; OnMenuFilesChange(MenuFilesEventArgs.EventType.MpnAttach); } private static void InitializeMyRoomProps() { PlacementData.CreateData(); List myRoomData = PlacementData.GetAllDatas(false); myRoomData.Sort((a, b) => { int res = a.categoryID.CompareTo(b.categoryID); if (res == 0) res = a.ID.CompareTo(b.ID); return res; }); foreach (PlacementData.Data data in myRoomData) { string category = PlacementData.GetCategoryName(data.categoryID); if (!MyRoomPropDict.ContainsKey(category)) { MyRoomPropCategories.Add(category); MyRoomPropDict[category] = new List(); } string asset = !string.IsNullOrEmpty(data.resourceName) ? data.resourceName : data.assetName; MyRoomItem item = new MyRoomItem() { PrefabName = asset, ID = data.ID }; MyRoomPropDict[category].Add(item); } } private static void InitializeModProps() { for (int i = 1; i < MenuCategories.Length; i++) { ModPropDict[MenuCategories[i]] = new List(); } if (!PropManager.ModItemsOnly) { MenuDataBase menuDatabase = GameMain.Instance.MenuDataBase; for (int i = 0; i < menuDatabase.GetDataSize(); i++) { menuDatabase.SetIndex(i); ModItem modItem = new ModItem(); if (ParseNativeMenuFile(i, modItem)) { ModPropDict[modItem.Category].Add(modItem); } } } MenuFileCache cache = new MenuFileCache(); foreach (string modMenuFile in GameUty.ModOnlysMenuFiles) { ModItem modItem; if (cache.Has(modMenuFile)) modItem = cache[modMenuFile]; else { modItem = ModItem.Mod(modMenuFile); ParseMenuFile(modMenuFile, modItem); cache[modMenuFile] = modItem; } if (ValidBG2MenuFile(modItem)) ModPropDict[modItem.Category].Add(modItem); } cache.Serialize(); foreach (string modFile in Menu.GetModFiles()) { ModItem modItem = ModItem.OfficialMod(modFile); if (ParseModMenuFile(modFile, modItem)) { ModPropDict[modItem.Category].Add(modItem); } } MenuFilesInitialized = true; } public static List GetModPropList(string category) { if (!PropManager.ModItemsOnly && !MenuFilesReady) { Utility.LogMessage("Menu files are not ready yet"); return null; } if (!MenuFilesInitialized) InitializeModProps(); if (!ModPropDict.ContainsKey(category)) return null; List selectedList = ModPropDict[category]; if (selectedList[0].Icon == null) { selectedList.Sort((a, b) => { int res = a.Priority.CompareTo(b.Priority); if (res == 0) res = string.Compare(a.Name, b.Name); return res; }); string previousMenuFile = string.Empty; selectedList.RemoveAll(item => { if (item.Icon == null) { Texture2D icon; string iconFile = item.IconFile; if (string.IsNullOrEmpty(iconFile)) { Utility.LogWarning($"Could not find icon '{iconFile}' for menu '{item.MenuFile}"); return true; } try { icon = ImportCM.CreateTexture(iconFile); } catch { try { icon = ImportCM.CreateTexture($"tex\\{iconFile}"); } catch { Utility.LogWarning($"Could not load '{iconFile}' for menu '{item.MenuFile}"); return true; } } item.Icon = icon; } return false; }); } return selectedList; } private static CsvParser OpenCsvParser(string nei, AFileSystemBase fs) { try { if (fs.IsExistentFile(nei)) { AFileBase file = fs.FileOpen(nei); CsvParser csvParser = new CsvParser(); if (csvParser.Open(file)) return csvParser; file?.Dispose(); } } catch { } return null; } private static void OnMenuFilesChange(MenuFilesEventArgs.EventType eventType) { MenuFilesChange?.Invoke(null, new MenuFilesEventArgs(eventType)); } private class SerializePoseList { public string UIName { get; set; } public List PoseList { get; set; } } } public class MenuFilesEventArgs : EventArgs { public EventType Type { get; } public enum EventType { HandItems, MenuFiles, MpnAttach } public MenuFilesEventArgs(EventType type) => Type = type; } public class PresetChangeEventArgs : EventArgs { public string Category { get; } public string Path { get; } public static new PresetChangeEventArgs Empty { get; } = new PresetChangeEventArgs(string.Empty, string.Empty); public PresetChangeEventArgs(string path, string category) { Path = path; Category = category; } } public readonly struct MpnAttachProp { public MPN Tag { get; } public string MenuFile { get; } public MpnAttachProp(MPN tag, string menuFile) { Tag = tag; MenuFile = menuFile; } } }