Program.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Ionic.Zlib;
  6. using ExIni;
  7. using BepInEx;
  8. using UnityEngine;
  9. using UnityEngine.SceneManagement;
  10. using MyRoomCustom;
  11. namespace COM3D2.MeidoPhotoStudio.Converter
  12. {
  13. [BepInPlugin(pluginGuid, pluginName, pluginVersion)]
  14. public class SceneConverter : BaseUnityPlugin
  15. {
  16. private const string pluginGuid = "com.habeebweeb.com3d2.meidophotostudio.converter";
  17. public const string pluginName = "MeidoPhotoStudio Converter";
  18. public const string pluginVersion = "0.0.0";
  19. private readonly byte[] noThumb = {
  20. 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00,
  21. 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x08, 0x02, 0x00, 0x00, 0x00, 0x91, 0x5D, 0x1F, 0xE6, 0x00, 0x00, 0x00,
  22. 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41,
  23. 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
  24. 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x01, 0x4E, 0x49, 0x44, 0x41,
  25. 0x54, 0x58, 0x47, 0xDD, 0xD2, 0x5B, 0x8E, 0x83, 0x30, 0x10, 0x44, 0xD1, 0x2C, 0x84, 0x4F, 0xF6, 0xBF, 0xB3,
  26. 0xAC, 0x21, 0xA9, 0xA8, 0x90, 0x01, 0x63, 0xEC, 0x7E, 0xD9, 0x46, 0xB9, 0xCA, 0xCF, 0x44, 0x72, 0xD7, 0x89,
  27. 0x34, 0xAF, 0xF7, 0xB2, 0x3E, 0xF0, 0xB3, 0xB1, 0x3E, 0x8F, 0x69, 0x67, 0xF1, 0x0F, 0x7E, 0x3B, 0xB7, 0x84,
  28. 0xD9, 0x58, 0xE9, 0xAB, 0x89, 0x1D, 0x25, 0x3B, 0x0B, 0x4D, 0x94, 0x65, 0x8C, 0x13, 0x0B, 0x4D, 0x91, 0x5D,
  29. 0x0D, 0x39, 0x0B, 0x0D, 0x96, 0x15, 0x01, 0x05, 0x16, 0x1A, 0x26, 0xBB, 0x5B, 0x2F, 0xB3, 0xD0, 0x00, 0x59,
  30. 0x65, 0xFA, 0x96, 0x85, 0xBA, 0xCA, 0xEA, 0xBB, 0x35, 0x16, 0xEA, 0x24, 0x6B, 0x8E, 0x36, 0x58, 0x28, 0x5C,
  31. 0x26, 0x59, 0x6C, 0xB3, 0x50, 0xA0, 0x4C, 0x38, 0x27, 0x62, 0xA1, 0x10, 0x99, 0x7C, 0x4B, 0xCA, 0x42, 0x4E,
  32. 0x99, 0x6A, 0x48, 0xC1, 0x42, 0x66, 0x99, 0x76, 0x45, 0xC7, 0x42, 0x06, 0x99, 0x61, 0x42, 0xCD, 0x42, 0xAA,
  33. 0x27, 0xB6, 0xFB, 0x16, 0x16, 0x12, 0xBE, 0x32, 0x1F, 0x37, 0xB2, 0x50, 0xF3, 0xA1, 0xE7, 0xB2, 0x9D, 0x85,
  34. 0x2A, 0x6F, 0x9D, 0x67, 0x5D, 0x2C, 0x54, 0x7C, 0xEE, 0xBF, 0xE9, 0x65, 0xA1, 0xEC, 0x42, 0xC8, 0xC1, 0x00,
  35. 0x16, 0x4A, 0x47, 0xA2, 0xAE, 0xC5, 0xB0, 0x10, 0xEE, 0x04, 0x9E, 0xFA, 0x6B, 0x56, 0x3A, 0x12, 0x75, 0xED,
  36. 0x4F, 0xFF, 0xE5, 0x8B, 0xCF, 0xFD, 0x37, 0x5D, 0xAC, 0xCA, 0x5B, 0xE7, 0x59, 0x3B, 0xAB, 0xF9, 0xD0, 0x73,
  37. 0xD9, 0xC8, 0x12, 0xBE, 0x32, 0x1F, 0xB7, 0xB0, 0x54, 0x4F, 0x6C, 0xF7, 0xD5, 0xAC, 0xDE, 0x3F, 0x03, 0xA9,
  38. 0x59, 0x06, 0x13, 0xD3, 0xAE, 0x28, 0x58, 0x66, 0x13, 0x53, 0x0D, 0x49, 0x59, 0x4E, 0x13, 0x93, 0x6F, 0x89,
  39. 0x58, 0x21, 0x26, 0x26, 0x9C, 0x6B, 0xB3, 0x02, 0x4D, 0x4C, 0xB2, 0xD8, 0x60, 0x85, 0x9B, 0x58, 0x73, 0xB4,
  40. 0xC6, 0xEA, 0x64, 0x62, 0xF5, 0xDD, 0x5B, 0x56, 0x57, 0x13, 0xAB, 0x4C, 0x97, 0x59, 0x03, 0x4C, 0xEC, 0x6E,
  41. 0xBD, 0xC0, 0x1A, 0x66, 0x62, 0x45, 0x40, 0xCE, 0x1A, 0x6C, 0x62, 0x57, 0xC3, 0x89, 0x35, 0xC5, 0xC4, 0x32,
  42. 0xC6, 0xCE, 0x9A, 0x68, 0x62, 0x47, 0xC9, 0xC6, 0x9A, 0x6E, 0x62, 0x09, 0xF3, 0x63, 0x3D, 0xC4, 0xC4, 0xE8,
  43. 0xD9, 0x58, 0xCF, 0xFA, 0x2C, 0xEB, 0x17, 0xC3, 0x4F, 0x1F, 0x0A, 0xB7, 0x49, 0x8A, 0x9E, 0x00, 0x00, 0x00,
  44. 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
  45. };
  46. private const string converterDirectoryName = "Converter";
  47. private static Dictionary<string, PlacementData.Data> myrAssetNameToData
  48. = new Dictionary<string, PlacementData.Data>(StringComparer.InvariantCultureIgnoreCase);
  49. private static readonly string[] faceKeys = {
  50. "eyeclose", "eyeclose2", "eyeclose3", "eyeclose6", "hitomih", "hitomis", "mayuha",
  51. "mayuup", "mayuv", "mayuvhalf", "moutha", "mouths", "mouthdw", "mouthup", "tangout",
  52. "tangup", "eyebig", "eyeclose5", "mayuw", "mouthhe", "mouthc", "mouthi", "mouthuphalf",
  53. "tangopen",
  54. "namida", "tear1", "tear2", "tear3", "shock", "yodare", "hoho", "hoho2", "hohos", "hohol",
  55. "toothoff", "nosefook"
  56. };
  57. private static readonly string[] mpnAttachProps = {
  58. /* "", "", "", "", "", "", "", "", "", */
  59. "kousokuu_tekaseone_i_.menu", "kousokuu_tekasetwo_i_.menu", "kousokul_ashikaseup_i_.menu",
  60. "kousokuu_tekasetwo_i_.menu", "kousokul_ashikasedown_i_.menu", "kousokuu_tekasetwodown_i_.menu",
  61. "kousokuu_ushirode_i_.menu", "kousokuu_smroom_haritsuke_i_.menu"
  62. };
  63. private static readonly int[] bodyRotations =
  64. {
  65. 71, 44, 40, 41, 42, 43, 57, 68, 69, 46, 49, 47, 50, 52, 55, 53, 56, 92, 94, 93, 95, 45, 48, 51, 54
  66. };
  67. public const int sceneVersion = 1000;
  68. public const int meidoDataVersion = 1000;
  69. public const int propDataVersion = 1000;
  70. public const int kankyoMagic = -765;
  71. private static BepInEx.Logging.ManualLogSource Log;
  72. private static readonly int faceToggleIndex = Array.IndexOf(faceKeys, "tangopen") + 1;
  73. private static string configPath = Path.Combine(Paths.ConfigPath, converterDirectoryName);
  74. private bool active = false;
  75. private Rect windowRect = new Rect(30f, 30f, 300f, 200f);
  76. private void Awake()
  77. {
  78. DontDestroyOnLoad(this);
  79. if (!Directory.Exists(configPath)) Directory.CreateDirectory(configPath);
  80. Log = Logger;
  81. }
  82. private void Start()
  83. {
  84. UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
  85. List<PlacementData.Data> dataList = PlacementData.GetAllDatas(false);
  86. foreach (var data in dataList)
  87. {
  88. string assetName = string.IsNullOrEmpty(data.assetName) ? data.resourceName : data.assetName;
  89. myrAssetNameToData[assetName] = data;
  90. }
  91. }
  92. private void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
  93. {
  94. int index = scene.buildIndex;
  95. active = index == 9 || index == 3;
  96. }
  97. private void OnGUI()
  98. {
  99. if (active)
  100. {
  101. windowRect.width = 300f;
  102. windowRect.height = 200f;
  103. windowRect.x = UnityEngine.Mathf.Clamp(windowRect.x, 0, Screen.width - windowRect.width);
  104. windowRect.y = UnityEngine.Mathf.Clamp(windowRect.y, 0, Screen.height - windowRect.height);
  105. windowRect = GUI.Window(0xEA4040, windowRect, GUIFunc, "MeidoPhotoStudio Converter");
  106. }
  107. }
  108. private void GUIFunc(int id)
  109. {
  110. if (GUILayout.Button("Convert ModifiedMM")) ProcessModifedMM();
  111. if (GUILayout.Button("Convert ModifiedMM (Scene Manager)")) ProcessModifiedMMPng();
  112. GUILayout.Space(30f);
  113. if (GUILayout.Button("Convert modifedMM quickSave")) ProcessQuickSave();
  114. // GUILayout.Button("Convert MultipleMaids");
  115. }
  116. private void ProcessModifiedMMPng()
  117. {
  118. string modPath = Path.Combine(Paths.GameRootPath, "Mod");
  119. string scenePath = Path.Combine(modPath, "MultipleMaidsScene");
  120. string kankyoPath = Path.Combine(modPath, "MultipleMaidsScene");
  121. }
  122. private string GetModifiedMMSceneData(string pngPath)
  123. {
  124. return String.Empty;
  125. }
  126. private void ProcessQuickSave()
  127. {
  128. string sybarisPath = Path.Combine(Paths.GameRootPath, "Sybaris");
  129. string iniPath = BepInEx.Utility.CombinePaths(sybarisPath, "UnityInjector", "Config", "MultipleMaids.ini");
  130. IniFile mmIniFile = IniFile.FromFile(iniPath);
  131. IniSection sceneSection = mmIniFile.GetSection("scene");
  132. if (sceneSection != null)
  133. {
  134. if (sceneSection.HasKey("s9999"))
  135. {
  136. string sceneData = sceneSection["s9999"].Value;
  137. if (!string.IsNullOrEmpty(sceneData))
  138. {
  139. byte[] convertedSceneData = ProcessScene(sceneData, false);
  140. string path = Path.Combine(configPath, $"mmtempscene_{GetMMDateString(sceneData)}.png");
  141. SaveSceneToFile(path, convertedSceneData, noThumb);
  142. }
  143. }
  144. }
  145. }
  146. private void ProcessModifedMM()
  147. {
  148. string sybarisPath = Path.Combine(Paths.GameRootPath, "Sybaris");
  149. string iniPath = BepInEx.Utility.CombinePaths(sybarisPath, "UnityInjector", "Config", "MultipleMaids.ini");
  150. IniFile mmIniFile = IniFile.FromFile(iniPath);
  151. IniSection sceneSection = mmIniFile.GetSection("scene");
  152. if (sceneSection != null)
  153. {
  154. foreach (IniKey key in sceneSection.Keys)
  155. {
  156. if (key.Key.StartsWith("ss")) continue;
  157. int sceneIndex = int.Parse(key.Key.Substring(1));
  158. bool kankyo = sceneIndex >= 10000;
  159. string sceneData = key.Value;
  160. if (!string.IsNullOrEmpty(sceneData))
  161. {
  162. byte[] convertedSceneData = ProcessScene(sceneData, kankyo);
  163. string prefix = kankyo
  164. ? "mmkankyo"
  165. : sceneIndex == 9999
  166. ? "mmtempscene" : $"mmscene{sceneIndex}";
  167. string path = Path.Combine(configPath, $"{prefix}_{GetMMDateString(sceneData)}.png");
  168. byte[] thumbnail = noThumb;
  169. string screenshotKey = $"s{key.Key}";
  170. if (sceneSection.HasKey(screenshotKey))
  171. {
  172. string screenshotBase64 = sceneSection[screenshotKey].Value;
  173. if (!string.IsNullOrEmpty(screenshotBase64))
  174. {
  175. thumbnail = Convert.FromBase64String(screenshotBase64);
  176. }
  177. }
  178. SaveSceneToFile(path, convertedSceneData, thumbnail);
  179. }
  180. }
  181. }
  182. }
  183. public static void SaveSceneToFile(string path, byte[] sceneData, byte[] thumbnailData)
  184. {
  185. using (FileStream fileStream = File.Create(path))
  186. {
  187. fileStream.Write(thumbnailData, 0, thumbnailData.Length);
  188. fileStream.Write(sceneData, 0, sceneData.Length);
  189. }
  190. }
  191. public static string GetMMDateString(string sceneData)
  192. {
  193. string dateString = sceneData.Split('_')[0].Split(',')[0];
  194. DateTime date = DateTime.Parse(dateString);
  195. return $"{date:yyyyMMddHHmm}";
  196. }
  197. public static byte[] ProcessScene(string sceneData, bool kankyo)
  198. {
  199. string[] strArray1 = sceneData.Split('_');
  200. string[] strArray2 = strArray1[1].Split(';');
  201. string[] strArray3 = strArray1[0].Split(',');
  202. string[] strArray4 = null;
  203. string[] strArray5 = null;
  204. string[] strArray6 = null;
  205. string[] strArray7 = null;
  206. if (strArray1.Length >= 5)
  207. {
  208. strArray4 = strArray1[2].Split(',');
  209. strArray5 = strArray1[3].Split(';');
  210. strArray6 = strArray1[4].Split(';');
  211. }
  212. if (strArray1.Length >= 6)
  213. {
  214. strArray7 = strArray1[5].Split(';');
  215. }
  216. using (MemoryStream memoryStream = new MemoryStream())
  217. using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
  218. using (BinaryWriter binaryWriter = new BinaryWriter(deflateStream, System.Text.Encoding.UTF8))
  219. {
  220. binaryWriter.Write("MPS_SCENE");
  221. binaryWriter.Write(sceneVersion);
  222. binaryWriter.Write(kankyo ? kankyoMagic : int.Parse(strArray3[1]));
  223. SerializeEnvironment(strArray3, binaryWriter, kankyo);
  224. SerializeLights(strArray3, strArray4, strArray5, strArray7, binaryWriter);
  225. SerializeEffect(strArray4, binaryWriter);
  226. SerializeProp(strArray3, strArray6, binaryWriter);
  227. if (!kankyo)
  228. {
  229. SerializeMessage(strArray3, binaryWriter);
  230. SerializeMaid(strArray2, binaryWriter);
  231. }
  232. binaryWriter.Write("END");
  233. deflateStream.Close();
  234. return memoryStream.ToArray();
  235. }
  236. }
  237. private static void SerializeMaid(string[] strArray2, BinaryWriter binaryWriter)
  238. {
  239. binaryWriter.Write("MEIDO");
  240. // MM scene converted to MPS
  241. binaryWriter.Write(true);
  242. binaryWriter.Write(meidoDataVersion);
  243. int numberOfMaids = strArray2.Length;
  244. binaryWriter.Write(numberOfMaids);
  245. /*
  246. TODO: Investigate why serialized maid data may only have 64 items.
  247. https://git.coder.horse/meidomustard/modifiedMM/src/master/MultipleMaids/CM3D2/MultipleMaids/Plugin/MultipleMaids.Update.cs#L3745
  248. The difference affects whether or not rotations are local or world.
  249. Certain body rotations would be missing as well particularly the toes.
  250. Other data like free look and attached items like hand/vag/anl would be missing.
  251. */
  252. for (int i = 0; i < numberOfMaids; i++)
  253. {
  254. using (MemoryStream memoryStream = new MemoryStream())
  255. using (BinaryWriter tempWriter = new BinaryWriter(memoryStream))
  256. {
  257. string[] maidData = strArray2[i].Split(':');
  258. tempWriter.WriteVector3(Utility.Vector3String(maidData[59])); // position
  259. tempWriter.WriteQuaternion(Utility.EulerString(maidData[58])); // rotation
  260. tempWriter.WriteVector3(Utility.Vector3String(maidData[60])); // scale
  261. // fingers
  262. for (int j = 0; j < 40; j++)
  263. {
  264. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  265. }
  266. // toes
  267. for (int k = 0; k < 2; k++)
  268. {
  269. for (int j = 72 + k; j < 90; j += 2)
  270. {
  271. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  272. }
  273. }
  274. // the rest of the limbs
  275. foreach (int j in bodyRotations)
  276. {
  277. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  278. }
  279. tempWriter.WriteVector3(Utility.Vector3String(maidData[96])); // hip position
  280. // cached pose stuff
  281. tempWriter.Write("normal");
  282. tempWriter.Write("pose_taiki_f");
  283. tempWriter.Write(false);
  284. // eye rotation delta
  285. // MM saves the rotations directly so just save the identity
  286. tempWriter.WriteQuaternion(Quaternion.identity);
  287. tempWriter.WriteQuaternion(Quaternion.identity);
  288. string[] freeLookData = maidData[64].Split(',');
  289. bool isFreeLook = int.Parse(freeLookData[0]) == 1;
  290. tempWriter.Write(isFreeLook);
  291. if (isFreeLook)
  292. {
  293. tempWriter.WriteVector3(new Vector3(
  294. float.Parse(freeLookData[2]), 1f, float.Parse(freeLookData[1])
  295. ));
  296. }
  297. string[] faceValues = maidData[63].Split(',');
  298. tempWriter.Write("MPS_FACE");
  299. for (int j = 0; j < faceKeys.Length - 2; j++)
  300. {
  301. tempWriter.Write(faceKeys[j]);
  302. if (j >= faceToggleIndex) tempWriter.Write(float.Parse(faceValues[j]) > 0f);
  303. else tempWriter.Write(float.Parse(faceValues[j]));
  304. }
  305. if (faceValues.Length > 65)
  306. {
  307. tempWriter.Write(faceKeys[faceKeys.Length - 1]);
  308. tempWriter.Write(float.Parse(faceValues[faceValues.Length - 1]) > 0f);
  309. }
  310. tempWriter.Write("END_FACE");
  311. tempWriter.Write(true); // body visible
  312. // MM does not serialize clothing
  313. for (int j = 0; j < 29; j++) tempWriter.Write(true);
  314. // MM does not serialize curling
  315. tempWriter.Write(false);
  316. tempWriter.Write(false);
  317. tempWriter.Write(false);
  318. string kousokuUpperMenu = string.Empty;
  319. string kousokuLowerMenu = string.Empty;
  320. int mpnIndex = int.Parse(maidData[65].Split(',')[0]);
  321. // MM can attach accvag, accanl and handitem stuff as well as kousoku_upper/lower
  322. // MPS attach prop is preferred for non kousoku_upper/lower props because unlike kousoku_upper/lower
  323. // props, accvag etc. props attach only to a single place.
  324. if (mpnIndex >= 9 && mpnIndex <= 16)
  325. {
  326. int actualIndex = mpnIndex - 9;
  327. if (mpnIndex == 12)
  328. {
  329. kousokuUpperMenu = mpnAttachProps[actualIndex];
  330. kousokuLowerMenu = mpnAttachProps[actualIndex - 1];
  331. }
  332. else if (mpnIndex == 13)
  333. {
  334. kousokuUpperMenu = mpnAttachProps[actualIndex + 1];
  335. kousokuLowerMenu = mpnAttachProps[actualIndex];
  336. }
  337. else
  338. {
  339. if (mpnIndex > 13) actualIndex += 1;
  340. string kousokuMenu = mpnAttachProps[actualIndex];
  341. if (mpnAttachProps[actualIndex][7] == 'u') kousokuUpperMenu = kousokuMenu;
  342. else kousokuLowerMenu = kousokuMenu;
  343. }
  344. }
  345. bool kousokuUpper = !string.IsNullOrEmpty(kousokuUpperMenu);
  346. tempWriter.Write(kousokuUpper);
  347. if (kousokuUpper) tempWriter.Write(kousokuUpperMenu);
  348. bool kousokuLower = !string.IsNullOrEmpty(kousokuLowerMenu);
  349. tempWriter.Write(kousokuLower);
  350. if (kousokuLower) tempWriter.Write(kousokuLowerMenu);
  351. binaryWriter.Write(memoryStream.Length);
  352. binaryWriter.Write(memoryStream.ToArray());
  353. }
  354. }
  355. }
  356. private static void SerializeProp(string[] strArray3, string[] strArray6, BinaryWriter binaryWriter)
  357. {
  358. binaryWriter.Write("PROP");
  359. binaryWriter.Write(propDataVersion);
  360. bool hasWProp = strArray3.Length > 37 && !string.IsNullOrEmpty(strArray3[37]);
  361. int numberOfProps = hasWProp ? 1 : 0;
  362. numberOfProps += strArray6 == null ? 0 : strArray6.Length - 1;
  363. binaryWriter.Write(numberOfProps);
  364. if (hasWProp)
  365. {
  366. // For the prop that spawns when you push (shift +) W
  367. binaryWriter.WriteVector3(new Vector3(
  368. float.Parse(strArray3[41]), float.Parse(strArray3[42]), float.Parse(strArray3[43])
  369. ));
  370. binaryWriter.WriteQuaternion(Quaternion.Euler(
  371. float.Parse(strArray3[38]), float.Parse(strArray3[39]), float.Parse(strArray3[40])
  372. ));
  373. binaryWriter.WriteVector3(new Vector3(
  374. float.Parse(strArray3[44]), float.Parse(strArray3[45]), float.Parse(strArray3[46])
  375. ));
  376. SerializeAttachPoint(binaryWriter);
  377. binaryWriter.Write(false); // shadow casting
  378. binaryWriter.Write(strArray3[37].Replace(' ', '_'));
  379. }
  380. if (strArray6 != null)
  381. {
  382. for (int i = 0; i < strArray6.Length - 1; i++)
  383. {
  384. string[] assetParts = strArray6[i].Split(',');
  385. string assetName = assetParts[0].Replace(' ', '_');
  386. bool shadowCasting = assetName.EndsWith(".menu") ? true : false;
  387. if (assetName.StartsWith("creative_"))
  388. {
  389. // modifiedMM my room creative prop
  390. // modifiedMM serializes the prefabName rather than the ID.
  391. assetName = assetName.Replace("creative_", String.Empty);
  392. assetName = $"MYR_{myrAssetNameToData[assetName].ID}#{assetName}";
  393. }
  394. else if (assetName.StartsWith("MYR_"))
  395. {
  396. // MM 23.0+ my room creative prop
  397. int assetID = int.Parse(assetName.Replace("MYR_", string.Empty));
  398. PlacementData.Data data = PlacementData.GetData(assetID);
  399. string asset = string.IsNullOrEmpty(data.assetName) ? data.resourceName : data.assetName;
  400. assetName = $"{assetName}#{asset}";
  401. }
  402. else if (assetName.Contains('#'))
  403. {
  404. if (assetName.Contains(".menu"))
  405. {
  406. // modifiedMM official mod prop
  407. string[] modComponents = assetParts[0].Split('#');
  408. string baseMenuFile = modComponents[0].Replace(' ', '_');
  409. string modItem = modComponents[1].Replace(' ', '_');
  410. assetName = $"{modItem}#{baseMenuFile}";
  411. }
  412. else
  413. {
  414. assetName = assetName.Split('#')[1].Replace(' ', '_');
  415. }
  416. }
  417. else if (assetName.StartsWith("BGOdogu", StringComparison.InvariantCultureIgnoreCase))
  418. {
  419. // I don't know why multiplemaids even prepends BG
  420. assetName = assetName.Substring(2);
  421. }
  422. binaryWriter.WriteVector3(new Vector3(
  423. float.Parse(assetParts[4]), float.Parse(assetParts[5]), float.Parse(assetParts[6])
  424. ));
  425. binaryWriter.WriteQuaternion(Quaternion.Euler(
  426. float.Parse(assetParts[1]), float.Parse(assetParts[2]), float.Parse(assetParts[3])
  427. ));
  428. binaryWriter.WriteVector3(new Vector3(
  429. float.Parse(assetParts[7]), float.Parse(assetParts[8]), float.Parse(assetParts[9])
  430. ));
  431. SerializeAttachPoint(binaryWriter);
  432. binaryWriter.Write(shadowCasting);
  433. binaryWriter.Write(assetName);
  434. }
  435. }
  436. }
  437. private static void SerializeEffect(string[] strArray4, BinaryWriter binaryWriter)
  438. {
  439. binaryWriter.Write("EFFECT");
  440. if (strArray4 != null)
  441. {
  442. // bloom
  443. binaryWriter.Write("EFFECT_BLOOM");
  444. binaryWriter.Write(float.Parse(strArray4[2])); // intensity
  445. binaryWriter.Write((int)float.Parse(strArray4[3])); // blur iterations
  446. binaryWriter.WriteColour(new Color( // bloom threshold colour
  447. float.Parse(strArray4[4]), float.Parse(strArray4[5]), float.Parse(strArray4[6]), 1f
  448. ));
  449. binaryWriter.Write(int.Parse(strArray4[7]) == 1); // hdr
  450. binaryWriter.Write(int.Parse(strArray4[1]) == 1); // active
  451. // vignetting
  452. binaryWriter.Write("EFFECT_VIGNETTE");
  453. binaryWriter.Write(float.Parse(strArray4[9])); // intensity
  454. binaryWriter.Write(float.Parse(strArray4[10])); // blur
  455. binaryWriter.Write(float.Parse(strArray4[11])); // blur spread
  456. binaryWriter.Write(float.Parse(strArray4[12])); // chromatic aberration
  457. binaryWriter.Write(int.Parse(strArray4[8]) == 1); // active
  458. // bokashi
  459. // TODO: implement bokashi in MPS
  460. float bokashi = float.Parse(strArray4[13]);
  461. // TODO: implement sepia in MPS too
  462. if (strArray4.Length > 15)
  463. {
  464. binaryWriter.Write("EFFECT_DOF");
  465. binaryWriter.Write(float.Parse(strArray4[16])); // focal length
  466. binaryWriter.Write(float.Parse(strArray4[17])); // focal size
  467. binaryWriter.Write(float.Parse(strArray4[18])); // aperture
  468. binaryWriter.Write(float.Parse(strArray4[19])); // max blur size
  469. binaryWriter.Write(int.Parse(strArray4[20]) == 1); // visualize focus
  470. binaryWriter.Write(int.Parse(strArray4[15]) == 1); // active
  471. binaryWriter.Write("EFFECT_FOG");
  472. binaryWriter.Write(float.Parse(strArray4[22])); // fog distance
  473. binaryWriter.Write(float.Parse(strArray4[23])); // density
  474. binaryWriter.Write(float.Parse(strArray4[24])); // height scale
  475. binaryWriter.Write(float.Parse(strArray4[25])); // height
  476. binaryWriter.WriteColour(new Color( // fog colour
  477. float.Parse(strArray4[26]), float.Parse(strArray4[27]), float.Parse(strArray4[28]), 1f
  478. ));
  479. binaryWriter.Write(int.Parse(strArray4[21]) == 1); // active
  480. }
  481. }
  482. binaryWriter.Write("END_EFFECT");
  483. }
  484. private static void SerializeMessage(string[] strArray3, BinaryWriter binaryWriter)
  485. {
  486. binaryWriter.Write("TEXTBOX");
  487. bool showingMessage = false;
  488. string name = "Maid";
  489. string message = "Hello world";
  490. if (strArray3.Length > 16)
  491. {
  492. showingMessage = int.Parse(strArray3[34]) == 1;
  493. name = strArray3[35];
  494. message = strArray3[36].Replace("&kaigyo", "\n");
  495. // MM does not serialize message font size
  496. }
  497. binaryWriter.Write(showingMessage);
  498. binaryWriter.Write(25);
  499. binaryWriter.WriteNullableString(name);
  500. binaryWriter.WriteNullableString(message);
  501. }
  502. private static void SerializeLights(string[] strArray3, string[] strArray4, string[] strArray5, string[] strArray7, BinaryWriter binaryWriter)
  503. {
  504. // Lights
  505. binaryWriter.Write("LIGHT");
  506. int numberOfLights = 1;
  507. numberOfLights += strArray5 == null ? 0 : strArray5.Length - 1;
  508. binaryWriter.Write(numberOfLights);
  509. if (strArray3.Length > 16)
  510. {
  511. // Main Light
  512. /*
  513. 0 = Directional
  514. 1 = Spot
  515. 2 = Point
  516. 3 = Directional (Colour Mode)
  517. */
  518. int lightType = int.Parse(strArray3[17]);
  519. Color lightColour = new Color(
  520. float.Parse(strArray3[18]), float.Parse(strArray3[19]), float.Parse(strArray3[20]), 1f
  521. );
  522. Quaternion lightRotation = Quaternion.Euler(
  523. float.Parse(strArray3[21]), float.Parse(strArray3[22]), float.Parse(strArray3[23])
  524. );
  525. // MM uses spotAngle for both range and spotAngle based on which light type is used
  526. float intensity = float.Parse(strArray3[24]);
  527. float spotAngle = float.Parse(strArray3[25]);
  528. float range = lightType == 2 ? spotAngle / 5f : spotAngle; ;
  529. float shadowStrength = 0.098f;
  530. if (strArray4 != null) shadowStrength = float.Parse(strArray4[0]);
  531. for (int i = 0; i < 3; i++)
  532. {
  533. if (i == lightType || (i == 0 && lightType == 3))
  534. {
  535. SerializeLightProperty(
  536. binaryWriter, lightRotation, lightColour, intensity, spotAngle, range, shadowStrength
  537. );
  538. }
  539. else SerializeDefaultLight(binaryWriter);
  540. }
  541. if (strArray7 != null)
  542. {
  543. binaryWriter.WriteVector3(Utility.Vector3String(strArray7[0]));
  544. }
  545. else binaryWriter.WriteVector3(new Vector3(0f, 1.9f, 0.4f));
  546. binaryWriter.Write(lightType == 3 ? 0 : lightType);
  547. binaryWriter.Write(lightType == 3);
  548. binaryWriter.Write(false);
  549. // lightKage[0] is the only value that's serialized
  550. }
  551. if (strArray5 != null)
  552. {
  553. int otherLights = strArray5.Length - 1;
  554. for (int i = 0; i < otherLights; i++)
  555. {
  556. string[] lightProperties = strArray5[i].Split(',');
  557. int lightType = int.Parse(lightProperties[0]);
  558. Color lightColour = new Color(
  559. float.Parse(lightProperties[1]), float.Parse(lightProperties[2]),
  560. float.Parse(lightProperties[3]), 1f
  561. );
  562. Quaternion lightRotation = Quaternion.Euler(
  563. float.Parse(lightProperties[4]), float.Parse(lightProperties[5]), 18f
  564. );
  565. float intensity = float.Parse(lightProperties[6]);
  566. float spotAngle = float.Parse(lightProperties[7]);
  567. float range = lightType == 2 ? spotAngle / 5f : spotAngle;
  568. float shadowStrength = 0.098f;
  569. for (int j = 0; j < 3; j++)
  570. {
  571. if (j == lightType)
  572. {
  573. SerializeLightProperty(
  574. binaryWriter, lightRotation, lightColour, intensity, spotAngle, range, shadowStrength
  575. );
  576. }
  577. else SerializeDefaultLight(binaryWriter);
  578. }
  579. if (strArray7 != null)
  580. {
  581. binaryWriter.WriteVector3(Utility.Vector3String(strArray7[i + 1]));
  582. }
  583. else binaryWriter.WriteVector3(new Vector3(0f, 1.9f, 0.4f));
  584. binaryWriter.Write(lightType == 3 ? 0 : lightType);
  585. binaryWriter.Write(false);
  586. binaryWriter.Write(lightType == 3);
  587. }
  588. }
  589. }
  590. private static void SerializeEnvironment(string[] data, BinaryWriter binaryWriter, bool kankyo)
  591. {
  592. binaryWriter.Write("ENVIRONMENT");
  593. int bgIndex;
  594. string bgAsset = "Theater";
  595. if (!int.TryParse(data[2], out bgIndex))
  596. {
  597. bgAsset = data[2].Replace(" ", "_");
  598. }
  599. binaryWriter.Write(bgAsset);
  600. binaryWriter.WriteVector3(new Vector3(
  601. float.Parse(data[6]), float.Parse(data[7]), float.Parse(data[8])
  602. ));
  603. binaryWriter.WriteQuaternion(Quaternion.Euler(
  604. float.Parse(data[3]), float.Parse(data[4]), float.Parse(data[5])
  605. ));
  606. binaryWriter.WriteVector3(new Vector3(
  607. float.Parse(data[9]), float.Parse(data[10]), float.Parse(data[11])
  608. ));
  609. binaryWriter.Write(kankyo);
  610. Vector3 cameraTargetPos = new Vector3(0f, 0.9f, 0f);
  611. float cameraDistance = 3f;
  612. Quaternion cameraRotation = Quaternion.identity;
  613. if (data.Length > 16)
  614. {
  615. cameraTargetPos = new Vector3(
  616. float.Parse(data[27]), float.Parse(data[28]), float.Parse(data[29])
  617. );
  618. cameraDistance = float.Parse(data[30]);
  619. cameraRotation = Quaternion.Euler(
  620. float.Parse(data[31]), float.Parse(data[32]), float.Parse(data[33])
  621. );
  622. }
  623. binaryWriter.WriteVector3(cameraTargetPos);
  624. binaryWriter.Write(cameraDistance);
  625. binaryWriter.WriteQuaternion(cameraRotation);
  626. }
  627. public static void SerializeAttachPoint(BinaryWriter binaryWriter)
  628. {
  629. binaryWriter.Write(0);
  630. binaryWriter.Write(-1);
  631. }
  632. public static void SerializeDefaultLight(BinaryWriter binaryWriter)
  633. {
  634. SerializeLightProperty(binaryWriter, Quaternion.Euler(40f, 180f, 0f), Color.white);
  635. }
  636. public static void SerializeLightProperty(
  637. BinaryWriter binaryWriter,
  638. Quaternion rotation, Color colour, float intensity = 0.95f, float range = 50f,
  639. float spotAngle = 50f, float shadowStrength = 0.1f
  640. )
  641. {
  642. binaryWriter.WriteQuaternion(rotation);
  643. binaryWriter.Write(intensity);
  644. binaryWriter.Write(range);
  645. binaryWriter.Write(spotAngle);
  646. binaryWriter.Write(shadowStrength);
  647. binaryWriter.WriteColour(colour);
  648. }
  649. }
  650. public static class BinaryExtensions
  651. {
  652. public static void WriteVector3(this BinaryWriter binaryWriter, Vector3 vector3)
  653. {
  654. binaryWriter.Write(vector3.x);
  655. binaryWriter.Write(vector3.y);
  656. binaryWriter.Write(vector3.z);
  657. }
  658. public static void WriteQuaternion(this BinaryWriter binaryWriter, Quaternion quaternion)
  659. {
  660. binaryWriter.Write(quaternion.x);
  661. binaryWriter.Write(quaternion.y);
  662. binaryWriter.Write(quaternion.z);
  663. binaryWriter.Write(quaternion.w);
  664. }
  665. public static void WriteColour(this BinaryWriter binaryWriter, UnityEngine.Color colour)
  666. {
  667. binaryWriter.Write(colour.r);
  668. binaryWriter.Write(colour.g);
  669. binaryWriter.Write(colour.b);
  670. binaryWriter.Write(colour.a);
  671. }
  672. public static void WriteNullableString(this BinaryWriter binaryWriter, string str)
  673. {
  674. binaryWriter.Write(str != null);
  675. if (str != null) binaryWriter.Write(str);
  676. }
  677. }
  678. public static class Utility
  679. {
  680. public static Quaternion EulerString(string euler)
  681. {
  682. string[] data = euler.Split(',');
  683. return Quaternion.Euler(
  684. float.Parse(data[0]), float.Parse(data[1]), float.Parse(data[2])
  685. );
  686. }
  687. public static Vector3 Vector3String(string vector3)
  688. {
  689. string[] data = vector3.Split(',');
  690. return new Vector3(
  691. float.Parse(data[0]), float.Parse(data[1]), float.Parse(data[2])
  692. );
  693. }
  694. }
  695. }