Hooks.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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 = 1021;
  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 menuFilesHashSize = br.ReadInt32();
  168. var menuFilesHash = br.ReadBytes(menuFilesHashSize);
  169. if (!menuFilesHash.SequenceEqual(HashMenus(GameUty.MenuFiles)))
  170. {
  171. Debug.LogWarning(".menu files changed, rebuilding cache...");
  172. return false;
  173. }
  174. var modMenuFilesHashSize = br.ReadInt32();
  175. var modMenuFilesHash = br.ReadBytes(modMenuFilesHashSize);
  176. if (!modMenuFilesHash.SequenceEqual(HashMenus(GameUty.ModOnlysMenuFiles)))
  177. {
  178. Debug.LogWarning("Mod .menu files changed, rebuilding cache...");
  179. return false;
  180. }
  181. while (true)
  182. {
  183. var menuInfo = new MenuInfo();
  184. menuInfo.Deserialize(br);
  185. infoCache[menuInfo.key] = menuInfo;
  186. }
  187. }
  188. catch (FormatException e)
  189. {
  190. Debug.Log($"Failed to deserialize cache because {e.Message}. Rebuilding the cache...");
  191. return false;
  192. }
  193. catch (EndOfStreamException)
  194. {
  195. // End of file, no need to read more
  196. }
  197. }
  198. return true;
  199. }
  200. private static void Init()
  201. {
  202. if (cacheWriter != null)
  203. return;
  204. var cacheDir = Path.Combine(Patcher.Patcher.SybarisPath, "EditMenuCache");
  205. var cachePath = Path.Combine(cacheDir, "EditMenuCache.dat");
  206. Directory.CreateDirectory(cacheDir);
  207. var rebuildCache = !ReadCache(cachePath);
  208. var stream = rebuildCache ? File.Create(cachePath) : File.OpenWrite(cachePath);
  209. cacheWriter = new BinaryWriter(stream);
  210. if (rebuildCache)
  211. {
  212. cacheWriter.Write(CACHE_VERSION);
  213. var menuHash = HashMenus(GameUty.MenuFiles);
  214. var modMenuHash = HashMenus(GameUty.ModOnlysMenuFiles);
  215. cacheWriter.Write(menuHash.Length);
  216. cacheWriter.Write(menuHash);
  217. cacheWriter.Write(modMenuHash.Length);
  218. cacheWriter.Write(modMenuHash);
  219. foreach (var keyValuePair in infoCache)
  220. keyValuePair.Value.Serialize(cacheWriter);
  221. cacheWriter.Flush();
  222. }
  223. cacheWriter.BaseStream.Seek(0, SeekOrigin.End);
  224. }
  225. private class MenuInfo
  226. {
  227. public string key;
  228. public SceneEdit.SMenuItem mi;
  229. public string texName;
  230. public void Deserialize(BinaryReader br)
  231. {
  232. key = br.ReadNullableString();
  233. texName = br.ReadNullableString();
  234. mi = new SceneEdit.SMenuItem
  235. {
  236. m_strMenuName = br.ReadNullableString(),
  237. m_strInfo = br.ReadNullableString(),
  238. m_mpn = (MPN) br.ReadInt32(),
  239. m_strCateName = br.ReadNullableString(),
  240. m_eColorSetMPN = (MPN) br.ReadInt32(),
  241. m_strMenuNameInColorSet = br.ReadNullableString(),
  242. m_pcMultiColorID = (MaidParts.PARTS_COLOR) br.ReadInt32(),
  243. m_boDelOnly = br.ReadBoolean(),
  244. m_fPriority = br.ReadSingle(),
  245. m_bMan = br.ReadBoolean()
  246. };
  247. }
  248. public void Serialize(BinaryWriter bw)
  249. {
  250. bw.WriteNullableString(key);
  251. bw.WriteNullableString(texName);
  252. bw.WriteNullableString(mi.m_strMenuName);
  253. bw.WriteNullableString(mi.m_strInfo);
  254. bw.Write((int) mi.m_mpn);
  255. bw.WriteNullableString(mi.m_strCateName);
  256. bw.Write((int) mi.m_eColorSetMPN);
  257. bw.WriteNullableString(mi.m_strMenuNameInColorSet);
  258. bw.Write((int) mi.m_pcMultiColorID);
  259. bw.Write(mi.m_boDelOnly);
  260. bw.Write(mi.m_fPriority);
  261. bw.Write(mi.m_bMan);
  262. }
  263. public void CopyTo(SceneEdit.SMenuItem other)
  264. {
  265. other.m_strMenuName = mi.m_strMenuName;
  266. other.m_strInfo = mi.m_strInfo;
  267. other.m_mpn = mi.m_mpn;
  268. other.m_strCateName = mi.m_strCateName;
  269. other.m_eColorSetMPN = mi.m_eColorSetMPN;
  270. other.m_strMenuNameInColorSet = mi.m_strMenuNameInColorSet;
  271. other.m_pcMultiColorID = mi.m_pcMultiColorID;
  272. other.m_boDelOnly = mi.m_boDelOnly;
  273. other.m_fPriority = mi.m_fPriority;
  274. other.m_bMan = mi.m_bMan;
  275. if (string.IsNullOrEmpty(texName))
  276. return;
  277. setNowMenuFlg(true);
  278. setNowStrMenuFileID(other.m_nMenuFileRID);
  279. getIdItemDic()[other.m_nMenuFileRID] = other;
  280. other.m_texIcon = ImportCM.CreateTexture(texName);
  281. setNowMenuFlg(false);
  282. }
  283. }
  284. }
  285. }