Support for Dune II SHP files used for cursors in Red Alert.
This commit is contained in:
129
OpenRa.FileFormats/Dune2ShpReader.cs
Normal file
129
OpenRa.FileFormats/Dune2ShpReader.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace OpenRa.FileFormats
|
||||||
|
{
|
||||||
|
public enum Dune2ImageFlags : int
|
||||||
|
{
|
||||||
|
F80_F2 = 0,
|
||||||
|
F2 = 2,
|
||||||
|
L16_F80_F2_1 = 1,
|
||||||
|
L16_F80_F2_2 = 3,
|
||||||
|
Ln_F80_F2 = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dune2ImageHeader
|
||||||
|
{
|
||||||
|
public readonly Dune2ImageFlags Flags;
|
||||||
|
public readonly int Width;
|
||||||
|
public readonly int Height;
|
||||||
|
public readonly int Slices;
|
||||||
|
public readonly int FileSize;
|
||||||
|
public readonly int DataSize;
|
||||||
|
|
||||||
|
public readonly byte[] LookupTable;
|
||||||
|
public byte[] Image;
|
||||||
|
|
||||||
|
public Dune2ImageHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Flags = (Dune2ImageFlags)reader.ReadUInt16();
|
||||||
|
Slices = reader.ReadByte();
|
||||||
|
Width = reader.ReadUInt16();
|
||||||
|
Height = reader.ReadByte();
|
||||||
|
FileSize = reader.ReadUInt16();
|
||||||
|
DataSize = reader.ReadUInt16();
|
||||||
|
|
||||||
|
if (Flags == Dune2ImageFlags.L16_F80_F2_1 ||
|
||||||
|
Flags == Dune2ImageFlags.L16_F80_F2_2 ||
|
||||||
|
Flags == Dune2ImageFlags.Ln_F80_F2)
|
||||||
|
{
|
||||||
|
int n = Flags == Dune2ImageFlags.Ln_F80_F2 ? reader.ReadByte() : 16;
|
||||||
|
LookupTable = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
LookupTable[i] = reader.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size Size
|
||||||
|
{
|
||||||
|
get { return new Size(Width, Height); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dune2ShpReader : IEnumerable<Dune2ImageHeader>
|
||||||
|
{
|
||||||
|
public readonly int ImageCount;
|
||||||
|
|
||||||
|
List<Dune2ImageHeader> headers = new List<Dune2ImageHeader>();
|
||||||
|
|
||||||
|
public Dune2ShpReader(Stream stream)
|
||||||
|
{
|
||||||
|
BinaryReader reader = new BinaryReader(stream);
|
||||||
|
|
||||||
|
ImageCount = reader.ReadUInt16();
|
||||||
|
|
||||||
|
//Last offset is pointer to end of file.
|
||||||
|
uint[] offsets = new uint[ImageCount + 1];
|
||||||
|
|
||||||
|
uint temp = reader.ReadUInt32();
|
||||||
|
|
||||||
|
//If fourth byte in file is non-zero, the offsets are two bytes each.
|
||||||
|
bool twoByteOffsets = (temp & 0xFF0000) > 0;
|
||||||
|
if (twoByteOffsets)
|
||||||
|
{
|
||||||
|
offsets[0] = ((temp & 0xFFFF0000) >> 16) + 2; //Offset does not account for image count bytes
|
||||||
|
offsets[1] = (temp & 0xFFFF) + 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
offsets[0] = temp + 2;
|
||||||
|
|
||||||
|
for (int i = twoByteOffsets ? 2 : 1; i < ImageCount + 1; i++)
|
||||||
|
offsets[i] = (twoByteOffsets ? reader.ReadUInt16() : reader.ReadUInt32()) + 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < ImageCount; i++)
|
||||||
|
{
|
||||||
|
reader.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||||
|
Dune2ImageHeader header = new Dune2ImageHeader(reader);
|
||||||
|
byte[] imgData = reader.ReadBytes(header.FileSize);
|
||||||
|
header.Image = new byte[header.Height * header.Width];
|
||||||
|
|
||||||
|
//Decode image data
|
||||||
|
if (header.Flags != Dune2ImageFlags.F2)
|
||||||
|
{
|
||||||
|
byte[] tempData = new byte[header.DataSize];
|
||||||
|
Format80.DecodeInto(imgData, tempData);
|
||||||
|
Format2.DecodeInto(tempData, header.Image);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Format2.DecodeInto(imgData, header.Image);
|
||||||
|
|
||||||
|
//Lookup values in lookup table
|
||||||
|
if (header.LookupTable != null)
|
||||||
|
for (int j = 0; j < header.Image.Length; j++)
|
||||||
|
header.Image[j] = header.LookupTable[header.Image[j]];
|
||||||
|
|
||||||
|
headers.Add(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dune2ImageHeader this[int index]
|
||||||
|
{
|
||||||
|
get { return headers[index]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<Dune2ImageHeader> GetEnumerator()
|
||||||
|
{
|
||||||
|
return headers.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
OpenRa.FileFormats/Format2.cs
Normal file
31
OpenRa.FileFormats/Format2.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace OpenRa.FileFormats
|
||||||
|
{
|
||||||
|
public static class Format2
|
||||||
|
{
|
||||||
|
public static int DecodeInto(byte[] src, byte[] dest)
|
||||||
|
{
|
||||||
|
FastByteReader r = new FastByteReader(src);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (!r.Done())
|
||||||
|
{
|
||||||
|
byte cmd = r.ReadByte();
|
||||||
|
if (cmd == 0)
|
||||||
|
{
|
||||||
|
byte count = r.ReadByte();
|
||||||
|
while (count-- > 0)
|
||||||
|
dest[i++] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dest[i++] = cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,8 +46,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Blowfish.cs" />
|
<Compile Include="Blowfish.cs" />
|
||||||
<Compile Include="BlowfishKeyProvider.cs" />
|
<Compile Include="BlowfishKeyProvider.cs" />
|
||||||
|
<Compile Include="Dune2ShpReader.cs" />
|
||||||
<Compile Include="FileSystem.cs" />
|
<Compile Include="FileSystem.cs" />
|
||||||
<Compile Include="Folder.cs" />
|
<Compile Include="Folder.cs" />
|
||||||
|
<Compile Include="Format2.cs" />
|
||||||
<Compile Include="Format40.cs" />
|
<Compile Include="Format40.cs" />
|
||||||
<Compile Include="Format80.cs" />
|
<Compile Include="Format80.cs" />
|
||||||
<Compile Include="IniFile.cs" />
|
<Compile Include="IniFile.cs" />
|
||||||
|
|||||||
34
OpenRa.Game/Graphics/CursorSheetBuilder.cs
Normal file
34
OpenRa.Game/Graphics/CursorSheetBuilder.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using OpenRa.FileFormats;
|
||||||
|
|
||||||
|
namespace OpenRa.Game.Graphics
|
||||||
|
{
|
||||||
|
static class CursorSheetBuilder
|
||||||
|
{
|
||||||
|
static Dictionary<string, Sprite[]> cursors =
|
||||||
|
new Dictionary<string, Sprite[]>();
|
||||||
|
|
||||||
|
public static Sprite LoadSprite(string filename, params string[] exts)
|
||||||
|
{
|
||||||
|
return LoadAllSprites(filename, exts)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Sprite[] LoadAllSprites(string filename, params string[] exts)
|
||||||
|
{
|
||||||
|
Sprite[] value;
|
||||||
|
if (!cursors.TryGetValue(filename, out value))
|
||||||
|
{
|
||||||
|
Dune2ShpReader shp = new Dune2ShpReader(FileSystem.OpenWithExts(filename, exts));
|
||||||
|
value = new Sprite[shp.ImageCount];
|
||||||
|
for (int i = 0; i < shp.ImageCount; i++)
|
||||||
|
value[i] = SheetBuilder.Add(shp[i].Image, shp[i].Size);
|
||||||
|
cursors.Add(filename, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
<Compile Include="GameRules\UnitInfo.cs" />
|
<Compile Include="GameRules\UnitInfo.cs" />
|
||||||
<Compile Include="Graphics\Animation.cs" />
|
<Compile Include="Graphics\Animation.cs" />
|
||||||
<Compile Include="Game.cs" />
|
<Compile Include="Game.cs" />
|
||||||
|
<Compile Include="Graphics\CursorSheetBuilder.cs" />
|
||||||
<Compile Include="Graphics\LineRenderer.cs" />
|
<Compile Include="Graphics\LineRenderer.cs" />
|
||||||
<Compile Include="Graphics\OverlayRenderer.cs" />
|
<Compile Include="Graphics\OverlayRenderer.cs" />
|
||||||
<Compile Include="Graphics\WorldRenderer.cs" />
|
<Compile Include="Graphics\WorldRenderer.cs" />
|
||||||
|
|||||||
57
doc/shp dune 2.txt
Normal file
57
doc/shp dune 2.txt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
======================
|
||||||
|
Dune 2 SHP file format
|
||||||
|
======================
|
||||||
|
Sourced from Red Horizon Utilities by Emanuel Rabina
|
||||||
|
http://www.ultraq.net.nz/redhorizon/
|
||||||
|
|
||||||
|
The Dune 2 SHP file, is a multiple image filetype, where each image can have
|
||||||
|
it's own set of dimensions. The file is structured as follows:
|
||||||
|
|
||||||
|
File header:
|
||||||
|
NumImages (2 bytes) - the number of images in the file
|
||||||
|
Offsets[NumImages + 1] - offset to the image header for an image. The last
|
||||||
|
(2 or 4 bytes each) offset points to the end of the file. The offsets
|
||||||
|
don't take into account the NumImages bytes at the
|
||||||
|
beginning, so add 2 bytes to the offset value to
|
||||||
|
get the actual position of an image header in the
|
||||||
|
file
|
||||||
|
|
||||||
|
The size of the offsets can be either 2 or 4 bytes. There is no simple way
|
||||||
|
to determine which it will be, but checking the file's 4th byte to see if
|
||||||
|
it's 0, seems to be a commonly accepted practice amongst existing Dune 2 SHP
|
||||||
|
file readers:
|
||||||
|
|
||||||
|
eg: A 2-byte offset file: 01 00 06 00 EC 00 45 0A ...
|
||||||
|
A 4-byte offset file: 01 00 08 00 00 00 EC 00 ...
|
||||||
|
^^
|
||||||
|
The marked byte will be 0 in 4-byte offset files, non 0 in 2-byte offset
|
||||||
|
files.
|
||||||
|
Lastly, like C&C SHP files, there is an extra offset, pointing to the end of
|
||||||
|
the file (or what would have been the position of another image header/data
|
||||||
|
pair).
|
||||||
|
|
||||||
|
Following the file header, are a series of image header & image data pairs.
|
||||||
|
The image header is structured as follows:
|
||||||
|
|
||||||
|
Image header:
|
||||||
|
Flags (2 bytes) - flags to identify the type of data following the
|
||||||
|
Datasize field, and/or the compression schemes used
|
||||||
|
Slices (1 byte) - number of Format2 slices used to encode the image
|
||||||
|
data. Often this is the same as the height of the
|
||||||
|
image
|
||||||
|
Width (2 bytes) - width of the image
|
||||||
|
Height (1 byte) - height of the image
|
||||||
|
Filesize (2 bytes) - size of the image data in the file
|
||||||
|
Datasize (2 bytes) - size of the image data when Format2 encoded/compressed
|
||||||
|
|
||||||
|
Regarding the flags, there seem to be 4 values:
|
||||||
|
- 00000000 (0) = Decompress with Format80, then Format2
|
||||||
|
- 00000001 (1) = (see 3)
|
||||||
|
- 00000010 (2) = Decompress with Format2
|
||||||
|
- 00000011 (3) = A small 16-byte lookup table follows, and the image data
|
||||||
|
should be decompressed with Format80 then Format2.
|
||||||
|
- 00000101 (5) = The first byte gives the size of the lookup table that
|
||||||
|
follows, and the image data should be decompressed with
|
||||||
|
Format80 then Format2.
|
||||||
|
|
||||||
|
And after this image header is the image data.
|
||||||
Reference in New Issue
Block a user