MenuFileUtility.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using UnityEngine;
  8. namespace MeidoPhotoStudio.Plugin;
  9. public static class MenuFileUtility
  10. {
  11. // TODO: What's this for?
  12. public const string NoCategory = "noCategory";
  13. public static readonly string[] MenuCategories =
  14. {
  15. NoCategory, "acchat", "headset", "wear", "skirt", "onepiece", "mizugi", "bra", "panz", "stkg", "shoes",
  16. "acckami", "megane", "acchead", "acchana", "accmimi", "glove", "acckubi", "acckubiwa", "acckamisub", "accnip",
  17. "accude", "accheso", "accashi", "accsenaka", "accshippo", "accxxx",
  18. };
  19. private static readonly HashSet<string> AccMpn = new(StringComparer.InvariantCultureIgnoreCase);
  20. private static byte[] fileBuffer;
  21. static MenuFileUtility()
  22. {
  23. AccMpn.UnionWith(MenuCategories.Skip(1));
  24. GameMain.Instance.StartCoroutine(CheckMenuDataBaseJob());
  25. }
  26. public static event EventHandler MenuFilesReadyChange;
  27. public static bool MenuFilesReady { get; private set; }
  28. public static byte[] ReadAFileBase(string filename)
  29. {
  30. using var aFileBase = GameUty.FileOpen(filename);
  31. if (!aFileBase.IsValid() || aFileBase.GetSize() is 0)
  32. {
  33. Utility.LogError($"AFileBase '{filename}' is invalid");
  34. return null;
  35. }
  36. // INFO: Don't really understand what this does.
  37. ref var buffer = ref GetFileBuffer(aFileBase.GetSize());
  38. aFileBase.Read(ref buffer, aFileBase.GetSize());
  39. return buffer;
  40. }
  41. public static byte[] ReadOfficialMod(string filename)
  42. {
  43. using var fileStream = new FileStream(filename, FileMode.Open);
  44. if (fileStream.Length is 0)
  45. {
  46. Utility.LogWarning($"Mod menu file '{filename}' is invalid");
  47. return null;
  48. }
  49. ref var buffer = ref GetFileBuffer(fileStream.Length);
  50. fileStream.Read(buffer, 0, (int)fileStream.Length);
  51. return buffer;
  52. }
  53. public static bool ParseNativeMenuFile(int menuIndex, ModItem modItem)
  54. {
  55. var menuDataBase = GameMain.Instance.MenuDataBase;
  56. menuDataBase.SetIndex(menuIndex);
  57. if (menuDataBase.GetBoDelOnly())
  58. return false;
  59. modItem.Category = menuDataBase.GetCategoryMpnText();
  60. if (!AccMpn.Contains(modItem.Category))
  61. return false;
  62. modItem.MenuFile = menuDataBase.GetMenuFileName().ToLower();
  63. if (!ValidBG2MenuFile(modItem.MenuFile))
  64. return false;
  65. modItem.Name = menuDataBase.GetMenuName();
  66. modItem.IconFile = menuDataBase.GetIconS();
  67. modItem.Priority = menuDataBase.GetPriority();
  68. return true;
  69. }
  70. public static void ParseMenuFile(string menuFile, ModItem modItem)
  71. {
  72. if (!ValidBG2MenuFile(menuFile))
  73. return;
  74. byte[] buffer;
  75. try
  76. {
  77. buffer = ReadAFileBase(menuFile);
  78. }
  79. catch (Exception e)
  80. {
  81. Utility.LogError($"Could not read menu file '{menuFile}' because {e.Message}");
  82. return;
  83. }
  84. try
  85. {
  86. using var binaryReader = new BinaryReader(new MemoryStream(buffer), Encoding.UTF8);
  87. if (binaryReader.ReadString() is not "CM3D2_MENU")
  88. return;
  89. binaryReader.ReadInt32(); // file version
  90. binaryReader.ReadString(); // txt path
  91. modItem.Name = binaryReader.ReadString(); // name
  92. binaryReader.ReadString(); // category
  93. binaryReader.ReadString(); // description
  94. binaryReader.ReadInt32(); // idk (as long)
  95. while (true)
  96. {
  97. var numberOfProps = binaryReader.ReadByte();
  98. var menuPropString = string.Empty;
  99. if (numberOfProps is 0)
  100. break;
  101. for (var i = 0; i < numberOfProps; i++)
  102. menuPropString = $"{menuPropString}\"{binaryReader.ReadString()}\"";
  103. if (string.IsNullOrEmpty(menuPropString))
  104. continue;
  105. var header = UTY.GetStringCom(menuPropString);
  106. var menuProps = UTY.GetStringList(menuPropString);
  107. if (header is "end")
  108. break;
  109. if (header is "category")
  110. {
  111. modItem.Category = menuProps[1];
  112. if (!AccMpn.Contains(modItem.Category))
  113. return;
  114. }
  115. else if (header is "icons" or "icon")
  116. {
  117. modItem.IconFile = menuProps[1];
  118. break;
  119. }
  120. else if (header is "priority")
  121. {
  122. modItem.Priority = float.Parse(menuProps[1]);
  123. }
  124. }
  125. }
  126. catch (Exception e)
  127. {
  128. Utility.LogWarning($"Could not parse menu file '{menuFile}' because {e.Message}");
  129. }
  130. }
  131. public static bool ParseModMenuFile(string modMenuFile, ModItem modItem)
  132. {
  133. if (!ValidBG2MenuFile(modMenuFile))
  134. return false;
  135. byte[] modBuffer;
  136. try
  137. {
  138. modBuffer = ReadOfficialMod(modMenuFile);
  139. }
  140. catch (Exception e)
  141. {
  142. Utility.LogError($"Could not read mod menu file '{modMenuFile} because {e.Message}'");
  143. return false;
  144. }
  145. try
  146. {
  147. using var binaryReader = new BinaryReader(new MemoryStream(modBuffer), Encoding.UTF8);
  148. if (binaryReader.ReadString() is not "CM3D2_MOD")
  149. return false;
  150. binaryReader.ReadInt32();
  151. var iconName = binaryReader.ReadString();
  152. var baseItemPath = binaryReader.ReadString().Replace(":", " ");
  153. modItem.BaseMenuFile = Path.GetFileName(baseItemPath);
  154. modItem.Name = binaryReader.ReadString();
  155. modItem.Category = binaryReader.ReadString();
  156. if (!AccMpn.Contains(modItem.Category))
  157. return false;
  158. binaryReader.ReadString();
  159. var mpnValue = binaryReader.ReadString();
  160. var mpn = MPN.null_mpn;
  161. try
  162. {
  163. mpn = (MPN)Enum.Parse(typeof(MPN), mpnValue, true);
  164. }
  165. catch
  166. {
  167. // Ignored.
  168. }
  169. if (mpn is not MPN.null_mpn)
  170. binaryReader.ReadString();
  171. binaryReader.ReadString();
  172. var entryCount = binaryReader.ReadInt32();
  173. for (var i = 0; i < entryCount; i++)
  174. {
  175. var key = binaryReader.ReadString();
  176. var count = binaryReader.ReadInt32();
  177. var data = binaryReader.ReadBytes(count);
  178. if (!string.Equals(key, iconName, StringComparison.InvariantCultureIgnoreCase))
  179. continue;
  180. var tex = new Texture2D(1, 1, TextureFormat.RGBA32, false);
  181. tex.LoadImage(data);
  182. modItem.Icon = tex;
  183. break;
  184. }
  185. }
  186. catch (Exception e)
  187. {
  188. Utility.LogWarning($"Could not parse mod menu file '{modMenuFile}' because {e}");
  189. return false;
  190. }
  191. return true;
  192. }
  193. public static bool ValidBG2MenuFile(ModItem modItem) =>
  194. AccMpn.Contains(modItem.Category) && ValidBG2MenuFile(modItem.MenuFile);
  195. public static bool ValidBG2MenuFile(string menu)
  196. {
  197. menu = Path.GetFileNameWithoutExtension(menu).ToLower();
  198. return !(menu.EndsWith("_del") || menu.Contains("zurashi") || menu.Contains("mekure") || menu.Contains("porori")
  199. || menu.Contains("moza") || menu.Contains("folder"));
  200. }
  201. private static IEnumerator CheckMenuDataBaseJob()
  202. {
  203. if (MenuFilesReady)
  204. yield break;
  205. while (!GameMain.Instance.MenuDataBase.JobFinished())
  206. yield return null;
  207. MenuFilesReady = true;
  208. MenuFilesReadyChange?.Invoke(null, EventArgs.Empty);
  209. }
  210. private static ref byte[] GetFileBuffer(long size)
  211. {
  212. if (fileBuffer is null)
  213. fileBuffer = new byte[Math.Max(500000, size)];
  214. else if (fileBuffer.Length < size)
  215. fileBuffer = new byte[size];
  216. return ref fileBuffer;
  217. }
  218. }