GamePlugin.cs 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773
  1. //
  2. // Copyright 2020 Electronic Arts Inc.
  3. //
  4. // The Command & Conquer Map Editor and corresponding source code is free
  5. // software: you can redistribute it and/or modify it under the terms of
  6. // the GNU General Public License as published by the Free Software Foundation,
  7. // either version 3 of the License, or (at your option) any later version.
  8. // The Command & Conquer Map Editor and corresponding source code is distributed
  9. // in the hope that it will be useful, but with permitted additional restrictions
  10. // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
  11. // distributed with this program. You should have received a copy of the
  12. // GNU General Public License along with permitted additional restrictions
  13. // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
  14. using MobiusEditor.Interface;
  15. using MobiusEditor.Model;
  16. using MobiusEditor.Utility;
  17. using Newtonsoft.Json;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.ComponentModel;
  21. using System.Drawing;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Text;
  25. using System.Windows.Forms;
  26. namespace MobiusEditor.RedAlert
  27. {
  28. class GamePlugin : IGamePlugin
  29. {
  30. private static readonly IEnumerable<string> movieTypes = new string[]
  31. {
  32. "x",
  33. "AAGUN",
  34. "AFTRMATH",
  35. "AIRFIELD",
  36. "ALLIEND",
  37. "ALLY1",
  38. "ALLY10",
  39. "ALLY10B",
  40. "ALLY11",
  41. "ALLY12",
  42. "ALLY14",
  43. "ALLY2",
  44. "ALLY4",
  45. "ALLY5",
  46. "ALLY6",
  47. "ALLY8",
  48. "ALLY9",
  49. "ALLYMORF",
  50. "ANTEND",
  51. "ANTINTRO",
  52. "APCESCPE",
  53. "ASSESS",
  54. "AVERTED",
  55. "BATTLE",
  56. "BEACHEAD",
  57. "BINOC",
  58. "BMAP",
  59. "BOMBRUN",
  60. "BRDGTILT",
  61. "COUNTDWN",
  62. "CRONFAIL",
  63. "CRONTEST",
  64. "DESTROYR",
  65. "DOUBLE",
  66. "DPTHCHRG",
  67. "DUD",
  68. "ELEVATOR",
  69. "ENGLISH",
  70. "EXECUTE",
  71. "FLARE",
  72. "FROZEN",
  73. "GRVESTNE",
  74. "LANDING",
  75. "MASASSLT",
  76. "MCV",
  77. "MCVBRDGE",
  78. "MCV_LAND",
  79. "MIG",
  80. "MONTPASS",
  81. "MOVINGIN",
  82. "MTNKFACT",
  83. "NUKESTOK",
  84. "OILDRUM",
  85. "ONTHPRWL",
  86. "OVERRUN",
  87. "PERISCOP",
  88. "PROLOG",
  89. "RADRRAID",
  90. "REDINTRO",
  91. "RETALIATION_ALLIED1",
  92. "RETALIATION_ALLIED10",
  93. "RETALIATION_ALLIED2",
  94. "RETALIATION_ALLIED3",
  95. "RETALIATION_ALLIED4",
  96. "RETALIATION_ALLIED5",
  97. "RETALIATION_ALLIED6",
  98. "RETALIATION_ALLIED7",
  99. "RETALIATION_ALLIED8",
  100. "RETALIATION_ALLIED9",
  101. "RETALIATION_ANTS",
  102. "RETALIATION_SOVIET1",
  103. "RETALIATION_SOVIET10",
  104. "RETALIATION_SOVIET2",
  105. "RETALIATION_SOVIET3",
  106. "RETALIATION_SOVIET4",
  107. "RETALIATION_SOVIET5",
  108. "RETALIATION_SOVIET6",
  109. "RETALIATION_SOVIET7",
  110. "RETALIATION_SOVIET8",
  111. "RETALIATION_SOVIET9",
  112. "RETALIATION_WINA",
  113. "RETALIATION_WINS",
  114. "SEARCH",
  115. "SFROZEN",
  116. "SHIPSINK",
  117. "SHIPYARD", // MISSING
  118. "SHORBOM1",
  119. "SHORBOM2",
  120. "SHORBOMB",
  121. "SITDUCK",
  122. "SIZZLE", //MISSING
  123. "SIZZLE2", //MISSING
  124. "SLNTSRVC",
  125. "SNOWBASE",
  126. "SNOWBOMB",
  127. "SNSTRAFE",
  128. "SOVBATL",
  129. "SOVCEMET",
  130. "SOVFINAL",
  131. "SOVIET1",
  132. "SOVIET10",
  133. "SOVIET11",
  134. "SOVIET12",
  135. "SOVIET13",
  136. "SOVIET14",
  137. "SOVIET2",
  138. "SOVIET3",
  139. "SOVIET4",
  140. "SOVIET5",
  141. "SOVIET6",
  142. "SOVIET7",
  143. "SOVIET8",
  144. "SOVIET9",
  145. "SOVMCV",
  146. "SOVTSTAR",
  147. "SPOTTER",
  148. "SPY",
  149. "STRAFE",
  150. "TAKE_OFF",
  151. "TANYA1",
  152. "TANYA2",
  153. "TESLA",
  154. "TOOFAR",
  155. "TRINITY",
  156. "V2ROCKET",
  157. };
  158. private static readonly IEnumerable<ITechnoType> technoTypes;
  159. public GameType GameType => GameType.RedAlert;
  160. public Map Map { get; }
  161. public Image MapImage { get; private set; }
  162. public bool Dirty { get; set; }
  163. private INISectionCollection extraSections;
  164. static GamePlugin()
  165. {
  166. technoTypes = InfantryTypes.GetTypes().Cast<ITechnoType>().Concat(UnitTypes.GetTypes().Cast<ITechnoType>());
  167. }
  168. public GamePlugin(bool mapImage)
  169. {
  170. var playerWaypoints = Enumerable.Range(0, 8).Select(i => new Waypoint(string.Format("P{0}", i), WaypointFlag.PlayerStart));
  171. var generalWaypoints = Enumerable.Range(8, 90).Select(i => new Waypoint(i.ToString()));
  172. var specialWaypoints = new Waypoint[] { new Waypoint("Home"), new Waypoint("Reinf."), new Waypoint("Special") };
  173. var waypoints = playerWaypoints.Concat(generalWaypoints).Concat(specialWaypoints);
  174. var basicSection = new BasicSection();
  175. basicSection.SetDefault();
  176. var houseTypes = HouseTypes.GetTypes();
  177. basicSection.Player = houseTypes.First().Name;
  178. Map = new Map(basicSection, null, Constants.MaxSize, typeof(House),
  179. houseTypes, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(), TerrainTypes.GetTypes(),
  180. OverlayTypes.GetTypes(), SmudgeTypes.GetTypes(), EventTypes.GetTypes(), ActionTypes.GetTypes(),
  181. MissionTypes.GetTypes(), DirectionTypes.GetTypes(), InfantryTypes.GetTypes(), UnitTypes.GetTypes(),
  182. BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(), waypoints, movieTypes)
  183. {
  184. TiberiumOrGoldValue = 35,
  185. GemValue = 110
  186. };
  187. Map.BasicSection.PropertyChanged += BasicSection_PropertyChanged;
  188. Map.MapSection.PropertyChanged += MapSection_PropertyChanged;
  189. if (mapImage)
  190. {
  191. MapImage = new Bitmap(Map.Metrics.Width * Globals.TileWidth, Map.Metrics.Height * Globals.TileHeight);
  192. }
  193. }
  194. public GamePlugin()
  195. : this(true)
  196. {
  197. }
  198. public void New(string theater)
  199. {
  200. Map.Theater = Map.TheaterTypes.Where(t => t.Equals(theater)).FirstOrDefault() ?? TheaterTypes.Temperate;
  201. Map.TopLeft = new Point(1, 1);
  202. Map.Size = Map.Metrics.Size - new Size(2, 2);
  203. UpdateBasePlayerHouse();
  204. Dirty = true;
  205. }
  206. public IEnumerable<string> Load(string path, FileType fileType)
  207. {
  208. var errors = new List<string>();
  209. switch (fileType)
  210. {
  211. case FileType.INI:
  212. case FileType.BIN:
  213. {
  214. var ini = new INI();
  215. using (var reader = new StreamReader(path))
  216. {
  217. ini.Parse(reader);
  218. }
  219. errors.AddRange(LoadINI(ini));
  220. }
  221. break;
  222. case FileType.MEG:
  223. case FileType.PGM:
  224. {
  225. using (var megafile = new Megafile(path))
  226. {
  227. var mprFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".mpr").FirstOrDefault();
  228. if (mprFile != null)
  229. {
  230. var ini = new INI();
  231. using (var reader = new StreamReader(megafile.Open(mprFile)))
  232. {
  233. ini.Parse(reader);
  234. }
  235. errors.AddRange(LoadINI(ini));
  236. }
  237. }
  238. }
  239. break;
  240. default:
  241. throw new NotSupportedException();
  242. }
  243. return errors;
  244. }
  245. private IEnumerable<string> LoadINI(INI ini)
  246. {
  247. var errors = new List<string>();
  248. Map.BeginUpdate();
  249. var basicSection = ini.Sections.Extract("Basic");
  250. if (basicSection != null)
  251. {
  252. INI.ParseSection(new MapContext(Map, true), basicSection, Map.BasicSection);
  253. }
  254. Map.BasicSection.Player = Map.HouseTypes.Where(t => t.Equals(Map.BasicSection.Player)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name;
  255. Map.BasicSection.BasePlayer = HouseTypes.GetBasePlayer(Map.BasicSection.Player);
  256. var mapSection = ini.Sections.Extract("Map");
  257. if (mapSection != null)
  258. {
  259. INI.ParseSection(new MapContext(Map, true), mapSection, Map.MapSection);
  260. }
  261. var steamSection = ini.Sections.Extract("Steam");
  262. if (steamSection != null)
  263. {
  264. INI.ParseSection(new MapContext(Map, true), steamSection, Map.SteamSection);
  265. }
  266. string indexToType(IList<string> list, string index)
  267. {
  268. return (int.TryParse(index, out int result) && (result >= 0) && (result < list.Count)) ? list[result] : list.First();
  269. }
  270. var teamTypesSection = ini.Sections.Extract("TeamTypes");
  271. if (teamTypesSection != null)
  272. {
  273. foreach (var (Key, Value) in teamTypesSection)
  274. {
  275. try
  276. {
  277. var teamType = new TeamType { Name = Key };
  278. var tokens = Value.Split(',').ToList();
  279. teamType.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[0]))).FirstOrDefault(); tokens.RemoveAt(0);
  280. var flags = int.Parse(tokens[0]); tokens.RemoveAt(0);
  281. teamType.IsRoundAbout = (flags & 0x01) != 0;
  282. teamType.IsSuicide = (flags & 0x02) != 0;
  283. teamType.IsAutocreate = (flags & 0x04) != 0;
  284. teamType.IsPrebuilt = (flags & 0x08) != 0;
  285. teamType.IsReinforcable = (flags & 0x10) != 0;
  286. teamType.RecruitPriority = int.Parse(tokens[0]); tokens.RemoveAt(0);
  287. teamType.InitNum = byte.Parse(tokens[0]); tokens.RemoveAt(0);
  288. teamType.MaxAllowed = byte.Parse(tokens[0]); tokens.RemoveAt(0);
  289. teamType.Origin = int.Parse(tokens[0]) + 1; tokens.RemoveAt(0);
  290. teamType.Trigger = tokens[0]; tokens.RemoveAt(0);
  291. var numClasses = int.Parse(tokens[0]); tokens.RemoveAt(0);
  292. for (int i = 0; i < Math.Min(Globals.MaxTeamClasses, numClasses); ++i)
  293. {
  294. var classTokens = tokens[0].Split(':'); tokens.RemoveAt(0);
  295. if (classTokens.Length == 2)
  296. {
  297. var type = technoTypes.Where(t => t.Equals(classTokens[0])).FirstOrDefault();
  298. var count = byte.Parse(classTokens[1]);
  299. if (type != null)
  300. {
  301. teamType.Classes.Add(new TeamTypeClass { Type = type, Count = count });
  302. }
  303. else
  304. {
  305. errors.Add(string.Format("Team '{0}' references unknown class '{1}'", Key, classTokens[0]));
  306. }
  307. }
  308. else
  309. {
  310. errors.Add(string.Format("Team '{0}' has wrong number of tokens for class index {1} (expecting 2)", Key, i));
  311. }
  312. }
  313. var numMissions = int.Parse(tokens[0]); tokens.RemoveAt(0);
  314. for (int i = 0; i < Math.Min(Globals.MaxTeamMissions, numMissions); ++i)
  315. {
  316. var missionTokens = tokens[0].Split(':'); tokens.RemoveAt(0);
  317. if (missionTokens.Length == 2)
  318. {
  319. teamType.Missions.Add(new TeamTypeMission { Mission = indexToType(Map.TeamMissionTypes, missionTokens[0]), Argument = int.Parse(missionTokens[1]) });
  320. }
  321. else
  322. {
  323. errors.Add(string.Format("Team '{0}' has wrong number of tokens for mission index {1} (expecting 2)", Key, i));
  324. }
  325. }
  326. Map.TeamTypes.Add(teamType);
  327. }
  328. catch (ArgumentOutOfRangeException) { }
  329. }
  330. }
  331. var triggersSection = ini.Sections.Extract("Trigs");
  332. if (triggersSection != null)
  333. {
  334. foreach (var (Key, Value) in triggersSection)
  335. {
  336. var tokens = Value.Split(',');
  337. if (tokens.Length == 18)
  338. {
  339. var trigger = new Trigger { Name = Key };
  340. trigger.PersistantType = (TriggerPersistantType)int.Parse(tokens[0]);
  341. trigger.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[1]))).FirstOrDefault()?.Name ?? "None";
  342. trigger.EventControl = (TriggerMultiStyleType)int.Parse(tokens[2]);
  343. trigger.Event1.EventType = indexToType(Map.EventTypes, tokens[4]);
  344. trigger.Event1.Team = tokens[5];
  345. trigger.Event1.Data = long.Parse(tokens[6]);
  346. trigger.Event2.EventType = indexToType(Map.EventTypes, tokens[7]);
  347. trigger.Event2.Team = tokens[8];
  348. trigger.Event2.Data = long.Parse(tokens[9]);
  349. trigger.Action1.ActionType = indexToType(Map.ActionTypes, tokens[10]);
  350. trigger.Action1.Team = tokens[11];
  351. trigger.Action1.Trigger = tokens[12];
  352. trigger.Action1.Data = long.Parse(tokens[13]);
  353. trigger.Action2.ActionType = indexToType(Map.ActionTypes, tokens[14]);
  354. trigger.Action2.Team = tokens[15];
  355. trigger.Action2.Trigger = tokens[16];
  356. trigger.Action2.Data = long.Parse(tokens[17]);
  357. // Fix up data caused by union usage in the legacy game
  358. Action<TriggerEvent> fixEvent = (TriggerEvent e) =>
  359. {
  360. switch (e.EventType)
  361. {
  362. case EventTypes.TEVENT_THIEVED:
  363. case EventTypes.TEVENT_PLAYER_ENTERED:
  364. case EventTypes.TEVENT_CROSS_HORIZONTAL:
  365. case EventTypes.TEVENT_CROSS_VERTICAL:
  366. case EventTypes.TEVENT_ENTERS_ZONE:
  367. case EventTypes.TEVENT_HOUSE_DISCOVERED:
  368. case EventTypes.TEVENT_BUILDINGS_DESTROYED:
  369. case EventTypes.TEVENT_UNITS_DESTROYED:
  370. case EventTypes.TEVENT_ALL_DESTROYED:
  371. case EventTypes.TEVENT_LOW_POWER:
  372. case EventTypes.TEVENT_BUILDING_EXISTS:
  373. case EventTypes.TEVENT_BUILD:
  374. case EventTypes.TEVENT_BUILD_UNIT:
  375. case EventTypes.TEVENT_BUILD_INFANTRY:
  376. case EventTypes.TEVENT_BUILD_AIRCRAFT:
  377. e.Data &= 0xFF;
  378. break;
  379. default:
  380. break;
  381. }
  382. };
  383. Action<TriggerAction> fixAction = (TriggerAction a) =>
  384. {
  385. switch (a.ActionType)
  386. {
  387. case ActionTypes.TACTION_1_SPECIAL:
  388. case ActionTypes.TACTION_FULL_SPECIAL:
  389. case ActionTypes.TACTION_FIRE_SALE:
  390. case ActionTypes.TACTION_WIN:
  391. case ActionTypes.TACTION_LOSE:
  392. case ActionTypes.TACTION_ALL_HUNT:
  393. case ActionTypes.TACTION_BEGIN_PRODUCTION:
  394. case ActionTypes.TACTION_AUTOCREATE:
  395. case ActionTypes.TACTION_BASE_BUILDING:
  396. case ActionTypes.TACTION_CREATE_TEAM:
  397. case ActionTypes.TACTION_DESTROY_TEAM:
  398. case ActionTypes.TACTION_REINFORCEMENTS:
  399. case ActionTypes.TACTION_FORCE_TRIGGER:
  400. case ActionTypes.TACTION_DESTROY_TRIGGER:
  401. case ActionTypes.TACTION_DZ:
  402. case ActionTypes.TACTION_REVEAL_SOME:
  403. case ActionTypes.TACTION_REVEAL_ZONE:
  404. case ActionTypes.TACTION_PLAY_MUSIC:
  405. case ActionTypes.TACTION_PLAY_MOVIE:
  406. case ActionTypes.TACTION_PLAY_SOUND:
  407. case ActionTypes.TACTION_PLAY_SPEECH:
  408. case ActionTypes.TACTION_PREFERRED_TARGET:
  409. a.Data &= 0xFF;
  410. break;
  411. case ActionTypes.TACTION_TEXT_TRIGGER:
  412. a.Data = Math.Max(1, Math.Min(209, a.Data));
  413. break;
  414. default:
  415. break;
  416. }
  417. };
  418. fixEvent(trigger.Event1);
  419. fixEvent(trigger.Event2);
  420. fixAction(trigger.Action1);
  421. fixAction(trigger.Action2);
  422. Map.Triggers.Add(trigger);
  423. }
  424. else
  425. {
  426. errors.Add(string.Format("Trigger '{0}' has too few tokens (expecting 18)", Key));
  427. }
  428. }
  429. }
  430. var mapPackSection = ini.Sections.Extract("MapPack");
  431. if (mapPackSection != null)
  432. {
  433. Map.Templates.Clear();
  434. var data = DecompressLCWSection(mapPackSection, 3);
  435. using (var reader = new BinaryReader(new MemoryStream(data)))
  436. {
  437. for (var y = 0; y < Map.Metrics.Height; ++y)
  438. {
  439. for (var x = 0; x < Map.Metrics.Width; ++x)
  440. {
  441. var typeValue = reader.ReadUInt16();
  442. var templateType = Map.TemplateTypes.Where(t => t.Equals(typeValue)).FirstOrDefault();
  443. if ((templateType != null) && !templateType.Theaters.Contains(Map.Theater))
  444. {
  445. templateType = null;
  446. }
  447. Map.Templates[x, y] = (templateType != null) ? new Template { Type = templateType } : null;
  448. }
  449. }
  450. for (var y = 0; y < Map.Metrics.Height; ++y)
  451. {
  452. for (var x = 0; x < Map.Metrics.Width; ++x)
  453. {
  454. var iconValue = reader.ReadByte();
  455. var template = Map.Templates[x, y];
  456. if (template != null)
  457. {
  458. if ((template.Type != TemplateTypes.Clear) && (iconValue >= template.Type.NumIcons))
  459. {
  460. Map.Templates[x, y] = null;
  461. }
  462. else
  463. {
  464. template.Icon = iconValue;
  465. }
  466. }
  467. }
  468. }
  469. }
  470. }
  471. var terrainSection = ini.Sections.Extract("Terrain");
  472. if (terrainSection != null)
  473. {
  474. foreach (var (Key, Value) in terrainSection)
  475. {
  476. var cell = int.Parse(Key);
  477. var name = Value.Split(',')[0];
  478. var terrainType = Map.TerrainTypes.Where(t => t.Equals(name)).FirstOrDefault();
  479. if (terrainType != null)
  480. {
  481. if (!Map.Technos.Add(cell, new Terrain
  482. {
  483. Type = terrainType,
  484. Icon = terrainType.IsTransformable ? 22 : 0,
  485. Trigger = Trigger.None
  486. }))
  487. {
  488. var techno = Map.Technos[cell];
  489. if (techno is Building building)
  490. {
  491. errors.Add(string.Format("Terrain '{0}' overlaps structure '{1}' in cell {2}, skipping", name, building.Type.Name, cell));
  492. }
  493. else if (techno is Overlay overlay)
  494. {
  495. errors.Add(string.Format("Terrain '{0}' overlaps overlay '{1}' in cell {2}, skipping", name, overlay.Type.Name, cell));
  496. }
  497. else if (techno is Terrain terrain)
  498. {
  499. errors.Add(string.Format("Terrain '{0}' overlaps terrain '{1}' in cell {2}, skipping", name, terrain.Type.Name, cell));
  500. }
  501. else if (techno is InfantryGroup infantry)
  502. {
  503. errors.Add(string.Format("Terrain '{0}' overlaps infantry in cell {1}, skipping", name, cell));
  504. }
  505. else if (techno is Unit unit)
  506. {
  507. errors.Add(string.Format("Terrain '{0}' overlaps unit '{1}' in cell {2}, skipping", name, unit.Type.Name, cell));
  508. }
  509. else
  510. {
  511. errors.Add(string.Format("Terrain '{0}' overlaps unknown techno in cell {1}, skipping", name, cell));
  512. }
  513. }
  514. }
  515. else
  516. {
  517. errors.Add(string.Format("Terrain '{0}' references unknown terrain", name));
  518. }
  519. }
  520. }
  521. var overlayPackSection = ini.Sections.Extract("OverlayPack");
  522. if (overlayPackSection != null)
  523. {
  524. Map.Overlay.Clear();
  525. var data = DecompressLCWSection(overlayPackSection, 1);
  526. using (var reader = new BinaryReader(new MemoryStream(data)))
  527. {
  528. for (var i = 0; i < Map.Metrics.Length; ++i)
  529. {
  530. var overlayId = reader.ReadSByte();
  531. if (overlayId >= 0)
  532. {
  533. var overlayType = Map.OverlayTypes.Where(t => t.Equals(overlayId)).FirstOrDefault();
  534. if (overlayType != null)
  535. {
  536. Map.Overlay[i] = new Overlay { Type = overlayType, Icon = 0 };
  537. }
  538. else
  539. {
  540. errors.Add(string.Format("Overlay ID {0} references unknown overlay", overlayId));
  541. }
  542. }
  543. }
  544. }
  545. }
  546. var smudgeSection = ini.Sections.Extract("Smudge");
  547. if (smudgeSection != null)
  548. {
  549. foreach (var (Key, Value) in smudgeSection)
  550. {
  551. var cell = int.Parse(Key);
  552. var tokens = Value.Split(',');
  553. if (tokens.Length == 3)
  554. {
  555. var smudgeType = Map.SmudgeTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault();
  556. if (smudgeType != null)
  557. {
  558. if ((smudgeType.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None)
  559. {
  560. Map.Smudge[cell] = new Smudge
  561. {
  562. Type = smudgeType,
  563. Icon = 0,
  564. Data = int.Parse(tokens[2])
  565. };
  566. }
  567. else
  568. {
  569. errors.Add(string.Format("Smudge '{0}' is a bib, skipped", tokens[0]));
  570. }
  571. }
  572. else
  573. {
  574. errors.Add(string.Format("Smudge '{0}' references unknown smudge", tokens[0]));
  575. }
  576. }
  577. }
  578. }
  579. var unitsSection = ini.Sections.Extract("Units");
  580. if (unitsSection != null)
  581. {
  582. foreach (var (_, Value) in unitsSection)
  583. {
  584. var tokens = Value.Split(',');
  585. if (tokens.Length == 7)
  586. {
  587. var unitType = Map.UnitTypes.Where(t => t.IsUnit && t.Equals(tokens[1])).FirstOrDefault();
  588. if (unitType != null)
  589. {
  590. var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
  591. var cell = int.Parse(tokens[3]);
  592. if (!Map.Technos.Add(cell, new Unit()
  593. {
  594. Type = unitType,
  595. House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
  596. Strength = int.Parse(tokens[2]),
  597. Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
  598. Mission = tokens[5],
  599. Trigger = tokens[6]
  600. }))
  601. {
  602. var techno = Map.Technos[cell];
  603. if (techno is Building building)
  604. {
  605. errors.Add(string.Format("Unit '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
  606. }
  607. else if (techno is Overlay overlay)
  608. {
  609. errors.Add(string.Format("Unit '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
  610. }
  611. else if (techno is Terrain terrain)
  612. {
  613. errors.Add(string.Format("Unit '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
  614. }
  615. else if (techno is InfantryGroup infantry)
  616. {
  617. errors.Add(string.Format("Unit '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
  618. }
  619. else if (techno is Unit unit)
  620. {
  621. errors.Add(string.Format("Unit '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
  622. }
  623. else
  624. {
  625. errors.Add(string.Format("Unit '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
  626. }
  627. }
  628. }
  629. else
  630. {
  631. errors.Add(string.Format("Unit '{0}' references unknown unit", tokens[1]));
  632. }
  633. }
  634. else
  635. {
  636. errors.Add(string.Format("Unit '{0}' has wrong number of tokens (expecting 7)", tokens[1]));
  637. }
  638. }
  639. }
  640. var aircraftSections = ini.Sections.Extract("Aircraft");
  641. if (aircraftSections != null)
  642. {
  643. foreach (var (_, Value) in aircraftSections)
  644. {
  645. var tokens = Value.Split(',');
  646. if (tokens.Length == 6)
  647. {
  648. var aircraftType = Map.UnitTypes.Where(t => t.IsAircraft && t.Equals(tokens[1])).FirstOrDefault();
  649. if (aircraftType != null)
  650. {
  651. var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
  652. var cell = int.Parse(tokens[3]);
  653. if (!Map.Technos.Add(cell, new Unit()
  654. {
  655. Type = aircraftType,
  656. House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
  657. Strength = int.Parse(tokens[2]),
  658. Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
  659. Mission = tokens[5]
  660. }))
  661. {
  662. var techno = Map.Technos[cell];
  663. if (techno is Building building)
  664. {
  665. errors.Add(string.Format("Aircraft '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
  666. }
  667. else if (techno is Overlay overlay)
  668. {
  669. errors.Add(string.Format("Aircraft '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
  670. }
  671. else if (techno is Terrain terrain)
  672. {
  673. errors.Add(string.Format("Aircraft '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
  674. }
  675. else if (techno is InfantryGroup infantry)
  676. {
  677. errors.Add(string.Format("Aircraft '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
  678. }
  679. else if (techno is Unit unit)
  680. {
  681. errors.Add(string.Format("Aircraft '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
  682. }
  683. else
  684. {
  685. errors.Add(string.Format("Aircraft '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
  686. }
  687. }
  688. }
  689. else
  690. {
  691. errors.Add(string.Format("Aircraft '{0}' references unknown aircraft", tokens[1]));
  692. }
  693. }
  694. else
  695. {
  696. errors.Add(string.Format("Aircraft '{0}' has wrong number of tokens (expecting 6)", tokens[1]));
  697. }
  698. }
  699. }
  700. var shipsSection = ini.Sections.Extract("Ships");
  701. if (shipsSection != null)
  702. {
  703. foreach (var (_, Value) in shipsSection)
  704. {
  705. var tokens = Value.Split(',');
  706. if (tokens.Length == 7)
  707. {
  708. var vesselType = Map.UnitTypes.Where(t => t.IsVessel && t.Equals(tokens[1])).FirstOrDefault();
  709. if (vesselType != null)
  710. {
  711. var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
  712. var cell = int.Parse(tokens[3]);
  713. if (!Map.Technos.Add(cell, new Unit()
  714. {
  715. Type = vesselType,
  716. House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
  717. Strength = int.Parse(tokens[2]),
  718. Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
  719. Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(),
  720. Trigger = tokens[6]
  721. }))
  722. {
  723. var techno = Map.Technos[cell];
  724. if (techno is Building building)
  725. {
  726. errors.Add(string.Format("Ship '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
  727. }
  728. else if (techno is Overlay overlay)
  729. {
  730. errors.Add(string.Format("Ship '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
  731. }
  732. else if (techno is Terrain terrain)
  733. {
  734. errors.Add(string.Format("Ship '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
  735. }
  736. else if (techno is InfantryGroup infantry)
  737. {
  738. errors.Add(string.Format("Ship '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
  739. }
  740. else if (techno is Unit unit)
  741. {
  742. errors.Add(string.Format("Ship '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
  743. }
  744. else
  745. {
  746. errors.Add(string.Format("Ship '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
  747. }
  748. }
  749. }
  750. else
  751. {
  752. errors.Add(string.Format("Ship '{0}' references unknown ship", tokens[1]));
  753. }
  754. }
  755. else
  756. {
  757. errors.Add(string.Format("Ship '{0}' has wrong number of tokens (expecting 7)", tokens[1]));
  758. }
  759. }
  760. }
  761. var infantrySections = ini.Sections.Extract("Infantry");
  762. if (infantrySections != null)
  763. {
  764. foreach (var (_, Value) in infantrySections)
  765. {
  766. var tokens = Value.Split(',');
  767. if (tokens.Length == 8)
  768. {
  769. var infantryType = Map.InfantryTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault();
  770. if (infantryType != null)
  771. {
  772. var cell = int.Parse(tokens[3]);
  773. var infantryGroup = Map.Technos[cell] as InfantryGroup;
  774. if ((infantryGroup == null) && (Map.Technos[cell] == null))
  775. {
  776. infantryGroup = new InfantryGroup();
  777. Map.Technos.Add(cell, infantryGroup);
  778. }
  779. if (infantryGroup != null)
  780. {
  781. var stoppingPos = int.Parse(tokens[4]);
  782. if (stoppingPos < Globals.NumInfantryStops)
  783. {
  784. var direction = (byte)((int.Parse(tokens[6]) + 0x08) & ~0x0F);
  785. if (infantryGroup.Infantry[stoppingPos] == null)
  786. {
  787. infantryGroup.Infantry[stoppingPos] = new Infantry(infantryGroup)
  788. {
  789. Type = infantryType,
  790. House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
  791. Strength = int.Parse(tokens[2]),
  792. Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
  793. Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(),
  794. Trigger = tokens[7]
  795. };
  796. }
  797. else
  798. {
  799. errors.Add(string.Format("Infantry '{0}' overlaps another infantry at position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell));
  800. }
  801. }
  802. else
  803. {
  804. errors.Add(string.Format("Infantry '{0}' has invalid position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell));
  805. }
  806. }
  807. else
  808. {
  809. var techno = Map.Technos[cell];
  810. if (techno is Building building)
  811. {
  812. errors.Add(string.Format("Infantry '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
  813. }
  814. else if (techno is Overlay overlay)
  815. {
  816. errors.Add(string.Format("Infantry '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
  817. }
  818. else if (techno is Terrain terrain)
  819. {
  820. errors.Add(string.Format("Infantry '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
  821. }
  822. else if (techno is Unit unit)
  823. {
  824. errors.Add(string.Format("Infantry '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
  825. }
  826. else
  827. {
  828. errors.Add(string.Format("Infantry '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
  829. }
  830. }
  831. }
  832. else
  833. {
  834. errors.Add(string.Format("Infantry '{0}' references unknown infantry", tokens[1]));
  835. }
  836. }
  837. else
  838. {
  839. errors.Add(string.Format("Infantry '{0}' has wrong number of tokens (expecting 8)", tokens[1]));
  840. }
  841. }
  842. }
  843. var structuresSection = ini.Sections.Extract("Structures");
  844. if (structuresSection != null)
  845. {
  846. foreach (var (_, Value) in structuresSection)
  847. {
  848. var tokens = Value.Split(',');
  849. if (tokens.Length >= 6)
  850. {
  851. var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault();
  852. if (buildingType != null)
  853. {
  854. var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
  855. bool sellable = (tokens.Length >= 7) ? (int.Parse(tokens[6]) != 0) : false;
  856. bool rebuild = (tokens.Length >= 8) ? (int.Parse(tokens[7]) != 0) : false;
  857. var cell = int.Parse(tokens[3]);
  858. if (!Map.Buildings.Add(cell, new Building()
  859. {
  860. Type = buildingType,
  861. House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
  862. Strength = int.Parse(tokens[2]),
  863. Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
  864. Trigger = tokens[5],
  865. Sellable = sellable,
  866. Rebuild = rebuild
  867. }))
  868. {
  869. var techno = Map.Technos[cell];
  870. if (techno is Building building)
  871. {
  872. errors.Add(string.Format("Structure '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
  873. }
  874. else if (techno is Overlay overlay)
  875. {
  876. errors.Add(string.Format("Structure '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
  877. }
  878. else if (techno is Terrain terrain)
  879. {
  880. errors.Add(string.Format("Structure '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
  881. }
  882. else if (techno is InfantryGroup infantry)
  883. {
  884. errors.Add(string.Format("Structure '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
  885. }
  886. else if (techno is Unit unit)
  887. {
  888. errors.Add(string.Format("Structure '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
  889. }
  890. else
  891. {
  892. errors.Add(string.Format("Structure '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
  893. }
  894. }
  895. }
  896. else
  897. {
  898. errors.Add(string.Format("Structure '{0}' references unknown structure", tokens[1]));
  899. }
  900. }
  901. else
  902. {
  903. errors.Add(string.Format("Structure '{0}' has wrong number of tokens (expecting 6)", tokens[1]));
  904. }
  905. }
  906. }
  907. var baseSection = ini.Sections.Extract("Base");
  908. if (baseSection != null)
  909. {
  910. foreach (var (Key, Value) in baseSection)
  911. {
  912. if (Key.Equals("Player", StringComparison.OrdinalIgnoreCase))
  913. {
  914. Map.BasicSection.BasePlayer = Map.HouseTypes.Where(t => t.Equals(Value)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name;
  915. }
  916. else if (int.TryParse(Key, out int priority))
  917. {
  918. var tokens = Value.Split(',');
  919. if (tokens.Length == 2)
  920. {
  921. var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault();
  922. if (buildingType != null)
  923. {
  924. var cell = int.Parse(tokens[1]);
  925. Map.Metrics.GetLocation(cell, out Point location);
  926. if (Map.Buildings.OfType<Building>().Where(x => x.Location == location).FirstOrDefault().Occupier is Building building)
  927. {
  928. building.BasePriority = priority;
  929. }
  930. else
  931. {
  932. Map.Buildings.Add(cell, new Building()
  933. {
  934. Type = buildingType,
  935. Strength = 256,
  936. Direction = DirectionTypes.North,
  937. BasePriority = priority,
  938. IsPrebuilt = false
  939. });
  940. }
  941. }
  942. else
  943. {
  944. errors.Add(string.Format("Base priority {0} references unknown structure '{1}'", priority, tokens[0]));
  945. }
  946. }
  947. else
  948. {
  949. errors.Add(string.Format("Base priority {0} has wrong number of tokens (expecting 2)", priority));
  950. }
  951. }
  952. else if (!Key.Equals("Count", StringComparison.CurrentCultureIgnoreCase))
  953. {
  954. errors.Add(string.Format("Invalid base priority '{0}' (expecting integer)", Key));
  955. }
  956. }
  957. }
  958. var waypointsSection = ini.Sections.Extract("Waypoints");
  959. if (waypointsSection != null)
  960. {
  961. foreach (var (Key, Value) in waypointsSection)
  962. {
  963. if (int.TryParse(Key, out int waypoint))
  964. {
  965. if (int.TryParse(Value, out int cell))
  966. {
  967. if ((waypoint >= 0) && (waypoint < Map.Waypoints.Length))
  968. {
  969. if (Map.Metrics.Contains(cell))
  970. {
  971. Map.Waypoints[waypoint].Cell = cell;
  972. }
  973. else
  974. {
  975. Map.Waypoints[waypoint].Cell = null;
  976. if (cell != -1)
  977. {
  978. errors.Add(string.Format("Waypoint {0} cell value {1} out of range (expecting between {2} and {3})", waypoint, cell, 0, Map.Metrics.Length - 1));
  979. }
  980. }
  981. }
  982. else if (cell != -1)
  983. {
  984. errors.Add(string.Format("Waypoint {0} out of range (expecting between {1} and {2})", waypoint, 0, Map.Waypoints.Length - 1));
  985. }
  986. }
  987. else
  988. {
  989. errors.Add(string.Format("Waypoint {0} has invalid cell '{1}' (expecting integer)", waypoint, Value));
  990. }
  991. }
  992. else
  993. {
  994. errors.Add(string.Format("Invalid waypoint '{0}' (expecting integer)", Key));
  995. }
  996. }
  997. }
  998. var cellTriggersSection = ini.Sections.Extract("CellTriggers");
  999. if (cellTriggersSection != null)
  1000. {
  1001. foreach (var (Key, Value) in cellTriggersSection)
  1002. {
  1003. if (int.TryParse(Key, out int cell))
  1004. {
  1005. if (Map.Metrics.Contains(cell))
  1006. {
  1007. Map.CellTriggers[cell] = new CellTrigger
  1008. {
  1009. Trigger = Value
  1010. };
  1011. }
  1012. else
  1013. {
  1014. errors.Add(string.Format("Cell trigger {0} outside map bounds", cell));
  1015. }
  1016. }
  1017. else
  1018. {
  1019. errors.Add(string.Format("Invalid cell trigger '{0}' (expecting integer)", Key));
  1020. }
  1021. }
  1022. }
  1023. var briefingSection = ini.Sections.Extract("Briefing");
  1024. if (briefingSection != null)
  1025. {
  1026. if (briefingSection.Keys.Contains("Text"))
  1027. {
  1028. Map.BriefingSection.Briefing = briefingSection["Text"].Replace("@", Environment.NewLine);
  1029. }
  1030. else
  1031. {
  1032. Map.BriefingSection.Briefing = string.Join(" ", briefingSection.Keys.Select(k => k.Value)).Replace("@", Environment.NewLine);
  1033. }
  1034. }
  1035. foreach (var house in Map.Houses)
  1036. {
  1037. if (house.Type.ID < 0)
  1038. {
  1039. continue;
  1040. }
  1041. var houseSection = ini.Sections.Extract(house.Type.Name);
  1042. if (houseSection != null)
  1043. {
  1044. INI.ParseSection(new MapContext(Map, true), houseSection, house);
  1045. house.Enabled = true;
  1046. }
  1047. else
  1048. {
  1049. house.Enabled = false;
  1050. }
  1051. }
  1052. string indexToName<T>(IList<T> list, string index, string defaultValue) where T : INamedType
  1053. {
  1054. return (int.TryParse(index, out int result) && (result >= 0) && (result < list.Count)) ? list[result].Name : defaultValue;
  1055. }
  1056. foreach (var teamType in Map.TeamTypes)
  1057. {
  1058. teamType.Trigger = indexToName(Map.Triggers, teamType.Trigger, Trigger.None);
  1059. }
  1060. foreach (var trigger in Map.Triggers)
  1061. {
  1062. trigger.Event1.Team = indexToName(Map.TeamTypes, trigger.Event1.Team, TeamType.None);
  1063. trigger.Event2.Team = indexToName(Map.TeamTypes, trigger.Event2.Team, TeamType.None);
  1064. trigger.Action1.Team = indexToName(Map.TeamTypes, trigger.Action1.Team, TeamType.None);
  1065. trigger.Action1.Trigger = indexToName(Map.Triggers, trigger.Action1.Trigger, Trigger.None);
  1066. trigger.Action2.Team = indexToName(Map.TeamTypes, trigger.Action2.Team, TeamType.None);
  1067. trigger.Action2.Trigger = indexToName(Map.Triggers, trigger.Action2.Trigger, Trigger.None);
  1068. }
  1069. UpdateBasePlayerHouse();
  1070. extraSections = ini.Sections;
  1071. Map.EndUpdate();
  1072. return errors;
  1073. }
  1074. public bool Save(string path, FileType fileType)
  1075. {
  1076. if (!Validate())
  1077. {
  1078. return false;
  1079. }
  1080. switch (fileType)
  1081. {
  1082. case FileType.INI:
  1083. case FileType.BIN:
  1084. {
  1085. var mprPath = Path.ChangeExtension(path, ".mpr");
  1086. var tgaPath = Path.ChangeExtension(path, ".tga");
  1087. var jsonPath = Path.ChangeExtension(path, ".json");
  1088. var ini = new INI();
  1089. using (var mprWriter = new StreamWriter(mprPath))
  1090. using (var tgaStream = new FileStream(tgaPath, FileMode.Create))
  1091. using (var jsonStream = new FileStream(jsonPath, FileMode.Create))
  1092. using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream)))
  1093. {
  1094. SaveINI(ini, fileType);
  1095. mprWriter.Write(ini.ToString());
  1096. SaveMapPreview(tgaStream);
  1097. SaveJSON(jsonWriter);
  1098. }
  1099. }
  1100. break;
  1101. case FileType.MEG:
  1102. case FileType.PGM:
  1103. {
  1104. using (var iniStream = new MemoryStream())
  1105. using (var tgaStream = new MemoryStream())
  1106. using (var jsonStream = new MemoryStream())
  1107. using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream)))
  1108. using (var megafileBuilder = new MegafileBuilder(@"", path))
  1109. {
  1110. var ini = new INI();
  1111. SaveINI(ini, fileType);
  1112. var iniWriter = new StreamWriter(iniStream);
  1113. iniWriter.Write(ini.ToString());
  1114. iniWriter.Flush();
  1115. iniStream.Position = 0;
  1116. SaveMapPreview(tgaStream);
  1117. tgaStream.Position = 0;
  1118. SaveJSON(jsonWriter);
  1119. jsonWriter.Flush();
  1120. jsonStream.Position = 0;
  1121. var mprFile = Path.ChangeExtension(Path.GetFileName(path), ".mpr").ToUpper();
  1122. var tgaFile = Path.ChangeExtension(Path.GetFileName(path), ".tga").ToUpper();
  1123. var jsonFile = Path.ChangeExtension(Path.GetFileName(path), ".json").ToUpper();
  1124. megafileBuilder.AddFile(mprFile, iniStream);
  1125. megafileBuilder.AddFile(tgaFile, tgaStream);
  1126. megafileBuilder.AddFile(jsonFile, jsonStream);
  1127. megafileBuilder.Write();
  1128. }
  1129. }
  1130. break;
  1131. default:
  1132. throw new NotSupportedException();
  1133. }
  1134. return true;
  1135. }
  1136. private void SaveINI(INI ini, FileType fileType)
  1137. {
  1138. if (extraSections != null)
  1139. {
  1140. ini.Sections.AddRange(extraSections);
  1141. }
  1142. INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Basic"), Map.BasicSection);
  1143. INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Map"), Map.MapSection);
  1144. if (fileType != FileType.PGM)
  1145. {
  1146. INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Steam"), Map.SteamSection);
  1147. }
  1148. ini["Basic"]["NewINIFormat"] = "3";
  1149. var smudgeSection = ini.Sections.Add("SMUDGE");
  1150. foreach (var (cell, smudge) in Map.Smudge.Where(item => (item.Value.Type.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None))
  1151. {
  1152. smudgeSection[cell.ToString()] = string.Format("{0},{1},{2}", smudge.Type.Name.ToUpper(), cell, smudge.Data);
  1153. }
  1154. var terrainSection = ini.Sections.Add("TERRAIN");
  1155. foreach (var (location, terrain) in Map.Technos.OfType<Terrain>())
  1156. {
  1157. Map.Metrics.GetCell(location, out int cell);
  1158. terrainSection[cell.ToString()] = terrain.Type.Name.ToUpper();
  1159. }
  1160. var cellTriggersSection = ini.Sections.Add("CellTriggers");
  1161. foreach (var (cell, cellTrigger) in Map.CellTriggers)
  1162. {
  1163. cellTriggersSection[cell.ToString()] = cellTrigger.Trigger;
  1164. }
  1165. int nameToIndex<T>(IList<T> list, string name)
  1166. {
  1167. var index = list.TakeWhile(x => !x.Equals(name)).Count();
  1168. return (index < list.Count) ? index : -1;
  1169. }
  1170. string nameToIndexString<T>(IList<T> list, string name) => nameToIndex(list, name).ToString();
  1171. var teamTypesSection = ini.Sections.Add("TeamTypes");
  1172. foreach (var teamType in Map.TeamTypes)
  1173. {
  1174. var classes = teamType.Classes
  1175. .Select(c => string.Format("{0}:{1}", c.Type.Name.ToUpper(), c.Count))
  1176. .ToArray();
  1177. var missions = teamType.Missions
  1178. .Select(m => string.Format("{0}:{1}", nameToIndexString(Map.TeamMissionTypes, m.Mission), m.Argument))
  1179. .ToArray();
  1180. int flags = 0;
  1181. if (teamType.IsRoundAbout) flags |= 0x01;
  1182. if (teamType.IsSuicide) flags |= 0x02;
  1183. if (teamType.IsAutocreate) flags |= 0x04;
  1184. if (teamType.IsPrebuilt) flags |= 0x08;
  1185. if (teamType.IsReinforcable) flags |= 0x10;
  1186. var tokens = new List<string>
  1187. {
  1188. teamType.House.ID.ToString(),
  1189. flags.ToString(),
  1190. teamType.RecruitPriority.ToString(),
  1191. teamType.InitNum.ToString(),
  1192. teamType.MaxAllowed.ToString(),
  1193. (teamType.Origin - 1).ToString(),
  1194. nameToIndexString(Map.Triggers, teamType.Trigger),
  1195. classes.Length.ToString(),
  1196. string.Join(",", classes),
  1197. missions.Length.ToString(),
  1198. string.Join(",", missions)
  1199. };
  1200. teamTypesSection[teamType.Name] = string.Join(",", tokens.Where(t => !string.IsNullOrEmpty(t)));
  1201. }
  1202. var infantrySection = ini.Sections.Add("INFANTRY");
  1203. var infantryIndex = 0;
  1204. foreach (var (location, infantryGroup) in Map.Technos.OfType<InfantryGroup>())
  1205. {
  1206. for (var i = 0; i < infantryGroup.Infantry.Length; ++i)
  1207. {
  1208. var infantry = infantryGroup.Infantry[i];
  1209. if (infantry == null)
  1210. {
  1211. continue;
  1212. }
  1213. var key = infantryIndex.ToString("D3");
  1214. infantryIndex++;
  1215. Map.Metrics.GetCell(location, out int cell);
  1216. infantrySection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}",
  1217. infantry.House.Name,
  1218. infantry.Type.Name,
  1219. infantry.Strength,
  1220. cell,
  1221. i,
  1222. infantry.Mission,
  1223. infantry.Direction.ID,
  1224. infantry.Trigger
  1225. );
  1226. }
  1227. }
  1228. var structuresSection = ini.Sections.Add("STRUCTURES");
  1229. var structureIndex = 0;
  1230. foreach (var (location, building) in Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt))
  1231. {
  1232. var key = structureIndex.ToString("D3");
  1233. structureIndex++;
  1234. Map.Metrics.GetCell(location, out int cell);
  1235. structuresSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}",
  1236. building.House.Name,
  1237. building.Type.Name,
  1238. building.Strength,
  1239. cell,
  1240. building.Direction.ID,
  1241. building.Trigger,
  1242. building.Sellable ? 1 : 0,
  1243. building.Rebuild ? 1 : 0
  1244. );
  1245. }
  1246. var baseSection = ini.Sections.Add("Base");
  1247. var baseBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.BasePriority >= 0).OrderBy(x => x.Occupier.BasePriority).ToArray();
  1248. baseSection["Player"] = Map.BasicSection.BasePlayer;
  1249. baseSection["Count"] = baseBuildings.Length.ToString();
  1250. var baseIndex = 0;
  1251. foreach (var (location, building) in baseBuildings)
  1252. {
  1253. var key = baseIndex.ToString("D3");
  1254. baseIndex++;
  1255. Map.Metrics.GetCell(location, out int cell);
  1256. baseSection[key] = string.Format("{0},{1}",
  1257. building.Type.Name.ToUpper(),
  1258. cell
  1259. );
  1260. }
  1261. var unitsSection = ini.Sections.Add("UNITS");
  1262. var unitIndex = 0;
  1263. foreach (var (location, unit) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit))
  1264. {
  1265. var key = unitIndex.ToString("D3");
  1266. unitIndex++;
  1267. Map.Metrics.GetCell(location, out int cell);
  1268. unitsSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6}",
  1269. unit.House.Name,
  1270. unit.Type.Name,
  1271. unit.Strength,
  1272. cell,
  1273. unit.Direction.ID,
  1274. unit.Mission,
  1275. unit.Trigger
  1276. );
  1277. }
  1278. var aircraftSection = ini.Sections.Add("AIRCRAFT");
  1279. var aircraftIndex = 0;
  1280. foreach (var (location, aircraft) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft))
  1281. {
  1282. var key = aircraftIndex.ToString("D3");
  1283. aircraftIndex++;
  1284. Map.Metrics.GetCell(location, out int cell);
  1285. aircraftSection[key] = string.Format("{0},{1},{2},{3},{4},{5}",
  1286. aircraft.House.Name,
  1287. aircraft.Type.Name,
  1288. aircraft.Strength,
  1289. cell,
  1290. aircraft.Direction.ID,
  1291. aircraft.Mission
  1292. );
  1293. }
  1294. var shipsSection = ini.Sections.Add("SHIPS");
  1295. var shipsIndex = 0;
  1296. foreach (var (location, ship) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsVessel))
  1297. {
  1298. var key = shipsIndex.ToString("D3");
  1299. shipsIndex++;
  1300. Map.Metrics.GetCell(location, out int cell);
  1301. shipsSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6}",
  1302. ship.House.Name,
  1303. ship.Type.Name,
  1304. ship.Strength,
  1305. cell,
  1306. ship.Direction.ID,
  1307. ship.Mission,
  1308. ship.Trigger
  1309. );
  1310. }
  1311. var triggersSection = ini.Sections.Add("Trigs");
  1312. foreach (var trigger in Map.Triggers)
  1313. {
  1314. if (string.IsNullOrEmpty(trigger.Name))
  1315. {
  1316. continue;
  1317. }
  1318. var action2TypeIndex = nameToIndex(Map.ActionTypes, trigger.Action2.ActionType);
  1319. var actionControl = (action2TypeIndex > 0) ? TriggerMultiStyleType.And : TriggerMultiStyleType.Only;
  1320. var tokens = new List<string>
  1321. {
  1322. ((int)trigger.PersistantType).ToString(),
  1323. !string.IsNullOrEmpty(trigger.House) ? (Map.HouseTypes.Where(h => h.Equals(trigger.House)).FirstOrDefault()?.ID.ToString() ?? "-1") : "-1",
  1324. ((int)trigger.EventControl).ToString(),
  1325. ((int)actionControl).ToString(),
  1326. nameToIndexString(Map.EventTypes, trigger.Event1.EventType),
  1327. nameToIndexString(Map.TeamTypes, trigger.Event1.Team),
  1328. trigger.Event1.Data.ToString(),
  1329. nameToIndexString(Map.EventTypes, trigger.Event2.EventType),
  1330. nameToIndexString(Map.TeamTypes, trigger.Event2.Team),
  1331. trigger.Event2.Data.ToString(),
  1332. nameToIndexString(Map.ActionTypes, trigger.Action1.ActionType),
  1333. nameToIndexString(Map.TeamTypes, trigger.Action1.Team),
  1334. nameToIndexString(Map.Triggers, trigger.Action1.Trigger),
  1335. trigger.Action1.Data.ToString(),
  1336. action2TypeIndex.ToString(),
  1337. nameToIndexString(Map.TeamTypes, trigger.Action2.Team),
  1338. nameToIndexString(Map.Triggers, trigger.Action2.Trigger),
  1339. trigger.Action2.Data.ToString()
  1340. };
  1341. triggersSection[trigger.Name] = string.Join(",", tokens);
  1342. }
  1343. var waypointsSection = ini.Sections.Add("Waypoints");
  1344. for (var i = 0; i < Map.Waypoints.Length; ++i)
  1345. {
  1346. var waypoint = Map.Waypoints[i];
  1347. if (waypoint.Cell.HasValue)
  1348. {
  1349. waypointsSection[i.ToString()] = waypoint.Cell.Value.ToString();
  1350. }
  1351. }
  1352. foreach (var house in Map.Houses)
  1353. {
  1354. if ((house.Type.ID < 0) || !house.Enabled)
  1355. {
  1356. continue;
  1357. }
  1358. INI.WriteSection(new MapContext(Map, true), ini.Sections.Add(house.Type.Name), house);
  1359. }
  1360. ini.Sections.Remove("Briefing");
  1361. if (!string.IsNullOrEmpty(Map.BriefingSection.Briefing))
  1362. {
  1363. var briefingSection = ini.Sections.Add("Briefing");
  1364. briefingSection["Text"] = Map.BriefingSection.Briefing.Replace(Environment.NewLine, "@");
  1365. }
  1366. using (var stream = new MemoryStream())
  1367. {
  1368. using (var writer = new BinaryWriter(stream))
  1369. {
  1370. for (var y = 0; y < Map.Metrics.Height; ++y)
  1371. {
  1372. for (var x = 0; x < Map.Metrics.Width; ++x)
  1373. {
  1374. var template = Map.Templates[x, y];
  1375. if (template != null)
  1376. {
  1377. writer.Write(template.Type.ID);
  1378. }
  1379. else
  1380. {
  1381. writer.Write(ushort.MaxValue);
  1382. }
  1383. }
  1384. }
  1385. for (var y = 0; y < Map.Metrics.Height; ++y)
  1386. {
  1387. for (var x = 0; x < Map.Metrics.Width; ++x)
  1388. {
  1389. var template = Map.Templates[x, y];
  1390. if (template != null)
  1391. {
  1392. writer.Write((byte)template.Icon);
  1393. }
  1394. else
  1395. {
  1396. writer.Write(byte.MaxValue);
  1397. }
  1398. }
  1399. }
  1400. }
  1401. ini.Sections.Remove("MapPack");
  1402. CompressLCWSection(ini.Sections.Add("MapPack"), stream.ToArray());
  1403. }
  1404. using (var stream = new MemoryStream())
  1405. {
  1406. using (var writer = new BinaryWriter(stream))
  1407. {
  1408. for (var i = 0; i < Map.Metrics.Length; ++i)
  1409. {
  1410. var overlay = Map.Overlay[i];
  1411. if (overlay != null)
  1412. {
  1413. writer.Write(overlay.Type.ID);
  1414. }
  1415. else
  1416. {
  1417. writer.Write((sbyte)-1);
  1418. }
  1419. }
  1420. }
  1421. ini.Sections.Remove("OverlayPack");
  1422. CompressLCWSection(ini.Sections.Add("OverlayPack"), stream.ToArray());
  1423. }
  1424. }
  1425. private void SaveMapPreview(Stream stream)
  1426. {
  1427. Map.GenerateMapPreview().Save(stream);
  1428. }
  1429. private void SaveJSON(JsonTextWriter writer)
  1430. {
  1431. writer.WriteStartObject();
  1432. writer.WritePropertyName("MapTileX");
  1433. writer.WriteValue(Map.MapSection.X);
  1434. writer.WritePropertyName("MapTileY");
  1435. writer.WriteValue(Map.MapSection.Y);
  1436. writer.WritePropertyName("MapTileWidth");
  1437. writer.WriteValue(Map.MapSection.Width);
  1438. writer.WritePropertyName("MapTileHeight");
  1439. writer.WriteValue(Map.MapSection.Height);
  1440. writer.WritePropertyName("Theater");
  1441. writer.WriteValue(Map.MapSection.Theater.Name.ToUpper());
  1442. writer.WritePropertyName("Waypoints");
  1443. writer.WriteStartArray();
  1444. foreach (var waypoint in Map.Waypoints.Where(w => (w.Flag == WaypointFlag.PlayerStart) && w.Cell.HasValue))
  1445. {
  1446. writer.WriteValue(waypoint.Cell.Value);
  1447. }
  1448. writer.WriteEndArray();
  1449. writer.WriteEndObject();
  1450. }
  1451. private bool Validate()
  1452. {
  1453. StringBuilder sb = new StringBuilder("Error(s) during map validation:");
  1454. bool ok = true;
  1455. int numAircraft = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft).Count();
  1456. int numBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt).Count();
  1457. int numInfantry = Map.Technos.OfType<InfantryGroup>().Sum(item => item.Occupier.Infantry.Count(i => i != null));
  1458. int numTerrain = Map.Technos.OfType<Terrain>().Count();
  1459. int numUnits = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit).Count();
  1460. int numVessels = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsVessel).Count();
  1461. int numWaypoints = Map.Waypoints.Count(w => w.Cell.HasValue);
  1462. if (numAircraft > Constants.MaxAircraft)
  1463. {
  1464. sb.Append(Environment.NewLine + string.Format("Maximum number of aircraft exceeded ({0} > {1})", numAircraft, Constants.MaxAircraft));
  1465. ok = false;
  1466. }
  1467. if (numBuildings > Constants.MaxBuildings)
  1468. {
  1469. sb.Append(Environment.NewLine + string.Format("Maximum number of structures exceeded ({0} > {1})", numBuildings, Constants.MaxBuildings));
  1470. ok = false;
  1471. }
  1472. if (numInfantry > Constants.MaxInfantry)
  1473. {
  1474. sb.Append(Environment.NewLine + string.Format("Maximum number of infantry exceeded ({0} > {1})", numInfantry, Constants.MaxInfantry));
  1475. ok = false;
  1476. }
  1477. if (numTerrain > Constants.MaxTerrain)
  1478. {
  1479. sb.Append(Environment.NewLine + string.Format("Maximum number of terrain objects exceeded ({0} > {1})", numTerrain, Constants.MaxTerrain));
  1480. ok = false;
  1481. }
  1482. if (numUnits > Constants.MaxUnits)
  1483. {
  1484. sb.Append(Environment.NewLine + string.Format("Maximum number of units exceeded ({0} > {1})", numUnits, Constants.MaxUnits));
  1485. ok = false;
  1486. }
  1487. if (numVessels > Constants.MaxVessels)
  1488. {
  1489. sb.Append(Environment.NewLine + string.Format("Maximum number of ships exceeded ({0} > {1})", numVessels, Constants.MaxVessels));
  1490. ok = false;
  1491. }
  1492. if (Map.TeamTypes.Count > Constants.MaxTeams)
  1493. {
  1494. sb.Append(Environment.NewLine + string.Format("Maximum number of team types exceeded ({0} > {1})", Map.TeamTypes.Count, Constants.MaxTeams));
  1495. ok = false;
  1496. }
  1497. if (Map.Triggers.Count > Constants.MaxTriggers)
  1498. {
  1499. sb.Append(Environment.NewLine + string.Format("Maximum number of triggers exceeded ({0} > {1})", Map.Triggers.Count, Constants.MaxTriggers));
  1500. ok = false;
  1501. }
  1502. if (!Map.BasicSection.SoloMission && (numWaypoints < 2))
  1503. {
  1504. sb.Append(Environment.NewLine + "Skirmish/Multiplayer maps need at least 2 waypoints for player starting locations.");
  1505. ok = false;
  1506. }
  1507. var homeWaypoint = Map.Waypoints.Where(w => w.Equals("Home")).FirstOrDefault();
  1508. if (Map.BasicSection.SoloMission && !homeWaypoint.Cell.HasValue)
  1509. {
  1510. sb.Append(Environment.NewLine + string.Format("Single-player maps need the Home waypoint to be placed.", Map.Triggers.Count, Constants.MaxTriggers));
  1511. ok = false;
  1512. }
  1513. if (!ok)
  1514. {
  1515. MessageBox.Show(sb.ToString(), "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  1516. }
  1517. return ok;
  1518. }
  1519. private void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
  1520. {
  1521. switch (e.PropertyName)
  1522. {
  1523. case "BasePlayer":
  1524. {
  1525. UpdateBasePlayerHouse();
  1526. }
  1527. break;
  1528. }
  1529. }
  1530. private void MapSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
  1531. {
  1532. switch (e.PropertyName)
  1533. {
  1534. case "Theater":
  1535. {
  1536. Map.InitTheater(GameType);
  1537. }
  1538. break;
  1539. }
  1540. }
  1541. private void UpdateBasePlayerHouse()
  1542. {
  1543. var basePlayer = Map.HouseTypes.Where(h => h.Equals(Map.BasicSection.BasePlayer)).FirstOrDefault() ?? Map.HouseTypes.First();
  1544. foreach (var (_, building) in Map.Buildings.OfType<Building>())
  1545. {
  1546. if (!building.IsPrebuilt)
  1547. {
  1548. building.House = basePlayer;
  1549. }
  1550. }
  1551. }
  1552. private void CompressLCWSection(INISection section, byte[] decompressedBytes)
  1553. {
  1554. using (var stream = new MemoryStream())
  1555. using (var writer = new BinaryWriter(stream))
  1556. {
  1557. foreach (var decompressedChunk in decompressedBytes.Split(8192))
  1558. {
  1559. var compressedChunk = WWCompression.LcwCompress(decompressedChunk);
  1560. writer.Write((ushort)compressedChunk.Length);
  1561. writer.Write((ushort)decompressedChunk.Length);
  1562. writer.Write(compressedChunk);
  1563. }
  1564. writer.Flush();
  1565. stream.Position = 0;
  1566. var values = Convert.ToBase64String(stream.ToArray()).Split(70).ToArray();
  1567. for (var i = 0; i < values.Length; ++i)
  1568. {
  1569. section[(i + 1).ToString()] = values[i];
  1570. }
  1571. }
  1572. }
  1573. private byte[] DecompressLCWSection(INISection section, int bytesPerCell)
  1574. {
  1575. var sb = new StringBuilder();
  1576. foreach (var (key, value) in section)
  1577. {
  1578. sb.Append(value);
  1579. }
  1580. var compressedBytes = Convert.FromBase64String(sb.ToString());
  1581. int readPtr = 0;
  1582. int writePtr = 0;
  1583. var decompressedBytes = new byte[Map.Metrics.Width * Map.Metrics.Height * bytesPerCell];
  1584. while ((readPtr + 4) <= compressedBytes.Length)
  1585. {
  1586. uint uLength;
  1587. using (var reader = new BinaryReader(new MemoryStream(compressedBytes, readPtr, 4)))
  1588. {
  1589. uLength = reader.ReadUInt32();
  1590. }
  1591. var length = (int)(uLength & 0x0000FFFF);
  1592. readPtr += 4;
  1593. var dest = new byte[8192];
  1594. var readPtr2 = readPtr;
  1595. var decompressed = WWCompression.LcwDecompress(compressedBytes, ref readPtr2, dest, 0);
  1596. Array.Copy(dest, 0, decompressedBytes, writePtr, decompressed);
  1597. readPtr += length;
  1598. writePtr += decompressed;
  1599. }
  1600. return decompressedBytes;
  1601. }
  1602. #region IDisposable Support
  1603. private bool disposedValue = false;
  1604. protected virtual void Dispose(bool disposing)
  1605. {
  1606. if (!disposedValue)
  1607. {
  1608. if (disposing)
  1609. {
  1610. MapImage?.Dispose();
  1611. }
  1612. disposedValue = true;
  1613. }
  1614. }
  1615. public void Dispose()
  1616. {
  1617. Dispose(true);
  1618. }
  1619. #endregion
  1620. }
  1621. }