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>
|
||||
<Compile Include="Blowfish.cs" />
|
||||
<Compile Include="BlowfishKeyProvider.cs" />
|
||||
<Compile Include="Dune2ShpReader.cs" />
|
||||
<Compile Include="FileSystem.cs" />
|
||||
<Compile Include="Folder.cs" />
|
||||
<Compile Include="Format2.cs" />
|
||||
<Compile Include="Format40.cs" />
|
||||
<Compile Include="Format80.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="Graphics\Animation.cs" />
|
||||
<Compile Include="Game.cs" />
|
||||
<Compile Include="Graphics\CursorSheetBuilder.cs" />
|
||||
<Compile Include="Graphics\LineRenderer.cs" />
|
||||
<Compile Include="Graphics\OverlayRenderer.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