Compare commits

..

5 Commits

Author SHA1 Message Date
Adrian 82d8551ab1 Update Readme, add notes and conversion example 2016-02-15 20:42:18 +01:00
Adrian 41cb3e798b Rudimentary AOE1 to AOE2 converter and unit dumper 2016-02-15 20:22:58 +01:00
Adrian bb5ac298a4 Fix some serialization bugs 2016-02-15 04:26:53 +01:00
Adrian b65cbcd980 ScnWriter completed
Additionally:
- Use a factory to create empty objects
- Move constants to Scenario class
- Scenario class is in serialization order
- Some refactoring

The code is untested, because DeflateStream does not write using Flush.
2016-02-15 02:10:16 +01:00
Adrian 4f98f03cfa Small refactoring 2016-02-14 21:03:01 +01:00
29 changed files with 1139 additions and 305 deletions

View File

@ -4,7 +4,61 @@ Command line application and library to edit scenario and campaign files.
**Alpha version** **Alpha version**
## Usage
Available commands are:
copymap Copy parts of scenarios
compress Recompress a decompressed scenario file
decompress Decompress a scenario file for hex editing
extract Extract scenarios from campaign file
dump Dump a scenario file to JSON
dumpunits Dump units and map tiles below
The command `copymap` is able convert an AOE1 to an AOE2 scenario.
There seem to be some issues with cliffs.
[OpeningMoves-AOE2.scx](files/OpeningMoves-AOE2.scx)
is a fully automated conversion of a scenario from the AOE1 demo version:
$ GenieEdit.exe copymap files/examples/aoe2x-Empty.scx "files/examples/aoe1demo-Reigno_1-Opening Moves.scn" files/OpeningMoves-AOE2.scx
(Stone are set to 100 to be able to build a town center from the beginning.)
It can also be used to dump units from any version of a scenario:
$ GenieEdit.exe dumpunits Debug2.scx
Scenario Editor
ID UnitID TileID Position
Units of player 0:
0 69 23 ( 1,5, 11,5)
1 69 23 ( 2,5, 11,5)
...
### Code
Have a look at the command implementations. They are very short.
## Build ## Build
Clone my projects MainUtil.cs ExUtil.cs and this repository Clone my projects MainUtil.cs ExUtil.cs and this repository
and build `src/GenieEdit.sln`. and build `src/GenieEdit.sln`.
## Contributing
Contributions are very welcome. ☺
Keep in mind that the API is still very unstable.
Suggestions:
1. Gather UnitIDs of AOE1
2. Much help is appreciated to figure out ID mappings
for the conversion process.
3. Ideas how to configure the converter (mapping tables).
There are some notes in German:
[AOE1](files/units-aoe1.txt),
[AOE2](files/units-aoe2.txt)
3. The application should be able to generate maps from images
in the future. How should units be placed using the image?
4. Feedback about the API.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,59 @@
Gold 66
Bush 59
Stone 102
Klippe 3x3 264
Dorfbewohner 83
Kleiner Wall 72
Mittelgrosser Wall 117
Befestigungsanlage 155
Beobachtungsturm 2x2 79
Abwehrturm 2x2 199
Wachturm 2x2 69
Geschützturm 2x2 278
Haus 2x2 70
Artefakt 159
Bauernhof=>Feld 3x3 50
Dorfzentrum 3x3 109
Kaserne 3x3 12
Kornspeicher 3x3 68
Lagergrube 3x3 103
Stall 101
Baum 134
Buche 140
Eiche 141 144 145 146
Esche, Fichte 148 136
Kiefer 197 194 193 192 135 137 138 139 391 392
Palme 150-153
Wald Gelaende TileID=10:
142 143 147 195 365 366 367 393
Palmenhain Gelaende TileID=13:
113 114 121 129
Dschungel TileID=20:
113 114 121 129
Kiefernwald TileID=19:
161 198 203 226 391 392
Fisch Kueste 260
Fisch Kueste 263
Fisch Lachs 53
Fisch Thun 52
Fisch Wal 370
Aligator 1
Aligator Koenig 362
Elefant 48
Elefant Koenig 90
Gazelle 65
Gazelle Koenig 384
Loewe 126
Loewe Koenig 89
Vogel Adler 96
Vogel Habicht 95

View File

@ -0,0 +1,74 @@
Gold 66
Bush 59
Stone 102
Klippe 3x3 264
Dorfbewohner 83
Palisadenwall 72
Steinwallall 117
Befestigter Wall 155
Aussenposten 1x1 598
Beobachtungsturm 1x1 79
Wachturm 1x1 79
Hauptturm 1x1 79
Haus 2x2 70
Reliquie 285
Bauernhof=>Feld 3x3 50 (TileID=7)
Dorfzentrum 4x4 109
Kaserne 3x3 12
Muehle 2x2 68
Holzlager 2x2 562
Stall 3x3 101
Bambus 348
Baum A-L 399-410
Baum TD 248
Dschungelbaum 414
Eiche 349
Kiefer 350
Palme 351
Verschneiter Kiefer 413
Waldbaum 411
Wald Gelaende TileID=10:
411
Palmenhain Gelaende TileID=13:
351
Dschungel TileID=17:
414
Kiefernwald TileID=19:
350
Bambus TileID=18:
348
Eichenwald TileID=20:
349
Fisch Barsch 53
Fisch Dorado 455
Fisch Lachs 456
Fisch Schnapper 458
Fisch Thun 457
Fisch Marlin 1 451
Fisch Marlin 2 450
Fisch Kueste 69
Tiere:
Vogel Falke 96
Jaguar 812
Wildes Pferd 835
Pferd 814
Reh 65
Schaf 594
Truthahn 833
Urwolf 89
Wolf 126
Wilder Eber 810
Wildschwein 48

View File

@ -36,6 +36,9 @@
<Compile Include="src\CompressCommand.cs" /> <Compile Include="src\CompressCommand.cs" />
<Compile Include="src\ExtractCommand.cs" /> <Compile Include="src\ExtractCommand.cs" />
<Compile Include="src\DumpCommand.cs" /> <Compile Include="src\DumpCommand.cs" />
<Compile Include="src\CopymapCommand.cs" />
<Compile Include="src\DumpunitsCommand.cs" />
<Compile Include="src\CopymapConverter.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -50,6 +53,10 @@
<Project>{662FDCF9-3F6C-48DA-BFE9-F317BEFED692}</Project> <Project>{662FDCF9-3F6C-48DA-BFE9-F317BEFED692}</Project>
<Name>ExUtil</Name> <Name>ExUtil</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\..\MainUtil.cs\src\main\MainUtil.csproj">
<Project>{8306E345-3A99-4F22-AF14-3CEA7425C2EF}</Project>
<Name>MainUtil</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />

View File

@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine
var reader = new ScnReader(file); var reader = new ScnReader(file);
var writer = new ScnWriter(outfile); var writer = new ScnWriter(outfile);
var header = reader.ReadScenarioHeader(null); var header = reader.ReadScenarioHeader();
reader.ReadSeparator("Custom"); reader.ReadSeparator("Custom");
writer.Write(header); writer.Write(header);
writer.Flush(); writer.Flush();

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

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public class CopymapConverter
{
public void Convert(Scenario dst, Scenario src)
{
dst.Map = src.Map;
dst.Units = ChangeUnits_AOE1_AOE2(src.Units);
MoveUnits_AOE1_AOE2(dst.Units);
ChangeTiles_AOE1_AOE2(dst.Units, dst.Map);
RandomizeTrees_AOE2(dst.Units);
}
/**
* Some buildings are not the same size.
* They need to be moved by half a unit.
*/
private void MoveUnits_AOE1_AOE2(IList<UnitInfo>[] units)
{
var mapping = new int[] {
// Towers
79, 199, 69, 278,
// Other Buildings
109, 68, 103,
};
units.ForEach((i, list) => {
list.ForEach((j, unit) => {
mapping.ForEach((k, buildingid) => {
if (unit.UnitId == buildingid) {
unit.PosX -= 0.5f;
unit.PosY -= 0.5f;
}
});
});
});
}
/**
* Simple mapping of unit IDs
*/
private List<UnitInfo>[] ChangeUnits_AOE1_AOE2(IList<UnitInfo>[] units)
{
var array = new int[] {
66, 66,
59, 59,
102, 102,
264, 264,
83, 83,
125, 125,
// Buildings
72, 72,
117, 117,
155, 155,
79, 598,
199, 79,
69, 79,
278, 79,
70, 70,
159, 285,
109, 109,
12, 12,
68, 68,
103, 562,
101, 101,
// Trees
141, 349,
144, 349,
145, 349,
146, 349,
197, 350,
194, 350,
193, 350,
192, 350,
135, 350,
137, 350,
138, 350,
139, 350,
161, 350,
198, 350,
203, 350,
226, 350,
391, 350,
392, 350,
150, 351,
151, 351,
152, 351,
153, 351,
134, 411,
140, 411,
148, 411,
136, 411,
142, 411,
143, 411,
147, 411,
195, 411,
365, 411,
366, 411,
367, 411,
393, 411,
// Fish
260, 69,
263, 69,
53, 456,
52, 457,
370, 451,
// Animals
48, 48,
90, 48,
65, 65,
384, 65,
126, 126,
89, 126,
96, 96,
// Keep and process later
50, 50,
113, 113,
114, 114,
121, 121,
129, 129,
};
var mapping = new Dictionary<int, int>();
var ai = 0;
while (ai < array.Length)
mapping.Add(array[ai++], array[ai++]);
var result = new List<UnitInfo>[Scenario.NumPlayerSections];
result.Fill();
units.ForEach((i, list) => {
list.ForEach((j, unit) => {
if (mapping.ContainsKey(unit.UnitId)) {
unit.UnitId = (short)mapping[unit.UnitId];
result[i].Add(unit);
}
});
});
return result;
}
/**
* Farms need to be converted including the underlying terrain.
*/
private void ChangeTiles_AOE1_AOE2(IList<UnitInfo>[] units, Map map)
{
units.ForEach((i, list) => {
list.ForEach((j, unit) => {
var posx = (int)(unit.PosX - 0.5);
var posy = (int)(unit.PosY - 0.5);
var linearpos = posy * map.SizeX + posx;
if (false) {
// switch
} else if (unit.UnitId == 50) {
map.LinearTiles[linearpos].Id = 7;
map.LinearTiles[linearpos - 1].Id = 7;
map.LinearTiles[linearpos + 1].Id = 7;
map.LinearTiles[linearpos - map.SizeX].Id = 7;
map.LinearTiles[linearpos - map.SizeX - 1].Id = 7;
map.LinearTiles[linearpos - map.SizeX + 1].Id = 7;
map.LinearTiles[linearpos + map.SizeX].Id = 7;
map.LinearTiles[linearpos + map.SizeX - 1].Id = 7;
map.LinearTiles[linearpos + map.SizeX + 1].Id = 7;
} else if (unit.UnitId == 113 || unit.UnitId == 114 || unit.UnitId == 121 || unit.UnitId == 129) {
if (map.LinearTiles[linearpos].Id == 20) {
map.LinearTiles[linearpos].Id = 17;
unit.UnitId = 414;
} else {
unit.UnitId = 351;
}
}
});
});
}
/**
* Forests would contain the same tree graphic
* without this function
*/
private void RandomizeTrees_AOE2(IList<UnitInfo>[] units)
{
var treeids = new int[] {
411, 351, 414, 350, 348, 349,
};
units.ForEach((i, list) => {
list.ForEach((j, unit) => {
treeids.ForEach((k, treeid) => {
if (unit.UnitId == treeid) {
unit.InitialFrame = (short)Util.Math.Rand(0, 13); // Whats the maximum?
unit.Rotation = unit.InitialFrame;
}
});
});
});
}
}
}

View File

@ -12,9 +12,9 @@ namespace AdrianKousz.GenieEngine
var reader = new ScnReader(file); var reader = new ScnReader(file);
var writer = new ScnWriter(outfile); var writer = new ScnWriter(outfile);
var header = reader.ReadScenarioHeader(null); var header = reader.ReadScenarioHeader();
writer.Write(header); writer.Write(header);
writer.WriteSeperator(); writer.WriteSeperator("Custom");
writer.Flush(); writer.Flush();
using (var comp = new DeflateStream(file, CompressionMode.Decompress)) { using (var comp = new DeflateStream(file, CompressionMode.Decompress)) {

View File

@ -9,7 +9,7 @@ namespace AdrianKousz.GenieEngine
{ {
using (var stream = File.OpenRead(args[0])) { using (var stream = File.OpenRead(args[0])) {
var aoereader = new ScnReader(stream); var aoereader = new ScnReader(stream);
var scn = aoereader.ReadScenario(null); var scn = aoereader.ReadScenario();
var ser = new System.Web.Script.Serialization.JavaScriptSerializer(); var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
var result = ser.Serialize(scn); var result = ser.Serialize(scn);
Console.WriteLine(result); Console.WriteLine(result);
@ -23,7 +23,7 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription() override public string GetDescription()
{ {
return "Dump a scenario file to XML"; return "Dump a scenario file to JSON";
} }
} }
} }

View File

@ -0,0 +1,44 @@
using System;
using System.IO;
using System.Collections.Generic;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public class DumpunitsCommand : BaseCommand
{
override public void Run()
{
Scenario scn;
using (var stream = File.OpenRead(args[0])) {
var aoereader = new ScnReader(stream);
scn = aoereader.ReadScenario();
}
Console.WriteLine("{0,-8} {1,-8} {2,-8} {3}", "ID", "UnitID", "TileID", "Position");
Console.WriteLine();
scn.Units.ForEach((i, list) => {
Console.WriteLine("Units of player {0}:", i);
list.ForEach((j, unit) => {
var tilex = (int)(unit.PosX - 0.5);
var tiley = (int)(unit.PosY - 0.5);
var tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex];
Console.WriteLine("{0,-8} {1,-8} {2,-8} ({3,6:0.##},{4,6:0.##})", unit.Id, unit.UnitId, tile.Id, unit.PosX, unit.PosY);
});
Console.WriteLine();
});
}
override public int GetArgumentCount()
{
return 1;
}
override public string GetDescription()
{
return "Dump units and map tiles below";
}
}
}

View File

@ -9,6 +9,11 @@ namespace AdrianKousz.GenieEngine
{ {
System.Threading.Thread.CurrentThread.Name = "main"; System.Threading.Thread.CurrentThread.Name = "main";
if (System.Diagnostics.Debugger.IsAttached) {
debug();
return;
}
new CmdApp(new Program(), args).Run(); new CmdApp(new Program(), args).Run();
} }
@ -20,11 +25,23 @@ 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("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("dump", new DumpCommand()); result.Add("dump", new DumpCommand());
result.Add("dumpunits", new DumpunitsCommand());
return result; return result;
} }
private static void debug()
{
var fn = "";
using (var istream = System.IO.File.OpenRead(fn)) {
var ireader = new ScnReader(istream);
var scn = ireader.ReadScenario();
System.Diagnostics.Debugger.Break(); // Great to inspect scn
}
}
} }
} }

View File

@ -11,7 +11,7 @@ namespace AdrianKousz.GenieEngine.Data
public String[] PlayerCustomAIs; public String[] PlayerCustomAIs;
public String[] PlayerCustomCTYs; public String[] PlayerCustomCTYs;
public String[] PlayerCustomPERs; public String[] PlayerCustomPERs;
public Byte[] RawAITypes; public Byte[] AITypes;
} }
} }

View File

@ -9,6 +9,10 @@ namespace AdrianKousz.GenieEngine.Data
public String CinemaLossFn; public String CinemaLossFn;
public String BackgroundFn; public String BackgroundFn;
public Int32 BitmapWidth;
public Int32 BitmapHeight;
public Int16 BitmapUnknown;
public Byte[] RawBitmap; public Byte[] RawBitmap;
} }
} }

View File

@ -1,14 +0,0 @@
using System;
using System.Text;
namespace AdrianKousz.GenieEngine.Data
{
public static class Consts
{
public const Int32 Separator = -99;
public const Int32 NumPlayers = 16;
public const Int32 NumUnitSections = 9;
public static readonly Encoding Encoding = Encoding.GetEncoding(1252);
public static readonly Char[] NullChar = new Char[] { '\0' };
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace AdrianKousz.GenieEngine.Data
{
public class GlobalVictoryInfo
{
public Boolean RequireConquest;
public Int32 Ruins;
public Int32 Artifacts;
public Int32 Discovery;
public Int32 PercentExplored;
public Int32 Unknown;
public Boolean RequireAllCustom;
public Int32 Mode;
public Int32 Score;
public Int32 Time;
}
}

View File

@ -6,8 +6,6 @@ namespace AdrianKousz.GenieEngine.Data
{ {
public const Int32 ExpectedUnknown = 4; public const Int32 ExpectedUnknown = 4;
public String Name;
public Int32 StringTable;
public Boolean Active; public Boolean Active;
public Boolean Human; public Boolean Human;
public Int32 Civ; public Int32 Civ;

View File

@ -1,12 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AdrianKousz.GenieEngine.Data; using System.Text;
namespace AdrianKousz.GenieEngine.Data namespace AdrianKousz.GenieEngine.Data
{ {
public class Scenario public class Scenario
{ {
public const Int32 PlayerSections = 9; public const Int32 Separator = -99;
public const Int32 NumPlayers = 16;
public const Int32 NumPlayerSections = 9;
public static readonly Encoding Encoding = Encoding.GetEncoding(1252);
public ScenarioHeader Header; public ScenarioHeader Header;
@ -15,22 +18,36 @@ namespace AdrianKousz.GenieEngine.Data
public Int32 NextId; public Int32 NextId;
public Single OriginalVersion; public Single OriginalVersion;
public String[] PlayerNames;
public Int32[] StringTablePlayerNames;
public PlayerInfo[] Players; public PlayerInfo[] Players;
public Messages Messages; public Messages Messages;
public Cinematics Cinematics; public Cinematics Cinematics;
public AIData AIData; public AIData AIData;
// Seperator "Resources"
public ResourceInfo[] Resources; public ResourceInfo[] Resources;
// Seperator "Victory"
public GlobalVictoryInfo GlobalVictory;
public Int32[] LinearDiplomacy;
public Byte[] RawIndividualVictory;
// Seperator "Player Settings"
public Int32[] AlliedVictory;
public Byte[] RawDisables;
public Int32[] StartingAges;
// Seperator "Map"
public Map Map; public Map Map;
public ResourceInfoCopy[] ResourcesCopy; public ResourceInfoCopy[] ResourcesCopy;
public IList<UnitInfo>[] Units; public IList<UnitInfo>[] Units;
public Byte[] RawGlobalVictory; public byte[] RawRemaining;
public Byte[] RawDiplomacy;
public Byte[] RawIndividualVictory;
public Byte[] RawAlliedVictory;
public Byte[] RawDisables;
public Int32[] StartingAges;
} }
} }

View File

@ -4,13 +4,14 @@ namespace AdrianKousz.GenieEngine.Data
{ {
public class UnitInfo public class UnitInfo
{ {
public const Single ExpectedUnknown = 2f; public const Single ExpectedUnknown1 = 1f;
public const Byte ExpectedUnknown2 = 2;
public Single PosX; public Single PosX;
public Single PosY; public Single PosY;
public Single Unknown1; public Single Unknown1;
public Int32 Id; public Int32 Id;
public Int32 UnitId; public Int16 UnitId;
public Byte Unknown2; public Byte Unknown2;
public Single Rotation; public Single Rotation;
public Int16 InitialFrame; public Int16 InitialFrame;

View File

@ -12,7 +12,7 @@ namespace AdrianKousz.GenieEngine
public CpnReader(Stream input) public CpnReader(Stream input)
{ {
reader = new ExtendedBinaryReader(input, Data.Consts.Encoding); reader = new ExtendedBinaryReader(input, Scenario.Encoding);
} }
public void ExtractFiles() public void ExtractFiles()

View File

@ -0,0 +1,20 @@
using System;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public interface IScnFactory
{
Scenario MakeScenario(bool deep);
ScenarioHeader MakeScenarioHeader();
PlayerInfo MakePlayerInfo();
Messages MakeMessages();
Cinematics MakeCinematics();
AIData MakeAIData();
ResourceInfo MakeResourceInfo();
ResourceInfoCopy MakeResourceInfoCopy();
GlobalVictoryInfo MakeGlobalVictoryInfo();
Map MakeMap();
UnitInfo MakeUnitInfo();
}
}

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

@ -0,0 +1,148 @@
using System.Collections.Generic;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public class ScnDefaultFactory : IScnFactory
{
public Scenario MakeScenario(bool deep)
{
var result = new Scenario();
result.OriginalVersion = 0;
result.NextId = 1;
result.StringTablePlayerNames = new int[Scenario.NumPlayers];
result.Units = new IList<UnitInfo>[Scenario.NumPlayerSections]
.Fill(() => new List<UnitInfo>());
return result;
}
public ScenarioHeader MakeScenarioHeader()
{
var result = new ScenarioHeader();
result.Unknown1 = ScenarioHeader.ExpectedUnknown1;
result.Unknown2 = ScenarioHeader.ExpectedUnknown2;
result.OriginalVersion = "0.00";
result.Timestamp = System.DateTime.Now;
result.Instructions = "";
result.PlayerCount = 2;
return result;
}
public PlayerInfo MakePlayerInfo()
{
var result = new PlayerInfo();
result.Unknown = PlayerInfo.ExpectedUnknown;
result.Active = false;
result.Human = false;
result.Civ = 0;
return result;
}
public Messages MakeMessages()
{
var result = new Messages();
result.Unknown1 = Messages.ExpectedUnknown1;
result.Unknown2 = Messages.ExpectedUnknown2;
result.Unknown3 = Messages.ExpectedUnknown3;
result.OriginalFilename = "";
result.TextInstructions = "";
result.TextHints = "";
result.TextVictory = "";
result.TextLoss = "";
result.TextHistory = "";
result.TextScouts = "";
result.StringTableInstructions = -1;
result.StringTableHints = -1;
result.StringTableVictory = -1;
result.StringTableLoss = -1;
result.StringTableHistory = -1;
result.StringTableScouts = -1;
return result;
}
public Cinematics MakeCinematics()
{
var result = new Cinematics();
result.CinemaPregameFn = "";
result.CinemaVictoryFn = "";
result.CinemaLossFn = "";
result.BackgroundFn = "";
result.RawBitmap = new byte[0];
return result;
}
public AIData MakeAIData()
{
var result = new AIData();
result.PlayerAIs = new string[Scenario.NumPlayers].Fill("");
result.PlayerCTYs = new string[Scenario.NumPlayers].Fill("");
result.PlayerPERs = new string[Scenario.NumPlayers].Fill("");
result.PlayerCustomAIs = new string[Scenario.NumPlayers].Fill("");
result.PlayerCustomCTYs = new string[Scenario.NumPlayers].Fill("");
result.PlayerCustomPERs = new string[Scenario.NumPlayers].Fill("");
result.AITypes = new byte[Scenario.NumPlayers].Fill((byte)1);
return result;
}
public GlobalVictoryInfo MakeGlobalVictoryInfo()
{
var result = new GlobalVictoryInfo();
result.RequireConquest = true;
result.RequireAllCustom = false;
return result;
}
public ResourceInfo MakeResourceInfo()
{
// AOE2 standard
var result = new ResourceInfo();
result.Unknown = ResourceInfo.ExpectedUnknown;
result.Food = 200;
result.Wood = 200;
result.Gold = 100;
result.Stone = 200;
return result;
}
public ResourceInfoCopy MakeResourceInfoCopy()
{
// AOE2 standard
var result = new ResourceInfoCopy();
result.Unknown = ResourceInfoCopy.ExpectedUnknown;
result.Food = 200;
result.Wood = 200;
result.Gold = 100;
result.Stone = 200;
result.PopulationLimit = 75;
return result;
}
public Map MakeMap()
{
var result = new Map();
var size = 144;
result.CameraX = -1;
result.CameraY = -1;
result.GeneratorId = 2;
result.SizeX = size;
result.SizeY = size;
result.LinearTiles = new Map.Tile[size*size].Fill(() => new Map.Tile(0, 1, 0));
return result;
}
public UnitInfo MakeUnitInfo()
{
var result = new UnitInfo();
result.Unknown1 = UnitInfo.ExpectedUnknown1;
result.Unknown2 = UnitInfo.ExpectedUnknown2;
result.GarrisonnedInId = -1;
return result;
}
}
}

View File

@ -1,137 +0,0 @@
using System.Collections.Generic;
using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data;
namespace AdrianKousz.GenieEngine
{
public class ScnFactory
{
public Scenario GetScenario()
{
var result = new Scenario();
result.OriginalVersion = -1;
result.NextId = 1;
result.Header = GetScenarioHeader();
result.Players = new PlayerInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.Players.Fill(GetPlayerInfo);
result.Messages = GetMessages();
result.Cinematics = GetCinematics();
result.AIData = GetAIData();
result.Resources = new ResourceInfo[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.Resources.Fill(GetResourceInfo);
result.Units = new List<UnitInfo>[Scenario.PlayerSections];
result.Units.Fill(() => new List<UnitInfo>());
return result;
}
public ScenarioHeader GetScenarioHeader()
{
var result = new ScenarioHeader();
result.Unknown1 = ScenarioHeader.ExpectedUnknown1;
result.Unknown2 = ScenarioHeader.ExpectedUnknown2;
result.OriginalVersion = "0.99";
result.Timestamp = System.DateTime.Now;
result.Instructions = "";
result.PlayerCount = 0;
return result;
}
public PlayerInfo GetPlayerInfo(int number)
{
var result = new PlayerInfo();
result.Unknown = PlayerInfo.ExpectedUnknown;
result.Name = "Player " + number;
result.StringTable = -1;
result.Active = true;
result.Human = true;
result.Civ = number;
return result;
}
public Messages GetMessages()
{
var result = new Messages();
result.Unknown1 = Messages.ExpectedUnknown1;
result.Unknown2 = Messages.ExpectedUnknown2;
result.Unknown3 = Messages.ExpectedUnknown3;
result.OriginalFilename = "";
result.TextInstructions = "";
result.TextHints = "";
result.TextVictory = "";
result.TextLoss = "";
result.TextHistory = "";
result.TextScouts = "";
result.StringTableInstructions = -1;
result.StringTableHints = -1;
result.StringTableVictory = -1;
result.StringTableLoss = -1;
result.StringTableHistory = -1;
result.StringTableScouts = -1;
return result;
}
public Cinematics GetCinematics()
{
var result = new Cinematics();
result.CinemaPregameFn = "";
result.CinemaVictoryFn = "";
result.CinemaLossFn = "";
result.BackgroundFn = "";
return result;
}
public AIData GetAIData()
{
var result = new AIData();
result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerAIs.Fill("");
result.PlayerCTYs.Fill("");
result.PlayerPERs.Fill("");
result.PlayerCustomAIs.Fill("");
result.PlayerCustomCTYs.Fill("");
result.PlayerCustomPERs.Fill("");
result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.RawAITypes.Fill((byte)1);
return result;
}
public ResourceInfo GetResourceInfo()
{
// AOE2 standard
var result = new ResourceInfo();
result.Unknown = ResourceInfo.ExpectedUnknown;
result.Food = 200;
result.Wood = 200;
result.Gold = 100;
result.Stone = 200;
result.Ore = 0;
return result;
}
public Map GetMap()
{
var result = new Map();
var size = 200;
result.CameraX = 0;
result.CameraY = 0;
result.GeneratorId = 0;
result.SizeX = size;
result.SizeY = size;
result.LinearTiles = new Map.Tile[size*size];
result.LinearTiles.Fill();
return result;
}
}
}

View File

@ -8,81 +8,90 @@ namespace AdrianKousz.GenieEngine
{ {
public class ScnReader public class ScnReader
{ {
private static readonly string TAG = typeof(ScnReader).Name; public float Version = 0;
private float version = 0.99f;
private ExtendedBinaryReader reader; private ExtendedBinaryReader reader;
private IScnFactory factory;
public ScnReader(Stream input) public ScnReader(Stream input) : this (input, new ScnDefaultFactory())
{ {
reader = new ExtendedBinaryReader(input, Data.Consts.Encoding); }
public ScnReader(Stream input, IScnFactory factory)
{
reader = new ExtendedBinaryReader(input, Scenario.Encoding);
this.factory = factory;
} }
#region Reader Methods #region Reader Methods
public Scenario ReadScenario(Scenario result) public Scenario ReadScenario()
{ {
result = Misc.MayNew(result); var result = factory.MakeScenario(false);
int number = 0; int number = 0;
result.Header = ReadScenarioHeader(result.Header); result.Header = ReadScenarioHeader();
var comp = new DeflateStream(reader.BaseStream, CompressionMode.Decompress); var comp = new DeflateStream(reader.BaseStream, CompressionMode.Decompress);
reader = new ExtendedBinaryReader(comp, Data.Consts.Encoding); reader = new ExtendedBinaryReader(comp, Scenario.Encoding);
result.NextId = reader.ReadInt32(); result.NextId = reader.ReadInt32();
version = reader.ReadSingle(); Version = reader.ReadSingle();
result.OriginalVersion = version; result.OriginalVersion = Version;
Log.i(TAG, "Detected version {0}", version);
result.PlayerNames = FillArray(() => reader.ReadZString(256), result.PlayerNames, Scenario.NumPlayers);
result.Players = FillArray(ReadPlayerInfo, result.Players, AdrianKousz.GenieEngine.Data.Consts.NumPlayers); if (Version >= 1.18f) {
result.Messages = ReadMessages(result.Messages); result.StringTablePlayerNames = FillArray(reader.ReadInt32, result.StringTablePlayerNames, Scenario.NumPlayers);
result.Cinematics = ReadCinematics(result.Cinematics); }
result.AIData = ReadAIData(result.AIData); result.Players = FillArray(ReadPlayerInfo, result.Players, Scenario.NumPlayers);
result.Messages = ReadMessages();
result.Cinematics = ReadCinematics();
result.AIData = ReadAIData();
ReadSeparator("Resources"); ReadSeparator("Resources");
result.Resources = FillArray(ReadResourceInfo, result.Resources, AdrianKousz.GenieEngine.Data.Consts.NumPlayers); result.Resources = FillArray(ReadResourceInfo, result.Resources, Scenario.NumPlayers);
ReadSeparator("Victory"); ReadSeparator("Victory");
result.RawGlobalVictory = reader.ReadBytes(10 * 4); result.GlobalVictory = ReadGlobalVictoryInfo();
result.RawDiplomacy = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4); result.LinearDiplomacy = FillArray(reader.ReadInt32, result.LinearDiplomacy, Scenario.NumPlayers * Scenario.NumPlayers);
result.RawIndividualVictory = reader.ReadBytes(11520); result.RawIndividualVictory = reader.ReadBytes(11520);
ReadSeparator("Player Settings"); ReadSeparator("Player Settings");
result.RawAlliedVictory = reader.ReadBytes(AdrianKousz.GenieEngine.Data.Consts.NumPlayers * 4); result.AlliedVictory = FillArray(reader.ReadInt32, result.AlliedVictory, Scenario.NumPlayers);
if (1.15f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; if (Version >= 1.15f) number = (Scenario.NumPlayers * ( 20 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
if (1.18f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; if (Version >= 1.18f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 80 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
if (1.30f <= version) number = (AdrianKousz.GenieEngine.Data.Consts.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/; if (Version >= 1.30f) number = (Scenario.NumPlayers * (3 /*Lengths*/ + 180 /*Disables*/) + 3 /*Unknowns*/) * 4 /*Intsize*/;
result.RawDisables = reader.ReadBytes(number); result.RawDisables = reader.ReadBytes(number);
result.StartingAges = new int[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; result.StartingAges = FillArray(reader.ReadInt32, result.StartingAges, Scenario.NumPlayers);
result.StartingAges.Fill(reader.ReadInt32);
ReadSeparator("Map"); ReadSeparator("Map");
result.Map = ReadMap(result.Map); result.Map = ReadMap();
number = reader.ReadInt32(); number = reader.ReadInt32();
result.ResourcesCopy = FillArray(ReadResourceInfoCopy, result.ResourcesCopy, number - 1); result.ResourcesCopy = FillArray(ReadResourceInfoCopy, result.ResourcesCopy, number - 1);
result.Units = new IList<UnitInfo>[number]; result.Units = FillArray(ReadUnitInfoList, result.Units, number);
result.Units.Fill(ReadUnitInfo);
var bytestream = new MemoryStream();
reader.BaseStream.CopyTo(bytestream);
result.RawRemaining = bytestream.ToArray();
return result; return result;
} }
public ScenarioHeader ReadScenarioHeader(ScenarioHeader result) public ScenarioHeader ReadScenarioHeader()
{ {
result = Misc.MayNew(result); var result = factory.MakeScenarioHeader();
result.OriginalVersion = reader.ReadString(4); result.OriginalVersion = reader.ReadString(4);
Skip(4, "Header Length"); reader.ReadInt32(); // Header Length
result.Unknown1 = reader.ReadInt32(); result.Unknown1 = reader.ReadInt32();
result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32()); result.Timestamp = DateTimes.FromUnixTime(reader.ReadInt32());
result.Instructions = reader.ReadZStringInt32(); result.Instructions = reader.ReadZStringInt32();
@ -92,14 +101,10 @@ namespace AdrianKousz.GenieEngine
return result; return result;
} }
public PlayerInfo ReadPlayerInfo(PlayerInfo result) public PlayerInfo ReadPlayerInfo()
{ {
result = Misc.MayNew(result); var result = factory.MakePlayerInfo();
result.Name = reader.ReadZString(256);
if (1.18f <= version) {
result.StringTable = reader.ReadInt32();
}
result.Active = reader.ReadBoolean(); result.Active = reader.ReadBoolean();
result.Human = reader.ReadBoolean(); result.Human = reader.ReadBoolean();
result.Civ = reader.ReadInt32(); result.Civ = reader.ReadInt32();
@ -108,23 +113,23 @@ namespace AdrianKousz.GenieEngine
return result; return result;
} }
public Messages ReadMessages(Messages result) public Messages ReadMessages()
{ {
result = Misc.MayNew(result); var result = factory.MakeMessages();
result.Unknown1 = reader.ReadInt32(); result.Unknown1 = reader.ReadInt32();
result.Unknown2 = reader.ReadByte(); result.Unknown2 = reader.ReadByte();
result.Unknown3 = reader.ReadSingle(); result.Unknown3 = reader.ReadSingle();
result.OriginalFilename = reader.ReadStringInt16(); result.OriginalFilename = reader.ReadStringInt16();
if (1.18f <= version) { if (Version >= 1.18f) {
result.StringTableInstructions = reader.ReadInt32(); result.StringTableInstructions = reader.ReadInt32();
result.StringTableHints = reader.ReadInt32(); result.StringTableHints = reader.ReadInt32();
result.StringTableVictory = reader.ReadInt32(); result.StringTableVictory = reader.ReadInt32();
result.StringTableLoss = reader.ReadInt32(); result.StringTableLoss = reader.ReadInt32();
result.StringTableHistory = reader.ReadInt32(); result.StringTableHistory = reader.ReadInt32();
} }
if (1.22f <= version) { if (Version >= 1.22f) {
result.StringTableScouts = reader.ReadInt32(); result.StringTableScouts = reader.ReadInt32();
} }
@ -133,16 +138,16 @@ namespace AdrianKousz.GenieEngine
result.TextVictory = reader.ReadZStringInt16(); result.TextVictory = reader.ReadZStringInt16();
result.TextLoss = reader.ReadZStringInt16(); result.TextLoss = reader.ReadZStringInt16();
result.TextHistory = reader.ReadZStringInt16(); result.TextHistory = reader.ReadZStringInt16();
if (1.22f <= version) { if (Version >= 1.22f) {
result.TextScouts = reader.ReadZStringInt16(); result.TextScouts = reader.ReadZStringInt16();
} }
return result; return result;
} }
public Cinematics ReadCinematics(Cinematics result) public Cinematics ReadCinematics()
{ {
result = Misc.MayNew(result); var result = factory.MakeCinematics();
result.CinemaPregameFn = reader.ReadStringInt16(); result.CinemaPregameFn = reader.ReadStringInt16();
result.CinemaVictoryFn = reader.ReadStringInt16(); result.CinemaVictoryFn = reader.ReadStringInt16();
@ -151,37 +156,31 @@ namespace AdrianKousz.GenieEngine
var hasBitmap = reader.ReadBoolean(); var hasBitmap = reader.ReadBoolean();
Skip(4, "Bitmap width"); result.BitmapWidth = reader.ReadInt32();
Skip(4, "Bitmap height"); result.BitmapHeight = reader.ReadInt32();
Skip(2, "Bitmap Unknown"); result.BitmapUnknown = reader.ReadInt16();
if (hasBitmap) { if (hasBitmap) {
result.RawBitmap = BitmapUtil.ReadRawBitmap(reader.BaseStream); result.RawBitmap = BitmapUtil.ReadRawBitmap(reader.BaseStream);
} else {
result.RawBitmap = new byte[0];
} }
return result; return result;
} }
public AIData ReadAIData(AIData result) public AIData ReadAIData()
{ {
result = Misc.MayNew(result); var result = factory.MakeAIData();
result.PlayerAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; result.PlayerAIs = FillArray(reader.ReadStringInt16, result.PlayerAIs, Scenario.NumPlayers);
result.PlayerCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; result.PlayerCTYs = FillArray(reader.ReadStringInt16, result.PlayerCTYs, Scenario.NumPlayers);
result.PlayerPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers]; result.PlayerPERs = FillArray(reader.ReadStringInt16, result.PlayerPERs, Scenario.NumPlayers);
result.PlayerCustomAIs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCustomCTYs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerCustomPERs = new string[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.RawAITypes = new byte[AdrianKousz.GenieEngine.Data.Consts.NumPlayers];
result.PlayerAIs.Fill(reader.ReadStringInt16); result.PlayerCustomAIs = new string[Scenario.NumPlayers];
result.PlayerCTYs.Fill(reader.ReadStringInt16); result.PlayerCustomCTYs = new string[Scenario.NumPlayers];
result.PlayerPERs.Fill(reader.ReadStringInt16); result.PlayerCustomPERs = new string[Scenario.NumPlayers];
int l1, l2, l3; int l1, l2, l3;
for (var i = 0; i < AdrianKousz.GenieEngine.Data.Consts.NumPlayers; ++i) { for (var i = 0; i < Scenario.NumPlayers; ++i) {
l1 = reader.ReadInt32(); l1 = reader.ReadInt32();
l2 = reader.ReadInt32(); l2 = reader.ReadInt32();
l3 = reader.ReadInt32(); l3 = reader.ReadInt32();
@ -190,88 +189,112 @@ namespace AdrianKousz.GenieEngine
result.PlayerCustomPERs[i] = reader.ReadString(l3); result.PlayerCustomPERs[i] = reader.ReadString(l3);
} }
if (1.18f <= version) { if (Version >= 1.18f) {
result.RawAITypes.Fill(reader.ReadByte); result.AITypes = FillArray(reader.ReadByte, result.AITypes, Scenario.NumPlayers);
} }
return result; return result;
} }
public ResourceInfo ReadResourceInfo(ResourceInfo result) public ResourceInfo ReadResourceInfo()
{ {
result = Misc.MayNew(result); var result = factory.MakeResourceInfo();
result.Gold = reader.ReadInt32(); result.Gold = reader.ReadInt32();
result.Wood = reader.ReadInt32(); result.Wood = reader.ReadInt32();
result.Food = reader.ReadInt32(); result.Food = reader.ReadInt32();
result.Stone = reader.ReadInt32(); result.Stone = reader.ReadInt32();
if (1.18f <= version) { if (Version >= 1.18f) {
result.Ore = reader.ReadInt32(); result.Ore = reader.ReadInt32();
if (Version < 1.3f) {
result.Unknown = reader.ReadInt32(); result.Unknown = reader.ReadInt32();
} }
}
return result; return result;
} }
public Map ReadMap(Map result) public ResourceInfoCopy ReadResourceInfoCopy()
{ {
result = Misc.MayNew(result); var result = factory.MakeResourceInfoCopy();
if (1.18f <= version) {
result.CameraX = reader.ReadInt32();
result.CameraY = reader.ReadInt32();
}
if (1.22f <= version) {
result.GeneratorId = reader.ReadInt32();
}
result.SizeX = reader.ReadInt32();
result.SizeY = reader.ReadInt32();
result.LinearTiles = new Map.Tile[result.SizeX * result.SizeY];
result.LinearTiles.Fill(() => new Map.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()));
return result;
}
public ResourceInfoCopy ReadResourceInfoCopy(ResourceInfoCopy result)
{
result = Misc.MayNew(result);
result.Food = reader.ReadSingle(); result.Food = reader.ReadSingle();
result.Wood = reader.ReadSingle(); result.Wood = reader.ReadSingle();
result.Gold = reader.ReadSingle(); result.Gold = reader.ReadSingle();
result.Stone = reader.ReadSingle(); result.Stone = reader.ReadSingle();
if (1.18f <= version) { if (Version >= 1.18f) {
result.Ore = reader.ReadInt32(); result.Ore = reader.ReadInt32();
if (Version < 1.3f) {
result.Unknown = reader.ReadInt32(); result.Unknown = reader.ReadInt32();
} }
if (1.22f <= version) { }
if (Version >= 1.22f) {
result.PopulationLimit = reader.ReadSingle(); result.PopulationLimit = reader.ReadSingle();
} }
return result; return result;
} }
public IList<UnitInfo> ReadUnitInfo() public GlobalVictoryInfo ReadGlobalVictoryInfo()
{ {
var number = reader.ReadInt32(); var result = factory.MakeGlobalVictoryInfo();
var result = new List<UnitInfo>(number);
for (var i = 0; i < number; i++) { result.RequireConquest = reader.ReadBoolean();
var unit = new UnitInfo(); result.Ruins = reader.ReadInt32();
unit.PosX = reader.ReadSingle(); result.Artifacts = reader.ReadInt32();
unit.PosY = reader.ReadSingle(); result.Discovery = reader.ReadInt32();
unit.Unknown1 = reader.ReadSingle(); result.PercentExplored = reader.ReadInt32();
unit.Id = reader.ReadInt32(); result.Unknown = reader.ReadInt32();
unit.UnitId = reader.ReadInt16(); result.RequireAllCustom = reader.ReadBoolean();
unit.Unknown2 = reader.ReadByte(); result.Mode = reader.ReadInt32();
unit.Rotation = reader.ReadSingle(); result.Score = reader.ReadInt32();
if (1.18f <= version) { result.Time = reader.ReadInt32();
unit.InitialFrame = reader.ReadInt16();
unit.GarrisonnedInId = reader.ReadInt32(); return result;
} }
result.Add(unit);
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; return result;
@ -279,7 +302,7 @@ namespace AdrianKousz.GenieEngine
public void ReadSeparator(string name) { public void ReadSeparator(string name) {
int v = reader.ReadInt32(); int v = reader.ReadInt32();
if (v != AdrianKousz.GenieEngine.Data.Consts.Separator) { if (v != Scenario.Separator) {
var msg = "Separator \"{0}\" = {1}"; var msg = "Separator \"{0}\" = {1}";
msg = string.Format(msg, name, v); msg = string.Format(msg, name, v);
throw new InvalidDataException(msg); throw new InvalidDataException(msg);
@ -290,26 +313,14 @@ namespace AdrianKousz.GenieEngine
#region Debug Methods #region Debug Methods
private T[] FillArray<T>(System.Func<T, T> f, T[] array, int length) where T : new() private T[] FillArray<T>(System.Func<T> f, T[] array, int length)
{ {
if (array == null) { if (array == null || array.Length != length)
array = new T[length]; array = new T[length];
} else if (array.Length != length) {
var temp = new T[length];
System.Array.Copy(array, temp, System.Math.Min(array.Length, temp.Length));
}
array.Fill(f); array.Fill(f);
return array; return array;
} }
private void Skip(int count, string name)
{
Log.d(TAG, "Skipping {0} bytes in \"{1}\"", count, name);
reader.ReadBytes(count);
}
#endregion #endregion
} }
} }

View File

@ -1,4 +1,6 @@
using System.IO; using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using AdrianKousz.Util; using AdrianKousz.Util;
using AdrianKousz.GenieEngine.Data; using AdrianKousz.GenieEngine.Data;
@ -6,18 +8,76 @@ namespace AdrianKousz.GenieEngine
{ {
public class ScnWriter public class ScnWriter
{ {
private static readonly string TAG = typeof(ScnWriter).Name; public float Version = 0;
private ExtendedBinaryWriter writer; private ExtendedBinaryWriter writer;
public ScnWriter(Stream input) public ScnWriter(Stream output)
{ {
writer = new ExtendedBinaryWriter(input, Data.Consts.Encoding); writer = new ExtendedBinaryWriter(output, Scenario.Encoding);
}
public void Write(Scenario value)
{
Version = Version == 0 ? value.OriginalVersion : Version;
Write(value.Header);
Flush();
// TODO: Make wrapper.
// This is a workaround for now. A wrapper for the reader and writer will be created.
var originalWriter = writer;
var compressedPart = new MemoryStream();
var comp = new DeflateStream(compressedPart, CompressionMode.Compress);
writer = new ExtendedBinaryWriter(comp, Scenario.Encoding);
writer.Write(value.NextId);
writer.Write(Version);
value.PlayerNames.ForEach((x) => writer.WriteStringPadded(x, 256));
if (Version >= 1.18f) {
value.StringTablePlayerNames.ForEach(writer.Write);
}
value.Players.ForEach(Write);
Write(value.Messages);
Write(value.Cinematics);
Write(value.AIData);
WriteSeperator("Resources");
value.Resources.ForEach(Write);
WriteSeperator("Victory");
Write(value.GlobalVictory);
value.LinearDiplomacy.ForEach(writer.Write);
writer.Write(value.RawIndividualVictory);
WriteSeperator("Player Settings");
value.AlliedVictory.ForEach(writer.Write);
writer.Write(value.RawDisables);
value.StartingAges.ForEach(writer.Write);
WriteSeperator("Map");
Write(value.Map);
writer.Write((int)value.Units.Length);
value.ResourcesCopy.ForEach(Write);
value.Units.ForEach(Write);
writer.Write(value.RawRemaining);
Flush();
comp.Dispose();
originalWriter.Write(compressedPart.ToArray());
} }
public void Write(ScenarioHeader value) public void Write(ScenarioHeader value)
{ {
var instructionlength = writer.GetByteCount(value.Instructions + " "); var instructionlength = writer.GetZByteCount(value.Instructions);
writer.WriteStringRaw(value.OriginalVersion); writer.WriteStringRaw(value.OriginalVersion);
writer.Write(20 + instructionlength); writer.Write(20 + instructionlength);
writer.Write(value.Unknown1); writer.Write(value.Unknown1);
@ -27,15 +87,180 @@ namespace AdrianKousz.GenieEngine
writer.Write(value.PlayerCount); writer.Write(value.PlayerCount);
} }
public void WriteSeperator() public void Write(PlayerInfo value)
{ {
writer.Write(AdrianKousz.GenieEngine.Data.Consts.Separator); writer.Write(value.Active);
writer.Write(value.Human);
writer.Write(value.Civ);
writer.Write(value.Unknown);
}
public void Write(Messages value)
{
writer.Write(value.Unknown1);
writer.Write(value.Unknown2);
writer.Write(value.Unknown3);
writer.WriteStringInt16(value.OriginalFilename);
if (Version >= 1.18f) {
writer.Write(value.StringTableInstructions);
writer.Write(value.StringTableHints);
writer.Write(value.StringTableVictory);
writer.Write(value.StringTableLoss);
writer.Write(value.StringTableHistory);
}
if (Version >= 1.22f) {
writer.Write(value.StringTableScouts);
}
writer.WriteZStringInt16(value.TextInstructions);
writer.WriteZStringInt16(value.TextHints);
writer.WriteZStringInt16(value.TextVictory);
writer.WriteZStringInt16(value.TextLoss);
writer.WriteZStringInt16(value.TextHistory);
if (Version >= 1.22f) {
writer.WriteZStringInt16(value.TextScouts);
}
}
public void Write(Cinematics value)
{
writer.WriteStringInt16(value.CinemaPregameFn);
writer.WriteStringInt16(value.CinemaVictoryFn);
writer.WriteStringInt16(value.CinemaLossFn);
writer.WriteStringInt16(value.BackgroundFn);
var hasBitmap = value.RawBitmap.Length > 0;
writer.Write(hasBitmap);
writer.Write(value.BitmapWidth);
writer.Write(value.BitmapHeight);
writer.Write(value.BitmapUnknown);
if (hasBitmap)
writer.Write(value.RawBitmap);
}
public void Write(AIData value)
{
value.PlayerAIs.ForEach(writer.WriteStringInt16);
value.PlayerCTYs.ForEach(writer.WriteStringInt16);
value.PlayerPERs.ForEach(writer.WriteStringInt16);
for (var i = 0; i < Scenario.NumPlayers; ++i) {
var str1 = value.PlayerCustomAIs[i];
var str2 = value.PlayerCustomCTYs[i];
var str3 = value.PlayerCustomPERs[i];
writer.Write(writer.GetByteCount(str1));
writer.Write(writer.GetByteCount(str2));
writer.Write(writer.GetByteCount(str3));
writer.WriteStringRaw(str1);
writer.WriteStringRaw(str2);
writer.WriteStringRaw(str3);
}
if (Version >= 1.18f) {
value.AITypes.ForEach(writer.Write);
}
}
public void Write(ResourceInfo value)
{
writer.Write(value.Gold);
writer.Write(value.Wood);
writer.Write(value.Food);
writer.Write(value.Stone);
if (Version >= 1.18f) {
writer.Write(value.Ore);
if (Version < 1.3f) {
writer.Write(value.Unknown);
}
}
}
public void Write(ResourceInfoCopy value)
{
writer.Write(value.Food);
writer.Write(value.Wood);
writer.Write(value.Gold);
writer.Write(value.Stone);
if (Version >= 1.18f) {
writer.Write(value.Ore);
if (Version < 1.3f) {
writer.Write(value.Unknown);
}
}
if (Version >= 1.22f) {
writer.Write(value.PopulationLimit);
}
}
public void Write(GlobalVictoryInfo value)
{
writer.Write(value.RequireConquest);
writer.Write(value.Ruins);
writer.Write(value.Artifacts);
writer.Write(value.Discovery);
writer.Write(value.PercentExplored);
writer.Write(value.Unknown);
writer.Write(value.RequireAllCustom);
writer.Write(value.Mode);
writer.Write(value.Score);
writer.Write(value.Time);
}
public void Write(Map value)
{
if (Version >= 1.18f) {
writer.Write(value.CameraX);
writer.Write(value.CameraY);
}
if (Version >= 1.22f) {
writer.Write(value.GeneratorId);
}
writer.Write(value.SizeX);
writer.Write(value.SizeY);
value.LinearTiles.ForEach(x => { writer.Write(x.Id); writer.Write(x.Elevation); writer.Write(x.Unknown); });
}
public void Write(IList<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() public void Flush()
{ {
writer.Flush(); writer.Flush();
} }
private void EnsureSetVersion()
{
if (Version <= 1)
throw new System.InvalidOperationException("Set output version");
}
} }
} }

View File

@ -34,13 +34,11 @@
<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\ScnFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnReader.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnValidator.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnValidator.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnWriter.cs" /> <Compile Include="AdrianKousz.GenieEngine\ScnWriter.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\AIData.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\AIData.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\Cinematics.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\Cinematics.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\Consts.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\Map.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\Map.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\Messages.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\Messages.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\PlayerInfo.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\PlayerInfo.cs" />
@ -50,6 +48,10 @@
<Compile Include="AdrianKousz.GenieEngine.Data\ScenarioHeader.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\ScenarioHeader.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\UnitInfo.cs" /> <Compile Include="AdrianKousz.GenieEngine.Data\UnitInfo.cs" />
<Compile Include="AssemblyInfo.cs" /> <Compile Include="AssemblyInfo.cs" />
<Compile Include="AdrianKousz.GenieEngine.Data\GlobalVictoryInfo.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnConvert.cs" />
<Compile Include="AdrianKousz.GenieEngine\ScnDefaultFactory.cs" />
<Compile Include="AdrianKousz.GenieEngine\IScnFactory.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>