Compare commits

..

No commits in common. "15ce34ae195bdf0dfdb77be8765ec482266d0bb9" and "7694fb0c030db92f92d8f0e9d07265756e4dae70" have entirely different histories.

28 changed files with 830 additions and 347 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
[Dd]ebug [Dd]ebug
[Rr]elease [Rr]elease
*.userprefs *.userprefs
packages

View File

@ -1,38 +1,30 @@
# Age of Empires Scenario Editor # 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.*
**Beta version** **Alpha version**
# Command Line Application ## Usage
## Features 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
* Convert AOE1 Scenario to AOE2 Scenario The command `copymap` is able convert an AOE1 to an AOE2 scenario.
* Convert a Scenario file to JSON and vice-versa There seem to be some issues with cliffs.
* 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) [OpeningMoves-AOE2.scx](files/OpeningMoves-AOE2.scx)
is a fully automated conversion of a Scenario file from the AOE1 demo version: is a fully automated conversion of a scenario from the AOE1 demo version:
$ GenieEdit.exe convert files/examples/aoe2x-Empty.scx "files/examples/aoe1demo-Reigno_1-Opening Moves.scn" files/OpeningMoves-AOE2.scx $ 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.) (Stone are set to 100 to be able to build a town center from the beginning.)
A unit dump looks like the following: It can also be used to dump units from any version of a scenario:
$ GenieEdit.exe dumpunits Debug2.scx $ GenieEdit.exe dumpunits Debug2.scx
Scenario Editor Scenario Editor
@ -44,37 +36,13 @@ A unit dump looks like the following:
1 69 23 ( 2,5, 11,5) 1 69 23 ( 2,5, 11,5)
... ...
# Library ### Code
The namespace `AdrianKousz.GenieEngine` contains classes Have a look at the command implementations. They are very short.
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<Scenario>(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 ## Build
Clone the projects MainUtil.cs, ExUtil.cs, and this one, Clone my projects MainUtil.cs ExUtil.cs and this repository
and build `src/GenieEdit.sln`. and build `src/GenieEdit.sln`.
## Contributing ## Contributing
@ -85,10 +53,12 @@ Keep in mind that the API is still very unstable.
Suggestions: Suggestions:
1. Gather UnitIDs of AOE1 1. Gather UnitIDs of AOE1
2. Much help is appreciated to figure out Unit ID mappings 2. Much help is appreciated to figure out ID mappings
for the conversion process. for the conversion process.
3. Ideas how to configure the converter (mapping tables). 3. Ideas how to configure the converter (mapping tables).
There are some notes in German: There are some notes in German:
[AOE1](files/units-aoe1.txt), [AOE1](files/units-aoe1.txt),
[AOE2](files/units-aoe2.txt) [AOE2](files/units-aoe2.txt)
3. Feedback about the API. 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

View File

@ -31,15 +31,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="src\Program.cs" /> <Compile Include="src\Program.cs" />
<Compile Include="src\BaseCommand.cs" />
<Compile Include="src\ExtractCommand.cs" />
<Compile Include="src\ToJsonCommand.cs" />
<Compile Include="src\FromJsonCommand.cs" />
<Compile Include="src\CompressCommand.cs" />
<Compile Include="src\DecompressCommand.cs" /> <Compile Include="src\DecompressCommand.cs" />
<Compile Include="src\BaseCommand.cs" />
<Compile Include="src\CompressCommand.cs" />
<Compile Include="src\ExtractCommand.cs" />
<Compile Include="src\DumpCommand.cs" />
<Compile Include="src\CopymapCommand.cs" />
<Compile Include="src\DumpunitsCommand.cs" /> <Compile Include="src\DumpunitsCommand.cs" />
<Compile Include="src\CopymapConverter.cs" /> <Compile Include="src\CopymapConverter.cs" />
<Compile Include="src\ConvertCommand.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -62,11 +61,5 @@
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Web.Extensions" /> <Reference Include="System.Web.Extensions" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
</packages>

View File

@ -7,10 +7,20 @@ namespace AdrianKousz.GenieEngine
{ {
override public void Run() override public void Run()
{ {
using (var input = File.OpenRead(args[0])) using (var file = File.OpenRead(args[0])) {
using (var output = File.OpenWrite(args[1])) using (var outfile = File.OpenWrite(args[1])) {
{ var reader = new ScnReader(file);
GenieFile.ScenarioCompression(input, output, false); 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);
}
}
} }
} }

View File

@ -1,56 +0,0 @@
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<Scenario>(refstream);
var srcscn = GenieFile.Deserialize<Scenario>(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<AOE2 reference scenario> <AOE1 scenario> <output scenario>"
;
}
}
}

View File

@ -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"
;
}
}
}

View File

@ -1,24 +1,26 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AdrianKousz.Util; using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
public class CopymapConverter public class CopymapConverter
{ {
public void Convert(Scenario scn) public void Convert(Scenario dst, Scenario src)
{ {
scn.Units = ChangeUnits_AOE1_AOE2(scn.Units); dst.Map = src.Map;
MoveUnits_AOE1_AOE2(scn.Units); dst.Units = ChangeUnits_AOE1_AOE2(src.Units);
ChangeTiles_AOE1_AOE2(scn.Units, scn.Map); MoveUnits_AOE1_AOE2(dst.Units);
RandomizeTrees_AOE2(scn.Units); ChangeTiles_AOE1_AOE2(dst.Units, dst.Map);
RandomizeTrees_AOE2(dst.Units);
} }
/** /**
* Some buildings are not the same size. * Some buildings are not the same size.
* They need to be moved by half a unit. * They need to be moved by half a unit.
*/ */
private void MoveUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units) private void MoveUnits_AOE1_AOE2(IList<UnitInfo>[] units)
{ {
var mapping = new int[] { var mapping = new int[] {
// Towers // Towers
@ -41,7 +43,7 @@ namespace AdrianKousz.GenieEngine
/** /**
* Simple mapping of unit IDs * Simple mapping of unit IDs
*/ */
private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units) private List<UnitInfo>[] ChangeUnits_AOE1_AOE2(IList<UnitInfo>[] units)
{ {
var array = new int[] { var array = new int[] {
// Resources // Resources
@ -140,17 +142,18 @@ namespace AdrianKousz.GenieEngine
121, 121, 121, 121,
129, 129, 129, 129,
}; };
var mapping = new Dictionary<short, short>(); var mapping = new Dictionary<int, int>();
var ai = 0; var ai = 0;
while (ai < array.Length) while (ai < array.Length)
mapping.Add((short)array[ai++], (short)array[ai++]); mapping.Add(array[ai++], array[ai++]);
var result = new List<Scenario.ScnUnit>[Scenario.NumPlayerSections].Fill(); var result = new List<UnitInfo>[Scenario.NumPlayerSections];
result.Fill();
units.ForEach((i, list) => { units.ForEach((i, list) => {
list.ForEach((j, unit) => { list.ForEach((j, unit) => {
if (mapping.ContainsKey(unit.UnitId)) { if (mapping.ContainsKey(unit.UnitId)) {
unit.UnitId = mapping[unit.UnitId]; unit.UnitId = (short)mapping[unit.UnitId];
result[i].Add(unit); result[i].Add(unit);
} }
}); });
@ -162,7 +165,7 @@ namespace AdrianKousz.GenieEngine
/** /**
* Farms need to be converted including the underlying terrain. * Farms need to be converted including the underlying terrain.
*/ */
private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] units, Scenario.ScnMap map) private void ChangeTiles_AOE1_AOE2(IList<UnitInfo>[] units, Map map)
{ {
units.ForEach((i, list) => { units.ForEach((i, list) => {
list.ForEach((j, unit) => { list.ForEach((j, unit) => {
@ -172,7 +175,6 @@ namespace AdrianKousz.GenieEngine
if (false) { if (false) {
// switch // switch
} else if (unit.UnitId == 50) { } else if (unit.UnitId == 50) {
// Farm
map.LinearTiles[linearpos].Id = 7; map.LinearTiles[linearpos].Id = 7;
map.LinearTiles[linearpos - 1].Id = 7; map.LinearTiles[linearpos - 1].Id = 7;
map.LinearTiles[linearpos + 1].Id = 7; map.LinearTiles[linearpos + 1].Id = 7;
@ -198,7 +200,7 @@ namespace AdrianKousz.GenieEngine
* Forests would contain the same tree graphic * Forests would contain the same tree graphic
* without this function * without this function
*/ */
private void RandomizeTrees_AOE2(IList<Scenario.ScnUnit>[] units) private void RandomizeTrees_AOE2(IList<UnitInfo>[] units)
{ {
var treeids = new int[] { var treeids = new int[] {
411, 351, 414, 350, 348, 349, 411, 351, 414, 350, 348, 349,

View File

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.IO.Compression;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
@ -6,10 +7,20 @@ namespace AdrianKousz.GenieEngine
{ {
override public void Run() override public void Run()
{ {
using (var input = File.OpenRead(args[0])) using (var file = File.OpenRead(args[0])) {
using (var output = File.OpenWrite(args[1])) using (var outfile = File.OpenWrite(args[1])) {
{ var reader = new ScnReader(file);
GenieFile.ScenarioCompression(input, output, true); 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);
}
}
} }
} }

View File

@ -0,0 +1,29 @@
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";
}
}
}

View File

@ -2,6 +2,7 @@
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using AdrianKousz.Util; using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
@ -10,9 +11,9 @@ namespace AdrianKousz.GenieEngine
override public void Run() override public void Run()
{ {
Scenario scn; Scenario scn;
using (var input = File.OpenRead(args[0])) using (var stream = File.OpenRead(args[0])) {
{ var aoereader = new ScnReader(stream);
scn = GenieFile.Deserialize<Scenario>(input); scn = aoereader.ReadScenario();
} }
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position"); Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position");

View File

@ -1,32 +0,0 @@
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<Scenario>(str);
GenieFile.Serialize(scn, output);
}
}
override public int GetArgumentCount()
{
return 2;
}
override public string GetDescription()
{
return "Read a JSON file and save as scenario";
}
}
}

View File

@ -25,12 +25,11 @@ namespace AdrianKousz.GenieEngine
public IDictionary<string, ICommand<string[]>> GetCommands() public IDictionary<string, ICommand<string[]>> GetCommands()
{ {
var result = new Dictionary<string, ICommand<string[]>>(); var result = new Dictionary<string, ICommand<string[]>>();
result.Add("convert", new ConvertCommand()); result.Add("copymap", new CopymapCommand());
result.Add("compress", new CompressCommand()); result.Add("compress", new CompressCommand());
result.Add("decompress", new DecompressCommand()); result.Add("decompress", new DecompressCommand());
result.Add("extract", new ExtractCommand()); result.Add("extract", new ExtractCommand());
result.Add("tojson", new ToJsonCommand()); result.Add("dump", new DumpCommand());
result.Add("fromjson", new FromJsonCommand());
result.Add("dumpunits", new DumpunitsCommand()); result.Add("dumpunits", new DumpunitsCommand());
return result; return result;
} }
@ -39,7 +38,8 @@ namespace AdrianKousz.GenieEngine
{ {
var fn = ""; var fn = "";
using (var istream = System.IO.File.OpenRead(fn)) { using (var istream = System.IO.File.OpenRead(fn)) {
var scn = GenieFile.Deserialize<Scenario>(istream); var ireader = new ScnReader(istream);
var scn = ireader.ReadScenario();
System.Diagnostics.Debugger.Break(); // Great to inspect scn System.Diagnostics.Debugger.Break(); // Great to inspect scn
} }
} }

View File

@ -1,31 +0,0 @@
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<Scenario>(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";
}
}
}

View File

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using AdrianKousz.Util; using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {

View File

@ -1,115 +0,0 @@
using System.IO;
using System.IO.Compression;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public static class GenieFile
{
#region Serialization
public static byte[] Serialize<T>(T value)
{
byte[] result;
using (var stream = new MemoryStream())
{
Serialize(value, stream);
result = stream.ToArray();
}
return result;
}
public static void Serialize<T>(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<T>(byte[] array)
{
return (T)Deserialize(array, typeof(T));
}
public static T Deserialize<T>(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
}
}

View File

@ -1,4 +1,7 @@
namespace AdrianKousz.GenieEngine using System;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{ {
public interface IScnFactory public interface IScnFactory
{ {

View File

@ -9,7 +9,7 @@ namespace AdrianKousz.GenieEngine
public const Int32 Separator = -99; public const Int32 Separator = -99;
public const Int32 NumPlayers = 16; public const Int32 NumPlayers = 16;
public const Int32 NumPlayerSections = 9; public const Int32 NumPlayerSections = 9;
public static readonly Encoding Encoding = Encoding.GetEncoding(1252); public const Encoding Encoding = Encoding.GetEncoding(1252);
public const Int32 ExpectedUnknown1 = 2; public const Int32 ExpectedUnknown1 = 2;
public const Int32 ExpectedUnknown2 = 0; public const Int32 ExpectedUnknown2 = 0;
@ -117,11 +117,11 @@ namespace AdrianKousz.GenieEngine
public Int32 Artifacts; public Int32 Artifacts;
public Int32 Discovery; public Int32 Discovery;
public Int32 PercentExplored; public Int32 PercentExplored;
public Int32 Unknown;
public Boolean RequireAllCustom; public Boolean RequireAllCustom;
public Int32 Mode; public Int32 Mode;
public Int32 Score; public Int32 Score;
public Int32 Time; public Int32 Time;
public Int32 Unknown;
} }
public class ScnResourceCopy public class ScnResourceCopy
@ -166,17 +166,17 @@ namespace AdrianKousz.GenieEngine
public class ScnUnit public class ScnUnit
{ {
public const Single ExpectedUnknown1 = 1; public const Single ExpectedUnknown1 = 1;
public const SByte ExpectedUnknown2 = 2; public const Byte ExpectedUnknown2 = 2;
public Single PosX; public Single PosX;
public Single PosY; public Single PosY;
public Single Unknown1;
public Int32 Id; public Int32 Id;
public Int16 UnitId; public Int16 UnitId;
public Byte Unknown2;
public Single Rotation; public Single Rotation;
public Int16 InitialFrame; public Int16 InitialFrame;
public Int32 GarrisonnedInId; public Int32 GarrisonnedInId;
public Single Unknown1;
public SByte Unknown2;
} }
} }
} }

View File

@ -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;
}
}
}
}

View File

@ -125,7 +125,7 @@ namespace AdrianKousz.GenieEngine
result.GeneratorId = 2; result.GeneratorId = 2;
result.SizeX = size; result.SizeX = size;
result.SizeY = size; result.SizeY = size;
result.LinearTiles = new Scenario.ScnMap.Tile[size*size].Fill(() => new Scenario.ScnMap.Tile(0, 1, 0)); result.LinearTiles = new Map.Tile[size*size].Fill(() => new Map.Tile(0, 1, 0));
return result; return result;
} }

View File

@ -0,0 +1,327 @@
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<UnitInfo> ReadUnitInfoList()
{
var count = reader.ReadInt32();
var result = new List<UnitInfo>().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<T>(System.Func<T> f, T[] array, int length)
{
if (array == null || array.Length != length)
array = new T[length];
array.Fill(f);
return array;
}
#endregion
}
}

View File

@ -5,7 +5,7 @@ using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
internal class ScnSerializerReader public class ScnSerializerReader
{ {
private IScnFactory factory; private IScnFactory factory;
@ -25,7 +25,7 @@ namespace AdrianKousz.GenieEngine
var Version = 0f; var Version = 0f;
var number = 0; var number = 0;
var result = factory is ScnDefaultFactory var result = factory is ScnDefaultFactory
? ((ScnDefaultFactory)factory).MakeScenario(false) ? factory.MakeScenario(false)
: factory.MakeScenario(); : factory.MakeScenario();
// Uncompressed header // Uncompressed header
@ -180,7 +180,7 @@ namespace AdrianKousz.GenieEngine
// PlayerSettings 3 // PlayerSettings 3
result.PlayerSettings.ForEach(x => { 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); result.RawIndividualVictory = reader.ReadBytes(11520);
@ -188,7 +188,7 @@ namespace AdrianKousz.GenieEngine
ReadSeparator(reader, "Player Environment"); ReadSeparator(reader, "Player Environment");
result.PlayerSettings.ForEach(x => { result.PlayerSettings.ForEach(x => {
x.AlliedVictory = reader.ReadBoolean(); x.AlliedVictory = reader.ReadInt32();
}); });
if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
@ -200,7 +200,7 @@ namespace AdrianKousz.GenieEngine
x.StartingAge = reader.ReadInt32(); x.StartingAge = reader.ReadInt32();
}); });
ReadSeparator(reader, "Map"); ReadSeparator("Map");
// Map // Map

View File

@ -1,10 +1,11 @@
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using AdrianKousz.Util; using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
internal class ScnSerializerWriter public class ScnSerializerWriter
{ {
public static ScnSerializerWriter CreateDefault() public static ScnSerializerWriter CreateDefault()
{ {
@ -22,7 +23,7 @@ namespace AdrianKousz.GenieEngine
// Uncompressed header // Uncompressed header
writer.WriteStringRaw(value.VersionString); writer.Write(value.VersionString);
writer.Write((int)UncompressedHeaderLength); writer.Write((int)UncompressedHeaderLength);
writer.Write(value.Unknown1); writer.Write(value.Unknown1);
writer.Write((System.Int32)DateTimes.ToUnixTime(value.Timestamp)); writer.Write((System.Int32)DateTimes.ToUnixTime(value.Timestamp));
@ -116,6 +117,9 @@ namespace AdrianKousz.GenieEngine
writer.Write((int)writer.GetByteCount(x.ScriptAI)); writer.Write((int)writer.GetByteCount(x.ScriptAI));
writer.Write((int)writer.GetByteCount(x.ScriptCTY)); writer.Write((int)writer.GetByteCount(x.ScriptCTY));
writer.Write((int)writer.GetByteCount(x.ScriptPER)); writer.Write((int)writer.GetByteCount(x.ScriptPER));
});
value.PlayerSettings.ForEach(x => {
writer.WriteStringRaw(x.ScriptAI); writer.WriteStringRaw(x.ScriptAI);
writer.WriteStringRaw(x.ScriptCTY); writer.WriteStringRaw(x.ScriptCTY);
writer.WriteStringRaw(x.ScriptPER); writer.WriteStringRaw(x.ScriptPER);

View File

@ -0,0 +1,23 @@
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public class ScnValidator
{
public ScnValidator()
{
}
public bool Validate(Scenario value)
{
return true;
}
public class ValidationError<T>
{
public T actualValue;
public T expectedValue;
public string message;
}
}
}

View File

@ -0,0 +1,266 @@
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<UnitInfo> 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");
}
}
}

View File

@ -34,13 +34,16 @@
<ItemGroup> <ItemGroup>
<Compile Include="AdrianKousz.GenieEngine\BitmapUtil.cs" /> <Compile Include="AdrianKousz.GenieEngine\BitmapUtil.cs" />
<Compile Include="AdrianKousz.GenieEngine\CpnReader.cs" /> <Compile Include="AdrianKousz.GenieEngine\CpnReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnValidator.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnWriter.cs" />
<Compile Include="AssemblyInfo.cs" /> <Compile Include="AssemblyInfo.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnConvert.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnDefaultFactory.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnDefaultFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\IScnFactory.cs" /> <Compile Include="AdrianKousz.GenieEngine\IScnFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnSerializerWriter.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnSerializerWriter.cs" />
<Compile Include="AdrianKousz.GenieEngine\Scenario.cs" /> <Compile Include="AdrianKousz.GenieEngine\Scenario.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnSerializerReader.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnSerializerReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\GenieFile.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>