Compare commits

...

5 Commits

Author SHA1 Message Date
Adrian d1593f38c5 Update documentation 2016-02-22 17:11:48 +01:00
Adrian 9cc13f0e8d Campaign support and bitmap to map conversion in application
* Full Campaign reading/writing support
* Convert bitmaps to maps
* Command arguments reordering: input and output are always first
* Adapt usage of utils API
2016-02-22 17:02:44 +01:00
Adrian 48929d1fbf Full Campaign reading/writing support 2016-02-22 16:45:55 +01:00
Adrian 35d80a6585 Change SByte to Byte, change usage of utils API 2016-02-22 16:41:47 +01:00
Adrian ce92d95bd1 App: Bitmap adding, Library: fixes
* Add commands to add and remove a pregame bitmap
* Add command help texts
* Calculate the palette length correctly in BitmapUtil
* Add some expected values
2016-02-18 21:30:42 +01:00
28 changed files with 571 additions and 141 deletions

View File

@ -10,25 +10,37 @@ This includes the *Age of Empires* series and *Star Wars: Galactic Battlegrounds
## Features ## Features
* Create maps from bitmaps
* Convert AOE1 Scenario to AOE2 Scenario * Convert AOE1 Scenario to AOE2 Scenario
* Convert a Scenario file to JSON and vice-versa * Convert a Scenario file to JSON and vice-versa
* Decompress and recompress compressed Scenario part for hex editing * Decompress and recompress compressed Scenario part for hex editing
* Dump unit information to easily gather Unit IDs * Dump unit information to easily gather Unit IDs
* Extract Scenario files from Campaign file * Full Campaign file editing
## Planned Features ## Planned Features
* Create maps from bitmaps
* Trigger implementation * Trigger implementation
* Full conversion between all Scenario versions * Full conversion between all Scenario versions
(mapping of Unit IDs are needed for this, see Section Contributing) (mapping of Unit IDs are needed for this, see Section Contributing)
## Examples ## Examples
### Map Generation
![Generated Map](doc/GeneratedMap.jpg)
The above map is generated using:
$ GenieEdit.exe bmp2map files/examples/aoe2x-Empty.scx Europe.scx /tmp/terrain.png /tmp/elevation.png
The images are taken from [NASA's Blue Marble](http://visibleearth.nasa.gov/) imagery.
### Scenario Conversion
[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 file 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 convert "files/examples/aoe1demo-Reigno_1-Opening Moves.scn" files/OpeningMoves-AOE2.scx files/examples/aoe2x-Empty.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.)
@ -47,7 +59,7 @@ A unit dump looks like the following:
# Library # Library
The namespace `AdrianKousz.GenieEngine` contains classes The namespace `AdrianKousz.GenieEngine` contains classes
to read, write, and manipulate Scenario files. to read, write, and manipulate Scenario and Campaign files.
`Scenario` contains all data structures related to a Scenario file. `Scenario` contains all data structures related to a Scenario file.
They are easily understandable. They are easily understandable.

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -19,15 +19,16 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole> <Externalconsole>true</Externalconsole>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>..\..\bin\Release</OutputPath> <OutputPath>..\..\bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole> <Externalconsole>true</Externalconsole>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="src\Program.cs" /> <Compile Include="src\Program.cs" />
@ -40,6 +41,11 @@
<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" /> <Compile Include="src\ConvertCommand.cs" />
<Compile Include="src\AddBitmapCommand.cs" />
<Compile Include="src\DelBitmapCommand.cs" />
<Compile Include="src\PackCommand.cs" />
<Compile Include="src\Bmp2MapCommand.cs" />
<Compile Include="src\BitmapHelpers.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -65,6 +71,7 @@
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Drawing" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />

View File

@ -0,0 +1,48 @@
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class AddBitmapCommand : BaseCommand
{
override public void Run()
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
using (var bmpstream = File.OpenRead(args[2]))
{
var scn = GenieFile.Deserialize<Scenario>(input);
var bmp = (Bitmap)Image.FromStream(bmpstream);
var cin = scn.Cinematics;
cin.BitmapWidth = bmp.Width;
cin.BitmapHeight = bmp.Height;
cin.BitmapUnknown = Scenario.ScnCinematics.ExpectedUnknownBitmapTrue;
cin.RawBitmap = bmp.ToRawBitmap();
GenieFile.Serialize(scn, output);
}
}
override public int GetArgumentCount()
{
return 3;
}
override public string GetDescription()
{
return "Add a pregame bitmap";
}
override public string GetHelp()
{
return "Add a pregame bitmap to a scenario."
+ "\nThe bitmap has to be indexed, otherwise, the game will probably crash."
+ "\nAdditionally, a game-specific palette will be used to display the image."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output scenario> <bitmap>"
;
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public static class BitmapHelpers
{
public static unsafe void FillMap(Scenario scn, Bitmap bmp, Bitmap bmp2)
{
using (var ubmp = new UnsafeBitmap(bmp))
using (var ubmp2 = new UnsafeBitmap(bmp2))
{
var factory = new ScnDefaultFactory();
Check(ubmp);
Check(ubmp2);
var ptr = ubmp.Scan0;
var last = ptr + ubmp.Stride * ubmp.Height;
var ptr2 = ubmp2.Scan0;
var last2 = ptr2 + ubmp2.Stride * ubmp2.Height;
var map = factory.MakeMap();
map.SizeX = ubmp.Width;
map.SizeY = ubmp.Height;
map.LinearTiles = new Scenario.ScnMap.Tile[map.SizeX * map.SizeY];
var gaiaunits = new List<Scenario.ScnUnit>();
var treemapping = new short[256];
treemapping[10] = 411;
treemapping[13] = 351;
treemapping[17] = 414;
treemapping[18] = 348;
treemapping[19] = 350;
treemapping[20] = 349;
var i = 0;
while (ptr < last && ptr2 < last2) {
var treeid = treemapping[*ptr];
if (treeid != 0) {
var unit = factory.MakeUnit();
var posy = i / map.SizeX;
var posx = i - (posy * map.SizeX);
unit.UnitId = treeid;
unit.PosX = posx + 0.5f;
unit.PosY = posy + 0.5f;
unit.Rotation = (short)Util.Math.Rand(0, 13);
gaiaunits.Add(unit);
}
map.LinearTiles[i++] = new Scenario.ScnMap.Tile(*ptr++, (byte)(*ptr2++ / 37), 0);
}
scn.Map = map;
scn.Units[0] = gaiaunits;
}
}
private static void Check(UnsafeBitmap ubmp)
{
if (ubmp.PixelFormat != PixelFormat.Format8bppIndexed)
throw new ArgumentException("Unsupported PixelFormat");
if (ubmp.Stride != ubmp.Width)
throw new NotImplementedException("bmpbit.Stride != bmpbit.Width");
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.IO;
using System.Drawing;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class Bmp2MapCommand : BaseCommand
{
override public void Run()
{
using (var srcstream = File.OpenRead(args[0]))
using (var outstream = File.OpenWrite(args[1]))
using (var bmpstream = File.OpenRead(args[2]))
using (var bmp2stream = File.OpenRead(args[3]))
{
var bmp = (Bitmap)Image.FromStream(bmpstream);
var bmp2 = (Bitmap)Image.FromStream(bmp2stream);
var scn = GenieFile.Deserialize<Scenario>(srcstream);
BitmapHelpers.FillMap(scn, bmp, bmp2);
GenieFile.Serialize(scn, outstream);
}
}
override public int GetArgumentCount()
{
return 4;
}
override public string GetDescription()
{
return "Generate a scenario map based on bitmaps";
}
override public string GetHelp()
{
return "Generate a scenario map based on bitmaps with automatic forest generation."
+ "\n"
+ "\nTwo bitmaps need to be specified: One terrain map and one elevation map."
+ "\nBoth bitmaps need to be indexed or grayscale."
+ "\nThe index/gray values of the first bitmap are translated directly to a terrain ID."
+ "\nThe index/gray values of the second bitmap are divided by 37 to generate elevations 0-6."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output scenario> <terrain bitmap> <elevation bitmap>"
;
}
}
}

View File

@ -1,5 +1,4 @@
using System.IO; using System.IO;
using System.IO.Compression;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
@ -23,5 +22,14 @@ namespace AdrianKousz.GenieEngine
{ {
return "Recompress a decompressed scenario file"; return "Recompress a decompressed scenario file";
} }
override public string GetHelp()
{
return "Recompress a previously decompressed scenario file."
+ "\n"
+ "\nParameters:"
+ "\n<input binary file> <output scenario>"
;
}
} }
} }

View File

@ -8,9 +8,9 @@ namespace AdrianKousz.GenieEngine
{ {
override public void Run() override public void Run()
{ {
using (var refstream = File.OpenRead(args[0])) using (var srcstream = File.OpenRead(args[0]))
using (var srcstream = File.OpenRead(args[1])) using (var outstream = File.OpenWrite(args[1]))
using (var outstream = File.OpenWrite(args[2])) using (var refstream = File.OpenRead(args[2]))
{ {
var refscn = GenieFile.Deserialize<Scenario>(refstream); var refscn = GenieFile.Deserialize<Scenario>(refstream);
var srcscn = GenieFile.Deserialize<Scenario>(srcstream); var srcscn = GenieFile.Deserialize<Scenario>(srcstream);
@ -22,8 +22,7 @@ namespace AdrianKousz.GenieEngine
srcscn.RawRemaining = refscn.RawRemaining; srcscn.RawRemaining = refscn.RawRemaining;
srcscn.PlayerSettings.ForEach(x => { srcscn.PlayerSettings.ForEach(x => {
x.FilenameAI = "RandomGame"; x.FilenameAI = x.FilenameCTY = x.FilenamePER = "RandomGame";
x.FilenameCTY = x.FilenamePER = "";
x.ScriptAI = x.ScriptCTY = x.ScriptPER = ""; x.ScriptAI = x.ScriptCTY = x.ScriptPER = "";
}); });
@ -48,8 +47,10 @@ namespace AdrianKousz.GenieEngine
{ {
return "Convert AOE1 scenario to AOE2" return "Convert AOE1 scenario to AOE2"
+ "\n" + "\n"
+ "\nThe converter needs an existing AOE2 scenario to start with. Use the following arguments:" + "\nThe converter needs an existing AOE2 scenario to start with."
+ "\n<AOE2 reference scenario> <AOE1 scenario> <output scenario>" + "\n"
+ "\nParameters:"
+ "\n<AOE1 scenario> <output scenario> <AOE2 reference scenario>"
; ;
} }
} }

View File

@ -26,9 +26,9 @@ namespace AdrianKousz.GenieEngine
// Other Buildings // Other Buildings
109, 68, 103, 109, 68, 103,
}; };
units.ForEach((i, list) => { units.ForEach(list => {
list.ForEach((j, unit) => { list.ForEach(unit => {
mapping.ForEach((k, buildingid) => { mapping.ForEach(buildingid => {
if (unit.UnitId == buildingid) { if (unit.UnitId == buildingid) {
unit.PosX -= 0.5f; unit.PosX -= 0.5f;
unit.PosY -= 0.5f; unit.PosY -= 0.5f;
@ -43,7 +43,7 @@ namespace AdrianKousz.GenieEngine
*/ */
private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units) private List<Scenario.ScnUnit>[] ChangeUnits_AOE1_AOE2(IList<Scenario.ScnUnit>[] units)
{ {
var array = new int[] { var mapping = new short[] {
// Resources // Resources
66, 66, 66, 66,
59, 59, 59, 59,
@ -139,22 +139,15 @@ namespace AdrianKousz.GenieEngine
114, 114, 114, 114,
121, 121, 121, 121,
129, 129, 129, 129,
}; }.ToDictionary();
var mapping = new Dictionary<short, short>();
var ai = 0;
while (ai < array.Length)
mapping.Add((short)array[ai++], (short)array[ai++]);
var result = new List<Scenario.ScnUnit>[Scenario.NumPlayerSections].Fill(); var result = units.Map(list => {
return list
units.ForEach((i, list) => { .Filter(unit => mapping.ContainsKey(unit.UnitId))
list.ForEach((j, unit) => { .ForEach(unit => {
if (mapping.ContainsKey(unit.UnitId)) {
unit.UnitId = mapping[unit.UnitId]; unit.UnitId = mapping[unit.UnitId];
result[i].Add(unit); }).ToList();
} }).ToArray();
});
});
return result; return result;
} }
@ -164,8 +157,8 @@ namespace AdrianKousz.GenieEngine
*/ */
private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] units, Scenario.ScnMap map) private void ChangeTiles_AOE1_AOE2(IList<Scenario.ScnUnit>[] units, Scenario.ScnMap map)
{ {
units.ForEach((i, list) => { units.ForEach(list => {
list.ForEach((j, unit) => { list.ForEach(unit => {
var posx = (int)(unit.PosX - 0.5); var posx = (int)(unit.PosX - 0.5);
var posy = (int)(unit.PosY - 0.5); var posy = (int)(unit.PosY - 0.5);
var linearpos = posy * map.SizeX + posx; var linearpos = posy * map.SizeX + posx;
@ -203,12 +196,11 @@ namespace AdrianKousz.GenieEngine
var treeids = new int[] { var treeids = new int[] {
411, 351, 414, 350, 348, 349, 411, 351, 414, 350, 348, 349,
}; };
units.ForEach((i, list) => { units.ForEach(list => {
list.ForEach((j, unit) => { list.ForEach(unit => {
treeids.ForEach((k, treeid) => { treeids.ForEach(treeid => {
if (unit.UnitId == treeid) { if (unit.UnitId == treeid) {
unit.InitialFrame = (short)Util.Math.Rand(0, 13); // Whats the maximum? unit.Rotation = (short)Util.Math.Rand(0, 13); // Whats the maximum?
unit.Rotation = unit.InitialFrame;
} }
}); });
}); });

View File

@ -20,7 +20,16 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription() override public string GetDescription()
{ {
return "Decompress a scenario file for hex editing"; return "Decompress a scenario file";
}
override public string GetHelp()
{
return "Decompress a scenario file for hex editing."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output binary file>"
;
} }
} }
} }

View File

@ -0,0 +1,43 @@
using System.IO;
using System.Drawing;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class DelBitmapCommand : BaseCommand
{
override public void Run()
{
using (var input = File.OpenRead(args[0]))
using (var output = File.OpenWrite(args[1]))
{
var scn = GenieFile.Deserialize<Scenario>(input);
var cin = scn.Cinematics;
cin.BitmapWidth = 0;
cin.BitmapHeight = 0;
cin.BitmapUnknown = Scenario.ScnCinematics.ExpectedUnknownBitmapFalse;
cin.RawBitmap = new byte[0];
GenieFile.Serialize(scn, output);
}
}
override public int GetArgumentCount()
{
return 2;
}
override public string GetDescription()
{
return "Remove the pregame bitmap";
}
override public string GetHelp()
{
return "Remove the pregame bitmap from a scenario."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output scenario>"
;
}
}
}

View File

@ -17,9 +17,10 @@ namespace AdrianKousz.GenieEngine
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");
Console.WriteLine(); Console.WriteLine();
scn.Units.ForEach((i, list) => { var i = 0;
Console.WriteLine("Units of player {0}:", i); scn.Units.ForEach(list => {
list.ForEach((j, unit) => { Console.WriteLine("Units of player {0}:", i++);
list.ForEach(unit => {
var tilex = (int)(unit.PosX - 0.5); var tilex = (int)(unit.PosX - 0.5);
var tiley = (int)(unit.PosY - 0.5); var tiley = (int)(unit.PosY - 0.5);
var tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex]; var tile = scn.Map.LinearTiles[tiley * scn.Map.SizeX + tilex];

View File

@ -1,4 +1,6 @@
using System;
using System.IO; using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
@ -6,9 +8,17 @@ namespace AdrianKousz.GenieEngine
{ {
override public void Run() override public void Run()
{ {
using (var file = File.OpenRead(args[0])) { using (var file = File.OpenRead(args[0]))
var reader = new CpnReader(file); {
reader.ExtractFiles(); var cpn = GenieFile.Deserialize<Campaign>(file);
Console.WriteLine("Extracting \"{0}\"...", cpn.Name);
cpn.Files.ForEach(x => {
using (var outfile = File.OpenWrite(x.Filename))
{
Console.WriteLine("Extracting \"{0}\" to \"{1}\"...", x.Name, x.Filename);
outfile.Write(x.RawData);
}
});
} }
} }
@ -19,7 +29,7 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription() override public string GetDescription()
{ {
return "Extract scenarios from campaign file"; return "Extract files from campaign file";
} }
} }
} }

View File

@ -25,7 +25,16 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription() override public string GetDescription()
{ {
return "Read a JSON file and save as scenario"; return "Convert a JSON file to a scenario";
}
override public string GetHelp()
{
return "Read a Scenario structure formatted as JSON and save it as a Scenario."
+ "\n"
+ "\nParameters:"
+ "\n<input json> <output scenario>"
;
} }
} }
} }

View File

@ -0,0 +1,47 @@
using System.IO;
using System.Collections.Generic;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class PackCommand : BaseCommand
{
override public void Run()
{
var files = new List<Campaign.CpnFile>();
for (var i = 2; i < args.Length; i++) {
var file = new Campaign.CpnFile();
file.Filename = args[i];
file.Name = Path.GetFileNameWithoutExtension(file.Filename);
using (var stream = File.OpenRead(file.Filename))
using (var memorystream = new MemoryStream())
{
stream.CopyTo(memorystream);
file.RawData = memorystream.ToArray();
}
files.Add(file);
}
var cpn = new Campaign();
cpn.Files = files;
cpn.Name = args[1];
cpn.Version = "1.00";
using (var file = File.OpenWrite(args[0]))
{
GenieFile.Serialize(cpn, file);
}
}
override public int GetArgumentCount()
{
return 3;
}
override public string GetDescription()
{
return "Pack files into a campaign file";
}
}
}

View File

@ -8,6 +8,7 @@ namespace AdrianKousz.GenieEngine
public static void Main(string[] args) public static void Main(string[] args)
{ {
System.Threading.Thread.CurrentThread.Name = "main"; System.Threading.Thread.CurrentThread.Name = "main";
Log.SetUncaughtExceptionHandler();
if (System.Diagnostics.Debugger.IsAttached) { if (System.Diagnostics.Debugger.IsAttached) {
debug(); debug();
@ -26,12 +27,16 @@ namespace AdrianKousz.GenieEngine
{ {
var result = new Dictionary<string, ICommand<string[]>>(); var result = new Dictionary<string, ICommand<string[]>>();
result.Add("convert", new ConvertCommand()); result.Add("convert", new ConvertCommand());
result.Add("bmp2map", new Bmp2MapCommand());
result.Add("addbmp", new AddBitmapCommand());
result.Add("delbmp", new DelBitmapCommand());
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("tojson", new ToJsonCommand()); result.Add("tojson", new ToJsonCommand());
result.Add("fromjson", new FromJsonCommand()); result.Add("fromjson", new FromJsonCommand());
result.Add("dumpunits", new DumpunitsCommand()); result.Add("dumpunits", new DumpunitsCommand());
result.Add("extract", new ExtractCommand());
result.Add("pack", new PackCommand());
return result; return result;
} }

View File

@ -25,7 +25,16 @@ namespace AdrianKousz.GenieEngine
override public string GetDescription() override public string GetDescription()
{ {
return "Dump a scenario file to JSON"; return "Convert a scenario to a JSON file";
}
override public string GetHelp()
{
return "Save a Scenario structure formatted as JSON."
+ "\n"
+ "\nParameters:"
+ "\n<input scenario> <output json>"
;
} }
} }
} }

View File

@ -2,6 +2,7 @@
using System.IO; using System.IO;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine namespace AdrianKousz.GenieEngine
{ {
@ -11,11 +12,11 @@ namespace AdrianKousz.GenieEngine
{ {
EnsureLittleEndian(); EnsureLittleEndian();
var temp = new byte[40]; var temp = input.ReadBytes(40);
input.Read(temp, 0, temp.Length);
var dibHeaderLength = BitConverter.ToInt32(temp, 0); var dibHeaderLength = BitConverter.ToInt32(temp, 0);
var paletteLength = BitConverter.ToInt32(temp, 32) * 4; var bitDepth = BitConverter.ToInt32(temp, 14);
var paletteLength = bitDepth > 8 ? 0 : (1 << bitDepth) * 4;
var pixelArrayLength = BitConverter.ToInt32(temp, 20); var pixelArrayLength = BitConverter.ToInt32(temp, 20);
var resultLength = dibHeaderLength + paletteLength + pixelArrayLength; var resultLength = dibHeaderLength + paletteLength + pixelArrayLength;
@ -37,7 +38,8 @@ namespace AdrianKousz.GenieEngine
var bmpHeaderLength = 14; var bmpHeaderLength = 14;
var dibHeaderLength = BitConverter.ToInt32(input, 0); var dibHeaderLength = BitConverter.ToInt32(input, 0);
var paletteLength = BitConverter.ToInt32(input, 32) * 4; var bitDepth = BitConverter.ToInt32(input, 14);
var paletteLength = bitDepth > 8 ? 0 : (1 << bitDepth) * 4;
var pixelArrayOffsetOutput = bmpHeaderLength + dibHeaderLength + paletteLength; var pixelArrayOffsetOutput = bmpHeaderLength + dibHeaderLength + paletteLength;
var resultLength = input.Length + bmpHeaderLength; var resultLength = input.Length + bmpHeaderLength;
@ -61,10 +63,12 @@ namespace AdrianKousz.GenieEngine
var bmpHeaderLength = 14; var bmpHeaderLength = 14;
var tempstream = new MemoryStream(); var tempstream = new MemoryStream();
input.Save(tempstream, ImageFormat.Bmp); input.Save(tempstream, ImageFormat.Bmp);
tempstream.Dispose();
var temp = tempstream.ToArray(); var temp = tempstream.ToArray();
var dibHeaderLength = BitConverter.ToInt32(temp, 0 + bmpHeaderLength); var dibHeaderLength = BitConverter.ToInt32(temp, 0 + bmpHeaderLength);
var paletteLength = BitConverter.ToInt32(temp, 32 + bmpHeaderLength) * 4; var bitDepth = BitConverter.ToInt32(temp, 14 + bmpHeaderLength);
var paletteLength = bitDepth > 8 ? 0 : (1 << bitDepth) * 4;
var pixelArrayOffsetInput = BitConverter.ToInt32(temp, 10); var pixelArrayOffsetInput = BitConverter.ToInt32(temp, 10);
var pixelArrayLength = temp.Length - pixelArrayOffsetInput; var pixelArrayLength = temp.Length - pixelArrayOffsetInput;
var resultLength = dibHeaderLength + paletteLength + pixelArrayLength; var resultLength = dibHeaderLength + paletteLength + pixelArrayLength;

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace AdrianKousz.GenieEngine
{
public class Campaign
{
public String Version;
public String Name;
public IList<CpnFile> Files;
public class CpnFile
{
public String Name;
public String Filename;
public byte[] RawData;
public Scenario Data;
}
}
}

View File

@ -1,52 +0,0 @@
using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
public class CpnReader
{
private static readonly string TAG = typeof(CpnReader).Name;
private ExtendedBinaryReader reader;
public CpnReader(Stream input)
{
reader = new ExtendedBinaryReader(input, Scenario.Encoding);
}
public void ExtractFiles()
{
var version = reader.ReadString(4);
if (!version.Equals("1.00")) {
throw new System.NotSupportedException(version);
}
var name = reader.ReadZString(256);
var count = reader.ReadInt32();
Log.i(TAG, "Extract scenarios from \"{0}\"", name);
var size = new int[count];
var offset = new int[count];
var scenname = new string[count];
var filename = new string[count];
for (var i = 0; i < count; i++) {
size[i] = reader.ReadInt32();
offset[i] = reader.ReadInt32();
scenname[i] = reader.ReadZString(255);
filename[i] = reader.ReadZString(257);
}
for (var i = 0; i < count; i++) {
Log.i(TAG, "Extract scenario {0} \"{1}\" to {2}", i, scenname[i], filename[i]);
using (var file = File.OpenWrite(filename[i])) {
reader.BaseStream.Seek(offset[i], SeekOrigin.Begin);
var data = reader.ReadBytes(size[i]);
file.Write(data, 0, data.Length);
}
}
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.IO;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
internal class CpnSerializerReader
{
private static readonly string TAG = typeof(CpnSerializerReader).Name;
public static CpnSerializerReader CreateDefault()
{
return new CpnSerializerReader();
}
private CpnSerializerReader()
{
}
public Campaign ReadCampaign(ExtendedBinaryReader reader)
{
var number = 0;
var result = new Campaign();
result.Version = reader.ReadString(4);
if (!result.Version.Equals("1.00")) {
Log.w(TAG, "Continue reading unsupported version {0}", result.Version);
}
result.Name = reader.ReadZString(256);
number = reader.ReadInt32();
result.Files = new PositionInfo()
.CreateList()
.Fill(number, () => {
return new PositionInfo {
Size = reader.ReadInt32(),
Offset = reader.ReadInt32(),
Name = reader.ReadZString(255),
Filename = reader.ReadZString(257),
};
})
.Map(x => {
var file = new Campaign.CpnFile();
reader.BaseStream.Seek(x.Offset, SeekOrigin.Begin);
file.RawData = reader.ReadBytes(x.Size);
file.Name = x.Name;
file.Filename = x.Filename;
return file;
})
.ToArray();
return result;
}
private class PositionInfo
{
public int Size;
public int Offset;
public string Name;
public string Filename;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using AdrianKousz.Util;
namespace AdrianKousz.GenieEngine
{
internal class CpnSerializerWriter
{
private static readonly string TAG = typeof(CpnSerializerWriter).Name;
public static CpnSerializerWriter CreateDefault()
{
return new CpnSerializerWriter();
}
private CpnSerializerWriter()
{
}
public void Write(ExtendedBinaryWriter writer, Campaign value)
{
var number = 0;
if (!value.Version.Equals("1.00")) {
Log.w(TAG, "Continue writing unsupported version {0}", value.Version);
}
var headerSize = 8 + 256;
var listingSize = 8 + 255 + 257;
var listingLength = listingSize * value.Files.Count;
number = headerSize + listingLength;
writer.WriteStringRaw(value.Version);
writer.WriteZString(value.Name, 256);
writer.Write((Int32)value.Files.Count);
value.Files.ForEach(x => {
writer.Write((Int32)x.RawData.Length);
writer.Write((Int32)number);
writer.WriteZString(x.Name, 255);
writer.WriteZString(x.Filename, 257);
number += x.RawData.Length;
});
value.Files.ForEach(x => {
writer.Write(x.RawData);
});
}
}
}

View File

@ -21,13 +21,22 @@ namespace AdrianKousz.GenieEngine
public static void Serialize<T>(T value, Stream output) public static void Serialize<T>(T value, Stream output)
{ {
var scenarioValue = value as Scenario; var scnValue = value as Scenario;
if (scenarioValue != null) { if (scnValue != null) {
var scnwriter = ScnSerializerWriter.CreateDefault(); var scnwriter = ScnSerializerWriter.CreateDefault();
using (var stream = new NonDisposingStreamWrapper(output)) using (var writer = GetWriter(output))
using (var writer = GetWriter(stream))
{ {
scnwriter.Write(writer, scenarioValue); scnwriter.Write(writer, scnValue);
return;
}
}
var cpnValue = value as Campaign;
if (cpnValue != null) {
var cpnwriter = CpnSerializerWriter.CreateDefault();
using (var writer = GetWriter(output))
{
cpnwriter.Write(writer, cpnValue);
return; return;
} }
} }
@ -61,13 +70,20 @@ namespace AdrianKousz.GenieEngine
{ {
if (type == typeof(Scenario)) { if (type == typeof(Scenario)) {
var scnreader = ScnSerializerReader.CreateDefault(); var scnreader = ScnSerializerReader.CreateDefault();
using (var stream = new NonDisposingStreamWrapper(input)) using (var reader = GetReader(input))
using (var reader = GetReader(stream))
{ {
return scnreader.ReadScenario(reader); return scnreader.ReadScenario(reader);
} }
} }
if (type == typeof(Campaign)) {
var cpnreader = CpnSerializerReader.CreateDefault();
using (var reader = GetReader(input))
{
return cpnreader.ReadCampaign(reader);
}
}
throw new System.ArgumentException("Unsupported Type"); throw new System.ArgumentException("Unsupported Type");
} }
@ -77,12 +93,12 @@ namespace AdrianKousz.GenieEngine
public static ExtendedBinaryReader GetReader(Stream stream) public static ExtendedBinaryReader GetReader(Stream stream)
{ {
return new ExtendedBinaryReader(stream, Scenario.Encoding); return new ExtendedBinaryReader(new NonDisposingStreamWrapper(stream), Scenario.Encoding);
} }
public static ExtendedBinaryWriter GetWriter(Stream stream) public static ExtendedBinaryWriter GetWriter(Stream stream)
{ {
return new ExtendedBinaryWriter(stream, Scenario.Encoding); return new ExtendedBinaryWriter(new NonDisposingStreamWrapper(stream), Scenario.Encoding);
} }
public static void ScenarioCompression(Stream input, Stream output, bool decompress) public static void ScenarioCompression(Stream input, Stream output, bool decompress)

View File

@ -14,7 +14,7 @@ namespace AdrianKousz.GenieEngine
public const Int32 ExpectedUnknown1 = 2; public const Int32 ExpectedUnknown1 = 2;
public const Int32 ExpectedUnknown2 = 0; public const Int32 ExpectedUnknown2 = 0;
public const Int32 ExpectedUnknown3 = 1; public const Int32 ExpectedUnknown3 = 1;
public const SByte ExpectedUnknown4 = 0; public const Byte ExpectedUnknown4 = 0;
public const Single ExpectedUnknown5 = -1; public const Single ExpectedUnknown5 = -1;
public String VersionString; public String VersionString;
@ -43,7 +43,7 @@ namespace AdrianKousz.GenieEngine
public Int32 Unknown1; public Int32 Unknown1;
public Int32 Unknown2; public Int32 Unknown2;
public Int32 Unknown3; public Int32 Unknown3;
public SByte Unknown4; public Byte Unknown4;
public Single Unknown5; public Single Unknown5;
public class ScnPlayerSettings public class ScnPlayerSettings
@ -64,7 +64,7 @@ namespace AdrianKousz.GenieEngine
public String ScriptAI; public String ScriptAI;
public String ScriptCTY; public String ScriptCTY;
public String ScriptPER; public String ScriptPER;
public SByte AIType; public Byte AIType;
public Int32 ResourceWood; public Int32 ResourceWood;
public Int32 ResourceFood; public Int32 ResourceFood;
@ -98,6 +98,9 @@ namespace AdrianKousz.GenieEngine
public class ScnCinematics public class ScnCinematics
{ {
public const Int16 ExpectedUnknownBitmapTrue = -1;
public const Int16 ExpectedUnknownBitmapFalse = 1;
public String FilenamePregame; public String FilenamePregame;
public String FilenameVictory; public String FilenameVictory;
public String FilenameLoss; public String FilenameLoss;
@ -148,13 +151,13 @@ namespace AdrianKousz.GenieEngine
public struct Tile public struct Tile
{ {
public const Int32 ExpectedUnknown = 0; public const Byte ExpectedUnknown = 0;
public SByte Id; public Byte Id;
public SByte Elevation; public Byte Elevation;
public SByte Unknown; public Byte Unknown;
public Tile(SByte id, SByte el, SByte un) public Tile(Byte id, Byte el, Byte un)
{ {
Id = id; Id = id;
Elevation = el; Elevation = el;
@ -166,7 +169,7 @@ 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;
@ -176,7 +179,7 @@ namespace AdrianKousz.GenieEngine
public Int16 InitialFrame; public Int16 InitialFrame;
public Int32 GarrisonnedInId; public Int32 GarrisonnedInId;
public Single Unknown1; public Single Unknown1;
public SByte Unknown2; public Byte Unknown2;
} }
} }
} }

View File

@ -50,12 +50,9 @@ namespace AdrianKousz.GenieEngine
result.UnknownInfo = Scenario.ScnPlayerSettings.ExpectedUnknownInfo; result.UnknownInfo = Scenario.ScnPlayerSettings.ExpectedUnknownInfo;
result.UnknownResource = Scenario.ScnPlayerSettings.ExpectedUnknownResource; result.UnknownResource = Scenario.ScnPlayerSettings.ExpectedUnknownResource;
result.Name = "Player"; result.Name = "Player";
result.FilenameAI = ""; result.NameStringID = -1;
result.FilenameCTY = ""; result.FilenameAI = result.FilenameCTY = result.FilenamePER = "RandomGame";
result.FilenamePER = ""; result.ScriptAI = result.ScriptCTY = result.ScriptPER = "";
result.ScriptAI = "";
result.ScriptCTY = "";
result.ScriptPER = "";
result.AIType = 1; result.AIType = 1;
result.ResourceWood = 200; result.ResourceWood = 200;
result.ResourceFood = 200; result.ResourceFood = 200;
@ -91,6 +88,7 @@ namespace AdrianKousz.GenieEngine
result.FilenameVictory = ""; result.FilenameVictory = "";
result.FilenameLoss = ""; result.FilenameLoss = "";
result.FilenameBitmap = ""; result.FilenameBitmap = "";
result.BitmapUnknown = Scenario.ScnCinematics.ExpectedUnknownBitmapFalse;
result.RawBitmap = new byte[0]; result.RawBitmap = new byte[0];
return result; return result;
} }

View File

@ -72,7 +72,7 @@ namespace AdrianKousz.GenieEngine
}); });
result.Unknown3 = reader.ReadInt32(); result.Unknown3 = reader.ReadInt32();
result.Unknown4 = reader.ReadSByte(); result.Unknown4 = reader.ReadByte();
result.Unknown5 = reader.ReadSingle(); result.Unknown5 = reader.ReadSingle();
result.Filename = reader.ReadStringInt16(); result.Filename = reader.ReadStringInt16();
@ -139,7 +139,7 @@ namespace AdrianKousz.GenieEngine
if (Version >= 1.18f) { if (Version >= 1.18f) {
result.PlayerSettings.ForEach(x => { result.PlayerSettings.ForEach(x => {
x.AIType = reader.ReadSByte(); x.AIType = reader.ReadByte();
}); });
} }
@ -217,7 +217,7 @@ namespace AdrianKousz.GenieEngine
result.Map.SizeY = reader.ReadInt32(); result.Map.SizeY = reader.ReadInt32();
result.Map.LinearTiles = new Scenario.ScnMap.Tile[result.Map.SizeX * result.Map.SizeY] result.Map.LinearTiles = new Scenario.ScnMap.Tile[result.Map.SizeX * result.Map.SizeY]
.Fill(() => new Scenario.ScnMap.Tile(reader.ReadSByte(), reader.ReadSByte(), reader.ReadSByte())); .Fill(() => new Scenario.ScnMap.Tile(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()));
// ResourceCopies // ResourceCopies
@ -248,21 +248,21 @@ namespace AdrianKousz.GenieEngine
result.Units.ForEach(x => { result.Units.ForEach(x => {
var count = reader.ReadInt32(); var count = reader.ReadInt32();
x.Fill(() => { x.Fill(count, () => {
var unit = factory.MakeUnit(); var unit = factory.MakeUnit();
unit.PosX = reader.ReadSingle(); unit.PosX = reader.ReadSingle();
unit.PosY = reader.ReadSingle(); unit.PosY = reader.ReadSingle();
unit.Unknown1 = reader.ReadSingle(); unit.Unknown1 = reader.ReadSingle();
unit.Id = reader.ReadInt32(); unit.Id = reader.ReadInt32();
unit.UnitId = reader.ReadInt16(); unit.UnitId = reader.ReadInt16();
unit.Unknown2 = reader.ReadSByte(); unit.Unknown2 = reader.ReadByte();
unit.Rotation = reader.ReadSingle(); unit.Rotation = reader.ReadSingle();
if (Version >= 1.18f) { if (Version >= 1.18f) {
unit.InitialFrame = reader.ReadInt16(); unit.InitialFrame = reader.ReadInt16();
unit.GarrisonnedInId = reader.ReadInt32(); unit.GarrisonnedInId = reader.ReadInt32();
} }
return unit; return unit;
}, count); });
}); });
// More Player Settings and Triggers // More Player Settings and Triggers

View File

@ -43,7 +43,7 @@ namespace AdrianKousz.GenieEngine
// PlayerSettings 1 // PlayerSettings 1
value.PlayerSettings.ForEach(x => { value.PlayerSettings.ForEach(x => {
writer.WriteStringPadded(x.Name, 256); writer.WriteZString(x.Name, 256);
}); });
if (Version >= 1.18f) { if (Version >= 1.18f) {

View File

@ -33,7 +33,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AdrianKousz.GenieEngine\BitmapUtil.cs" /> <Compile Include="AdrianKousz.GenieEngine\BitmapUtil.cs" />
<Compile Include="AdrianKousz.GenieEngine\CpnReader.cs" />
<Compile Include="AssemblyInfo.cs" /> <Compile Include="AssemblyInfo.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" />
@ -41,6 +40,9 @@
<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" /> <Compile Include="AdrianKousz.GenieEngine\GenieFile.cs" />
<Compile Include="AdrianKousz.GenieEngine\Campaign.cs" />
<Compile Include="AdrianKousz.GenieEngine\CpnSerializerReader.cs" />
<Compile Include="AdrianKousz.GenieEngine\CpnSerializerWriter.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>