Program.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  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. using static Plugin.BinaryExtensions;
  14. [BepInPlugin(pluginGuid, pluginName, pluginVersion)]
  15. public class SceneConverter : BaseUnityPlugin
  16. {
  17. private const string pluginGuid = "com.habeebweeb.com3d2.meidophotostudio.converter";
  18. public const string pluginName = "MeidoPhotoStudio Converter";
  19. public const string pluginVersion = "0.0.0";
  20. private readonly byte[] noThumb = Convert.FromBase64String(
  21. "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7D" +
  22. "AcdvqGQAAAFOSURBVFhH3dJbjoMwEETRLIRP9r+zrCGpqJABY+x+2Ua5ys9EcteJNK/3sj7ws7E+j2ln8Q9+O7eE2Vjpq4kdJTsLTZRl" +
  23. "jBMLTZFdDTkLDZYVAQUWGia7Wy+z0ABZZfqWhbrK6rs1Fuoka442WChcJllss1CgTDgnYqEQmXxLykJOmWpIwUJmmXZFx0IGmWFCzUKq" +
  24. "J7b7FhYSvjIfN7JQ86Hnsp2FKm+dZ10sVHzuv+lloexCyMEAFkpHoq7FsBDuBJ76a1Y6EnXtT//li8/9N12sylvnWTur+dBz2cgSvjIf" +
  25. "t7BUT2z31azePwOpWQYT064oWGYTUw1JWU4Tk2+JWCEmJpxrswJNTLLYYIWbWHO0xupkYvXdW1ZXE6tMl1kDTOxuvcAaZmJFQM4abGJX" +
  26. "w4k1xcQyxs6aaGJHycaabmIJ82M9xMTo2VjP+izrF8NPHwq3SYqeAAAAAElFTkSuQmCC"
  27. );
  28. private static readonly Dictionary<string, PlacementData.Data> myrAssetNameToData
  29. = new Dictionary<string, PlacementData.Data>(StringComparer.InvariantCultureIgnoreCase);
  30. private static readonly string[] faceKeys = {
  31. "eyeclose", "eyeclose2", "eyeclose3", "eyeclose6", "hitomih", "hitomis", "mayuha",
  32. "mayuup", "mayuv", "mayuvhalf", "moutha", "mouths", "mouthdw", "mouthup", "tangout",
  33. "tangup", "eyebig", "eyeclose5", "mayuw", "mouthhe", "mouthc", "mouthi", "mouthuphalf",
  34. "tangopen",
  35. "namida", "tear1", "tear2", "tear3", "shock", "yodare", "hoho", "hoho2", "hohos", "hohol",
  36. "toothoff", "nosefook"
  37. };
  38. private static readonly string[] mpnAttachProps = {
  39. /* "", "", "", "", "", "", "", "", "", */
  40. "kousokuu_tekaseone_i_.menu", "kousokuu_tekasetwo_i_.menu", "kousokul_ashikaseup_i_.menu",
  41. "kousokuu_tekasetwo_i_.menu", "kousokul_ashikasedown_i_.menu", "kousokuu_tekasetwodown_i_.menu",
  42. "kousokuu_ushirode_i_.menu", "kousokuu_smroom_haritsuke_i_.menu"
  43. };
  44. private static readonly int[] bodyRotations =
  45. {
  46. 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
  47. };
  48. private static BepInEx.Logging.ManualLogSource Log;
  49. private static readonly string scenesPath = Plugin.Constants.scenesPath;
  50. private static readonly Vector3 DefaultSoftG = new Vector3(0f, -3f / 1000f, 0f);
  51. private bool active;
  52. private Rect windowRect = new Rect(30f, 30f, 300f, 200f);
  53. private void Awake()
  54. {
  55. DontDestroyOnLoad(this);
  56. if (!Directory.Exists(scenesPath)) Directory.CreateDirectory(scenesPath);
  57. Log = Logger;
  58. }
  59. private void Start()
  60. {
  61. SceneManager.sceneLoaded += OnSceneLoaded;
  62. foreach (var data in PlacementData.GetAllDatas(false))
  63. {
  64. string assetName = string.IsNullOrEmpty(data.assetName) ? data.resourceName : data.assetName;
  65. myrAssetNameToData[assetName] = data;
  66. }
  67. }
  68. private void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
  69. {
  70. int index = scene.buildIndex;
  71. active = index == 9 || index == 3;
  72. }
  73. private void OnGUI()
  74. {
  75. if (active)
  76. {
  77. windowRect.width = 300f;
  78. windowRect.height = 200f;
  79. windowRect.x = Mathf.Clamp(windowRect.x, 0, Screen.width - windowRect.width);
  80. windowRect.y = Mathf.Clamp(windowRect.y, 0, Screen.height - windowRect.height);
  81. windowRect = GUI.Window(0xEA4040, windowRect, GUIFunc, "MeidoPhotoStudio Converter");
  82. }
  83. }
  84. private void GUIFunc(int id)
  85. {
  86. if (GUILayout.Button("Convert ModifiedMM")) ProcessModifedMM();
  87. if (GUILayout.Button("Convert ModifiedMM (Scene Manager)")) ProcessModifiedMMPng();
  88. GUILayout.Space(30f);
  89. if (GUILayout.Button("Convert modifedMM quickSave")) ProcessQuickSave();
  90. }
  91. private void ProcessModifiedMMPng()
  92. {
  93. string modPath = Path.Combine(Paths.GameRootPath, "Mod");
  94. string scenePath = Path.Combine(modPath, "MultipleMaidsScene");
  95. string kankyoPath = Path.Combine(modPath, "MultipleMaidsScene");
  96. }
  97. private string GetModifiedMMSceneData(string pngPath)
  98. {
  99. return string.Empty;
  100. }
  101. private void ProcessQuickSave()
  102. {
  103. string sybarisPath = Path.Combine(Paths.GameRootPath, "Sybaris");
  104. string iniPath = BepInEx.Utility.CombinePaths(sybarisPath, "UnityInjector", "Config", "MultipleMaids.ini");
  105. IniFile mmIniFile = IniFile.FromFile(iniPath);
  106. IniSection sceneSection = mmIniFile.GetSection("scene");
  107. if (sceneSection != null)
  108. {
  109. if (sceneSection.HasKey("s9999"))
  110. {
  111. string sceneData = sceneSection["s9999"].Value;
  112. if (!string.IsNullOrEmpty(sceneData))
  113. {
  114. byte[] convertedSceneData = ProcessScene(sceneData, false);
  115. string path = Path.Combine(scenesPath, $"mmtempscene_{GetMMDateString(sceneData)}.png");
  116. SaveSceneToFile(path, convertedSceneData, noThumb);
  117. }
  118. }
  119. }
  120. }
  121. private void ProcessModifedMM()
  122. {
  123. string sybarisPath = Path.Combine(Paths.GameRootPath, "Sybaris");
  124. string iniPath = BepInEx.Utility.CombinePaths(sybarisPath, "UnityInjector", "Config", "MultipleMaids.ini");
  125. IniFile mmIniFile = IniFile.FromFile(iniPath);
  126. IniSection sceneSection = mmIniFile.GetSection("scene");
  127. if (sceneSection != null)
  128. {
  129. foreach (IniKey iniKey in sceneSection.Keys)
  130. {
  131. if (iniKey.Key.StartsWith("ss")) continue;
  132. int sceneIndex = int.Parse(iniKey.Key.Substring(1));
  133. bool kankyo = sceneIndex >= 10000;
  134. string sceneData = iniKey.Value;
  135. if (!string.IsNullOrEmpty(sceneData))
  136. {
  137. byte[] convertedSceneData = ProcessScene(sceneData, kankyo);
  138. string prefix = kankyo
  139. ? "mmkankyo"
  140. : sceneIndex == 9999
  141. ? "mmtempscene" : $"mmscene{sceneIndex}";
  142. string path = Path.Combine(scenesPath, $"{prefix}_{GetMMDateString(sceneData)}.png");
  143. byte[] thumbnail = noThumb;
  144. string screenshotKey = $"s{iniKey.Key}";
  145. if (sceneSection.HasKey(screenshotKey))
  146. {
  147. string screenshotBase64 = sceneSection[screenshotKey].Value;
  148. if (!string.IsNullOrEmpty(screenshotBase64))
  149. {
  150. thumbnail = Convert.FromBase64String(screenshotBase64);
  151. }
  152. }
  153. SaveSceneToFile(path, convertedSceneData, thumbnail);
  154. }
  155. }
  156. }
  157. }
  158. public static void SaveSceneToFile(string path, byte[] sceneData, byte[] thumbnailData)
  159. {
  160. using (FileStream fileStream = File.Create(path))
  161. {
  162. fileStream.Write(thumbnailData, 0, thumbnailData.Length);
  163. fileStream.Write(sceneData, 0, sceneData.Length);
  164. }
  165. }
  166. public static string GetMMDateString(string sceneData)
  167. {
  168. string dateString = sceneData.Split('_')[0].Split(',')[0];
  169. DateTime date = DateTime.Parse(dateString);
  170. return $"{date:yyyyMMddHHmm}";
  171. }
  172. public static byte[] ProcessScene(string sceneData, bool kankyo)
  173. {
  174. string[] strArray1 = sceneData.Split('_');
  175. string[] strArray2 = strArray1[1].Split(';');
  176. string[] strArray3 = strArray1[0].Split(',');
  177. string[] strArray4 = null;
  178. string[] strArray5 = null;
  179. string[] strArray6 = null;
  180. string[] strArray7 = null;
  181. if (strArray1.Length >= 5)
  182. {
  183. strArray4 = strArray1[2].Split(',');
  184. strArray5 = strArray1[3].Split(';');
  185. strArray6 = strArray1[4].Split(';');
  186. }
  187. if (strArray1.Length >= 6)
  188. {
  189. strArray7 = strArray1[5].Split(';');
  190. }
  191. using (MemoryStream memoryStream = new MemoryStream())
  192. using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
  193. using (BinaryWriter binaryWriter = new BinaryWriter(deflateStream, System.Text.Encoding.UTF8))
  194. {
  195. binaryWriter.Write("MPS_SCENE");
  196. binaryWriter.Write(Plugin.MeidoPhotoStudio.sceneVersion);
  197. binaryWriter.Write(kankyo ? Plugin.MeidoPhotoStudio.kankyoMagic : int.Parse(strArray3[1]));
  198. SerializeEnvironment(strArray3, binaryWriter, kankyo);
  199. SerializeLights(strArray3, strArray4, strArray5, strArray7, binaryWriter);
  200. SerializeEffect(strArray4, binaryWriter);
  201. SerializeProp(strArray3, strArray6, binaryWriter);
  202. if (!kankyo)
  203. {
  204. SerializeMessage(strArray3, binaryWriter);
  205. SerializeMaid(strArray2, strArray3, binaryWriter);
  206. }
  207. binaryWriter.Write("END");
  208. deflateStream.Close();
  209. return memoryStream.ToArray();
  210. }
  211. }
  212. private static void SerializeMaid(string[] strArray2, string[] strArray3, BinaryWriter binaryWriter)
  213. {
  214. binaryWriter.Write(Plugin.MeidoManager.header);
  215. // MM scene converted to MPS
  216. binaryWriter.Write(true);
  217. binaryWriter.Write(Plugin.Meido.meidoDataVersion);
  218. int numberOfMaids = strArray2.Length;
  219. binaryWriter.Write(numberOfMaids);
  220. /*
  221. TODO: Investigate why serialized maid data may only have 64 items.
  222. https://git.coder.horse/meidomustard/modifiedMM/src/master/MultipleMaids/CM3D2/MultipleMaids/Plugin/MultipleMaids.Update.cs#L3745
  223. The difference affects whether or not rotations are local or world.
  224. Certain body rotations would be missing as well particularly the toes.
  225. Other data like free look and attached items like hand/vag/anl would be missing.
  226. */
  227. bool gravityEnabled = false;
  228. for (int i = 0; i < numberOfMaids; i++)
  229. {
  230. using (MemoryStream memoryStream = new MemoryStream())
  231. using (BinaryWriter tempWriter = new BinaryWriter(memoryStream))
  232. {
  233. string[] maidData = strArray2[i].Split(':');
  234. tempWriter.WriteVector3(Utility.Vector3String(maidData[59])); // position
  235. tempWriter.WriteQuaternion(Utility.EulerString(maidData[58])); // rotation
  236. tempWriter.WriteVector3(Utility.Vector3String(maidData[60])); // scale
  237. // fingers
  238. for (int j = 0; j < 40; j++)
  239. {
  240. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  241. }
  242. // toes
  243. for (int k = 0; k < 2; k++)
  244. {
  245. for (int j = 72 + k; j < 90; j += 2)
  246. {
  247. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  248. }
  249. }
  250. // the rest of the limbs
  251. foreach (int j in bodyRotations)
  252. {
  253. tempWriter.WriteQuaternion(Utility.EulerString(maidData[j]));
  254. }
  255. tempWriter.WriteVector3(Utility.Vector3String(maidData[96])); // hip position
  256. // cached pose stuff
  257. tempWriter.Write("normal");
  258. tempWriter.Write("maid_stand01");
  259. tempWriter.Write(false);
  260. // eye rotation delta
  261. // MM saves the rotations directly so just save the identity
  262. tempWriter.WriteQuaternion(Quaternion.identity);
  263. tempWriter.WriteQuaternion(Quaternion.identity);
  264. string[] freeLookData = maidData[64].Split(',');
  265. bool isFreeLook = int.Parse(freeLookData[0]) == 1;
  266. tempWriter.Write(isFreeLook);
  267. if (isFreeLook)
  268. {
  269. tempWriter.WriteVector3(new Vector3(
  270. float.Parse(freeLookData[2]), 1f, float.Parse(freeLookData[1])
  271. ));
  272. }
  273. // head/eye to camera
  274. // MM does not changes these values so they're always true
  275. tempWriter.Write(true);
  276. tempWriter.Write(true);
  277. string[] faceValues = maidData[63].Split(',');
  278. tempWriter.Write("MPS_FACE");
  279. for (int j = 0; j < faceKeys.Length - 2; j++)
  280. {
  281. tempWriter.Write(faceKeys[j]);
  282. tempWriter.Write(float.Parse(faceValues[j]));
  283. }
  284. if (faceValues.Length > 65)
  285. {
  286. tempWriter.Write(faceKeys[faceKeys.Length - 1]);
  287. tempWriter.Write(float.Parse(faceValues[faceValues.Length - 1]));
  288. }
  289. tempWriter.Write("END_FACE");
  290. tempWriter.Write(true); // body visible
  291. // MM does not serialize clothing
  292. for (int j = 0; j < 29; j++) tempWriter.Write(true);
  293. Vector3 softG = new Vector3(
  294. float.Parse(strArray3[12]), float.Parse(strArray3[13]), float.Parse(strArray3[14])
  295. );
  296. bool hairGravityActive = softG != DefaultSoftG;
  297. tempWriter.Write(hairGravityActive);
  298. if (hairGravityActive)
  299. {
  300. // MM gravity affects all maids
  301. gravityEnabled = true;
  302. tempWriter.WriteVector3(softG * 5f);
  303. }
  304. // MM doesn't serialize skirt gravity
  305. tempWriter.Write(false);
  306. // MM does not serialize curling
  307. tempWriter.Write(false);
  308. tempWriter.Write(false);
  309. tempWriter.Write(false);
  310. string kousokuUpperMenu = string.Empty;
  311. string kousokuLowerMenu = string.Empty;
  312. int mpnIndex = int.Parse(maidData[65].Split(',')[0]);
  313. // MM can attach accvag, accanl and handitem stuff as well as kousoku_upper/lower
  314. // MPS attach prop is preferred for non kousoku_upper/lower props because unlike kousoku_upper/lower
  315. // props, accvag etc. props attach only to a single place.
  316. if (mpnIndex >= 9 && mpnIndex <= 16)
  317. {
  318. int actualIndex = mpnIndex - 9;
  319. if (mpnIndex == 12)
  320. {
  321. kousokuUpperMenu = mpnAttachProps[actualIndex];
  322. kousokuLowerMenu = mpnAttachProps[actualIndex - 1];
  323. }
  324. else if (mpnIndex == 13)
  325. {
  326. kousokuUpperMenu = mpnAttachProps[actualIndex + 1];
  327. kousokuLowerMenu = mpnAttachProps[actualIndex];
  328. }
  329. else
  330. {
  331. if (mpnIndex > 13) actualIndex++;
  332. string kousokuMenu = mpnAttachProps[actualIndex];
  333. if (mpnAttachProps[actualIndex][7] == 'u') kousokuUpperMenu = kousokuMenu;
  334. else kousokuLowerMenu = kousokuMenu;
  335. }
  336. }
  337. bool kousokuUpper = !string.IsNullOrEmpty(kousokuUpperMenu);
  338. tempWriter.Write(kousokuUpper);
  339. if (kousokuUpper) tempWriter.Write(kousokuUpperMenu);
  340. bool kousokuLower = !string.IsNullOrEmpty(kousokuLowerMenu);
  341. tempWriter.Write(kousokuLower);
  342. if (kousokuLower) tempWriter.Write(kousokuLowerMenu);
  343. binaryWriter.Write(memoryStream.Length);
  344. binaryWriter.Write(memoryStream.ToArray());
  345. }
  346. }
  347. binaryWriter.Write(gravityEnabled);
  348. }
  349. private static void SerializeProp(string[] strArray3, string[] strArray6, BinaryWriter binaryWriter)
  350. {
  351. binaryWriter.Write(Plugin.PropManager.header);
  352. binaryWriter.Write(Plugin.PropManager.propDataVersion);
  353. bool hasWProp = strArray3.Length > 37 && !string.IsNullOrEmpty(strArray3[37]);
  354. int numberOfProps = hasWProp ? 1 : 0;
  355. numberOfProps += strArray6 == null ? 0 : strArray6.Length - 1;
  356. binaryWriter.Write(numberOfProps);
  357. if (hasWProp)
  358. {
  359. // For the prop that spawns when you push (shift +) W
  360. binaryWriter.WriteVector3(new Vector3(
  361. float.Parse(strArray3[41]), float.Parse(strArray3[42]), float.Parse(strArray3[43])
  362. ));
  363. binaryWriter.WriteQuaternion(Quaternion.Euler(
  364. float.Parse(strArray3[38]), float.Parse(strArray3[39]), float.Parse(strArray3[40])
  365. ));
  366. binaryWriter.WriteVector3(new Vector3(
  367. float.Parse(strArray3[44]), float.Parse(strArray3[45]), float.Parse(strArray3[46])
  368. ));
  369. SerializeAttachPoint(binaryWriter);
  370. binaryWriter.Write(false); // shadow casting
  371. binaryWriter.Write(strArray3[37].Replace(' ', '_'));
  372. }
  373. if (strArray6 != null)
  374. {
  375. for (int i = 0; i < strArray6.Length - 1; i++)
  376. {
  377. string[] assetParts = strArray6[i].Split(',');
  378. string assetName = assetParts[0].Replace(' ', '_');
  379. bool shadowCasting = assetName.EndsWith(".menu");
  380. if (assetName.StartsWith("creative_"))
  381. {
  382. // modifiedMM my room creative prop
  383. // modifiedMM serializes the prefabName rather than the ID.
  384. assetName = assetName.Replace("creative_", String.Empty);
  385. assetName = $"MYR_{myrAssetNameToData[assetName].ID}#{assetName}";
  386. }
  387. else if (assetName.StartsWith("MYR_"))
  388. {
  389. // MM 23.0+ my room creative prop
  390. int assetID = int.Parse(assetName.Replace("MYR_", string.Empty));
  391. PlacementData.Data data = PlacementData.GetData(assetID);
  392. string asset = string.IsNullOrEmpty(data.assetName) ? data.resourceName : data.assetName;
  393. assetName = $"{assetName}#{asset}";
  394. }
  395. else if (assetName.Contains('#'))
  396. {
  397. if (assetName.Contains(".menu"))
  398. {
  399. // modifiedMM official mod prop
  400. string[] modComponents = assetParts[0].Split('#');
  401. string baseMenuFile = modComponents[0].Replace(' ', '_');
  402. string modItem = modComponents[1].Replace(' ', '_');
  403. assetName = $"{modItem}#{baseMenuFile}";
  404. }
  405. else
  406. {
  407. assetName = assetName.Split('#')[1].Replace(' ', '_');
  408. }
  409. }
  410. else if (assetName.StartsWith("BGOdogu", StringComparison.InvariantCultureIgnoreCase))
  411. {
  412. // I don't know why multiplemaids even prepends BG
  413. assetName = assetName.Substring(2);
  414. }
  415. binaryWriter.WriteVector3(new Vector3(
  416. float.Parse(assetParts[4]), float.Parse(assetParts[5]), float.Parse(assetParts[6])
  417. ));
  418. binaryWriter.WriteQuaternion(Quaternion.Euler(
  419. float.Parse(assetParts[1]), float.Parse(assetParts[2]), float.Parse(assetParts[3])
  420. ));
  421. binaryWriter.WriteVector3(new Vector3(
  422. float.Parse(assetParts[7]), float.Parse(assetParts[8]), float.Parse(assetParts[9])
  423. ));
  424. SerializeAttachPoint(binaryWriter);
  425. binaryWriter.Write(shadowCasting);
  426. binaryWriter.Write(assetName);
  427. }
  428. }
  429. }
  430. private static void SerializeEffect(string[] strArray4, BinaryWriter binaryWriter)
  431. {
  432. binaryWriter.Write(Plugin.EffectManager.header);
  433. if (strArray4 != null)
  434. {
  435. // bloom
  436. binaryWriter.Write(Plugin.BloomEffectManager.header);
  437. binaryWriter.Write(float.Parse(strArray4[2])); // intensity
  438. binaryWriter.Write((int)float.Parse(strArray4[3])); // blur iterations
  439. binaryWriter.WriteColour(new Color( // bloom threshold colour
  440. float.Parse(strArray4[4]), float.Parse(strArray4[5]), float.Parse(strArray4[6]), 1f
  441. ));
  442. binaryWriter.Write(int.Parse(strArray4[7]) == 1); // hdr
  443. binaryWriter.Write(int.Parse(strArray4[1]) == 1); // active
  444. // vignetting
  445. binaryWriter.Write(Plugin.VignetteEffectManager.header);
  446. binaryWriter.Write(float.Parse(strArray4[9])); // intensity
  447. binaryWriter.Write(float.Parse(strArray4[10])); // blur
  448. binaryWriter.Write(float.Parse(strArray4[11])); // blur spread
  449. binaryWriter.Write(float.Parse(strArray4[12])); // chromatic aberration
  450. binaryWriter.Write(int.Parse(strArray4[8]) == 1); // active
  451. // bokashi
  452. binaryWriter.Write(Plugin.BlurEffectManager.header);
  453. float blurSize = float.Parse(strArray4[13]);
  454. binaryWriter.Write(blurSize);
  455. binaryWriter.Write(blurSize > 0f);
  456. binaryWriter.Write(Plugin.SepiaToneEffectManger.header);
  457. binaryWriter.Write(int.Parse(strArray4[29]) == 1);
  458. if (strArray4.Length > 15)
  459. {
  460. binaryWriter.Write(Plugin.DepthOfFieldEffectManager.header);
  461. binaryWriter.Write(float.Parse(strArray4[16])); // focal length
  462. binaryWriter.Write(float.Parse(strArray4[17])); // focal size
  463. binaryWriter.Write(float.Parse(strArray4[18])); // aperture
  464. binaryWriter.Write(float.Parse(strArray4[19])); // max blur size
  465. binaryWriter.Write(int.Parse(strArray4[20]) == 1); // visualize focus
  466. binaryWriter.Write(int.Parse(strArray4[15]) == 1); // active
  467. binaryWriter.Write(Plugin.FogEffectManager.header);
  468. binaryWriter.Write(float.Parse(strArray4[22])); // fog distance
  469. binaryWriter.Write(float.Parse(strArray4[23])); // density
  470. binaryWriter.Write(float.Parse(strArray4[24])); // height scale
  471. binaryWriter.Write(float.Parse(strArray4[25])); // height
  472. binaryWriter.WriteColour(new Color( // fog colour
  473. float.Parse(strArray4[26]), float.Parse(strArray4[27]), float.Parse(strArray4[28]), 1f
  474. ));
  475. binaryWriter.Write(int.Parse(strArray4[21]) == 1); // active
  476. }
  477. }
  478. binaryWriter.Write(Plugin.EffectManager.footer);
  479. }
  480. private static void SerializeMessage(string[] strArray3, BinaryWriter binaryWriter)
  481. {
  482. binaryWriter.Write(Plugin.MessageWindowManager.header);
  483. bool showingMessage = false;
  484. string name = "Maid";
  485. string message = "Hello world";
  486. if (strArray3.Length > 16)
  487. {
  488. showingMessage = int.Parse(strArray3[34]) == 1;
  489. name = strArray3[35];
  490. message = strArray3[36].Replace("&kaigyo", "\n");
  491. // MM does not serialize message font size
  492. }
  493. binaryWriter.Write(showingMessage);
  494. binaryWriter.Write(25);
  495. binaryWriter.WriteNullableString(name);
  496. binaryWriter.WriteNullableString(message);
  497. }
  498. private static void SerializeLights(string[] strArray3, string[] strArray4, string[] strArray5, string[] strArray7, BinaryWriter binaryWriter)
  499. {
  500. // Lights
  501. binaryWriter.Write(Plugin.LightManager.header);
  502. int numberOfLights = 1;
  503. numberOfLights += strArray5 == null ? 0 : strArray5.Length - 1;
  504. binaryWriter.Write(numberOfLights);
  505. if (strArray3.Length > 16)
  506. {
  507. // Main Light
  508. /*
  509. 0 = Directional
  510. 1 = Spot
  511. 2 = Point
  512. 3 = Directional (Colour Mode)
  513. */
  514. int lightType = int.Parse(strArray3[17]);
  515. Color lightColour = new Color(
  516. float.Parse(strArray3[18]), float.Parse(strArray3[19]), float.Parse(strArray3[20]), 1f
  517. );
  518. Quaternion lightRotation = Quaternion.Euler(
  519. float.Parse(strArray3[21]), float.Parse(strArray3[22]), float.Parse(strArray3[23])
  520. );
  521. // MM uses spotAngle for both range and spotAngle based on which light type is used
  522. float intensity = float.Parse(strArray3[24]);
  523. float spotAngle = float.Parse(strArray3[25]);
  524. float range = lightType == 2 ? spotAngle / 5f : spotAngle; ;
  525. float shadowStrength = 0.098f;
  526. if (strArray4 != null) shadowStrength = float.Parse(strArray4[0]);
  527. for (int i = 0; i < 3; i++)
  528. {
  529. if (i == lightType || (i == 0 && lightType == 3))
  530. {
  531. SerializeLightProperty(
  532. binaryWriter, lightRotation, lightColour, intensity, spotAngle, range, shadowStrength
  533. );
  534. }
  535. else SerializeDefaultLight(binaryWriter);
  536. }
  537. if (strArray7 != null)
  538. {
  539. binaryWriter.WriteVector3(Utility.Vector3String(strArray7[0]));
  540. }
  541. else binaryWriter.WriteVector3(new Vector3(0f, 1.9f, 0.4f));
  542. binaryWriter.Write(lightType == 3 ? 0 : lightType);
  543. binaryWriter.Write(lightType == 3);
  544. binaryWriter.Write(false);
  545. // lightKage[0] is the only value that's serialized
  546. }
  547. if (strArray5 != null)
  548. {
  549. int otherLights = strArray5.Length - 1;
  550. for (int i = 0; i < otherLights; i++)
  551. {
  552. string[] lightProperties = strArray5[i].Split(',');
  553. int lightType = int.Parse(lightProperties[0]);
  554. Color lightColour = new Color(
  555. float.Parse(lightProperties[1]), float.Parse(lightProperties[2]),
  556. float.Parse(lightProperties[3]), 1f
  557. );
  558. Quaternion lightRotation = Quaternion.Euler(
  559. float.Parse(lightProperties[4]), float.Parse(lightProperties[5]), 18f
  560. );
  561. float intensity = float.Parse(lightProperties[6]);
  562. float spotAngle = float.Parse(lightProperties[7]);
  563. float range = lightType == 2 ? spotAngle / 5f : spotAngle;
  564. float shadowStrength = 0.098f;
  565. for (int j = 0; j < 3; j++)
  566. {
  567. if (j == lightType)
  568. {
  569. SerializeLightProperty(
  570. binaryWriter, lightRotation, lightColour, intensity, spotAngle, range, shadowStrength
  571. );
  572. }
  573. else SerializeDefaultLight(binaryWriter);
  574. }
  575. if (strArray7 != null)
  576. {
  577. binaryWriter.WriteVector3(Utility.Vector3String(strArray7[i + 1]));
  578. }
  579. else binaryWriter.WriteVector3(new Vector3(0f, 1.9f, 0.4f));
  580. binaryWriter.Write(lightType == 3 ? 0 : lightType);
  581. binaryWriter.Write(false);
  582. binaryWriter.Write(lightType == 3);
  583. }
  584. }
  585. }
  586. private static void SerializeEnvironment(string[] data, BinaryWriter binaryWriter, bool kankyo)
  587. {
  588. binaryWriter.Write(Plugin.EnvironmentManager.header);
  589. string bgAsset = "Theater";
  590. if (!int.TryParse(data[2], out _))
  591. {
  592. bgAsset = data[2].Replace(" ", "_");
  593. }
  594. binaryWriter.Write(bgAsset);
  595. binaryWriter.WriteVector3(new Vector3(
  596. float.Parse(data[6]), float.Parse(data[7]), float.Parse(data[8])
  597. ));
  598. binaryWriter.WriteQuaternion(Quaternion.Euler(
  599. float.Parse(data[3]), float.Parse(data[4]), float.Parse(data[5])
  600. ));
  601. binaryWriter.WriteVector3(new Vector3(
  602. float.Parse(data[9]), float.Parse(data[10]), float.Parse(data[11])
  603. ));
  604. binaryWriter.Write(kankyo);
  605. Vector3 cameraTargetPos = new Vector3(0f, 0.9f, 0f);
  606. float cameraDistance = 3f;
  607. Quaternion cameraRotation = Quaternion.identity;
  608. if (data.Length > 16)
  609. {
  610. cameraTargetPos = new Vector3(
  611. float.Parse(data[27]), float.Parse(data[28]), float.Parse(data[29])
  612. );
  613. cameraDistance = float.Parse(data[30]);
  614. cameraRotation = Quaternion.Euler(
  615. float.Parse(data[31]), float.Parse(data[32]), float.Parse(data[33])
  616. );
  617. }
  618. binaryWriter.WriteVector3(cameraTargetPos);
  619. binaryWriter.Write(cameraDistance);
  620. binaryWriter.WriteQuaternion(cameraRotation);
  621. }
  622. public static void SerializeAttachPoint(BinaryWriter binaryWriter)
  623. {
  624. binaryWriter.Write(0);
  625. binaryWriter.Write(-1);
  626. }
  627. public static void SerializeDefaultLight(BinaryWriter binaryWriter)
  628. {
  629. SerializeLightProperty(binaryWriter, Quaternion.Euler(40f, 180f, 0f), Color.white);
  630. }
  631. public static void SerializeLightProperty(
  632. BinaryWriter binaryWriter,
  633. Quaternion rotation, Color colour, float intensity = 0.95f, float range = 50f,
  634. float spotAngle = 50f, float shadowStrength = 0.1f
  635. )
  636. {
  637. binaryWriter.WriteQuaternion(rotation);
  638. binaryWriter.Write(intensity);
  639. binaryWriter.Write(range);
  640. binaryWriter.Write(spotAngle);
  641. binaryWriter.Write(shadowStrength);
  642. binaryWriter.WriteColour(colour);
  643. }
  644. }
  645. public static class Utility
  646. {
  647. public static Quaternion EulerString(string euler)
  648. {
  649. string[] data = euler.Split(',');
  650. return Quaternion.Euler(
  651. float.Parse(data[0]), float.Parse(data[1]), float.Parse(data[2])
  652. );
  653. }
  654. public static Vector3 Vector3String(string vector3)
  655. {
  656. string[] data = vector3.Split(',');
  657. return new Vector3(
  658. float.Parse(data[0]), float.Parse(data[1]), float.Parse(data[2])
  659. );
  660. }
  661. }
  662. }