diff --git a/.gitignore b/.gitignore index 097ceb8..e60f4c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ [Dd]ebug [Rr]elease *.userprefs +packages diff --git a/README.md b/README.md index 5b30cc2..5117cad 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,38 @@ # Age of Empires Scenario Editor -Command line application and library to edit scenario and campaign files. +Command line application and library to edit Scenario and Campaign files +of games powered by the Genie Engine. +This includes the *Age of Empires* series and *Star Wars: Galactic Battlegrounds.* -**Alpha version** +**Beta version** -## Usage +# Command Line Application - 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 +## Features -The command `copymap` is able convert an AOE1 to an AOE2 scenario. -There seem to be some issues with cliffs. +* Convert AOE1 Scenario to AOE2 Scenario +* Convert a Scenario file to JSON and vice-versa +* Decompress and recompress compressed Scenario part for hex editing +* Dump unit information to easily gather Unit IDs +* Extract Scenario files from Campaign file + +## Planned Features + +* Create maps from bitmaps +* Trigger implementation +* Full conversion between all Scenario versions + (mapping of Unit IDs are needed for this, see Section Contributing) + +## Examples [OpeningMoves-AOE2.scx](files/OpeningMoves-AOE2.scx) -is a fully automated conversion of a scenario from the AOE1 demo version: +is a fully automated conversion of a Scenario file 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 + $ GenieEdit.exe convert 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: +A unit dump looks like the following: $ GenieEdit.exe dumpunits Debug2.scx Scenario Editor @@ -36,13 +44,37 @@ It can also be used to dump units from any version of a scenario: 1 69 23 ( 2,5, 11,5) ... -### Code +# Library -Have a look at the command implementations. They are very short. +The namespace `AdrianKousz.GenieEngine` contains classes +to read, write, and manipulate Scenario files. + +`Scenario` contains all data structures related to a Scenario file. +They are easily understandable. + +The map is stored in linear order. +This way, it is easier to process seperate tiles. +The code below gets the map tile at (10,5). +The map origin (0,0) is in the left corner of the map: + +![Map coordinate system](doc/MapCoord.png) + + Scenario scn; + int x = 10; + int y = 5; + Scenario.ScnMap.Tile tile = scn.Map.LinearTiles[scn.Map.SizeX * y + x]; + +`GenieFile` is the starting point for reading and writing: + + Scenario scn = GenieFile.Deserialize(filestream); + +`ScnDefaultFactory` is a class that is able to produce an empty +Scenario file. Eventually, it should be able to create a `Scenario` +that is usable without any modifications. ## Build -Clone my projects MainUtil.cs ExUtil.cs and this repository +Clone the projects MainUtil.cs, ExUtil.cs, and this one, and build `src/GenieEdit.sln`. ## Contributing @@ -53,12 +85,10 @@ 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 +2. Much help is appreciated to figure out Unit 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. +3. Feedback about the API. diff --git a/doc/MapCoord.png b/doc/MapCoord.png new file mode 100644 index 0000000..2ff2e3b Binary files /dev/null and b/doc/MapCoord.png differ diff --git a/files/OpeningMoves-AOE2.scx b/files/OpeningMoves-AOE2.scx index ae890f6..d8b4185 100644 Binary files a/files/OpeningMoves-AOE2.scx and b/files/OpeningMoves-AOE2.scx differ diff --git a/src/cmdapp/GenieEngineApp.csproj b/src/cmdapp/GenieEngineApp.csproj index b3128ef..b07e203 100644 --- a/src/cmdapp/GenieEngineApp.csproj +++ b/src/cmdapp/GenieEngineApp.csproj @@ -31,14 +31,15 @@ - - - - + + + + + @@ -61,5 +62,11 @@ + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + + \ No newline at end of file diff --git a/src/cmdapp/packages.config b/src/cmdapp/packages.config new file mode 100644 index 0000000..2abc396 --- /dev/null +++ b/src/cmdapp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/cmdapp/src/CompressCommand.cs b/src/cmdapp/src/CompressCommand.cs index 13a012e..86ea72f 100644 --- a/src/cmdapp/src/CompressCommand.cs +++ b/src/cmdapp/src/CompressCommand.cs @@ -7,20 +7,10 @@ namespace AdrianKousz.GenieEngine { override public void Run() { - using (var file = File.OpenRead(args[0])) { - using (var outfile = File.OpenWrite(args[1])) { - var reader = new ScnReader(file); - var writer = new ScnWriter(outfile); - - var header = reader.ReadScenarioHeader(); - reader.ReadSeparator("Custom"); - writer.Write(header); - writer.Flush(); - - using (var comp = new DeflateStream(outfile, CompressionMode.Compress)) { - file.CopyTo(comp); - } - } + using (var input = File.OpenRead(args[0])) + using (var output = File.OpenWrite(args[1])) + { + GenieFile.ScenarioCompression(input, output, false); } } diff --git a/src/cmdapp/src/ConvertCommand.cs b/src/cmdapp/src/ConvertCommand.cs new file mode 100644 index 0000000..69d3b64 --- /dev/null +++ b/src/cmdapp/src/ConvertCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public class ConvertCommand : BaseCommand + { + override public void Run() + { + using (var refstream = File.OpenRead(args[0])) + using (var srcstream = File.OpenRead(args[1])) + using (var outstream = File.OpenWrite(args[2])) + { + var refscn = GenieFile.Deserialize(refstream); + var srcscn = GenieFile.Deserialize(srcstream); + + srcscn.VersionString = refscn.VersionString; + srcscn.VersionNumber = refscn.VersionNumber; + srcscn.RawIndividualVictory = refscn.RawIndividualVictory; + srcscn.RawDisables = refscn.RawDisables; + srcscn.RawRemaining = refscn.RawRemaining; + + srcscn.PlayerSettings.ForEach(x => { + x.FilenameAI = "RandomGame"; + x.FilenameCTY = x.FilenamePER = ""; + x.ScriptAI = x.ScriptCTY = x.ScriptPER = ""; + }); + + var converter = new CopymapConverter(); + converter.Convert(srcscn); + + GenieFile.Serialize(srcscn, outstream); + } + } + + override public int GetArgumentCount() + { + return 3; + } + + override public string GetDescription() + { + return "Convert AOE1 scenario"; + } + + override public string GetHelp() + { + return "Convert AOE1 scenario to AOE2" + + "\n" + + "\nThe converter needs an existing AOE2 scenario to start with. Use the following arguments:" + + "\n " + ; + } + } +} diff --git a/src/cmdapp/src/CopymapCommand.cs b/src/cmdapp/src/CopymapCommand.cs deleted file mode 100644 index 3129569..0000000 --- a/src/cmdapp/src/CopymapCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -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 index e2b82a7..2bd5d17 100644 --- a/src/cmdapp/src/CopymapConverter.cs +++ b/src/cmdapp/src/CopymapConverter.cs @@ -1,26 +1,24 @@ 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) + public void Convert(Scenario scn) { - 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); + scn.Units = ChangeUnits_AOE1_AOE2(scn.Units); + MoveUnits_AOE1_AOE2(scn.Units); + ChangeTiles_AOE1_AOE2(scn.Units, scn.Map); + RandomizeTrees_AOE2(scn.Units); } /** * Some buildings are not the same size. * They need to be moved by half a unit. */ - private void MoveUnits_AOE1_AOE2(IList[] units) + private void MoveUnits_AOE1_AOE2(IList[] units) { var mapping = new int[] { // Towers @@ -43,7 +41,7 @@ namespace AdrianKousz.GenieEngine /** * Simple mapping of unit IDs */ - private List[] ChangeUnits_AOE1_AOE2(IList[] units) + private List[] ChangeUnits_AOE1_AOE2(IList[] units) { var array = new int[] { // Resources @@ -142,18 +140,17 @@ namespace AdrianKousz.GenieEngine 121, 121, 129, 129, }; - var mapping = new Dictionary(); + var mapping = new Dictionary(); var ai = 0; while (ai < array.Length) - mapping.Add(array[ai++], array[ai++]); + mapping.Add((short)array[ai++], (short)array[ai++]); - var result = new List[Scenario.NumPlayerSections]; - result.Fill(); + var result = new List[Scenario.NumPlayerSections].Fill(); units.ForEach((i, list) => { list.ForEach((j, unit) => { if (mapping.ContainsKey(unit.UnitId)) { - unit.UnitId = (short)mapping[unit.UnitId]; + unit.UnitId = mapping[unit.UnitId]; result[i].Add(unit); } }); @@ -165,7 +162,7 @@ namespace AdrianKousz.GenieEngine /** * Farms need to be converted including the underlying terrain. */ - private void ChangeTiles_AOE1_AOE2(IList[] units, Map map) + private void ChangeTiles_AOE1_AOE2(IList[] units, Scenario.ScnMap map) { units.ForEach((i, list) => { list.ForEach((j, unit) => { @@ -175,6 +172,7 @@ namespace AdrianKousz.GenieEngine if (false) { // switch } else if (unit.UnitId == 50) { + // Farm map.LinearTiles[linearpos].Id = 7; map.LinearTiles[linearpos - 1].Id = 7; map.LinearTiles[linearpos + 1].Id = 7; @@ -200,7 +198,7 @@ namespace AdrianKousz.GenieEngine * Forests would contain the same tree graphic * without this function */ - private void RandomizeTrees_AOE2(IList[] units) + private void RandomizeTrees_AOE2(IList[] units) { var treeids = new int[] { 411, 351, 414, 350, 348, 349, diff --git a/src/cmdapp/src/DecompressCommand.cs b/src/cmdapp/src/DecompressCommand.cs index bc4f905..7ac8591 100644 --- a/src/cmdapp/src/DecompressCommand.cs +++ b/src/cmdapp/src/DecompressCommand.cs @@ -1,5 +1,4 @@ using System.IO; -using System.IO.Compression; namespace AdrianKousz.GenieEngine { @@ -7,20 +6,10 @@ namespace AdrianKousz.GenieEngine { override public void Run() { - using (var file = File.OpenRead(args[0])) { - using (var outfile = File.OpenWrite(args[1])) { - var reader = new ScnReader(file); - var writer = new ScnWriter(outfile); - - var header = reader.ReadScenarioHeader(); - writer.Write(header); - writer.WriteSeperator("Custom"); - writer.Flush(); - - using (var comp = new DeflateStream(file, CompressionMode.Decompress)) { - comp.CopyTo(outfile); - } - } + using (var input = File.OpenRead(args[0])) + using (var output = File.OpenWrite(args[1])) + { + GenieFile.ScenarioCompression(input, output, true); } } diff --git a/src/cmdapp/src/DumpCommand.cs b/src/cmdapp/src/DumpCommand.cs deleted file mode 100644 index 19ef4cd..0000000 --- a/src/cmdapp/src/DumpCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.IO; - -namespace AdrianKousz.GenieEngine -{ - public class DumpCommand : BaseCommand - { - override public void Run() - { - using (var stream = File.OpenRead(args[0])) { - var aoereader = new ScnReader(stream); - var scn = aoereader.ReadScenario(); - var ser = new System.Web.Script.Serialization.JavaScriptSerializer(); - var result = ser.Serialize(scn); - Console.WriteLine(result); - } - } - - override public int GetArgumentCount() - { - return 1; - } - - override public string GetDescription() - { - return "Dump a scenario file to JSON"; - } - } -} diff --git a/src/cmdapp/src/DumpunitsCommand.cs b/src/cmdapp/src/DumpunitsCommand.cs index d995615..a85101a 100644 --- a/src/cmdapp/src/DumpunitsCommand.cs +++ b/src/cmdapp/src/DumpunitsCommand.cs @@ -2,7 +2,6 @@ using System.IO; using System.Collections.Generic; using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; namespace AdrianKousz.GenieEngine { @@ -11,9 +10,9 @@ namespace AdrianKousz.GenieEngine override public void Run() { Scenario scn; - using (var stream = File.OpenRead(args[0])) { - var aoereader = new ScnReader(stream); - scn = aoereader.ReadScenario(); + using (var input = File.OpenRead(args[0])) + { + scn = GenieFile.Deserialize(input); } Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position"); diff --git a/src/cmdapp/src/FromJsonCommand.cs b/src/cmdapp/src/FromJsonCommand.cs new file mode 100644 index 0000000..ef25d9f --- /dev/null +++ b/src/cmdapp/src/FromJsonCommand.cs @@ -0,0 +1,32 @@ +using System.IO; +using Newtonsoft.Json; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public class FromJsonCommand : BaseCommand + { + override public void Run() + { + using (var input = File.OpenRead(args[0])) + using (var reader = input.GetReader()) + using (var output = File.OpenWrite(args[1])) + { + var str = reader.ReadToEnd(); + var scn = JsonConvert.DeserializeObject(str); + GenieFile.Serialize(scn, output); + } + } + + override public int GetArgumentCount() + { + return 2; + } + + override public string GetDescription() + { + return "Read a JSON file and save as scenario"; + } + } +} + diff --git a/src/cmdapp/src/Program.cs b/src/cmdapp/src/Program.cs index 773b35a..84af326 100644 --- a/src/cmdapp/src/Program.cs +++ b/src/cmdapp/src/Program.cs @@ -25,11 +25,12 @@ namespace AdrianKousz.GenieEngine public IDictionary> GetCommands() { var result = new Dictionary>(); - result.Add("copymap", new CopymapCommand()); + result.Add("convert", new ConvertCommand()); result.Add("compress", new CompressCommand()); result.Add("decompress", new DecompressCommand()); result.Add("extract", new ExtractCommand()); - result.Add("dump", new DumpCommand()); + result.Add("tojson", new ToJsonCommand()); + result.Add("fromjson", new FromJsonCommand()); result.Add("dumpunits", new DumpunitsCommand()); return result; } @@ -38,8 +39,7 @@ namespace AdrianKousz.GenieEngine { var fn = ""; using (var istream = System.IO.File.OpenRead(fn)) { - var ireader = new ScnReader(istream); - var scn = ireader.ReadScenario(); + var scn = GenieFile.Deserialize(istream); System.Diagnostics.Debugger.Break(); // Great to inspect scn } } diff --git a/src/cmdapp/src/ToJsonCommand.cs b/src/cmdapp/src/ToJsonCommand.cs new file mode 100644 index 0000000..54faf2d --- /dev/null +++ b/src/cmdapp/src/ToJsonCommand.cs @@ -0,0 +1,31 @@ +using System.IO; +using Newtonsoft.Json; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public class ToJsonCommand : BaseCommand + { + override public void Run() + { + using (var input = File.OpenRead(args[0])) + using (var output = File.OpenWrite(args[1])) + using (var writer = output.GetWriter()) + { + var scn = GenieFile.Deserialize(input); + var result = JsonConvert.SerializeObject(scn, Formatting.Indented); + writer.WriteLine(result); + } + } + + override public int GetArgumentCount() + { + return 2; + } + + override public string GetDescription() + { + return "Dump a scenario file to JSON"; + } + } +} diff --git a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs index 197ebc0..4b96cee 100644 --- a/src/lib/AdrianKousz.GenieEngine/CpnReader.cs +++ b/src/lib/AdrianKousz.GenieEngine/CpnReader.cs @@ -1,6 +1,5 @@ using System.IO; using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; namespace AdrianKousz.GenieEngine { diff --git a/src/lib/AdrianKousz.GenieEngine/GenieFile.cs b/src/lib/AdrianKousz.GenieEngine/GenieFile.cs new file mode 100644 index 0000000..8d68a17 --- /dev/null +++ b/src/lib/AdrianKousz.GenieEngine/GenieFile.cs @@ -0,0 +1,115 @@ +using System.IO; +using System.IO.Compression; +using AdrianKousz.Util; + +namespace AdrianKousz.GenieEngine +{ + public static class GenieFile + { + #region Serialization + + public static byte[] Serialize(T value) + { + byte[] result; + using (var stream = new MemoryStream()) + { + Serialize(value, stream); + result = stream.ToArray(); + } + return result; + } + + public static void Serialize(T value, Stream output) + { + var scenarioValue = value as Scenario; + if (scenarioValue != null) { + var scnwriter = ScnSerializerWriter.CreateDefault(); + using (var stream = new NonDisposingStreamWrapper(output)) + using (var writer = GetWriter(stream)) + { + scnwriter.Write(writer, scenarioValue); + return; + } + } + + throw new System.ArgumentException("Unsupported Type"); + } + + #endregion + + #region Deserialization + + public static T Deserialize(byte[] array) + { + return (T)Deserialize(array, typeof(T)); + } + + public static T Deserialize(Stream input) + { + return (T)Deserialize(input, typeof(T)); + } + + private static object Deserialize(byte[] array, System.Type type) + { + using (var stream = new MemoryStream(array)) + { + return Deserialize(stream, type); + } + } + + private static object Deserialize(Stream input, System.Type type) + { + if (type == typeof(Scenario)) { + var scnreader = ScnSerializerReader.CreateDefault(); + using (var stream = new NonDisposingStreamWrapper(input)) + using (var reader = GetReader(stream)) + { + return scnreader.ReadScenario(reader); + } + } + + throw new System.ArgumentException("Unsupported Type"); + } + + #endregion + + #region Low Level Methods + + public static ExtendedBinaryReader GetReader(Stream stream) + { + return new ExtendedBinaryReader(stream, Scenario.Encoding); + } + + public static ExtendedBinaryWriter GetWriter(Stream stream) + { + return new ExtendedBinaryWriter(stream, Scenario.Encoding); + } + + public static void ScenarioCompression(Stream input, Stream output, bool decompress) + { + var mode = decompress ? CompressionMode.Decompress : CompressionMode.Compress; + + byte[] buffer; + + buffer = input.ReadBytes(8); + output.Write(buffer); + + var headerLength = buffer[4] + | buffer[5] << 8 + | buffer[6] << 16 + | buffer[7] << 24 + ; + + buffer = input.ReadBytes(headerLength); + output.Write(buffer); + + using (var comp = new DeflateStream(input, mode)) + { + comp.CopyTo(output); + } + } + + #endregion + } +} + diff --git a/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs index 2a36fe3..eee1a25 100644 --- a/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs +++ b/src/lib/AdrianKousz.GenieEngine/IScnFactory.cs @@ -1,7 +1,4 @@ -using System; -using AdrianKousz.GenieEngine.Data; - -namespace AdrianKousz.GenieEngine +namespace AdrianKousz.GenieEngine { public interface IScnFactory { diff --git a/src/lib/AdrianKousz.GenieEngine/Scenario.cs b/src/lib/AdrianKousz.GenieEngine/Scenario.cs index 92e9b11..a801b51 100644 --- a/src/lib/AdrianKousz.GenieEngine/Scenario.cs +++ b/src/lib/AdrianKousz.GenieEngine/Scenario.cs @@ -9,7 +9,7 @@ namespace AdrianKousz.GenieEngine public const Int32 Separator = -99; public const Int32 NumPlayers = 16; public const Int32 NumPlayerSections = 9; - public const Encoding Encoding = Encoding.GetEncoding(1252); + public static readonly Encoding Encoding = Encoding.GetEncoding(1252); public const Int32 ExpectedUnknown1 = 2; public const Int32 ExpectedUnknown2 = 0; @@ -117,11 +117,11 @@ namespace AdrianKousz.GenieEngine public Int32 Artifacts; public Int32 Discovery; public Int32 PercentExplored; - public Int32 Unknown; public Boolean RequireAllCustom; public Int32 Mode; public Int32 Score; public Int32 Time; + public Int32 Unknown; } public class ScnResourceCopy @@ -166,17 +166,17 @@ namespace AdrianKousz.GenieEngine public class ScnUnit { public const Single ExpectedUnknown1 = 1; - public const Byte ExpectedUnknown2 = 2; + public const SByte ExpectedUnknown2 = 2; public Single PosX; public Single PosY; - public Single Unknown1; public Int32 Id; public Int16 UnitId; - public Byte Unknown2; public Single Rotation; public Int16 InitialFrame; public Int32 GarrisonnedInId; + public Single Unknown1; + public SByte Unknown2; } } } diff --git a/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs b/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs deleted file mode 100644 index a0375bb..0000000 --- a/src/lib/AdrianKousz.GenieEngine/ScnConvert.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 index b346e09..8bea630 100644 --- a/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs +++ b/src/lib/AdrianKousz.GenieEngine/ScnDefaultFactory.cs @@ -125,7 +125,7 @@ namespace AdrianKousz.GenieEngine result.GeneratorId = 2; result.SizeX = size; result.SizeY = size; - result.LinearTiles = new Map.Tile[size*size].Fill(() => new Map.Tile(0, 1, 0)); + result.LinearTiles = new Scenario.ScnMap.Tile[size*size].Fill(() => new Scenario.ScnMap.Tile(0, 1, 0)); return result; } diff --git a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs b/src/lib/AdrianKousz.GenieEngine/ScnReader.cs deleted file mode 100644 index bf7811b..0000000 --- a/src/lib/AdrianKousz.GenieEngine/ScnReader.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System.IO; -using System.IO.Compression; -using System.Collections.Generic; -using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; - -namespace AdrianKousz.GenieEngine -{ - public class ScnReader - { - public float Version = 0; - - private ExtendedBinaryReader reader; - private IScnFactory factory; - - public ScnReader(Stream input) : this (input, new ScnDefaultFactory()) - { - } - - public ScnReader(Stream input, IScnFactory factory) - { - reader = new ExtendedBinaryReader(input, Scenario.Encoding); - this.factory = factory; - } - - #region Reader Methods - - public Scenario ReadScenario() - { - var result = factory.MakeScenario(false); - int number = 0; - - result.Header = ReadScenarioHeader(); - - var comp = new DeflateStream(reader.BaseStream, CompressionMode.Decompress); - reader = new ExtendedBinaryReader(comp, Scenario.Encoding); - - result.NextId = reader.ReadInt32(); - - Version = reader.ReadSingle(); - result.OriginalVersion = Version; - - 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, Scenario.NumPlayers); - - ReadSeparator("Victory"); - - result.GlobalVictory = ReadGlobalVictoryInfo(); - result.LinearDiplomacy = FillArray(reader.ReadInt32, result.LinearDiplomacy, Scenario.NumPlayers * Scenario.NumPlayers); - result.RawIndividualVictory = reader.ReadBytes(11520); - - ReadSeparator("Player Settings"); - - result.AlliedVictory = FillArray(reader.ReadInt32, result.AlliedVictory, Scenario.NumPlayers); - - 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 = FillArray(reader.ReadInt32, result.StartingAges, Scenario.NumPlayers); - - ReadSeparator("Map"); - - result.Map = ReadMap(); - - number = reader.ReadInt32(); - result.ResourcesCopy = FillArray(ReadResourceInfoCopy, result.ResourcesCopy, number - 1); - - result.Units = FillArray(ReadUnitInfoList, result.Units, number); - - var bytestream = new MemoryStream(); - reader.BaseStream.CopyTo(bytestream); - result.RawRemaining = bytestream.ToArray(); - - return result; - } - - public ScenarioHeader ReadScenarioHeader() - { - var result = factory.MakeScenarioHeader(); - - result.OriginalVersion = reader.ReadString(4); - reader.ReadInt32(); // Header Length - result.Unknown1 = reader.ReadInt32(); - result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32()); - result.Instructions = reader.ReadZStringInt32(); - result.Unknown2 = reader.ReadInt32(); - result.PlayerCount = reader.ReadInt32(); - - return result; - } - - public PlayerInfo ReadPlayerInfo() - { - var result = factory.MakePlayerInfo(); - - result.Active = reader.ReadBoolean(); - result.Human = reader.ReadBoolean(); - result.Civ = reader.ReadInt32(); - result.Unknown = reader.ReadInt32(); - - return result; - } - - public Messages ReadMessages() - { - var result = factory.MakeMessages(); - - result.Unknown1 = reader.ReadInt32(); - result.Unknown2 = reader.ReadByte(); - result.Unknown3 = reader.ReadSingle(); - result.OriginalFilename = reader.ReadStringInt16(); - - 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 (Version >= 1.22f) { - result.StringTableScouts = reader.ReadInt32(); - } - - result.TextInstructions = reader.ReadZStringInt16(); - result.TextHints = reader.ReadZStringInt16(); - result.TextVictory = reader.ReadZStringInt16(); - result.TextLoss = reader.ReadZStringInt16(); - result.TextHistory = reader.ReadZStringInt16(); - if (Version >= 1.22f) { - result.TextScouts = reader.ReadZStringInt16(); - } - - return result; - } - - public Cinematics ReadCinematics() - { - var result = factory.MakeCinematics(); - - result.CinemaPregameFn = reader.ReadStringInt16(); - result.CinemaVictoryFn = reader.ReadStringInt16(); - result.CinemaLossFn = reader.ReadStringInt16(); - result.BackgroundFn = reader.ReadStringInt16(); - - var hasBitmap = reader.ReadBoolean(); - - result.BitmapWidth = reader.ReadInt32(); - result.BitmapHeight = reader.ReadInt32(); - result.BitmapUnknown = reader.ReadInt16(); - - if (hasBitmap) { - result.RawBitmap = BitmapUtil.ReadRawBitmap(reader.BaseStream); - } - - return result; - } - - public AIData ReadAIData() - { - var result = factory.MakeAIData(); - - 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.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 < Scenario.NumPlayers; ++i) { - l1 = reader.ReadInt32(); - l2 = reader.ReadInt32(); - l3 = reader.ReadInt32(); - result.PlayerCustomAIs[i] = reader.ReadString(l1); - result.PlayerCustomCTYs[i] = reader.ReadString(l2); - result.PlayerCustomPERs[i] = reader.ReadString(l3); - } - - if (Version >= 1.18f) { - result.AITypes = FillArray(reader.ReadByte, result.AITypes, Scenario.NumPlayers); - } - - return result; - } - - public ResourceInfo ReadResourceInfo() - { - var result = factory.MakeResourceInfo(); - - result.Gold = reader.ReadInt32(); - result.Wood = reader.ReadInt32(); - result.Food = reader.ReadInt32(); - result.Stone = reader.ReadInt32(); - if (Version >= 1.18f) { - result.Ore = reader.ReadInt32(); - if (Version < 1.3f) { - result.Unknown = reader.ReadInt32(); - } - } - - return result; - } - - public ResourceInfoCopy ReadResourceInfoCopy() - { - var result = factory.MakeResourceInfoCopy(); - - result.Food = reader.ReadSingle(); - result.Wood = reader.ReadSingle(); - result.Gold = reader.ReadSingle(); - result.Stone = reader.ReadSingle(); - if (Version >= 1.18f) { - result.Ore = reader.ReadInt32(); - if (Version < 1.3f) { - result.Unknown = reader.ReadInt32(); - } - } - if (Version >= 1.22f) { - result.PopulationLimit = reader.ReadSingle(); - } - - return result; - } - - public GlobalVictoryInfo ReadGlobalVictoryInfo() - { - var result = factory.MakeGlobalVictoryInfo(); - - 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; - } - - public void ReadSeparator(string name) { - int v = reader.ReadInt32(); - if (v != Scenario.Separator) { - var msg = "Separator \"{0}\" = {1}"; - msg = string.Format(msg, name, v); - throw new InvalidDataException(msg); - } - } - - #endregion - - #region Debug Methods - - private T[] FillArray(System.Func f, T[] array, int length) - { - if (array == null || array.Length != length) - array = new T[length]; - array.Fill(f); - return array; - } - - #endregion - } -} - diff --git a/src/lib/AdrianKousz.GenieEngine/ScnSerializerReader.cs b/src/lib/AdrianKousz.GenieEngine/ScnSerializerReader.cs index 7e6ce29..be74a27 100644 --- a/src/lib/AdrianKousz.GenieEngine/ScnSerializerReader.cs +++ b/src/lib/AdrianKousz.GenieEngine/ScnSerializerReader.cs @@ -5,7 +5,7 @@ using AdrianKousz.Util; namespace AdrianKousz.GenieEngine { - public class ScnSerializerReader + internal class ScnSerializerReader { private IScnFactory factory; @@ -25,7 +25,7 @@ namespace AdrianKousz.GenieEngine var Version = 0f; var number = 0; var result = factory is ScnDefaultFactory - ? factory.MakeScenario(false) + ? ((ScnDefaultFactory)factory).MakeScenario(false) : factory.MakeScenario(); // Uncompressed header @@ -180,7 +180,7 @@ namespace AdrianKousz.GenieEngine // PlayerSettings 3 result.PlayerSettings.ForEach(x => { - x.Diplomacy = new int[Scenario.NumPlayers].Fill(reader.ReadInt32()); + x.Diplomacy = new int[Scenario.NumPlayers].Fill(reader.ReadInt32); }); result.RawIndividualVictory = reader.ReadBytes(11520); @@ -188,7 +188,7 @@ namespace AdrianKousz.GenieEngine ReadSeparator(reader, "Player Environment"); result.PlayerSettings.ForEach(x => { - x.AlliedVictory = reader.ReadInt32(); + x.AlliedVictory = reader.ReadBoolean(); }); if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; @@ -200,7 +200,7 @@ namespace AdrianKousz.GenieEngine x.StartingAge = reader.ReadInt32(); }); - ReadSeparator("Map"); + ReadSeparator(reader, "Map"); // Map diff --git a/src/lib/AdrianKousz.GenieEngine/ScnSerializerWriter.cs b/src/lib/AdrianKousz.GenieEngine/ScnSerializerWriter.cs index 5212be2..011341d 100644 --- a/src/lib/AdrianKousz.GenieEngine/ScnSerializerWriter.cs +++ b/src/lib/AdrianKousz.GenieEngine/ScnSerializerWriter.cs @@ -1,11 +1,10 @@ using System.IO; using System.IO.Compression; using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; namespace AdrianKousz.GenieEngine { - public class ScnSerializerWriter + internal class ScnSerializerWriter { public static ScnSerializerWriter CreateDefault() { @@ -23,7 +22,7 @@ namespace AdrianKousz.GenieEngine // Uncompressed header - writer.Write(value.VersionString); + writer.WriteStringRaw(value.VersionString); writer.Write((int)UncompressedHeaderLength); writer.Write(value.Unknown1); writer.Write((System.Int32)DateTimes.ToUnixTime(value.Timestamp)); @@ -117,9 +116,6 @@ namespace AdrianKousz.GenieEngine writer.Write((int)writer.GetByteCount(x.ScriptAI)); writer.Write((int)writer.GetByteCount(x.ScriptCTY)); writer.Write((int)writer.GetByteCount(x.ScriptPER)); - }); - - value.PlayerSettings.ForEach(x => { writer.WriteStringRaw(x.ScriptAI); writer.WriteStringRaw(x.ScriptCTY); writer.WriteStringRaw(x.ScriptPER); diff --git a/src/lib/AdrianKousz.GenieEngine/ScnValidator.cs b/src/lib/AdrianKousz.GenieEngine/ScnValidator.cs deleted file mode 100644 index ae90976..0000000 --- a/src/lib/AdrianKousz.GenieEngine/ScnValidator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using AdrianKousz.GenieEngine.Data; - -namespace AdrianKousz.GenieEngine -{ - public class ScnValidator - { - public ScnValidator() - { - } - - public bool Validate(Scenario value) - { - return true; - } - - public class ValidationError - { - public T actualValue; - public T expectedValue; - public string message; - } - } -} diff --git a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs b/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs deleted file mode 100644 index 2e78042..0000000 --- a/src/lib/AdrianKousz.GenieEngine/ScnWriter.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System.IO; -using System.IO.Compression; -using System.Collections.Generic; -using AdrianKousz.Util; -using AdrianKousz.GenieEngine.Data; - -namespace AdrianKousz.GenieEngine -{ - public class ScnWriter - { - public float Version = 0; - - private ExtendedBinaryWriter writer; - - public ScnWriter(Stream output) - { - 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.GetZByteCount(value.Instructions); - writer.WriteStringRaw(value.OriginalVersion); - writer.Write(20 + instructionlength); - writer.Write(value.Unknown1); - writer.Write((System.Int32)DateTimes.ToUnixTime(value.Timestamp)); - writer.WriteZStringInt32(value.Instructions); - writer.Write(value.Unknown2); - writer.Write(value.PlayerCount); - } - - public void Write(PlayerInfo value) - { - 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 816f6d7..ce8797e 100644 --- a/src/lib/GenieEngineLib.csproj +++ b/src/lib/GenieEngineLib.csproj @@ -34,16 +34,13 @@ - - - - +