Hooks.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Reflection.Emit;
  8. using System.Security.Cryptography;
  9. using System.Text;
  10. using UnityEngine;
  11. using Debug = UnityEngine.Debug;
  12. namespace COM3D2.CacheEditMenu
  13. {
  14. internal static class BinaryExtensions
  15. {
  16. public static string ReadNullableString(this BinaryReader br)
  17. {
  18. return br.ReadBoolean() ? br.ReadString() : null;
  19. }
  20. public static void WriteNullableString(this BinaryWriter bw, string value)
  21. {
  22. bw.Write(value != null);
  23. if (value != null)
  24. bw.Write(value);
  25. }
  26. }
  27. public static class Hooks
  28. {
  29. private const int CACHE_VERSION = 1030;
  30. private static readonly Dictionary<string, MenuInfo> infoCache = new Dictionary<string, MenuInfo>();
  31. private static BinaryWriter cacheWriter;
  32. private static readonly Func<Dictionary<int, SceneEdit.SMenuItem>> getIdItemDic = () =>
  33. new Dictionary<int, SceneEdit.SMenuItem>();
  34. private static readonly Func<Dictionary<int, string>> getTexFileIDDic = () => new Dictionary<int, string>();
  35. private static readonly Action<bool> setNowMenuFlg = b => { };
  36. private static readonly Action<int> setNowStrMenuFileID = b => { };
  37. static Hooks()
  38. {
  39. var quickEditAss =
  40. TryLoadFrom(Path.Combine(Patcher.Patcher.SybarisPath, "COM3D2.QuickEditStart.Managed.dll"));
  41. if (quickEditAss == null)
  42. return;
  43. var mainType = quickEditAss.GetType("COM3D2.QuickEditStart.Managed.QuickEditStartManaged");
  44. MakeStaticGetter(mainType.GetField("texFileIDDic", BindingFlags.Public | BindingFlags.Static),
  45. ref getTexFileIDDic);
  46. MakeStaticGetter(mainType.GetField("idItemDic", BindingFlags.Public | BindingFlags.Static),
  47. ref getIdItemDic);
  48. MakeStaticSetter(mainType.GetField("nowMenuFlg", BindingFlags.NonPublic | BindingFlags.Static),
  49. ref setNowMenuFlg);
  50. MakeStaticSetter(mainType.GetField("nowStrMenuFileID", BindingFlags.NonPublic | BindingFlags.Static),
  51. ref setNowStrMenuFileID);
  52. }
  53. private static void MakeStaticGetter<T>(FieldInfo field, ref T result) where T : Delegate
  54. {
  55. if (field == null)
  56. return;
  57. var dm = new DynamicMethod($"CacheEditMenu_Getter_{field.GetHashCode()}", field.FieldType, null,
  58. typeof(Hooks), true);
  59. var il = dm.GetILGenerator();
  60. il.Emit(OpCodes.Ldsfld, field);
  61. il.Emit(OpCodes.Ret);
  62. result = dm.CreateDelegate(typeof(T)) as T;
  63. }
  64. private static void MakeStaticSetter<T>(FieldInfo field, ref T result) where T : Delegate
  65. {
  66. if (field == null)
  67. return;
  68. var dm = new DynamicMethod($"CacheEditMenu_Getter_{field.GetHashCode()}", typeof(void),
  69. new[] {field.FieldType}, typeof(Hooks), true);
  70. var il = dm.GetILGenerator();
  71. il.Emit(OpCodes.Ldarg_0);
  72. il.Emit(OpCodes.Stsfld, field);
  73. il.Emit(OpCodes.Ret);
  74. result = dm.CreateDelegate(typeof(T)) as T;
  75. }
  76. private static Assembly TryLoadFrom(string path)
  77. {
  78. try
  79. {
  80. return Assembly.LoadFrom(path);
  81. }
  82. catch (Exception)
  83. {
  84. return null;
  85. }
  86. }
  87. public static bool Prefix(ref bool result, SceneEdit.SMenuItem mi, string menuFileName)
  88. {
  89. Init();
  90. if (menuFileName.Contains("_zurashi"))
  91. {
  92. result = false;
  93. return false;
  94. }
  95. if (menuFileName.Contains("_mekure"))
  96. {
  97. result = false;
  98. return false;
  99. }
  100. menuFileName = Path.GetFileName(menuFileName);
  101. mi.m_strMenuFileName = menuFileName;
  102. mi.m_nMenuFileRID = menuFileName.ToLower().GetHashCode();
  103. if (infoCache.TryGetValue(menuFileName, out var menuInfo))
  104. {
  105. menuInfo.CopyTo(mi);
  106. result = true;
  107. return false;
  108. }
  109. return true;
  110. }
  111. public static string texFileName = null;
  112. public static string CreateTexturePrefix(string fileName)
  113. {
  114. texFileName = fileName;
  115. return fileName;
  116. }
  117. public static bool Postfix(bool result, SceneEdit.SMenuItem mi, string menuFileName)
  118. {
  119. if (!result)
  120. return false;
  121. var menuInfo = new MenuInfo
  122. {
  123. mi = mi,
  124. key = menuFileName
  125. };
  126. if (texFileName != null)
  127. menuInfo.texName = texFileName;
  128. infoCache[menuFileName] = menuInfo;
  129. try
  130. {
  131. menuInfo.Serialize(cacheWriter);
  132. }
  133. catch (Exception e)
  134. {
  135. Debug.Log(
  136. $"Failed to serialize menu file {menuFileName}: {e.Message}. The cache may be corrupted and will be rebuilt on next game run.");
  137. }
  138. cacheWriter.Flush();
  139. texFileName = null;
  140. return true;
  141. }
  142. private static byte[] HashMenus(string[] menuList)
  143. {
  144. var md5 = MD5.Create();
  145. foreach (var s in menuList)
  146. {
  147. var buf = Encoding.Unicode.GetBytes(s);
  148. md5.TransformBlock(buf, 0, buf.Length, null, 0);
  149. }
  150. md5.TransformFinalBlock(new byte[0], 0, 0);
  151. return md5.Hash;
  152. }
  153. private static bool ReadCache(string cachePath)
  154. {
  155. if (!File.Exists(cachePath))
  156. return false;
  157. using (var br = new BinaryReader(File.Open(cachePath, FileMode.Open, FileAccess.Read)))
  158. {
  159. try
  160. {
  161. var cacheVer = br.ReadInt32();
  162. if (cacheVer != CACHE_VERSION)
  163. {
  164. Debug.LogWarning("Old cache version, rebuilding...");
  165. return false;
  166. }
  167. var modMenuFilesHashSize = br.ReadInt32();
  168. var modMenuFilesHash = br.ReadBytes(modMenuFilesHashSize);
  169. if (!modMenuFilesHash.SequenceEqual(HashMenus(GameUty.ModOnlysMenuFiles)))
  170. {
  171. Debug.LogWarning("Mod .menu files changed, rebuilding cache...");
  172. return false;
  173. }
  174. while (true)
  175. {
  176. var menuInfo = new MenuInfo();
  177. menuInfo.Deserialize(br);
  178. infoCache[menuInfo.key] = menuInfo;
  179. }
  180. }
  181. catch (FormatException e)
  182. {
  183. Debug.Log($"Failed to deserialize cache because {e.Message}. Rebuilding the cache...");
  184. return false;
  185. }
  186. catch (EndOfStreamException)
  187. {
  188. // End of file, no need to read more
  189. }
  190. }
  191. return true;
  192. }
  193. private static void Init()
  194. {
  195. if (cacheWriter != null)
  196. return;
  197. var cacheDir = Path.Combine(Patcher.Patcher.SybarisPath, "EditMenuCache");
  198. var cachePath = Path.Combine(cacheDir, "EditMenuCache.dat");
  199. Directory.CreateDirectory(cacheDir);
  200. var rebuildCache = !ReadCache(cachePath);
  201. var stream = rebuildCache ? File.Create(cachePath) : File.OpenWrite(cachePath);
  202. cacheWriter = new BinaryWriter(stream);
  203. if (rebuildCache)
  204. {
  205. cacheWriter.Write(CACHE_VERSION);
  206. var modMenuHash = HashMenus(GameUty.ModOnlysMenuFiles);
  207. cacheWriter.Write(modMenuHash.Length);
  208. cacheWriter.Write(modMenuHash);
  209. foreach (var keyValuePair in infoCache)
  210. keyValuePair.Value.Serialize(cacheWriter);
  211. cacheWriter.Flush();
  212. }
  213. cacheWriter.BaseStream.Seek(0, SeekOrigin.End);
  214. }
  215. private class MenuInfo
  216. {
  217. public string key;
  218. public SceneEdit.SMenuItem mi;
  219. public string texName;
  220. public void Deserialize(BinaryReader br)
  221. {
  222. key = br.ReadNullableString();
  223. texName = br.ReadNullableString();
  224. mi = new SceneEdit.SMenuItem
  225. {
  226. m_strMenuName = br.ReadNullableString(),
  227. m_strInfo = br.ReadNullableString(),
  228. m_mpn = (MPN) br.ReadInt32(),
  229. m_strCateName = br.ReadNullableString(),
  230. m_eColorSetMPN = (MPN) br.ReadInt32(),
  231. m_strMenuNameInColorSet = br.ReadNullableString(),
  232. m_pcMultiColorID = (MaidParts.PARTS_COLOR) br.ReadInt32(),
  233. m_boDelOnly = br.ReadBoolean(),
  234. m_fPriority = br.ReadSingle(),
  235. m_bMan = br.ReadBoolean()
  236. };
  237. }
  238. public void Serialize(BinaryWriter bw)
  239. {
  240. bw.WriteNullableString(key);
  241. bw.WriteNullableString(texName);
  242. bw.WriteNullableString(mi.m_strMenuName);
  243. bw.WriteNullableString(mi.m_strInfo);
  244. bw.Write((int) mi.m_mpn);
  245. bw.WriteNullableString(mi.m_strCateName);
  246. bw.Write((int) mi.m_eColorSetMPN);
  247. bw.WriteNullableString(mi.m_strMenuNameInColorSet);
  248. bw.Write((int) mi.m_pcMultiColorID);
  249. bw.Write(mi.m_boDelOnly);
  250. bw.Write(mi.m_fPriority);
  251. bw.Write(mi.m_bMan);
  252. }
  253. public void CopyTo(SceneEdit.SMenuItem other)
  254. {
  255. other.m_strMenuName = mi.m_strMenuName;
  256. other.m_strInfo = mi.m_strInfo;
  257. other.m_mpn = mi.m_mpn;
  258. other.m_strCateName = mi.m_strCateName;
  259. other.m_eColorSetMPN = mi.m_eColorSetMPN;
  260. other.m_strMenuNameInColorSet = mi.m_strMenuNameInColorSet;
  261. other.m_pcMultiColorID = mi.m_pcMultiColorID;
  262. other.m_boDelOnly = mi.m_boDelOnly;
  263. other.m_fPriority = mi.m_fPriority;
  264. other.m_bMan = mi.m_bMan;
  265. if (string.IsNullOrEmpty(texName))
  266. return;
  267. setNowMenuFlg(true);
  268. setNowStrMenuFileID(other.m_nMenuFileRID);
  269. getIdItemDic()[other.m_nMenuFileRID] = other;
  270. other.m_texIcon = ImportCM.CreateTexture(texName);
  271. setNowMenuFlg(false);
  272. }
  273. }
  274. }
  275. }