ModelUtility.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using UnityEngine;
  7. using UnityEngine.Rendering;
  8. using static MeidoPhotoStudio.Plugin.MenuFileUtility;
  9. using Object = UnityEngine.Object;
  10. namespace MeidoPhotoStudio.Plugin;
  11. public static class ModelUtility
  12. {
  13. private static GameObject deploymentObject;
  14. private enum IMode
  15. {
  16. None,
  17. ItemChange,
  18. TexChange,
  19. }
  20. public static GameObject LoadMyRoomModel(MyRoomItem item)
  21. {
  22. var data = MyRoomCustom.PlacementData.GetData(item.ID);
  23. var gameObject = Object.Instantiate(data.GetPrefab());
  24. if (!gameObject)
  25. {
  26. Utility.LogMessage($"Could not load MyRoomCreative model '{item.PrefabName}'");
  27. return null;
  28. }
  29. var final = new GameObject();
  30. gameObject.transform.SetParent(final.transform, true);
  31. final.transform.SetParent(GetDeploymentObject().transform, false);
  32. return final;
  33. }
  34. public static GameObject LoadBgModel(string bgName)
  35. {
  36. var gameObject = GameMain.Instance.BgMgr.CreateAssetBundle(bgName);
  37. if (!gameObject)
  38. gameObject = Resources.Load<GameObject>("BG/" + bgName);
  39. if (!gameObject)
  40. gameObject = Resources.Load<GameObject>("BG/2_0/" + bgName);
  41. if (!gameObject)
  42. {
  43. Utility.LogMessage($"Could not load BG model '{bgName}'");
  44. return null;
  45. }
  46. var final = Object.Instantiate(gameObject);
  47. final.transform.localScale = Vector3.one * 0.1f;
  48. return final;
  49. }
  50. public static GameObject LoadGameModel(string assetName)
  51. {
  52. var gameObject = GameMain.Instance.BgMgr.CreateAssetBundle(assetName);
  53. if (!gameObject)
  54. gameObject = Resources.Load<GameObject>("Prefab/" + assetName);
  55. if (!gameObject)
  56. gameObject = Resources.Load<GameObject>("BG/" + assetName);
  57. if (!gameObject)
  58. {
  59. Utility.LogMessage($"Could not load game model '{assetName}'");
  60. return null;
  61. }
  62. var final = Object.Instantiate(gameObject);
  63. final.transform.localPosition = Vector3.zero;
  64. var renderers = final.GetComponentsInChildren<Renderer>();
  65. foreach (var renderer in renderers)
  66. if (renderer && renderer.gameObject.name.Contains("castshadow"))
  67. renderer.shadowCastingMode = ShadowCastingMode.Off;
  68. var colliders = final.GetComponentsInChildren<Collider>();
  69. foreach (var collider in colliders)
  70. if (collider)
  71. collider.enabled = false;
  72. if (final.transform.localScale != Vector3.one)
  73. {
  74. var parent = new GameObject();
  75. final.transform.SetParent(parent.transform, true);
  76. final = parent;
  77. }
  78. return final;
  79. }
  80. public static GameObject LoadMenuModel(string menuFile) =>
  81. LoadMenuModel(new ModItem(menuFile));
  82. public static GameObject LoadMenuModel(ModItem modItem)
  83. {
  84. var menu = modItem.IsOfficialMod ? modItem.BaseMenuFile : modItem.MenuFile;
  85. byte[] modelBuffer;
  86. try
  87. {
  88. modelBuffer = ReadAFileBase(menu);
  89. }
  90. catch (Exception e)
  91. {
  92. Utility.LogError($"Could not read menu file '{menu}' because {e.Message}\n{e.StackTrace}");
  93. return null;
  94. }
  95. if (!ProcScriptBin(modelBuffer, out var modelInfo) || !InstantiateModel(modelInfo.ModelFile, out var finalModel))
  96. {
  97. Utility.LogMessage($"Could not load menu model '{modItem.MenuFile}'");
  98. return null;
  99. }
  100. IEnumerable<Renderer> renderers = GetRenderers(finalModel).ToList();
  101. foreach (var matChange in modelInfo.MaterialChanges)
  102. foreach (var renderer in renderers)
  103. if (matChange.MaterialIndex < renderer.materials.Length)
  104. renderer.materials[matChange.MaterialIndex] = ImportCM.LoadMaterial(
  105. matChange.MaterialFile, null, renderer.materials[matChange.MaterialIndex]);
  106. if (!modItem.IsOfficialMod)
  107. return finalModel;
  108. try
  109. {
  110. modelBuffer = ReadOfficialMod(modItem.MenuFile);
  111. }
  112. catch (Exception e)
  113. {
  114. Utility.LogError(
  115. $"Could not read mod menu file '{modItem.MenuFile}' because {e.Message}\n{e.StackTrace}");
  116. return null;
  117. }
  118. ProcModScriptBin(modelBuffer, finalModel);
  119. return finalModel;
  120. }
  121. private static GameObject GetDeploymentObject()
  122. {
  123. if (deploymentObject)
  124. return deploymentObject;
  125. if (!(deploymentObject = GameObject.Find("Deployment Object Parent")))
  126. deploymentObject = new("Deployment Object Parent");
  127. return deploymentObject;
  128. }
  129. private static IEnumerable<Renderer> GetRenderers(GameObject gameObject) =>
  130. gameObject.transform
  131. .GetComponentsInChildren<Transform>(true)
  132. .Select(transform => transform.GetComponent<Renderer>())
  133. .Where(renderer => renderer && renderer.material).ToList();
  134. private static GameObject CreateSeed() =>
  135. Object.Instantiate(Resources.Load<GameObject>("seed"));
  136. private static bool InstantiateModel(string modelFilename, out GameObject modelParent)
  137. {
  138. byte[] buffer;
  139. modelParent = default;
  140. try
  141. {
  142. buffer = ReadAFileBase(modelFilename);
  143. }
  144. catch
  145. {
  146. Utility.LogError($"Could not load model file '{modelFilename}'");
  147. return false;
  148. }
  149. using var binaryReader = new BinaryReader(new MemoryStream(buffer), Encoding.UTF8);
  150. if (binaryReader.ReadString() is not "CM3D2_MESH")
  151. {
  152. Utility.LogError($"{modelFilename} is not a model file");
  153. return false;
  154. }
  155. var modelVersion = binaryReader.ReadInt32();
  156. var modelName = binaryReader.ReadString();
  157. modelParent = CreateSeed();
  158. modelParent.layer = 1;
  159. modelParent.name = "_SM_" + modelName;
  160. var rootName = binaryReader.ReadString();
  161. var boneCount = binaryReader.ReadInt32();
  162. var boneDict = new Dictionary<string, GameObject>();
  163. var boneList = new List<GameObject>(boneCount);
  164. GameObject rootBone = null;
  165. try
  166. {
  167. // read bone data
  168. for (var i = 0; i < boneCount; i++)
  169. {
  170. var bone = CreateSeed();
  171. bone.layer = 1;
  172. bone.name = binaryReader.ReadString();
  173. if (binaryReader.ReadByte() is not 0)
  174. {
  175. var otherBone = CreateSeed();
  176. otherBone.name = bone.name + "_SCL_";
  177. otherBone.transform.parent = bone.transform;
  178. boneDict[bone.name + "$_SCL_"] = otherBone;
  179. }
  180. boneList.Add(bone);
  181. boneDict[bone.name] = bone;
  182. if (bone.name == rootName)
  183. rootBone = bone;
  184. }
  185. for (var i = 0; i < boneCount; i++)
  186. {
  187. var parentIndex = binaryReader.ReadInt32();
  188. boneList[i].transform.parent = parentIndex >= 0
  189. ? boneList[parentIndex].transform
  190. : modelParent.transform;
  191. }
  192. for (var i = 0; i < boneCount; i++)
  193. {
  194. var transform = boneList[i].transform;
  195. transform.localPosition = binaryReader.ReadVector3();
  196. transform.localRotation = binaryReader.ReadQuaternion();
  197. if (modelVersion >= 2001 && binaryReader.ReadBoolean())
  198. transform.localScale = binaryReader.ReadVector3();
  199. }
  200. // read mesh data
  201. var meshRenderer = rootBone.AddComponent<SkinnedMeshRenderer>();
  202. meshRenderer.updateWhenOffscreen = true;
  203. meshRenderer.skinnedMotionVectors = false;
  204. meshRenderer.lightProbeUsage = LightProbeUsage.Off;
  205. meshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
  206. meshRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
  207. var sharedMesh = meshRenderer.sharedMesh = new();
  208. var vertCount = binaryReader.ReadInt32();
  209. var subMeshCount = binaryReader.ReadInt32();
  210. var meshBoneCount = binaryReader.ReadInt32();
  211. var meshBones = new Transform[meshBoneCount];
  212. for (var i = 0; i < meshBoneCount; i++)
  213. {
  214. var boneName = binaryReader.ReadString();
  215. if (!boneDict.ContainsKey(boneName))
  216. {
  217. Debug.LogError("nullbone= " + boneName);
  218. }
  219. else
  220. {
  221. var keyName = boneName + "$_SCL_";
  222. var bone = boneDict.ContainsKey(keyName) ? boneDict[keyName] : boneDict[boneName];
  223. meshBones[i] = bone.transform;
  224. }
  225. }
  226. meshRenderer.bones = meshBones;
  227. var bindPoses = new Matrix4x4[meshBoneCount];
  228. for (var i = 0; i < meshBoneCount; i++)
  229. bindPoses[i] = binaryReader.ReadMatrix4x4();
  230. sharedMesh.bindposes = bindPoses;
  231. var vertices = new Vector3[vertCount];
  232. var normals = new Vector3[vertCount];
  233. var uv = new Vector2[vertCount];
  234. for (var i = 0; i < vertCount; i++)
  235. {
  236. vertices[i] = binaryReader.ReadVector3();
  237. normals[i] = binaryReader.ReadVector3();
  238. uv[i] = binaryReader.ReadVector2();
  239. }
  240. sharedMesh.vertices = vertices;
  241. sharedMesh.normals = normals;
  242. sharedMesh.uv = uv;
  243. var tangentCount = binaryReader.ReadInt32();
  244. if (tangentCount > 0)
  245. {
  246. var tangents = new Vector4[tangentCount];
  247. for (var i = 0; i < tangentCount; i++)
  248. tangents[i] = binaryReader.ReadVector4();
  249. sharedMesh.tangents = tangents;
  250. }
  251. var boneWeights = new BoneWeight[vertCount];
  252. for (var i = 0; i < vertCount; i++)
  253. {
  254. boneWeights[i].boneIndex0 = binaryReader.ReadUInt16();
  255. boneWeights[i].boneIndex1 = binaryReader.ReadUInt16();
  256. boneWeights[i].boneIndex2 = binaryReader.ReadUInt16();
  257. boneWeights[i].boneIndex3 = binaryReader.ReadUInt16();
  258. boneWeights[i].weight0 = binaryReader.ReadSingle();
  259. boneWeights[i].weight1 = binaryReader.ReadSingle();
  260. boneWeights[i].weight2 = binaryReader.ReadSingle();
  261. boneWeights[i].weight3 = binaryReader.ReadSingle();
  262. }
  263. sharedMesh.boneWeights = boneWeights;
  264. sharedMesh.subMeshCount = subMeshCount;
  265. for (var i = 0; i < subMeshCount; i++)
  266. {
  267. var pointCount = binaryReader.ReadInt32();
  268. var triangles = new int[pointCount];
  269. for (var j = 0; j < pointCount; j++)
  270. triangles[j] = binaryReader.ReadUInt16();
  271. sharedMesh.SetTriangles(triangles, i);
  272. }
  273. // read materials
  274. var materialCount = binaryReader.ReadInt32();
  275. var materials = new Material[materialCount];
  276. for (var i = 0; i < materialCount; i++)
  277. materials[i] = ImportCM.ReadMaterial(binaryReader);
  278. meshRenderer.materials = materials;
  279. modelParent.AddComponent<Animation>();
  280. return true;
  281. }
  282. catch (Exception e)
  283. {
  284. Utility.LogError($"Could not load mesh for '{modelFilename}' because {e.Message}\n{e.StackTrace}");
  285. foreach (var bone in boneList.Where(bone => bone))
  286. Object.Destroy(bone);
  287. if (modelParent)
  288. Object.Destroy(modelParent);
  289. modelParent = null;
  290. return false;
  291. }
  292. }
  293. private static bool ProcScriptBin(byte[] menuBuf, out ModelInfo modelInfo)
  294. {
  295. modelInfo = null;
  296. using var binaryReader = new BinaryReader(new MemoryStream(menuBuf), Encoding.UTF8);
  297. if (binaryReader.ReadString() is not "CM3D2_MENU")
  298. return false;
  299. modelInfo = new();
  300. binaryReader.ReadInt32(); // file version
  301. binaryReader.ReadString(); // txt path
  302. binaryReader.ReadString(); // name
  303. binaryReader.ReadString(); // category
  304. binaryReader.ReadString(); // description
  305. binaryReader.ReadInt32(); // idk (as long)
  306. try
  307. {
  308. while (true)
  309. {
  310. var numberOfProps = binaryReader.ReadByte();
  311. var menuPropString = string.Empty;
  312. if (numberOfProps is 0)
  313. break;
  314. for (var i = 0; i < numberOfProps; i++)
  315. menuPropString = $"{menuPropString}\"{binaryReader.ReadString()}\"";
  316. if (menuPropString == string.Empty)
  317. continue;
  318. var header = UTY.GetStringCom(menuPropString);
  319. var menuProps = UTY.GetStringList(menuPropString);
  320. if (header is "end")
  321. {
  322. break;
  323. }
  324. else if (header is "マテリアル変更")
  325. {
  326. var matNo = int.Parse(menuProps[2]);
  327. var materialFile = menuProps[3];
  328. modelInfo.MaterialChanges.Add(new MaterialChange(matNo, materialFile));
  329. }
  330. else if (header is "additem")
  331. {
  332. modelInfo.ModelFile = menuProps[1];
  333. }
  334. }
  335. }
  336. catch
  337. {
  338. return false;
  339. }
  340. return true;
  341. }
  342. private static void ProcModScriptBin(byte[] cd, GameObject go)
  343. {
  344. var matDict = new Dictionary<string, byte[]>();
  345. string modData;
  346. using (var binaryReader = new BinaryReader(new MemoryStream(cd), Encoding.UTF8))
  347. {
  348. if (binaryReader.ReadString() is not "CM3D2_MOD")
  349. return;
  350. binaryReader.ReadInt32();
  351. binaryReader.ReadString();
  352. binaryReader.ReadString();
  353. binaryReader.ReadString();
  354. binaryReader.ReadString();
  355. binaryReader.ReadString();
  356. var serializedMpn = binaryReader.ReadString();
  357. var mpn = MPN.null_mpn;
  358. try
  359. {
  360. mpn = (MPN)Enum.Parse(typeof(MPN), serializedMpn, true);
  361. }
  362. catch
  363. {
  364. // Ignored.
  365. }
  366. if (mpn is not MPN.null_mpn)
  367. binaryReader.ReadString();
  368. modData = binaryReader.ReadString();
  369. var entryCount = binaryReader.ReadInt32();
  370. for (var i = 0; i < entryCount; i++)
  371. {
  372. var key = binaryReader.ReadString();
  373. var count = binaryReader.ReadInt32();
  374. var value = binaryReader.ReadBytes(count);
  375. matDict.Add(key, value);
  376. }
  377. }
  378. var mode = IMode.None;
  379. var materialChange = false;
  380. Material material = null;
  381. var materialIndex = 0;
  382. using var stringReader = new StringReader(modData);
  383. string line;
  384. List<Renderer> renderers = null;
  385. while ((line = stringReader.ReadLine()) is not null)
  386. {
  387. var data = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
  388. var header = data[0];
  389. if (header is "アイテム変更" or "マテリアル変更")
  390. mode = IMode.ItemChange;
  391. else if (header is "テクスチャ変更")
  392. mode = IMode.TexChange;
  393. if (mode is IMode.ItemChange)
  394. {
  395. if (header is "スロット名")
  396. materialChange = true;
  397. if (!materialChange)
  398. continue;
  399. if (header is "マテリアル番号")
  400. {
  401. materialIndex = int.Parse(data[1]);
  402. renderers ??= GetRenderers(go).ToList();
  403. foreach (var renderer in renderers)
  404. if (materialIndex < renderer.materials.Length)
  405. material = renderer.materials[materialIndex];
  406. }
  407. if (!material)
  408. continue;
  409. if (header is "テクスチャ設定")
  410. ChangeTex(materialIndex, data[1], data[2].ToLower());
  411. else if (header is "色設定")
  412. material.SetColor(
  413. data[1],
  414. new(
  415. float.Parse(data[2]) / 255f,
  416. float.Parse(data[3]) / 255f,
  417. float.Parse(data[4]) / 255f,
  418. float.Parse(data[5]) / 255f));
  419. else if (header is "数値設定")
  420. material.SetFloat(data[1], float.Parse(data[2]));
  421. }
  422. else if (mode is IMode.TexChange)
  423. {
  424. var matno = int.Parse(data[2]);
  425. ChangeTex(matno, data[3], data[4].ToLower());
  426. }
  427. else if (mode is IMode.None)
  428. {
  429. continue;
  430. }
  431. else
  432. {
  433. throw new ArgumentOutOfRangeException(nameof(mode));
  434. }
  435. }
  436. void ChangeTex(int matno, string prop, string filename)
  437. {
  438. var buf = matDict[filename.ToLowerInvariant()];
  439. var textureResource = new TextureResource(2, 2, TextureFormat.ARGB32, null, buf);
  440. renderers ??= GetRenderers(go).ToList();
  441. foreach (var r in renderers)
  442. {
  443. r.materials[matno].SetTexture(prop, null);
  444. var texture2D = textureResource.CreateTexture2D();
  445. texture2D.name = filename;
  446. r.materials[matno].SetTexture(prop, texture2D);
  447. }
  448. }
  449. }
  450. private readonly struct MaterialChange
  451. {
  452. public MaterialChange(int materialIndex, string materialFile)
  453. {
  454. MaterialIndex = materialIndex;
  455. MaterialFile = materialFile;
  456. }
  457. public int MaterialIndex { get; }
  458. public string MaterialFile { get; }
  459. }
  460. private class ModelInfo
  461. {
  462. public List<MaterialChange> MaterialChanges { get; } = new();
  463. public string ModelFile { get; set; }
  464. }
  465. }