diff --git a/README.md b/README.md index 675ddfe..5b30cc2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,61 @@ Command line application and library to edit scenario and campaign files. **Alpha version** +## Usage + + Available commands are: + copymap Copy parts of scenarios + compress Recompress a decompressed scenario file + decompress Decompress a scenario file for hex editing + extract Extract scenarios from campaign file + dump Dump a scenario file to JSON + dumpunits Dump units and map tiles below + +The command `copymap` is able convert an AOE1 to an AOE2 scenario. +There seem to be some issues with cliffs. + +[OpeningMoves-AOE2.scx](files/OpeningMoves-AOE2.scx) +is a fully automated conversion of a scenario from the AOE1 demo version: + + $ GenieEdit.exe copymap files/examples/aoe2x-Empty.scx "files/examples/aoe1demo-Reigno_1-Opening Moves.scn" files/OpeningMoves-AOE2.scx + +(Stone are set to 100 to be able to build a town center from the beginning.) + +It can also be used to dump units from any version of a scenario: + + $ GenieEdit.exe dumpunits Debug2.scx + Scenario Editor + + ID UnitID TileID Position + + Units of player 0: + 0 69 23 ( 1,5, 11,5) + 1 69 23 ( 2,5, 11,5) + ... + +### Code + +Have a look at the command implementations. They are very short. + ## Build Clone my projects MainUtil.cs ExUtil.cs and this repository and build `src/GenieEdit.sln`. + +## Contributing + +Contributions are very welcome. ☺ +Keep in mind that the API is still very unstable. + +Suggestions: + +1. Gather UnitIDs of AOE1 +2. Much help is appreciated to figure out ID mappings + for the conversion process. +3. Ideas how to configure the converter (mapping tables). + There are some notes in German: + [AOE1](files/units-aoe1.txt), + [AOE2](files/units-aoe2.txt) +3. The application should be able to generate maps from images + in the future. How should units be placed using the image? +4. Feedback about the API. diff --git a/files/OpeningMoves-AOE2.scx b/files/OpeningMoves-AOE2.scx new file mode 100644 index 0000000..2f1bf01 Binary files /dev/null and b/files/OpeningMoves-AOE2.scx differ diff --git a/files/examples/aoe1x-Empty.scx b/files/examples/aoe1x-Empty.scx new file mode 100644 index 0000000..6f40b16 Binary files /dev/null and b/files/examples/aoe1x-Empty.scx differ diff --git a/files/examples/aoe2x-Empty.scx b/files/examples/aoe2x-Empty.scx new file mode 100644 index 0000000..7652ee5 Binary files /dev/null and b/files/examples/aoe2x-Empty.scx differ diff --git a/files/units-aoe1.txt b/files/units-aoe1.txt new file mode 100644 index 0000000..ee59d99 --- /dev/null +++ b/files/units-aoe1.txt @@ -0,0 +1,59 @@ +Gold 66 +Bush 59 +Stone 102 +Klippe 3x3 264 +Dorfbewohner 83 + +Kleiner Wall 72 +Mittelgrosser Wall 117 +Befestigungsanlage 155 + +Beobachtungsturm 2x2 79 +Abwehrturm 2x2 199 +Wachturm 2x2 69 +Geschützturm 2x2 278 + +Haus 2x2 70 +Artefakt 159 +Bauernhof=>Feld 3x3 50 +Dorfzentrum 3x3 109 +Kaserne 3x3 12 +Kornspeicher 3x3 68 +Lagergrube 3x3 103 +Stall 101 + +Baum 134 +Buche 140 +Eiche 141 144 145 146 +Esche, Fichte 148 136 +Kiefer 197 194 193 192 135 137 138 139 391 392 +Palme 150-153 + +Wald Gelaende TileID=10: +142 143 147 195 365 366 367 393 + +Palmenhain Gelaende TileID=13: +113 114 121 129 + +Dschungel TileID=20: +113 114 121 129 + +Kiefernwald TileID=19: +161 198 203 226 391 392 + +Fisch Kueste 260 +Fisch Kueste 263 +Fisch Lachs 53 +Fisch Thun 52 +Fisch Wal 370 + +Aligator 1 +Aligator Koenig 362 +Elefant 48 +Elefant Koenig 90 +Gazelle 65 +Gazelle Koenig 384 +Loewe 126 +Loewe Koenig 89 +Vogel Adler 96 +Vogel Habicht 95 diff --git a/files/units-aoe2.txt b/files/units-aoe2.txt new file mode 100644 index 0000000..2ed0091 --- /dev/null +++ b/files/units-aoe2.txt @@ -0,0 +1,74 @@ +Gold 66 +Bush 59 +Stone 102 +Klippe 3x3 264 +Dorfbewohner 83 + +Palisadenwall 72 +Steinwallall 117 +Befestigter Wall 155 + +Aussenposten 1x1 598 +Beobachtungsturm 1x1 79 +Wachturm 1x1 79 +Hauptturm 1x1 79 + +Haus 2x2 70 +Reliquie 285 +Bauernhof=>Feld 3x3 50 (TileID=7) +Dorfzentrum 4x4 109 +Kaserne 3x3 12 +Muehle 2x2 68 +Holzlager 2x2 562 +Stall 3x3 101 + +Bambus 348 +Baum A-L 399-410 +Baum TD 248 +Dschungelbaum 414 +Eiche 349 +Kiefer 350 +Palme 351 +Verschneiter Kiefer 413 +Waldbaum 411 + +Wald Gelaende TileID=10: +411 + +Palmenhain Gelaende TileID=13: +351 + +Dschungel TileID=17: +414 + +Kiefernwald TileID=19: +350 + +Bambus TileID=18: +348 + +Eichenwald TileID=20: +349 + +Fisch Barsch 53 +Fisch Dorado 455 +Fisch Lachs 456 +Fisch Schnapper 458 +Fisch Thun 457 + +Fisch Marlin 1 451 +Fisch Marlin 2 450 +Fisch Kueste 69 + +Tiere: +Vogel Falke 96 +Jaguar 812 +Wildes Pferd 835 +Pferd 814 +Reh 65 +Schaf 594 +Truthahn 833 +Urwolf 89 +Wolf 126 +Wilder Eber 810 +Wildschwein 48 diff --git a/src/cmdapp/GenieEngineApp.csproj b/src/cmdapp/GenieEngineApp.csproj index 067681b..b3128ef 100644 --- a/src/cmdapp/GenieEngineApp.csproj +++ b/src/cmdapp/GenieEngineApp.csproj @@ -36,6 +36,9 @@ + + + @@ -50,6 +53,10 @@ {662FDCF9-3F6C-48DA-BFE9-F317BEFED692} ExUtil + + {8306E345-3A99-4F22-AF14-3CEA7425C2EF} + MainUtil + diff --git a/src/cmdapp/src/CompressCommand.cs b/src/cmdapp/src/CompressCommand.cs index df42cb1..13a012e 100644 --- a/src/cmdapp/src/CompressCommand.cs +++ b/src/cmdapp/src/CompressCommand.cs @@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine var reader = new ScnReader(file); var writer = new ScnWriter(outfile); - var header = reader.ReadScenarioHeader(null); + var header = reader.ReadScenarioHeader(); reader.ReadSeparator("Custom"); writer.Write(header); writer.Flush(); diff --git a/src/cmdapp/src/CopymapCommand.cs b/src/cmdapp/src/CopymapCommand.cs new file mode 100644 index 0000000..3129569 --- /dev/null +++ b/src/cmdapp/src/CopymapCommand.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace AdrianKousz.GenieEngine +{ + public class CopymapCommand : BaseCommand + { + override public void Run() + { + Data.Scenario result; + using (var dststream = File.OpenRead(args[0])) + using (var srcstream = File.OpenRead(args[1])) + { + var srcreader = new ScnReader(srcstream); + var src = srcreader.ReadScenario(); + var dstreader = new ScnReader(dststream); + var dst = dstreader.ReadScenario(); + var converter = new CopymapConverter(); + dst.PlayerNames = src.PlayerNames; + dst.StringTablePlayerNames = src.StringTablePlayerNames; + dst.Players = src.Players; + dst.Resources = src.Resources; + dst.ResourcesCopy = src.ResourcesCopy; + dst.Messages = src.Messages; + converter.Convert(dst, src); + result = dst; + } + using (var outstream = File.OpenWrite(args[2])) { + var outwriter = new ScnWriter(outstream); + outwriter.Write(result); + } + } + + override public int GetArgumentCount() + { + return 3; + } + + override public string GetDescription() + { + return "Copy parts of scenarios"; + } + + override public string GetHelp() + { + return "Copy the parts below from scenario file 2 into scenario file 1." + + "\nThe result is written to scenario file 3." + + "\n- Messages" + + "\n- Player information" + + "\n- Resources" + + "\n- Map" + + "\n- Units" + ; + } + } +} + diff --git a/src/cmdapp/src/CopymapConverter.cs b/src/cmdapp/src/CopymapConverter.cs new file mode 100644 index 0000000..e752383 --- /dev/null +++ b/src/cmdapp/src/CopymapConverter.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using AdrianKousz.Util; +using AdrianKousz.GenieEngine.Data; + +namespace AdrianKousz.GenieEngine +{ + public class CopymapConverter + { + public void Convert(Scenario dst, Scenario src) + { + dst.Map = src.Map; + dst.Units = ChangeUnits_AOE1_AOE2(src.Units); + MoveUnits_AOE1_AOE2(dst.Units); + ChangeTiles_AOE1_AOE2(dst.Units, dst.Map); + RandomizeTrees_AOE2(dst.Units); + } + + /** + * Some buildings are not the same size. + * They need to be moved by half a unit. + */ + private void MoveUnits_AOE1_AOE2(IList[] units) + { + var mapping = new int[] { + // Towers + 79, 199, 69, 278, + // Other Buildings + 109, 68, 103, + }; + units.ForEach((i, list) => { + list.ForEach((j, unit) => { + mapping.ForEach((k, buildingid) => { + if (unit.UnitId == buildingid) { + unit.PosX -= 0.5f; + unit.PosY -= 0.5f; + } + }); + }); + }); + } + + /** + * Simple mapping of unit IDs + */ + private List[] ChangeUnits_AOE1_AOE2(IList[] units) + { + var array = new int[] { + 66, 66, + 59, 59, + 102, 102, + 264, 264, + 83, 83, + 125, 125, + + // Buildings + 72, 72, + 117, 117, + 155, 155, + 79, 598, + 199, 79, + 69, 79, + 278, 79, + 70, 70, + 159, 285, + 109, 109, + 12, 12, + 68, 68, + 103, 562, + 101, 101, + + // Trees + 141, 349, + 144, 349, + 145, 349, + 146, 349, + 197, 350, + 194, 350, + 193, 350, + 192, 350, + 135, 350, + 137, 350, + 138, 350, + 139, 350, + 161, 350, + 198, 350, + 203, 350, + 226, 350, + 391, 350, + 392, 350, + 150, 351, + 151, 351, + 152, 351, + 153, 351, + 134, 411, + 140, 411, + 148, 411, + 136, 411, + 142, 411, + 143, 411, + 147, 411, + 195, 411, + 365, 411, + 366, 411, + 367, 411, + 393, 411, + + // Fish + 260, 69, + 263, 69, + 53, 456, + 52, 457, + 370, 451, + + // Animals + 48, 48, + 90, 48, + 65, 65, + 384, 65, + 126, 126, + 89, 126, + 96, 96, + + // Keep and process later + 50, 50, + 113, 113, + 114, 114, + 121, 121, + 129, 129, + }; + var mapping = new Dictionary(); + var ai = 0; + while (ai < array.Length) + mapping.Add(array[ai++], array[ai++]); + + var result = new List[Scenario.NumPlayerSections]; + result.Fill(); + + units.ForEach((i, list) => { + list.ForEach((j, unit) => { + if (mapping.ContainsKey(unit.UnitId)) { + unit.UnitId = (short)mapping[unit.UnitId]; + result[i].Add(unit); + } + }); + }); + + return result; + } + + /** + * Farms need to be converted including the underlying terrain. + */ + private void ChangeTiles_AOE1_AOE2(IList[] units, Map map) + { + units.ForEach((i, list) => { + list.ForEach((j, unit) => { + var posx = (int)(unit.PosX - 0.5); + var posy = (int)(unit.PosY - 0.5); + var linearpos = posy * map.SizeX + posx; + if (false) { + // switch + } else if (unit.UnitId == 50) { + map.LinearTiles[linearpos].Id = 7; + map.LinearTiles[linearpos - 1].Id = 7; + map.LinearTiles[linearpos + 1].Id = 7; + map.LinearTiles[linearpos - map.SizeX].Id = 7; + map.LinearTiles[linearpos - map.SizeX - 1].Id = 7; + map.LinearTiles[linearpos - map.SizeX + 1].Id = 7; + map.LinearTiles[linearpos + map.SizeX].Id = 7; + map.LinearTiles[linearpos + map.SizeX - 1].Id = 7; + map.LinearTiles[linearpos + map.SizeX + 1].Id = 7; + } else if (unit.UnitId == 113 || unit.UnitId == 114 || unit.UnitId == 121 || unit.UnitId == 129) { + if (map.LinearTiles[linearpos].Id == 20) { + map.LinearTiles[linearpos].Id = 17; + unit.UnitId = 414; + } else { + unit.UnitId = 351; + } + } + }); + }); + } + + /** + * Forests would contain the same tree graphic + * without this function + */ + private void RandomizeTrees_AOE2(IList[] units) + { + var treeids = new int[] { + 411, 351, 414, 350, 348, 349, + }; + units.ForEach((i, list) => { + list.ForEach((j, unit) => { + treeids.ForEach((k, treeid) => { + if (unit.UnitId == treeid) { + unit.InitialFrame = (short)Util.Math.Rand(0, 13); // Whats the maximum? + unit.Rotation = unit.InitialFrame; + } + }); + }); + }); + } + } +} diff --git a/src/cmdapp/src/DecompressCommand.cs b/src/cmdapp/src/DecompressCommand.cs index 107474c..bc4f905 100644 --- a/src/cmdapp/src/DecompressCommand.cs +++ b/src/cmdapp/src/DecompressCommand.cs @@ -12,9 +12,9 @@ namespace AdrianKousz.GenieEngine var reader = new ScnReader(file); var writer = new ScnWriter(outfile); - var header = reader.ReadScenarioHeader(null); + var header = reader.ReadScenarioHeader(); writer.Write(header); - writer.WriteSeperator(); + writer.WriteSeperator("Custom"); writer.Flush(); using (var comp = new DeflateStream(file, CompressionMode.Decompress)) { diff --git a/src/cmdapp/src/DumpCommand.cs b/src/cmdapp/src/DumpCommand.cs index e046eb6..19ef4cd 100644 --- a/src/cmdapp/src/DumpCommand.cs +++ b/src/cmdapp/src/DumpCommand.cs @@ -9,7 +9,7 @@ namespace AdrianKousz.GenieEngine { using (var stream = File.OpenRead(args[0])) { var aoereader = new ScnReader(stream); - var scn = aoereader.ReadScenario(null); + var scn = aoereader.ReadScenario(); var ser = new System.Web.Script.Serialization.JavaScriptSerializer(); var result = ser.Serialize(scn); Console.WriteLine(result); @@ -23,7 +23,7 @@ namespace AdrianKousz.GenieEngine override public string GetDescription() { - return "Dump a scenario file to XML"; + return "Dump a scenario file to JSON"; } } } diff --git a/src/cmdapp/src/DumpunitsCommand.cs b/src/cmdapp/src/DumpunitsCommand.cs new file mode 100644 index 0000000..d995615 --- /dev/null +++ b/src/cmdapp/src/DumpunitsCommand.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Collections.Generic; +using AdrianKousz.Util; +using AdrianKousz.GenieEngine.Data; + +namespace AdrianKousz.GenieEngine +{ + public class DumpunitsCommand : BaseCommand + { + override public void Run() + { + Scenario scn; + using (var stream = File.OpenRead(args[0])) { + var aoereader = new ScnReader(stream); + scn = aoereader.ReadScenario(); + } + + Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position"); + Console.WriteLine(); + scn.Units.ForEach((i, list) => { + Console.WriteLine("Units of player {0}:", i); + list.ForEach((j, unit) => { + var tilex = (int)(unit.PosX - 0.5); + var tiley = (int)(unit.PosY - 0.5); + var tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex]; + Console.WriteLine("{0,-8} {1,-8} {2,-8} ({3,6:0.##},{4,6:0.##})", unit.Id, unit.UnitId, tile.Id, unit.PosX, unit.PosY); + }); + Console.WriteLine(); + }); + } + + override public int GetArgumentCount() + { + return 1; + } + + override public string GetDescription() + { + return "Dump units and map tiles below"; + } + } +} + diff --git a/src/cmdapp/src/Program.cs b/src/cmdapp/src/Program.cs index 83ec435..773b35a 100644 --- a/src/cmdapp/src/Program.cs +++ b/src/cmdapp/src/Program.cs @@ -9,6 +9,11 @@ namespace AdrianKousz.GenieEngine { System.Threading.Thread.CurrentThread.Name = "main"; + if (System.Diagnostics.Debugger.IsAttached) { + debug(); + return; + } + new CmdApp(new Program(), args).Run(); } @@ -20,11 +25,23 @@ namespace AdrianKousz.GenieEngine public IDictionary> GetCommands() { var result = new Dictionary>(); + result.Add("copymap", new CopymapCommand()); result.Add("compress", new CompressCommand()); result.Add("decompress", new DecompressCommand()); result.Add("extract", new ExtractCommand()); result.Add("dump", new DumpCommand()); + result.Add("dumpunits", new DumpunitsCommand()); return result; } + + private static void debug() + { + var fn = ""; + using (var istream = System.IO.File.OpenRead(fn)) { + var ireader = new ScnReader(istream); + var scn = ireader.ReadScenario(); + System.Diagnostics.Debugger.Break(); // Great to inspect scn + } + } } } diff --git a/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs b/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs index 72bc324..21d2011 100644 --- a/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs +++ b/src/lib/AdrianKousz.GenieEngine.Data/AIData.cs @@ -11,7 +11,7 @@ namespace AdrianKousz.GenieEngine.Data public String[] PlayerCustomAIs; public String[] PlayerCustomCTYs; public String[] PlayerCustomPERs; - public Byte[] RawAITypes; + public Byte[] AITypes; } } diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs b/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs index 33ff9d7..25411b9 100644 --- a/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs +++ b/src/lib/AdrianKousz.GenieEngine.Data/Cinematics.cs @@ -9,6 +9,10 @@ namespace AdrianKousz.GenieEngine.Data public String CinemaLossFn; public String BackgroundFn; + public Int32 BitmapWidth; + public Int32 BitmapHeight; + public Int16 BitmapUnknown; + public Byte[] RawBitmap; } } diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs b/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs deleted file mode 100644 index 9c8c3ad..0000000 --- a/src/lib/AdrianKousz.GenieEngine.Data/Consts.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Text; - -namespace AdrianKousz.GenieEngine.Data -{ - public static class Consts - { - public const Int32 Separator = -99; - public const Int32 NumPlayers = 16; - public const Int32 NumUnitSections = 9; - public static readonly Encoding Encoding = Encoding.GetEncoding(1252); - public static readonly Char[] NullChar = new Char[] { '\0' }; - } -} diff --git a/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs new file mode 100644 index 0000000..eb7a80b --- /dev/null +++ b/src/lib/AdrianKousz.GenieEngine.Data/GlobalVictoryInfo.cs @@ -0,0 +1,19 @@ +using System; + +namespace AdrianKousz.GenieEngine.Data +{ + public class GlobalVictoryInfo + { + public Boolean RequireConquest; + public Int32 Ruins; + public Int32 Artifacts; + public Int32 Discovery; + public Int32 PercentExplored; + public Int32 Unknown; + public Boolean RequireAllCustom; + public Int32 Mode; + public Int32 Score; + public Int32 Time; + } +} + diff --git a/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs index f48f24a..43050b3 100644 --- a/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs +++ b/src/lib/AdrianKousz.GenieEngine.Data/PlayerInfo.cs @@ -6,8 +6,6 @@ namespace AdrianKousz.GenieEngine.Data { public const Int32 ExpectedUnknown = 4; - public String Name; - public Int32 StringTable; public Boolean Active; public Boolean Human; public Int32 Civ; diff --git a/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs b/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs index abd8e66..053e1ff 100644 --- a/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs +++ b/src/lib/AdrianKousz.GenieEngine.Data/Scenario.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; -using AdrianKousz.GenieEngine.Data; +using System.Text; namespace AdrianKousz.GenieEngine.Data { public class Scenario { - public const Int32 PlayerSections = 9; + public const Int32 Separator = -99; + public const Int32 NumPlayers = 16; + public const Int32 NumPlayerSections = 9; + public static readonly Encoding Encoding = Encoding.GetEncoding(1252); public ScenarioHeader Header; @@ -15,22 +18,36 @@ namespace AdrianKousz.GenieEngine.Data public Int32 NextId; public Single OriginalVersion; + public String[] PlayerNames; + public Int32[] StringTablePlayerNames; public PlayerInfo[] Players; public Messages Messages; public Cinematics Cinematics; public AIData AIData; + + // Seperator "Resources" + public ResourceInfo[] Resources; + + // Seperator "Victory" + + public GlobalVictoryInfo GlobalVictory; + public Int32[] LinearDiplomacy; + public Byte[] RawIndividualVictory; + + // Seperator "Player Settings" + + public Int32[] AlliedVictory; + public Byte[] RawDisables; + public Int32[] StartingAges; + + // Seperator "Map" + public Map Map; public ResourceInfoCopy[] ResourcesCopy; public IList[] Units; - public Byte[] RawGlobalVictory; - public Byte[] RawDiplomacy; - public Byte[] RawIndividualVictory; - public Byte[] RawAlliedVictory; - public Byte[] RawDisables; - - public Int32[] StartingAges; + public byte[] RawRemaining; } } diff --git a/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs b/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs index 295c603..e6531b3 100644 --- a/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs +++ b/src/lib/AdrianKousz.GenieEngine.Data/UnitInfo.cs @@ -4,13 +4,14 @@ namespace AdrianKousz.GenieEngine.Data { public class UnitInfo { - public const Single ExpectedUnknown = 2f; + public const Single ExpectedUnknown1 = 1f; + public const Byte ExpectedUnknown2 = 2; public Single PosX; public Single PosY; public Single Unknown1; public Int32 Id; - public Int32 UnitId; + public Int16 UnitId; public Byte Unknown2; public Single Rotation; public Int16 InitialFrame; diff --git a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs index 159d138..197ebc0 100644 --- a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs +++ b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs @@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine public CpnReader(Stream input) { - reader = new ExtendedBinaryReader(input, Data.Consts.Encoding); + reader = new ExtendedBinaryReader(input, Scenario.Encoding); } public void ExtractFiles() diff --git a/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs new file mode 100644 index 0000000..229a636 --- /dev/null +++ b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs @@ -0,0 +1,20 @@ +using System; +using AdrianKousz.GenieEngine.Data; + +namespace AdrianKousz.GenieEngine +{ + public interface IScnFactory + { + Scenario MakeScenario(bool deep); + ScenarioHeader MakeScenarioHeader(); + PlayerInfo MakePlayerInfo(); + Messages MakeMessages(); + Cinematics MakeCinematics(); + AIData MakeAIData(); + ResourceInfo MakeResourceInfo(); + ResourceInfoCopy MakeResourceInfoCopy(); + GlobalVictoryInfo MakeGlobalVictoryInfo(); + Map MakeMap(); + UnitInfo MakeUnitInfo(); + } +} diff --git a/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs b/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs new file mode 100644 index 0000000..a0375bb --- /dev/null +++ b/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs @@ -0,0 +1,22 @@ +using System; +using AdrianKousz.Util; +using AdrianKousz.GenieEngine.Data; + +namespace AdrianKousz.GenieEngine +{ + public class ScnConvert + { + public void Convert(Scenario value, float version) + { + + } + public void Convert(UnitInfo value, float from, float to) + { + if (from < 1.18f && to >= 1.18f) { + value.InitialFrame = 0; + value.GarrisonnedInId = -1; + } + } + } +} + diff --git a/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs b/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs new file mode 100644 index 0000000..0288cb4 --- /dev/null +++ b/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using AdrianKousz.Util; +using AdrianKousz.GenieEngine.Data; + +namespace AdrianKousz.GenieEngine +{ + public class ScnDefaultFactory : IScnFactory + { + public Scenario MakeScenario(bool deep) + { + var result = new Scenario(); + result.OriginalVersion = 0; + result.NextId = 1; + + result.StringTablePlayerNames = new int[Scenario.NumPlayers]; + + result.Units = new IList[Scenario.NumPlayerSections] + .Fill(() => new List()); + + return result; + } + + public ScenarioHeader MakeScenarioHeader() + { + var result = new ScenarioHeader(); + result.Unknown1 = ScenarioHeader.ExpectedUnknown1; + result.Unknown2 = ScenarioHeader.ExpectedUnknown2; + result.OriginalVersion = "0.00"; + result.Timestamp = System.DateTime.Now; + result.Instructions = ""; + result.PlayerCount = 2; + return result; + } + + public PlayerInfo MakePlayerInfo() + { + var result = new PlayerInfo(); + result.Unknown = PlayerInfo.ExpectedUnknown; + result.Active = false; + result.Human = false; + result.Civ = 0; + return result; + } + + public Messages MakeMessages() + { + var result = new Messages(); + result.Unknown1 = Messages.ExpectedUnknown1; + result.Unknown2 = Messages.ExpectedUnknown2; + result.Unknown3 = Messages.ExpectedUnknown3; + result.OriginalFilename = ""; + result.TextInstructions = ""; + result.TextHints = ""; + result.TextVictory = ""; + result.TextLoss = ""; + result.TextHistory = ""; + result.TextScouts = ""; + result.StringTableInstructions = -1; + result.StringTableHints = -1; + result.StringTableVictory = -1; + result.StringTableLoss = -1; + result.StringTableHistory = -1; + result.StringTableScouts = -1; + + return result; + } + + public Cinematics MakeCinematics() + { + var result = new Cinematics(); + result.CinemaPregameFn = ""; + result.CinemaVictoryFn = ""; + result.CinemaLossFn = ""; + result.BackgroundFn = ""; + result.RawBitmap = new byte[0]; + return result; + } + + public AIData MakeAIData() + { + var result = new AIData(); + result.PlayerAIs = new string[Scenario.NumPlayers].Fill(""); + result.PlayerCTYs = new string[Scenario.NumPlayers].Fill(""); + result.PlayerPERs = new string[Scenario.NumPlayers].Fill(""); + result.PlayerCustomAIs = new string[Scenario.NumPlayers].Fill(""); + result.PlayerCustomCTYs = new string[Scenario.NumPlayers].Fill(""); + result.PlayerCustomPERs = new string[Scenario.NumPlayers].Fill(""); + result.AITypes = new byte[Scenario.NumPlayers].Fill((byte)1); + return result; + } + + public GlobalVictoryInfo MakeGlobalVictoryInfo() + { + var result = new GlobalVictoryInfo(); + result.RequireConquest = true; + result.RequireAllCustom = false; + return result; + } + + public ResourceInfo MakeResourceInfo() + { + // AOE2 standard + var result = new ResourceInfo(); + result.Unknown = ResourceInfo.ExpectedUnknown; + result.Food = 200; + result.Wood = 200; + result.Gold = 100; + result.Stone = 200; + return result; + } + + public ResourceInfoCopy MakeResourceInfoCopy() + { + // AOE2 standard + var result = new ResourceInfoCopy(); + result.Unknown = ResourceInfoCopy.ExpectedUnknown; + result.Food = 200; + result.Wood = 200; + result.Gold = 100; + result.Stone = 200; + result.PopulationLimit = 75; + return result; + } + + public Map MakeMap() + { + var result = new Map(); + var size = 144; + result.CameraX = -1; + result.CameraY = -1; + result.GeneratorId = 2; + result.SizeX = size; + result.SizeY = size; + result.LinearTiles = new Map.Tile[size*size].Fill(() => new Map.Tile(0, 1, 0)); + return result; + } + + public UnitInfo MakeUnitInfo() + { + var result = new UnitInfo(); + result.Unknown1 = UnitInfo.ExpectedUnknown1; + result.Unknown2 = UnitInfo.ExpectedUnknown2; + result.GarrisonnedInId = -1; + return result; + } + } +} + diff --git a/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs b/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs deleted file mode 100644 index 7c4ead8..0000000 --- a/src/lib/AdrianKousz.GenieEngine/ScnFactory.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Collections.Generic; -using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; - -namespace AdrianKousz.GenieEngine -{ - public class ScnFactory - { - public Scenario GetScenario() - { - var result = new Scenario(); - result.OriginalVersion = -1; - result.NextId = 1; - - result.Header = GetScenarioHeader(); - - result.Players = new PlayerInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.Players.Fill(GetPlayerInfo); - - result.Messages = GetMessages(); - result.Cinematics = GetCinematics(); - result.AIData = GetAIData(); - - result.Resources = new ResourceInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.Resources.Fill(GetResourceInfo); - result.Units = new List[Scenario.PlayerSections]; - result.Units.Fill(() => new List()); - - return result; - } - - public ScenarioHeader GetScenarioHeader() - { - var result = new ScenarioHeader(); - result.Unknown1 = ScenarioHeader.ExpectedUnknown1; - result.Unknown2 = ScenarioHeader.ExpectedUnknown2; - result.OriginalVersion = "0.99"; - result.Timestamp = System.DateTime.Now; - result.Instructions = ""; - result.PlayerCount = 0; - return result; - } - - public PlayerInfo GetPlayerInfo(int number) - { - var result = new PlayerInfo(); - result.Unknown = PlayerInfo.ExpectedUnknown; - result.Name = "Player " + number; - result.StringTable = -1; - result.Active = true; - result.Human = true; - result.Civ = number; - return result; - } - - public Messages GetMessages() - { - var result = new Messages(); - result.Unknown1 = Messages.ExpectedUnknown1; - result.Unknown2 = Messages.ExpectedUnknown2; - result.Unknown3 = Messages.ExpectedUnknown3; - result.OriginalFilename = ""; - result.TextInstructions = ""; - result.TextHints = ""; - result.TextVictory = ""; - result.TextLoss = ""; - result.TextHistory = ""; - result.TextScouts = ""; - result.StringTableInstructions = -1; - result.StringTableHints = -1; - result.StringTableVictory = -1; - result.StringTableLoss = -1; - result.StringTableHistory = -1; - result.StringTableScouts = -1; - - return result; - } - - public Cinematics GetCinematics() - { - var result = new Cinematics(); - result.CinemaPregameFn = ""; - result.CinemaVictoryFn = ""; - result.CinemaLossFn = ""; - result.BackgroundFn = ""; - return result; - } - - public AIData GetAIData() - { - var result = new AIData(); - result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerAIs.Fill(""); - result.PlayerCTYs.Fill(""); - result.PlayerPERs.Fill(""); - result.PlayerCustomAIs.Fill(""); - result.PlayerCustomCTYs.Fill(""); - result.PlayerCustomPERs.Fill(""); - result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.RawAITypes.Fill((byte)1); - return result; - } - - public ResourceInfo GetResourceInfo() - { - // AOE2 standard - var result = new ResourceInfo(); - result.Unknown = ResourceInfo.ExpectedUnknown; - result.Food = 200; - result.Wood = 200; - result.Gold = 100; - result.Stone = 200; - result.Ore = 0; - return result; - } - - public Map GetMap() - { - var result = new Map(); - var size = 200; - result.CameraX = 0; - result.CameraY = 0; - result.GeneratorId = 0; - result.SizeX = size; - result.SizeY = size; - result.LinearTiles = new Map.Tile[size*size]; - result.LinearTiles.Fill(); - return result; - } - } -} - diff --git a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs b/src/lib/AdrianKousz.GenieEngine/ScnReader.cs index f6e6164..bf7811b 100644 --- a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs +++ b/src/lib/AdrianKousz.GenieEngine/ScnReader.cs @@ -8,81 +8,90 @@ namespace AdrianKousz.GenieEngine { public class ScnReader { - private static readonly string TAG = typeof(ScnReader).Name; + public float Version = 0; - private float version = 0.99f; private ExtendedBinaryReader reader; + private IScnFactory factory; - public ScnReader(Stream input) + public ScnReader(Stream input) : this (input, new ScnDefaultFactory()) { - reader = new ExtendedBinaryReader(input, Data.Consts.Encoding); + } + + public ScnReader(Stream input, IScnFactory factory) + { + reader = new ExtendedBinaryReader(input, Scenario.Encoding); + this.factory = factory; } #region Reader Methods - public Scenario ReadScenario(Scenario result) + public Scenario ReadScenario() { - result = Misc.MayNew(result); + var result = factory.MakeScenario(false); int number = 0; - result.Header = ReadScenarioHeader(result.Header); + result.Header = ReadScenarioHeader(); var comp = new DeflateStream(reader.BaseStream, CompressionMode.Decompress); - reader = new ExtendedBinaryReader(comp, Data.Consts.Encoding); + reader = new ExtendedBinaryReader(comp, Scenario.Encoding); result.NextId = reader.ReadInt32(); - version = reader.ReadSingle(); - result.OriginalVersion = version; - Log.i(TAG, "Detected version {0}", version); + Version = reader.ReadSingle(); + result.OriginalVersion = Version; - - result.Players = FillArray(ReadPlayerInfo, result.Players, AdrianKousz.GenieEngine.Data.Consts.NumPlayers); - result.Messages = ReadMessages(result.Messages); - result.Cinematics = ReadCinematics(result.Cinematics); - result.AIData = ReadAIData(result.AIData); + result.PlayerNames = FillArray(() => reader.ReadZString(256), result.PlayerNames, Scenario.NumPlayers); + if (Version >= 1.18f) { + result.StringTablePlayerNames = FillArray(reader.ReadInt32, result.StringTablePlayerNames, Scenario.NumPlayers); + } + result.Players = FillArray(ReadPlayerInfo, result.Players, Scenario.NumPlayers); + result.Messages = ReadMessages(); + result.Cinematics = ReadCinematics(); + result.AIData = ReadAIData(); ReadSeparator("Resources"); - result.Resources = FillArray(ReadResourceInfo, result.Resources, AdrianKousz.GenieEngine.Data.Consts.NumPlayers); + result.Resources = FillArray(ReadResourceInfo, result.Resources, Scenario.NumPlayers); ReadSeparator("Victory"); - result.RawGlobalVictory = reader.ReadBytes(10 * 4); - result.RawDiplomacy = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4); + result.GlobalVictory = ReadGlobalVictoryInfo(); + result.LinearDiplomacy = FillArray(reader.ReadInt32, result.LinearDiplomacy, Scenario.NumPlayers * Scenario.NumPlayers); result.RawIndividualVictory = reader.ReadBytes(11520); ReadSeparator("Player Settings"); - result.RawAlliedVictory = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4); + result.AlliedVictory = FillArray(reader.ReadInt32, result.AlliedVictory, Scenario.NumPlayers); - if (1.15f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; - if (1.18f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; - if (1.30f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; + if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; + if (Version >= 1.18f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; + if (Version >= 1.30f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; result.RawDisables = reader.ReadBytes(number); - result.StartingAges = new int[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.StartingAges.Fill(reader.ReadInt32); + result.StartingAges = FillArray(reader.ReadInt32, result.StartingAges, Scenario.NumPlayers); ReadSeparator("Map"); - result.Map = ReadMap(result.Map); + result.Map = ReadMap(); number = reader.ReadInt32(); result.ResourcesCopy = FillArray(ReadResourceInfoCopy, result.ResourcesCopy, number - 1); - result.Units = new IList[number]; - result.Units.Fill(ReadUnitInfo); + result.Units = FillArray(ReadUnitInfoList, result.Units, number); + + var bytestream = new MemoryStream(); + reader.BaseStream.CopyTo(bytestream); + result.RawRemaining = bytestream.ToArray(); return result; } - public ScenarioHeader ReadScenarioHeader(ScenarioHeader result) + public ScenarioHeader ReadScenarioHeader() { - result = Misc.MayNew(result); + var result = factory.MakeScenarioHeader(); result.OriginalVersion = reader.ReadString(4); - Skip(4, "Header Length"); + reader.ReadInt32(); // Header Length result.Unknown1 = reader.ReadInt32(); result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32()); result.Instructions = reader.ReadZStringInt32(); @@ -92,14 +101,10 @@ namespace AdrianKousz.GenieEngine return result; } - public PlayerInfo ReadPlayerInfo(PlayerInfo result) + public PlayerInfo ReadPlayerInfo() { - result = Misc.MayNew(result); + var result = factory.MakePlayerInfo(); - result.Name = reader.ReadZString(256); - if (1.18f <= version) { - result.StringTable = reader.ReadInt32(); - } result.Active = reader.ReadBoolean(); result.Human = reader.ReadBoolean(); result.Civ = reader.ReadInt32(); @@ -108,23 +113,23 @@ namespace AdrianKousz.GenieEngine return result; } - public Messages ReadMessages(Messages result) + public Messages ReadMessages() { - result = Misc.MayNew(result); + var result = factory.MakeMessages(); result.Unknown1 = reader.ReadInt32(); result.Unknown2 = reader.ReadByte(); result.Unknown3 = reader.ReadSingle(); result.OriginalFilename = reader.ReadStringInt16(); - if (1.18f <= version) { + if (Version >= 1.18f) { result.StringTableInstructions = reader.ReadInt32(); result.StringTableHints = reader.ReadInt32(); result.StringTableVictory = reader.ReadInt32(); result.StringTableLoss = reader.ReadInt32(); result.StringTableHistory = reader.ReadInt32(); } - if (1.22f <= version) { + if (Version >= 1.22f) { result.StringTableScouts = reader.ReadInt32(); } @@ -133,16 +138,16 @@ namespace AdrianKousz.GenieEngine result.TextVictory = reader.ReadZStringInt16(); result.TextLoss = reader.ReadZStringInt16(); result.TextHistory = reader.ReadZStringInt16(); - if (1.22f <= version) { + if (Version >= 1.22f) { result.TextScouts = reader.ReadZStringInt16(); } return result; } - public Cinematics ReadCinematics(Cinematics result) + public Cinematics ReadCinematics() { - result = Misc.MayNew(result); + var result = factory.MakeCinematics(); result.CinemaPregameFn = reader.ReadStringInt16(); result.CinemaVictoryFn = reader.ReadStringInt16(); @@ -151,37 +156,31 @@ namespace AdrianKousz.GenieEngine var hasBitmap = reader.ReadBoolean(); - Skip(4, "Bitmap width"); - Skip(4, "Bitmap height"); - Skip(2, "Bitmap Unknown"); + result.BitmapWidth = reader.ReadInt32(); + result.BitmapHeight = reader.ReadInt32(); + result.BitmapUnknown = reader.ReadInt16(); if (hasBitmap) { result.RawBitmap = BitmapUtil.ReadRawBitmap(reader.BaseStream); - } else { - result.RawBitmap = new byte[0]; } return result; } - public AIData ReadAIData(AIData result) + public AIData ReadAIData() { - result = Misc.MayNew(result); + var result = factory.MakeAIData(); - result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; - result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; + result.PlayerAIs = FillArray(reader.ReadStringInt16, result.PlayerAIs, Scenario.NumPlayers); + result.PlayerCTYs = FillArray(reader.ReadStringInt16, result.PlayerCTYs, Scenario.NumPlayers); + result.PlayerPERs = FillArray(reader.ReadStringInt16, result.PlayerPERs, Scenario.NumPlayers); - result.PlayerAIs.Fill(reader.ReadStringInt16); - result.PlayerCTYs.Fill(reader.ReadStringInt16); - result.PlayerPERs.Fill(reader.ReadStringInt16); + result.PlayerCustomAIs = new string[Scenario.NumPlayers]; + result.PlayerCustomCTYs = new string[Scenario.NumPlayers]; + result.PlayerCustomPERs = new string[Scenario.NumPlayers]; int l1, l2, l3; - for (var i = 0; i < AdrianKousz.GenieEngine.Data.Consts.NumPlayers; ++i) { + for (var i = 0; i < Scenario.NumPlayers; ++i) { l1 = reader.ReadInt32(); l2 = reader.ReadInt32(); l3 = reader.ReadInt32(); @@ -190,88 +189,112 @@ namespace AdrianKousz.GenieEngine result.PlayerCustomPERs[i] = reader.ReadString(l3); } - if (1.18f <= version) { - result.RawAITypes.Fill(reader.ReadByte); + if (Version >= 1.18f) { + result.AITypes = FillArray(reader.ReadByte, result.AITypes, Scenario.NumPlayers); } return result; } - public ResourceInfo ReadResourceInfo(ResourceInfo result) + public ResourceInfo ReadResourceInfo() { - result = Misc.MayNew(result); + var result = factory.MakeResourceInfo(); result.Gold = reader.ReadInt32(); result.Wood = reader.ReadInt32(); result.Food = reader.ReadInt32(); result.Stone = reader.ReadInt32(); - if (1.18f <= version) { + if (Version >= 1.18f) { result.Ore = reader.ReadInt32(); - result.Unknown = reader.ReadInt32(); + if (Version < 1.3f) { + result.Unknown = reader.ReadInt32(); + } } return result; } - public Map ReadMap(Map result) + public ResourceInfoCopy ReadResourceInfoCopy() { - result = Misc.MayNew(result); - - if (1.18f <= version) { - result.CameraX = reader.ReadInt32(); - result.CameraY = reader.ReadInt32(); - } - if (1.22f <= version) { - result.GeneratorId = reader.ReadInt32(); - } - - result.SizeX = reader.ReadInt32(); - result.SizeY = reader.ReadInt32(); - - result.LinearTiles = new Map.Tile[result.SizeX * result.SizeY]; - result.LinearTiles.Fill(() => new Map.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte())); - - return result; - } - - public ResourceInfoCopy ReadResourceInfoCopy(ResourceInfoCopy result) - { - result = Misc.MayNew(result); + var result = factory.MakeResourceInfoCopy(); result.Food = reader.ReadSingle(); result.Wood = reader.ReadSingle(); result.Gold = reader.ReadSingle(); result.Stone = reader.ReadSingle(); - if (1.18f <= version) { + if (Version >= 1.18f) { result.Ore = reader.ReadInt32(); - result.Unknown = reader.ReadInt32(); + if (Version < 1.3f) { + result.Unknown = reader.ReadInt32(); + } } - if (1.22f <= version) { + if (Version >= 1.22f) { result.PopulationLimit = reader.ReadSingle(); } return result; } - public IList ReadUnitInfo() + public GlobalVictoryInfo ReadGlobalVictoryInfo() { - var number = reader.ReadInt32(); - var result = new List(number); + var result = factory.MakeGlobalVictoryInfo(); - for (var i = 0; i < number; i++) { - var unit = new UnitInfo(); - unit.PosX = reader.ReadSingle(); - unit.PosY = reader.ReadSingle(); - unit.Unknown1 = reader.ReadSingle(); - unit.Id = reader.ReadInt32(); - unit.UnitId = reader.ReadInt16(); - unit.Unknown2 = reader.ReadByte(); - unit.Rotation = reader.ReadSingle(); - if (1.18f <= version) { - unit.InitialFrame = reader.ReadInt16(); - unit.GarrisonnedInId = reader.ReadInt32(); - } - result.Add(unit); + result.RequireConquest = reader.ReadBoolean(); + result.Ruins = reader.ReadInt32(); + result.Artifacts = reader.ReadInt32(); + result.Discovery = reader.ReadInt32(); + result.PercentExplored = reader.ReadInt32(); + result.Unknown = reader.ReadInt32(); + result.RequireAllCustom = reader.ReadBoolean(); + result.Mode = reader.ReadInt32(); + result.Score = reader.ReadInt32(); + result.Time = reader.ReadInt32(); + + return result; + } + + public Map ReadMap() + { + var result = factory.MakeMap(); + + if (Version >= 1.18f) { + result.CameraX = reader.ReadInt32(); + result.CameraY = reader.ReadInt32(); + } + if (Version >= 1.22f) { + result.GeneratorId = reader.ReadInt32(); + } + + result.SizeX = reader.ReadInt32(); + result.SizeY = reader.ReadInt32(); + + result.LinearTiles = new Map.Tile[result.SizeX * result.SizeY] + .Fill(() => new Map.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte())); + + return result; + } + + public IList ReadUnitInfoList() + { + var count = reader.ReadInt32(); + var result = new List().Fill(ReadUnitInfo, count); + return result; + } + + public UnitInfo ReadUnitInfo() + { + var result = factory.MakeUnitInfo(); + + result.PosX = reader.ReadSingle(); + result.PosY = reader.ReadSingle(); + result.Unknown1 = reader.ReadSingle(); + result.Id = reader.ReadInt32(); + result.UnitId = reader.ReadInt16(); + result.Unknown2 = reader.ReadByte(); + result.Rotation = reader.ReadSingle(); + if (Version >= 1.18f) { + result.InitialFrame = reader.ReadInt16(); + result.GarrisonnedInId = reader.ReadInt32(); } return result; @@ -279,7 +302,7 @@ namespace AdrianKousz.GenieEngine public void ReadSeparator(string name) { int v = reader.ReadInt32(); - if (v != AdrianKousz.GenieEngine.Data.Consts.Separator) { + if (v != Scenario.Separator) { var msg = "Separator \"{0}\" = {1}"; msg = string.Format(msg, name, v); throw new InvalidDataException(msg); @@ -290,26 +313,14 @@ namespace AdrianKousz.GenieEngine #region Debug Methods - private T[] FillArray(System.Func f, T[] array, int length) where T : new() + private T[] FillArray(System.Func f, T[] array, int length) { - if (array == null) { + if (array == null || array.Length != length) array = new T[length]; - } else if (array.Length != length) { - var temp = new T[length]; - System.Array.Copy(array, temp, System.Math.Min(array.Length, temp.Length)); - } - array.Fill(f); - return array; } - private void Skip(int count, string name) - { - Log.d(TAG, "Skipping {0} bytes in \"{1}\"", count, name); - reader.ReadBytes(count); - } - #endregion } } diff --git a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs b/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs index f4876c5..2e78042 100644 --- a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs +++ b/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs @@ -1,4 +1,6 @@ using System.IO; +using System.IO.Compression; +using System.Collections.Generic; using AdrianKousz.Util; using AdrianKousz.GenieEngine.Data; @@ -6,18 +8,76 @@ namespace AdrianKousz.GenieEngine { public class ScnWriter { - private static readonly string TAG = typeof(ScnWriter).Name; + public float Version = 0; private ExtendedBinaryWriter writer; - public ScnWriter(Stream input) + public ScnWriter(Stream output) { - writer = new ExtendedBinaryWriter(input, Data.Consts.Encoding); + writer = new ExtendedBinaryWriter(output, Scenario.Encoding); + } + + public void Write(Scenario value) + { + Version = Version == 0 ? value.OriginalVersion : Version; + + Write(value.Header); + Flush(); + + // TODO: Make wrapper. + // This is a workaround for now. A wrapper for the reader and writer will be created. + var originalWriter = writer; + var compressedPart = new MemoryStream(); + var comp = new DeflateStream(compressedPart, CompressionMode.Compress); + writer = new ExtendedBinaryWriter(comp, Scenario.Encoding); + + writer.Write(value.NextId); + writer.Write(Version); + + value.PlayerNames.ForEach((x) => writer.WriteStringPadded(x, 256)); + if (Version >= 1.18f) { + value.StringTablePlayerNames.ForEach(writer.Write); + } + value.Players.ForEach(Write); + Write(value.Messages); + Write(value.Cinematics); + Write(value.AIData); + + WriteSeperator("Resources"); + + value.Resources.ForEach(Write); + + WriteSeperator("Victory"); + + Write(value.GlobalVictory); + value.LinearDiplomacy.ForEach(writer.Write); + writer.Write(value.RawIndividualVictory); + + WriteSeperator("Player Settings"); + + value.AlliedVictory.ForEach(writer.Write); + writer.Write(value.RawDisables); + value.StartingAges.ForEach(writer.Write); + + WriteSeperator("Map"); + + Write(value.Map); + + writer.Write((int)value.Units.Length); + value.ResourcesCopy.ForEach(Write); + value.Units.ForEach(Write); + + writer.Write(value.RawRemaining); + + Flush(); + + comp.Dispose(); + originalWriter.Write(compressedPart.ToArray()); } public void Write(ScenarioHeader value) { - var instructionlength = writer.GetByteCount(value.Instructions + " "); + var instructionlength = writer.GetZByteCount(value.Instructions); writer.WriteStringRaw(value.OriginalVersion); writer.Write(20 + instructionlength); writer.Write(value.Unknown1); @@ -27,15 +87,180 @@ namespace AdrianKousz.GenieEngine writer.Write(value.PlayerCount); } - public void WriteSeperator() + public void Write(PlayerInfo value) { - writer.Write(AdrianKousz.GenieEngine.Data.Consts.Separator); + writer.Write(value.Active); + writer.Write(value.Human); + writer.Write(value.Civ); + writer.Write(value.Unknown); + } + + public void Write(Messages value) + { + writer.Write(value.Unknown1); + writer.Write(value.Unknown2); + writer.Write(value.Unknown3); + writer.WriteStringInt16(value.OriginalFilename); + + if (Version >= 1.18f) { + writer.Write(value.StringTableInstructions); + writer.Write(value.StringTableHints); + writer.Write(value.StringTableVictory); + writer.Write(value.StringTableLoss); + writer.Write(value.StringTableHistory); + } + if (Version >= 1.22f) { + writer.Write(value.StringTableScouts); + } + + writer.WriteZStringInt16(value.TextInstructions); + writer.WriteZStringInt16(value.TextHints); + writer.WriteZStringInt16(value.TextVictory); + writer.WriteZStringInt16(value.TextLoss); + writer.WriteZStringInt16(value.TextHistory); + if (Version >= 1.22f) { + writer.WriteZStringInt16(value.TextScouts); + } + } + + public void Write(Cinematics value) + { + writer.WriteStringInt16(value.CinemaPregameFn); + writer.WriteStringInt16(value.CinemaVictoryFn); + writer.WriteStringInt16(value.CinemaLossFn); + writer.WriteStringInt16(value.BackgroundFn); + + var hasBitmap = value.RawBitmap.Length > 0; + + writer.Write(hasBitmap); + writer.Write(value.BitmapWidth); + writer.Write(value.BitmapHeight); + writer.Write(value.BitmapUnknown); + + if (hasBitmap) + writer.Write(value.RawBitmap); + } + + public void Write(AIData value) + { + value.PlayerAIs.ForEach(writer.WriteStringInt16); + value.PlayerCTYs.ForEach(writer.WriteStringInt16); + value.PlayerPERs.ForEach(writer.WriteStringInt16); + + for (var i = 0; i < Scenario.NumPlayers; ++i) { + var str1 = value.PlayerCustomAIs[i]; + var str2 = value.PlayerCustomCTYs[i]; + var str3 = value.PlayerCustomPERs[i]; + writer.Write(writer.GetByteCount(str1)); + writer.Write(writer.GetByteCount(str2)); + writer.Write(writer.GetByteCount(str3)); + writer.WriteStringRaw(str1); + writer.WriteStringRaw(str2); + writer.WriteStringRaw(str3); + } + + if (Version >= 1.18f) { + value.AITypes.ForEach(writer.Write); + } + } + + public void Write(ResourceInfo value) + { + writer.Write(value.Gold); + writer.Write(value.Wood); + writer.Write(value.Food); + writer.Write(value.Stone); + if (Version >= 1.18f) { + writer.Write(value.Ore); + if (Version < 1.3f) { + writer.Write(value.Unknown); + } + } + } + + public void Write(ResourceInfoCopy value) + { + writer.Write(value.Food); + writer.Write(value.Wood); + writer.Write(value.Gold); + writer.Write(value.Stone); + if (Version >= 1.18f) { + writer.Write(value.Ore); + if (Version < 1.3f) { + writer.Write(value.Unknown); + } + } + if (Version >= 1.22f) { + writer.Write(value.PopulationLimit); + } + } + + public void Write(GlobalVictoryInfo value) + { + writer.Write(value.RequireConquest); + writer.Write(value.Ruins); + writer.Write(value.Artifacts); + writer.Write(value.Discovery); + writer.Write(value.PercentExplored); + writer.Write(value.Unknown); + writer.Write(value.RequireAllCustom); + writer.Write(value.Mode); + writer.Write(value.Score); + writer.Write(value.Time); + } + + public void Write(Map value) + { + if (Version >= 1.18f) { + writer.Write(value.CameraX); + writer.Write(value.CameraY); + } + if (Version >= 1.22f) { + writer.Write(value.GeneratorId); + } + writer.Write(value.SizeX); + writer.Write(value.SizeY); + + value.LinearTiles.ForEach(x => { writer.Write(x.Id); writer.Write(x.Elevation); writer.Write(x.Unknown); }); + } + + public void Write(IList value) + { + int number = value.Count; + writer.Write(number); + value.ForEach(Write); + } + + public void Write(UnitInfo value) + { + writer.Write(value.PosX); + writer.Write(value.PosY); + writer.Write(value.Unknown1); + writer.Write(value.Id); + writer.Write(value.UnitId); + writer.Write(value.Unknown2); + writer.Write(value.Rotation); + if (Version >= 1.18f) { + writer.Write(value.InitialFrame); + writer.Write(value.GarrisonnedInId); + } + } + + public void WriteSeperator(string name) + { + writer.Write(Scenario.Separator); } public void Flush() { writer.Flush(); } + + private void EnsureSetVersion() + { + if (Version <= 1) + throw new System.InvalidOperationException("Set output version"); + } } } diff --git a/src/lib/GenieEngineLib.csproj b/src/lib/GenieEngineLib.csproj index c5e45d1..e62c696 100644 --- a/src/lib/GenieEngineLib.csproj +++ b/src/lib/GenieEngineLib.csproj @@ -34,13 +34,11 @@ - - @@ -50,6 +48,10 @@ + + + +