ModelUtility.cs 21 KB

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