Compare commits

..

6 Commits

Author SHA1 Message Date
Adrian 15ce34ae19 Update documentation 2016-02-18 17:14:46 +01:00
Adrian 900d6df4ee Adapt command line program to API changes, add JSON conversion
* Handle scenario compression in library
* Add Json.NET library to handle JSON conversion
2016-02-18 16:50:10 +01:00
Adrian e2d3285685 Add GenieFile wrapper for implementation detail 2016-02-18 15:28:29 +01:00
Adrian e246904835 Library refactoring finished
Fix bugs found by testing of the new code
2016-02-18 15:22:53 +01:00
Adrian 639ad2b418 Library refactoring 5
Code fixes
2016-02-18 13:58:18 +01:00
Adrian f71ed9159e Library refactoring 4
Remove obsolete files.
A converter and validator will be re-added at later.
2016-02-18 13:56:24 +01:00
28 changed files with 346 additions and 829 deletions

1
.gitignore vendored
View File

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

View File

@ -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<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
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.

BIN
doc/MapCoord.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

View File

@ -31,14 +31,15 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="src\Program.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\ToJsonCommand.cs" />
<Compile Include="src\FromJsonCommand.cs" />
<Compile Include="src\CompressCommand.cs" />
<Compile Include="src\DecompressCommand.cs" />
<Compile Include="src\DumpunitsCommand.cs" />
<Compile Include="src\CopymapConverter.cs" />
<Compile Include="src\ConvertCommand.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@ -61,5 +62,11 @@
<ItemGroup>
<Reference Include="System" />
<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>
</Project>

View File

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

View File

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

View File

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

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

View File

@ -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<UnitInfo>[] units)
private void MoveUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units)
{
var mapping = new int[] {
// Towers
@ -43,7 +41,7 @@ namespace AdrianKousz.GenieEngine
/**
* Simple mapping of unit IDs
*/
private List<UnitInfo>[] ChangeUnits_AOE1_AOE2(IList<UnitInfo>[] units)
private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units)
{
var array = new int[] {
// Resources
@ -142,18 +140,17 @@ namespace AdrianKousz.GenieEngine
121, 121,
129, 129,
};
var mapping = new Dictionary<int, int>();
var mapping = new Dictionary<short, short>();
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<UnitInfo>[Scenario.NumPlayerSections];
result.Fill();
var result = new List<Scenario.ScnUnit>[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<UnitInfo>[] units, Map map)
private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] 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<UnitInfo>[] units)
private void RandomizeTrees_AOE2(IList<Scenario.ScnUnit>[] units)
{
var treeids = new int[] {
411, 351, 414, 350, 348, 349,

View File

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

View File

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

View File

@ -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<Scenario>(input);
}
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position");

View File

@ -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<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,11 +25,12 @@ namespace AdrianKousz.GenieEngine
public IDictionary<string, ICommand<string[]>> GetCommands()
{
var result = new Dictionary<string, ICommand<string[]>>();
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<Scenario>(istream);
System.Diagnostics.Debugger.Break(); // Great to inspect scn
}
}

View File

@ -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<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,6 +1,5 @@
using System.IO;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{

View File

@ -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>(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,7 +1,4 @@
using System;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
namespace AdrianKousz.GenieEngine
{
public interface IScnFactory
{

View File

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

View File

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

View File

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

View File

@ -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<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
{
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

View File

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

View File

@ -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<T>
{
public T actualValue;
public T expectedValue;
public string message;
}
}
}

View File

@ -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<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,16 +34,13 @@
<ItemGroup>
<Compile Include="AdrianKousz.GenieEngine\BitmapUtil.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="AdrianKousz.GenieEngine\ScnConvert.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnDefaultFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\IScnFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnSerializerWriter.cs" />
<Compile Include="AdrianKousz.GenieEngine\Scenario.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnSerializerReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\GenieFile.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>