MenuFileUtility.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. using System;
  2. using System.Text;
  3. using System.IO;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using UnityEngine;
  8. using UnityEngine.Rendering;
  9. namespace COM3D2.MeidoPhotoStudio.Plugin
  10. {
  11. internal static class MenuFileUtility
  12. {
  13. private static byte[] menuFileBuffer;
  14. public const string noCategory = "noCategory";
  15. public static string[] MenuCategories = new[] {
  16. noCategory, "acchat", "headset", "wear", "skirt", "onepiece", "mizugi", "bra", "panz", "stkg", "shoes",
  17. "acckami", "megane", "acchead", "acchana", "accmimi", "glove", "acckubi", "acckubiwa", "acckamisub",
  18. "accnip", "accude", "accheso", "accashi", "accsenaka", "accshippo", "accxxx"
  19. };
  20. private static readonly HashSet<string> accMpn = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
  21. private enum IMode
  22. {
  23. None, ItemChange, TexChange
  24. }
  25. public static event EventHandler MenuFilesReadyChange;
  26. public static bool MenuFilesReady { get; private set; } = false;
  27. static MenuFileUtility()
  28. {
  29. accMpn.UnionWith(MenuCategories.Skip(1));
  30. GameMain.Instance.StartCoroutine(CheckMenuDataBaseJob());
  31. }
  32. private static IEnumerator CheckMenuDataBaseJob()
  33. {
  34. if (MenuFilesReady) yield break;
  35. while (!GameMain.Instance.MenuDataBase.JobFinished()) yield return null;
  36. MenuFilesReady = true;
  37. MenuFilesReadyChange?.Invoke(null, EventArgs.Empty);
  38. yield break;
  39. }
  40. private static bool ProcScriptBin(byte[] menuBuf, ModelInfo modelInfo)
  41. {
  42. using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(menuBuf), Encoding.UTF8))
  43. {
  44. string menuHeader = binaryReader.ReadString();
  45. NDebug.Assert(
  46. menuHeader == "CM3D2_MENU", "ProcScriptBin 例外 : ヘッダーファイルが不正です。" + menuHeader
  47. );
  48. binaryReader.ReadInt32(); // file version
  49. binaryReader.ReadString(); // txt path
  50. binaryReader.ReadString(); // name
  51. binaryReader.ReadString(); // category
  52. binaryReader.ReadString(); // description
  53. binaryReader.ReadInt32(); // idk (as long)
  54. string menuPropString = string.Empty;
  55. string menuPropStringTemp = string.Empty;
  56. try
  57. {
  58. while (true)
  59. {
  60. int numberOfProps = binaryReader.ReadByte();
  61. menuPropStringTemp = menuPropString;
  62. menuPropString = string.Empty;
  63. if (numberOfProps != 0)
  64. {
  65. for (int i = 0; i < numberOfProps; i++)
  66. {
  67. menuPropString = $"{menuPropString}\"{binaryReader.ReadString()}\"";
  68. }
  69. if (menuPropString != string.Empty)
  70. {
  71. string header = UTY.GetStringCom(menuPropString);
  72. string[] menuProps = UTY.GetStringList(menuPropString);
  73. if (header == "end") break;
  74. else if (header == "マテリアル変更")
  75. {
  76. int matNo = int.Parse(menuProps[2]);
  77. string materialFile = menuProps[3];
  78. modelInfo.MaterialChanges.Add(new MaterialChange(matNo, materialFile));
  79. }
  80. else if (header == "additem") modelInfo.ModelFile = menuProps[1];
  81. }
  82. }
  83. else break;
  84. }
  85. }
  86. catch
  87. {
  88. return false;
  89. }
  90. }
  91. return true;
  92. }
  93. private static void ProcModScriptBin(byte[] cd, GameObject go)
  94. {
  95. BinaryReader binaryReader = new BinaryReader(new MemoryStream(cd), Encoding.UTF8);
  96. string str1 = binaryReader.ReadString();
  97. NDebug.Assert(str1 == "CM3D2_MOD", "ProcModScriptBin 例外 : ヘッダーファイルが不正です。" + str1);
  98. binaryReader.ReadInt32();
  99. binaryReader.ReadString();
  100. binaryReader.ReadString();
  101. binaryReader.ReadString();
  102. binaryReader.ReadString();
  103. binaryReader.ReadString();
  104. string mpnValue = binaryReader.ReadString();
  105. MPN mpn = MPN.null_mpn;
  106. try
  107. {
  108. mpn = (MPN)Enum.Parse(typeof(MPN), mpnValue);
  109. }
  110. catch { }
  111. if (mpn != MPN.null_mpn)
  112. {
  113. binaryReader.ReadString();
  114. }
  115. string s = binaryReader.ReadString();
  116. int num2 = binaryReader.ReadInt32();
  117. Dictionary<string, byte[]> dictionary = new Dictionary<string, byte[]>();
  118. for (int i = 0; i < num2; i++)
  119. {
  120. string key = binaryReader.ReadString();
  121. int count = binaryReader.ReadInt32();
  122. byte[] value = binaryReader.ReadBytes(count);
  123. dictionary.Add(key, value);
  124. }
  125. binaryReader.Close();
  126. using (StringReader stringReader = new StringReader(s))
  127. {
  128. IMode mode = IMode.None;
  129. Material material = null;
  130. int num3 = 0;
  131. string line;
  132. bool change = false;
  133. while ((line = stringReader.ReadLine()) != null)
  134. {
  135. string[] array = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
  136. if (array[0] == "アイテム変更" || array[0] == "マテリアル変更") mode = IMode.ItemChange;
  137. else if (array[0] == "テクスチャ変更") mode = IMode.TexChange;
  138. if (mode == IMode.ItemChange)
  139. {
  140. if (array[0] == "スロット名") change = true;
  141. if (change)
  142. {
  143. if (array[0] == "マテリアル番号")
  144. {
  145. num3 = int.Parse(array[1]);
  146. foreach (Transform transform in go.GetComponentsInChildren<Transform>(true))
  147. {
  148. Renderer component = transform.GetComponent<Renderer>();
  149. if (component && component.materials != null)
  150. {
  151. Material[] materials = component.materials;
  152. for (int k = 0; k < materials.Length; k++)
  153. {
  154. if (k == num3)
  155. {
  156. material = materials[k];
  157. break;
  158. }
  159. }
  160. }
  161. }
  162. }
  163. if (material != null)
  164. {
  165. if (array[0] == "テクスチャ設定")
  166. {
  167. ChangeTex(num3, array[1], array[2].ToLower(), dictionary, go);
  168. }
  169. else if (array[0] == "色設定")
  170. {
  171. material.SetColor(array[1],
  172. new Color(
  173. float.Parse(array[2]) / 255f,
  174. float.Parse(array[3]) / 255f,
  175. float.Parse(array[4]) / 255f,
  176. float.Parse(array[5]) / 255f
  177. )
  178. );
  179. }
  180. else if (array[0] == "数値設定") material.SetFloat(array[1], float.Parse(array[2]));
  181. }
  182. }
  183. }
  184. else if (mode == IMode.TexChange)
  185. {
  186. int matno = int.Parse(array[2]);
  187. ChangeTex(matno, array[3], array[4].ToLower(), dictionary, go);
  188. }
  189. }
  190. }
  191. }
  192. private static void ChangeTex(
  193. int matno, string prop, string filename, Dictionary<string, byte[]> matDict, GameObject go
  194. )
  195. {
  196. TextureResource textureResource;
  197. byte[] buf = matDict[filename.ToLowerInvariant()];
  198. textureResource = new TextureResource(2, 2, TextureFormat.ARGB32, null, buf);
  199. List<Renderer> list = new List<Renderer>(3);
  200. go.transform.GetComponentsInChildren(true, list);
  201. foreach (Renderer r in list)
  202. {
  203. if (r && r.material && matno < r.materials.Length)
  204. {
  205. r.materials[matno].SetTexture(prop, null);
  206. Texture2D texture2D = textureResource.CreateTexture2D();
  207. texture2D.name = filename;
  208. r.materials[matno].SetTexture(prop, texture2D);
  209. }
  210. }
  211. }
  212. private static GameObject LoadSkinMesh_R(string modelFileName, int layer)
  213. {
  214. using (AFileBase afileBase = GameUty.FileOpen(modelFileName, null))
  215. {
  216. if (afileBase.IsValid() && afileBase.GetSize() != 0)
  217. {
  218. if (menuFileBuffer == null)
  219. {
  220. menuFileBuffer = new byte[Math.Max(500000, afileBase.GetSize())];
  221. }
  222. else if (menuFileBuffer.Length < afileBase.GetSize())
  223. {
  224. menuFileBuffer = new byte[afileBase.GetSize()];
  225. }
  226. afileBase.Read(ref menuFileBuffer, afileBase.GetSize());
  227. }
  228. else
  229. {
  230. Utility.LogError("invalid model");
  231. return null;
  232. }
  233. }
  234. using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(menuFileBuffer), Encoding.UTF8))
  235. {
  236. GameObject gameObject = UnityEngine.Object.Instantiate(Resources.Load("seed")) as GameObject;
  237. gameObject.layer = 1;
  238. GameObject gameObject2 = null;
  239. Hashtable hashtable = new Hashtable();
  240. string text = binaryReader.ReadString();
  241. if (text != "CM3D2_MESH")
  242. {
  243. NDebug.Assert("LoadSkinMesh_R 例外 : ヘッダーファイルが不正です。" + text, false);
  244. }
  245. int num = binaryReader.ReadInt32();
  246. string str = binaryReader.ReadString();
  247. gameObject.name = "_SM_" + str;
  248. string b = binaryReader.ReadString();
  249. int num2 = binaryReader.ReadInt32();
  250. List<GameObject> list = new List<GameObject>();
  251. for (int i = 0; i < num2; i++)
  252. {
  253. GameObject gameObject3 = UnityEngine.Object.Instantiate(Resources.Load("seed")) as GameObject;
  254. gameObject3.layer = layer;
  255. gameObject3.name = binaryReader.ReadString();
  256. list.Add(gameObject3);
  257. if (gameObject3.name == b)
  258. {
  259. gameObject2 = gameObject3;
  260. }
  261. hashtable[gameObject3.name] = gameObject3;
  262. bool flag = binaryReader.ReadByte() != 0;
  263. if (flag)
  264. {
  265. GameObject gameObject4 = UnityEngine.Object.Instantiate(Resources.Load("seed")) as GameObject;
  266. gameObject4.name = gameObject3.name + "_SCL_";
  267. gameObject4.transform.parent = gameObject3.transform;
  268. hashtable[gameObject3.name + "&_SCL_"] = gameObject4;
  269. }
  270. }
  271. for (int j = 0; j < num2; j++)
  272. {
  273. int num3 = binaryReader.ReadInt32();
  274. if (num3 >= 0)
  275. {
  276. list[j].transform.parent = list[num3].transform;
  277. }
  278. else
  279. {
  280. list[j].transform.parent = gameObject.transform;
  281. }
  282. }
  283. for (int k = 0; k < num2; k++)
  284. {
  285. Transform transform = list[k].transform;
  286. float x = binaryReader.ReadSingle();
  287. float y = binaryReader.ReadSingle();
  288. float z = binaryReader.ReadSingle();
  289. transform.localPosition = new Vector3(x, y, z);
  290. float x2 = binaryReader.ReadSingle();
  291. float y2 = binaryReader.ReadSingle();
  292. float z2 = binaryReader.ReadSingle();
  293. float w = binaryReader.ReadSingle();
  294. transform.localRotation = new Quaternion(x2, y2, z2, w);
  295. if (2001 <= num)
  296. {
  297. bool flag2 = binaryReader.ReadBoolean();
  298. if (flag2)
  299. {
  300. float x3 = binaryReader.ReadSingle();
  301. float y3 = binaryReader.ReadSingle();
  302. float z3 = binaryReader.ReadSingle();
  303. transform.localScale = new Vector3(x3, y3, z3);
  304. }
  305. }
  306. }
  307. int num4 = binaryReader.ReadInt32();
  308. int num5 = binaryReader.ReadInt32();
  309. int num6 = binaryReader.ReadInt32();
  310. SkinnedMeshRenderer skinnedMeshRenderer = gameObject2.AddComponent<SkinnedMeshRenderer>();
  311. skinnedMeshRenderer.updateWhenOffscreen = true;
  312. skinnedMeshRenderer.skinnedMotionVectors = false;
  313. skinnedMeshRenderer.lightProbeUsage = LightProbeUsage.Off;
  314. skinnedMeshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
  315. skinnedMeshRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
  316. Transform[] array2 = new Transform[num6];
  317. for (int l = 0; l < num6; l++)
  318. {
  319. string text2 = binaryReader.ReadString();
  320. if (!hashtable.ContainsKey(text2))
  321. {
  322. Utility.LogError("nullbone= " + text2);
  323. }
  324. else
  325. {
  326. GameObject gameObject5;
  327. if (hashtable.ContainsKey(text2 + "&_SCL_"))
  328. {
  329. gameObject5 = (GameObject)hashtable[text2 + "&_SCL_"];
  330. }
  331. else
  332. {
  333. gameObject5 = (GameObject)hashtable[text2];
  334. }
  335. array2[l] = gameObject5.transform;
  336. }
  337. }
  338. skinnedMeshRenderer.bones = array2;
  339. Mesh mesh = new Mesh();
  340. skinnedMeshRenderer.sharedMesh = mesh;
  341. Mesh mesh2 = mesh;
  342. // bodyskin.listDEL.Add(mesh2);
  343. Matrix4x4[] array4 = new Matrix4x4[num6];
  344. for (int m = 0; m < num6; m++)
  345. {
  346. for (int n = 0; n < 16; n++)
  347. {
  348. array4[m][n] = binaryReader.ReadSingle();
  349. }
  350. }
  351. mesh2.bindposes = array4;
  352. Vector3[] array5 = new Vector3[num4];
  353. Vector3[] array6 = new Vector3[num4];
  354. Vector2[] array7 = new Vector2[num4];
  355. BoneWeight[] array8 = new BoneWeight[num4];
  356. for (int num8 = 0; num8 < num4; num8++)
  357. {
  358. float num9 = binaryReader.ReadSingle();
  359. float num10 = binaryReader.ReadSingle();
  360. float new_z = binaryReader.ReadSingle();
  361. array5[num8].Set(num9, num10, new_z);
  362. num9 = binaryReader.ReadSingle();
  363. num10 = binaryReader.ReadSingle();
  364. new_z = binaryReader.ReadSingle();
  365. array6[num8].Set(num9, num10, new_z);
  366. num9 = binaryReader.ReadSingle();
  367. num10 = binaryReader.ReadSingle();
  368. array7[num8].Set(num9, num10);
  369. }
  370. mesh2.vertices = array5;
  371. mesh2.normals = array6;
  372. mesh2.uv = array7;
  373. int num11 = binaryReader.ReadInt32();
  374. if (num11 > 0)
  375. {
  376. Vector4[] array9 = new Vector4[num11];
  377. for (int num12 = 0; num12 < num11; num12++)
  378. {
  379. float x4 = binaryReader.ReadSingle();
  380. float y4 = binaryReader.ReadSingle();
  381. float z4 = binaryReader.ReadSingle();
  382. float w2 = binaryReader.ReadSingle();
  383. array9[num12] = new Vector4(x4, y4, z4, w2);
  384. }
  385. mesh2.tangents = array9;
  386. }
  387. for (int num13 = 0; num13 < num4; num13++)
  388. {
  389. array8[num13].boneIndex0 = binaryReader.ReadUInt16();
  390. array8[num13].boneIndex1 = binaryReader.ReadUInt16();
  391. array8[num13].boneIndex2 = binaryReader.ReadUInt16();
  392. array8[num13].boneIndex3 = binaryReader.ReadUInt16();
  393. array8[num13].weight0 = binaryReader.ReadSingle();
  394. array8[num13].weight1 = binaryReader.ReadSingle();
  395. array8[num13].weight2 = binaryReader.ReadSingle();
  396. array8[num13].weight3 = binaryReader.ReadSingle();
  397. }
  398. mesh2.boneWeights = array8;
  399. mesh2.subMeshCount = num5;
  400. for (int num19 = 0; num19 < num5; num19++)
  401. {
  402. int num20 = binaryReader.ReadInt32();
  403. int[] array10 = new int[num20];
  404. for (int num21 = 0; num21 < num20; num21++)
  405. {
  406. array10[num21] = (int)binaryReader.ReadUInt16();
  407. }
  408. mesh2.SetTriangles(array10, num19);
  409. }
  410. int num22 = binaryReader.ReadInt32();
  411. Material[] array11 = new Material[num22];
  412. for (int num23 = 0; num23 < num22; num23++)
  413. {
  414. Material material = ImportCM.ReadMaterial(binaryReader);
  415. array11[num23] = material;
  416. }
  417. skinnedMeshRenderer.materials = array11;
  418. gameObject.AddComponent<Animation>();
  419. return gameObject;
  420. }
  421. }
  422. public static GameObject LoadModel(string menuFile) => LoadModel(new ModItem(menuFile));
  423. public static GameObject LoadModel(ModItem modItem)
  424. {
  425. byte[] menuBuffer = null;
  426. byte[] modMenuBuffer = null;
  427. if (modItem.IsOfficialMod)
  428. {
  429. using (FileStream fileStream = new FileStream(modItem.MenuFile, FileMode.Open))
  430. {
  431. if (fileStream == null || fileStream.Length == 0)
  432. {
  433. Utility.LogWarning($"Could not open mod menu file '{modItem.MenuFile}'");
  434. return null;
  435. }
  436. else
  437. {
  438. modMenuBuffer = new byte[fileStream.Length];
  439. fileStream.Read(modMenuBuffer, 0, (int)fileStream.Length);
  440. }
  441. }
  442. }
  443. string menu = modItem.IsOfficialMod ? modItem.BaseMenuFile : modItem.MenuFile;
  444. using (AFileBase afileBase = GameUty.FileOpen(menu))
  445. {
  446. if (afileBase?.IsValid() != true)
  447. {
  448. Utility.LogWarning($"Could not open menu file '{menu}'");
  449. return null;
  450. }
  451. else if (afileBase.GetSize() == 0)
  452. {
  453. Utility.LogWarning($"Mod menu is empty '{menu}'");
  454. return null;
  455. }
  456. else menuBuffer = afileBase.ReadAll();
  457. }
  458. ModelInfo modelInfo = new ModelInfo();
  459. if (ProcScriptBin(menuBuffer, modelInfo))
  460. {
  461. GameObject gameObject = null;
  462. try
  463. {
  464. gameObject = LoadSkinMesh_R(modelInfo.ModelFile, 1);
  465. }
  466. catch
  467. {
  468. Utility.LogWarning($"Could not load mesh for '{modItem.MenuFile}'");
  469. }
  470. if (gameObject != null)
  471. {
  472. foreach (MaterialChange matChange in modelInfo.MaterialChanges)
  473. {
  474. foreach (Transform transform in gameObject.transform.GetComponentsInChildren<Transform>(true))
  475. {
  476. Renderer renderer = transform.GetComponent<Renderer>();
  477. if (renderer && renderer.material && matChange.MaterialIndex < renderer.materials.Length)
  478. {
  479. renderer.materials[matChange.MaterialIndex] = ImportCM.LoadMaterial(
  480. matChange.MaterialFile, null, renderer.materials[matChange.MaterialIndex]
  481. );
  482. }
  483. }
  484. }
  485. if (modItem.IsOfficialMod) ProcModScriptBin(modMenuBuffer, gameObject);
  486. }
  487. return gameObject;
  488. }
  489. else
  490. {
  491. Utility.LogMessage($"Could not parse menu file '{modItem.MenuFile}'");
  492. return null;
  493. }
  494. }
  495. public static bool ParseNativeMenuFile(int menuIndex, ModItem modItem)
  496. {
  497. MenuDataBase menuDataBase = GameMain.Instance.MenuDataBase;
  498. menuDataBase.SetIndex(menuIndex);
  499. if (menuDataBase.GetBoDelOnly()) return false;
  500. modItem.Category = menuDataBase.GetCategoryMpnText();
  501. if (!accMpn.Contains(modItem.Category)) return false;
  502. modItem.MenuFile = menuDataBase.GetMenuFileName().ToLower();
  503. if (!ValidBG2MenuFile(modItem.MenuFile)) return false;
  504. modItem.Name = menuDataBase.GetMenuName();
  505. modItem.IconFile = menuDataBase.GetIconS();
  506. modItem.Priority = menuDataBase.GetPriority();
  507. return true;
  508. }
  509. public static bool ParseMenuFile(string menuFile, ModItem modItem)
  510. {
  511. if (!ValidBG2MenuFile(menuFile)) return false;
  512. try
  513. {
  514. using (AFileBase afileBase = GameUty.FileOpen(menuFile))
  515. {
  516. if (afileBase?.IsValid() != true || afileBase.GetSize() == 0) return false;
  517. if (menuFileBuffer == null)
  518. {
  519. menuFileBuffer = new byte[Math.Max(500000, afileBase.GetSize())];
  520. }
  521. else if (menuFileBuffer.Length < afileBase.GetSize())
  522. {
  523. menuFileBuffer = new byte[afileBase.GetSize()];
  524. }
  525. afileBase.Read(ref menuFileBuffer, afileBase.GetSize());
  526. }
  527. }
  528. catch
  529. {
  530. Utility.LogError($"Could not read menu file '{menuFile}'");
  531. return false;
  532. }
  533. using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(menuFileBuffer), Encoding.UTF8))
  534. {
  535. string menuHeader = binaryReader.ReadString();
  536. NDebug.Assert(
  537. menuHeader == "CM3D2_MENU", "ProcScriptBin 例外 : ヘッダーファイルが不正です。" + menuHeader
  538. );
  539. binaryReader.ReadInt32(); // file version
  540. binaryReader.ReadString(); // txt path
  541. modItem.Name = binaryReader.ReadString(); // name
  542. binaryReader.ReadString(); // category
  543. binaryReader.ReadString(); // description
  544. binaryReader.ReadInt32(); // idk (as long)
  545. string menuPropString = string.Empty;
  546. string menuPropStringTemp = string.Empty;
  547. try
  548. {
  549. while (true)
  550. {
  551. int numberOfProps = binaryReader.ReadByte();
  552. menuPropStringTemp = menuPropString;
  553. menuPropString = string.Empty;
  554. if (numberOfProps != 0)
  555. {
  556. for (int i = 0; i < numberOfProps; i++)
  557. {
  558. menuPropString = $"{menuPropString}\"{binaryReader.ReadString()}\"";
  559. }
  560. if (menuPropString != string.Empty)
  561. {
  562. string header = UTY.GetStringCom(menuPropString);
  563. string[] menuProps = UTY.GetStringList(menuPropString);
  564. if (header == "end")
  565. {
  566. break;
  567. }
  568. else if (header == "category")
  569. {
  570. modItem.Category = menuProps[1];
  571. if (!accMpn.Contains(modItem.Category)) return false;
  572. }
  573. else if (header == "icons" || header == "icon")
  574. {
  575. modItem.IconFile = menuProps[1];
  576. break;
  577. }
  578. else if (header == "priority")
  579. {
  580. modItem.Priority = float.Parse(menuProps[1]);
  581. }
  582. }
  583. }
  584. else
  585. {
  586. break;
  587. }
  588. }
  589. }
  590. catch (Exception e)
  591. {
  592. Utility.LogWarning($"Could not parse menu file '{menuFile}' because {e.Message}");
  593. return false;
  594. }
  595. }
  596. return true;
  597. }
  598. public static bool ParseModMenuFile(string modMenuFile, ModItem modItem)
  599. {
  600. if (!ValidBG2MenuFile(modMenuFile)) return false;
  601. byte[] buf = null;
  602. try
  603. {
  604. using (FileStream fileStream = new FileStream(modMenuFile, FileMode.Open))
  605. {
  606. if (fileStream == null) return false;
  607. if (fileStream.Length == 0L)
  608. {
  609. Utility.LogError($"Mod menu file '{modMenuFile}' is empty");
  610. return false;
  611. }
  612. buf = new byte[fileStream.Length];
  613. fileStream.Read(buf, 0, (int)fileStream.Length);
  614. }
  615. }
  616. catch (Exception e)
  617. {
  618. Utility.LogError($"Could not read mod menu file '{modMenuFile} because {e.Message}'");
  619. return false;
  620. }
  621. if (buf == null) return false;
  622. using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(buf), Encoding.UTF8))
  623. {
  624. try
  625. {
  626. if (binaryReader.ReadString() != "CM3D2_MOD") return false;
  627. else
  628. {
  629. binaryReader.ReadInt32();
  630. string iconName = binaryReader.ReadString();
  631. string baseItemPath = binaryReader.ReadString().Replace(":", " ");
  632. modItem.BaseMenuFile = Path.GetFileName(baseItemPath);
  633. modItem.Name = binaryReader.ReadString();
  634. modItem.Category = binaryReader.ReadString();
  635. if (!accMpn.Contains(modItem.Category)) return false;
  636. binaryReader.ReadString();
  637. string mpnValue = binaryReader.ReadString();
  638. MPN mpn = MPN.null_mpn;
  639. try
  640. {
  641. mpn = (MPN)Enum.Parse(typeof(MPN), mpnValue, true);
  642. }
  643. catch
  644. {
  645. return false;
  646. }
  647. if (mpn != MPN.null_mpn)
  648. {
  649. binaryReader.ReadString();
  650. }
  651. binaryReader.ReadString();
  652. int size = binaryReader.ReadInt32();
  653. for (int i = 0; i < size; i++)
  654. {
  655. string key = binaryReader.ReadString();
  656. int count = binaryReader.ReadInt32();
  657. byte[] data = binaryReader.ReadBytes(count);
  658. if (string.Equals(key, iconName, StringComparison.InvariantCultureIgnoreCase))
  659. {
  660. Texture2D tex = new Texture2D(1, 1, TextureFormat.RGBA32, false);
  661. tex.LoadImage(data);
  662. modItem.Icon = tex;
  663. break;
  664. }
  665. }
  666. }
  667. }
  668. catch (Exception e)
  669. {
  670. Utility.LogWarning($"Could not parse mod menu file '{modMenuFile}' because {e}");
  671. return false;
  672. }
  673. }
  674. return true;
  675. }
  676. public static bool ValidBG2MenuFile(ModItem modItem)
  677. {
  678. return accMpn.Contains(modItem.Category) && ValidBG2MenuFile(modItem.MenuFile);
  679. }
  680. public static bool ValidBG2MenuFile(string menu)
  681. {
  682. menu = Path.GetFileNameWithoutExtension(menu).ToLower();
  683. return !(menu.EndsWith("_del") || menu.Contains("zurashi") || menu.Contains("mekure")
  684. || menu.Contains("porori") || menu.Contains("moza") || menu.Contains("folder"));
  685. }
  686. public abstract class MenuItem
  687. {
  688. public string IconFile { get; set; }
  689. public Texture2D Icon { get; set; }
  690. }
  691. public class ModItem : MenuItem
  692. {
  693. public string MenuFile { get; set; }
  694. public string BaseMenuFile { get; set; }
  695. public string Name { get; set; }
  696. public string Category { get; set; }
  697. public float Priority { get; set; }
  698. public bool IsMod { get; set; }
  699. public bool IsOfficialMod { get; set; }
  700. public static ModItem OfficialMod(string menuFile) => new ModItem()
  701. {
  702. MenuFile = menuFile,
  703. IsMod = true,
  704. IsOfficialMod = true,
  705. Priority = 1000f
  706. };
  707. public static ModItem Mod(string menuFile) => new ModItem()
  708. {
  709. MenuFile = menuFile,
  710. IsMod = true
  711. };
  712. public ModItem() { }
  713. public ModItem(string menuFile) => MenuFile = menuFile;
  714. public override string ToString()
  715. {
  716. return IsOfficialMod ? $"{Path.GetFileName(MenuFile)}#{BaseMenuFile}" : MenuFile;
  717. }
  718. public static ModItem Deserialize(BinaryReader binaryReader)
  719. {
  720. return new ModItem()
  721. {
  722. MenuFile = binaryReader.ReadNullableString(),
  723. BaseMenuFile = binaryReader.ReadNullableString(),
  724. IconFile = binaryReader.ReadNullableString(),
  725. Name = binaryReader.ReadNullableString(),
  726. Category = binaryReader.ReadNullableString(),
  727. Priority = float.Parse(binaryReader.ReadNullableString()),
  728. IsMod = binaryReader.ReadBoolean(),
  729. IsOfficialMod = binaryReader.ReadBoolean()
  730. };
  731. }
  732. public void Serialize(BinaryWriter binaryWriter)
  733. {
  734. if (IsOfficialMod) return;
  735. binaryWriter.WriteNullableString(MenuFile);
  736. binaryWriter.WriteNullableString(BaseMenuFile);
  737. binaryWriter.WriteNullableString(IconFile);
  738. binaryWriter.WriteNullableString(Name);
  739. binaryWriter.WriteNullableString(Category);
  740. binaryWriter.WriteNullableString(Priority.ToString());
  741. binaryWriter.Write(IsMod);
  742. binaryWriter.Write(IsOfficialMod);
  743. }
  744. }
  745. public class MyRoomItem : MenuItem
  746. {
  747. public int ID { get; set; }
  748. public string PrefabName { get; set; }
  749. public override string ToString() => $"MYR_{ID}#{PrefabName}";
  750. }
  751. private class ModelInfo
  752. {
  753. public List<MaterialChange> MaterialChanges { get; set; } = new List<MaterialChange>();
  754. public string ModelFile { get; set; }
  755. }
  756. private struct MaterialChange
  757. {
  758. public int MaterialIndex { get; }
  759. public string MaterialFile { get; }
  760. public MaterialChange(int materialIndex, string materialFile)
  761. {
  762. MaterialIndex = materialIndex;
  763. MaterialFile = materialFile;
  764. }
  765. }
  766. }
  767. }