Merge pull request #6673 from pchote/unhardcode-sprites
Move sprite parsers into mod code
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class R8Reader : ISpriteSource
|
||||
{
|
||||
class R8Image : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get; private set; }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public R8Image(Stream s)
|
||||
{
|
||||
// Scan forward until we find some data
|
||||
var type = s.ReadUInt8();
|
||||
while (type == 0)
|
||||
type = s.ReadUInt8();
|
||||
|
||||
var width = s.ReadInt32();
|
||||
var height = s.ReadInt32();
|
||||
var x = s.ReadInt32();
|
||||
var y = s.ReadInt32();
|
||||
|
||||
Size = new Size(width, height);
|
||||
Offset = new int2(width / 2 - x, height / 2 - y);
|
||||
|
||||
/*var imageOffset = */
|
||||
s.ReadInt32();
|
||||
var paletteOffset = s.ReadInt32();
|
||||
var bpp = s.ReadUInt8();
|
||||
if (bpp != 8)
|
||||
throw new InvalidDataException("Error: {0} bits per pixel are not supported.".F(bpp));
|
||||
|
||||
var frameHeight = s.ReadUInt8();
|
||||
var frameWidth = s.ReadUInt8();
|
||||
FrameSize = new Size(frameWidth, frameHeight);
|
||||
|
||||
// Skip alignment byte
|
||||
s.ReadUInt8();
|
||||
|
||||
Data = s.ReadBytes(width * height);
|
||||
|
||||
// Ignore palette
|
||||
if (type == 1 && paletteOffset != 0)
|
||||
s.Seek(520, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return true; } }
|
||||
|
||||
public readonly int ImageCount;
|
||||
public R8Reader(Stream stream)
|
||||
{
|
||||
var frames = new List<R8Image>();
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
frames.Add(new R8Image(stream));
|
||||
ImageCount++;
|
||||
}
|
||||
|
||||
Frames = frames.ToArray().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class ShpD2Reader : ISpriteSource
|
||||
{
|
||||
[Flags] enum FormatFlags : int
|
||||
{
|
||||
PaletteTable = 1,
|
||||
SkipFormat80 = 2,
|
||||
VariableLengthTable = 4
|
||||
}
|
||||
|
||||
class Frame : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get { return Size; } }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public Frame(Stream s)
|
||||
{
|
||||
var flags = (FormatFlags)s.ReadUInt16();
|
||||
s.Position += 1;
|
||||
var width = s.ReadUInt16();
|
||||
var height = s.ReadUInt8();
|
||||
Size = new Size(width, height);
|
||||
|
||||
// Subtract header size
|
||||
var dataLeft = s.ReadUInt16() - 10;
|
||||
var dataSize = s.ReadUInt16();
|
||||
|
||||
byte[] table;
|
||||
if ((flags & FormatFlags.PaletteTable) != 0)
|
||||
{
|
||||
var n = (flags & FormatFlags.VariableLengthTable) != 0 ? s.ReadUInt8() : (byte)16;
|
||||
table = new byte[n];
|
||||
for (var i = 0; i < n; i++)
|
||||
table[i] = s.ReadUInt8();
|
||||
|
||||
dataLeft -= n;
|
||||
}
|
||||
else
|
||||
{
|
||||
table = new byte[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
table[i] = (byte)i;
|
||||
table[1] = 0x7f;
|
||||
table[2] = 0x7e;
|
||||
table[3] = 0x7d;
|
||||
table[4] = 0x7c;
|
||||
}
|
||||
|
||||
Data = new byte[width * height];
|
||||
|
||||
// Decode image data
|
||||
var compressed = s.ReadBytes(dataLeft);
|
||||
if ((flags & FormatFlags.SkipFormat80) == 0)
|
||||
{
|
||||
var temp = new byte[dataSize];
|
||||
Format80.DecodeInto(compressed, temp);
|
||||
compressed = temp;
|
||||
}
|
||||
|
||||
Format2.DecodeInto(compressed, Data, 0);
|
||||
|
||||
// Lookup values in lookup table
|
||||
for (var j = 0; j < Data.Length; j++)
|
||||
Data[j] = table[Data[j]];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
|
||||
public ShpD2Reader(Stream s)
|
||||
{
|
||||
var imageCount = s.ReadUInt16();
|
||||
|
||||
// Last offset is pointer to end of file.
|
||||
var offsets = new uint[imageCount + 1];
|
||||
var temp = s.ReadUInt32();
|
||||
|
||||
// If fourth byte in file is non-zero, the offsets are two bytes each.
|
||||
var twoByteOffset = (temp & 0xFF0000) > 0;
|
||||
s.Position = 2;
|
||||
|
||||
for (var i = 0; i < imageCount + 1; i++)
|
||||
offsets[i] = (twoByteOffset ? s.ReadUInt16() : s.ReadUInt32()) + 2;
|
||||
|
||||
var frames = new Frame[imageCount];
|
||||
Frames = frames.AsReadOnly();
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
s.Position = offsets[i];
|
||||
frames[i] = new Frame(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class ShpReader : ISpriteSource
|
||||
{
|
||||
enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
|
||||
|
||||
class ImageHeader : ISpriteFrame
|
||||
{
|
||||
public Size Size { get { return reader.Size; } }
|
||||
public Size FrameSize { get { return reader.Size; } }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public uint FileOffset;
|
||||
public Format Format;
|
||||
|
||||
public uint RefOffset;
|
||||
public Format RefFormat;
|
||||
public ImageHeader RefImage;
|
||||
|
||||
ShpReader reader;
|
||||
|
||||
// Used by ShpWriter
|
||||
public ImageHeader() { }
|
||||
|
||||
public ImageHeader(Stream stream, ShpReader reader)
|
||||
{
|
||||
this.reader = reader;
|
||||
var data = stream.ReadUInt32();
|
||||
FileOffset = data & 0xffffff;
|
||||
Format = (Format)(data >> 24);
|
||||
|
||||
RefOffset = stream.ReadUInt16();
|
||||
RefFormat = (Format)stream.ReadUInt16();
|
||||
}
|
||||
|
||||
public void WriteTo(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(FileOffset | ((uint)Format << 24));
|
||||
writer.Write((ushort)RefOffset);
|
||||
writer.Write((ushort)RefFormat);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
public readonly Size Size;
|
||||
|
||||
int recurseDepth = 0;
|
||||
readonly int imageCount;
|
||||
|
||||
readonly long shpBytesFileOffset;
|
||||
readonly byte[] shpBytes;
|
||||
|
||||
public ShpReader(Stream stream)
|
||||
{
|
||||
imageCount = stream.ReadUInt16();
|
||||
stream.Position += 4;
|
||||
var width = stream.ReadUInt16();
|
||||
var height = stream.ReadUInt16();
|
||||
Size = new Size(width, height);
|
||||
|
||||
stream.Position += 4;
|
||||
var headers = new ImageHeader[imageCount];
|
||||
Frames = headers.AsReadOnly();
|
||||
for (var i = 0; i < headers.Length; i++)
|
||||
headers[i] = new ImageHeader(stream, this);
|
||||
|
||||
// Skip eof and zero headers
|
||||
stream.Position += 16;
|
||||
|
||||
var offsets = headers.ToDictionary(h => h.FileOffset, h => h);
|
||||
for (var i = 0; i < imageCount; i++)
|
||||
{
|
||||
var h = headers[i];
|
||||
if (h.Format == Format.Format20)
|
||||
h.RefImage = headers[i - 1];
|
||||
else if (h.Format == Format.Format40 && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
||||
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
|
||||
}
|
||||
|
||||
shpBytesFileOffset = stream.Position;
|
||||
shpBytes = stream.ReadBytes((int)(stream.Length - stream.Position));
|
||||
|
||||
foreach (var h in headers)
|
||||
Decompress(h);
|
||||
}
|
||||
|
||||
void Decompress(ImageHeader h)
|
||||
{
|
||||
// No extra work is required for empty frames
|
||||
if (h.Size.Width == 0 || h.Size.Height == 0)
|
||||
return;
|
||||
|
||||
if (recurseDepth > imageCount)
|
||||
throw new InvalidDataException("Format20/40 headers contain infinite loop");
|
||||
|
||||
switch (h.Format)
|
||||
{
|
||||
case Format.Format20:
|
||||
case Format.Format40:
|
||||
{
|
||||
if (h.RefImage.Data == null)
|
||||
{
|
||||
++recurseDepth;
|
||||
Decompress(h.RefImage);
|
||||
--recurseDepth;
|
||||
}
|
||||
|
||||
h.Data = CopyImageData(h.RefImage.Data);
|
||||
Format40.DecodeInto(shpBytes, h.Data, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
break;
|
||||
}
|
||||
|
||||
case Format.Format80:
|
||||
{
|
||||
var imageBytes = new byte[Size.Width * Size.Height];
|
||||
Format80.DecodeInto(shpBytes, imageBytes, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
h.Data = imageBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] CopyImageData(byte[] baseImage)
|
||||
{
|
||||
var imageData = new byte[Size.Width * Size.Height];
|
||||
Array.Copy(baseImage, imageData, imageData.Length);
|
||||
return imageData;
|
||||
}
|
||||
|
||||
public static ShpReader Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return new ShpReader(s);
|
||||
}
|
||||
|
||||
public static void Write(Stream s, Size size, IEnumerable<byte[]> frames)
|
||||
{
|
||||
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();
|
||||
|
||||
// note: end-of-file and all-zeroes headers
|
||||
var dataOffset = 14 + (compressedFrames.Length + 2) * 8;
|
||||
|
||||
using (var bw = new BinaryWriter(s))
|
||||
{
|
||||
bw.Write((ushort)compressedFrames.Length);
|
||||
bw.Write((ushort)0);
|
||||
bw.Write((ushort)0);
|
||||
bw.Write((ushort)size.Width);
|
||||
bw.Write((ushort)size.Height);
|
||||
bw.Write((uint)0);
|
||||
|
||||
foreach (var f in compressedFrames)
|
||||
{
|
||||
var ih = new ImageHeader { Format = Format.Format80, FileOffset = (uint)dataOffset };
|
||||
dataOffset += f.Length;
|
||||
|
||||
ih.WriteTo(bw);
|
||||
}
|
||||
|
||||
var eof = new ImageHeader { FileOffset = (uint)dataOffset };
|
||||
eof.WriteTo(bw);
|
||||
|
||||
var allZeroes = new ImageHeader { };
|
||||
allZeroes.WriteTo(bw);
|
||||
|
||||
foreach (var f in compressedFrames)
|
||||
bw.Write(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class ShpTSReader : ISpriteSource
|
||||
{
|
||||
class FrameHeader : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get; private set; }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public readonly uint FileOffset;
|
||||
public readonly byte Format;
|
||||
|
||||
public FrameHeader(Stream stream, Size frameSize)
|
||||
{
|
||||
var x = stream.ReadUInt16();
|
||||
var y = stream.ReadUInt16();
|
||||
var width = stream.ReadUInt16();
|
||||
var height = stream.ReadUInt16();
|
||||
|
||||
// Note: the mixed Integer / fp division is intentional, and required for calculating the correct offset.
|
||||
Offset = new float2(x + width / 2 - 0.5f * frameSize.Width, y + height / 2 - 0.5f * frameSize.Height);
|
||||
Size = new Size(width, height);
|
||||
FrameSize = frameSize;
|
||||
|
||||
Format = stream.ReadUInt8();
|
||||
stream.Position += 11;
|
||||
FileOffset = stream.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
|
||||
public ShpTSReader(Stream stream)
|
||||
{
|
||||
stream.ReadUInt16();
|
||||
var width = stream.ReadUInt16();
|
||||
var height = stream.ReadUInt16();
|
||||
var size = new Size(width, height);
|
||||
var frameCount = stream.ReadUInt16();
|
||||
|
||||
var frames = new FrameHeader[frameCount];
|
||||
Frames = frames.AsReadOnly();
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
frames[i] = new FrameHeader(stream, size);
|
||||
|
||||
for (var i = 0; i < frameCount; i++)
|
||||
{
|
||||
var f = frames[i];
|
||||
if (f.FileOffset == 0)
|
||||
continue;
|
||||
|
||||
stream.Position = f.FileOffset;
|
||||
|
||||
var frameSize = f.Size.Width * f.Size.Height;
|
||||
|
||||
// Uncompressed
|
||||
if (f.Format == 1 || f.Format == 0)
|
||||
f.Data = stream.ReadBytes(frameSize);
|
||||
|
||||
// Uncompressed scanlines
|
||||
else if (f.Format == 2)
|
||||
{
|
||||
f.Data = new byte[frameSize];
|
||||
for (var j = 0; j < f.Size.Height; j++)
|
||||
{
|
||||
var length = stream.ReadUInt16() - 2;
|
||||
var offset = f.Size.Width * j;
|
||||
stream.ReadBytes(f.Data, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
// RLE-zero compressed scanlines
|
||||
else if (f.Format == 3)
|
||||
{
|
||||
f.Data = new byte[frameSize];
|
||||
for (var j = 0; j < f.Size.Height; j++)
|
||||
{
|
||||
var length = stream.ReadUInt16() - 2;
|
||||
var offset = f.Size.Width * j;
|
||||
Format2.DecodeInto(stream.ReadBytes(length), f.Data, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class TmpRAReader : ISpriteSource
|
||||
{
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
|
||||
public TmpRAReader(Stream s)
|
||||
{
|
||||
var width = s.ReadUInt16();
|
||||
var height = s.ReadUInt16();
|
||||
var size = new Size(width, height);
|
||||
|
||||
s.Position += 12;
|
||||
var imgStart = s.ReadUInt32();
|
||||
s.Position += 8;
|
||||
var indexEnd = s.ReadInt32();
|
||||
s.Position += 4;
|
||||
var indexStart = s.ReadInt32();
|
||||
|
||||
s.Position = indexStart;
|
||||
var count = indexEnd - indexStart;
|
||||
var tiles = new TmpTile[count];
|
||||
Frames = tiles.AsReadOnly();
|
||||
var tilesIndex = 0;
|
||||
foreach (var b in s.ReadBytes(count))
|
||||
{
|
||||
if (b != 255)
|
||||
{
|
||||
s.Position = imgStart + b * width * height;
|
||||
tiles[tilesIndex++] = new TmpTile(s.ReadBytes(width * height), size);
|
||||
}
|
||||
else
|
||||
tiles[tilesIndex++] = new TmpTile(null, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class TmpTile : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public TmpTile(byte[] data, Size size)
|
||||
{
|
||||
FrameSize = size;
|
||||
Data = data;
|
||||
|
||||
if (data == null)
|
||||
Data = new byte[0];
|
||||
else
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
public class TmpTDReader : ISpriteSource
|
||||
{
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
|
||||
public TmpTDReader(Stream s)
|
||||
{
|
||||
var width = s.ReadUInt16();
|
||||
var height = s.ReadUInt16();
|
||||
var size = new Size(width, height);
|
||||
|
||||
s.Position += 8;
|
||||
var imgStart = s.ReadUInt32();
|
||||
s.Position += 8;
|
||||
var indexEnd = s.ReadInt32();
|
||||
var indexStart = s.ReadInt32();
|
||||
|
||||
s.Position = indexStart;
|
||||
var count = indexEnd - indexStart;
|
||||
var tiles = new TmpTile[count];
|
||||
Frames = tiles.AsReadOnly();
|
||||
var tilesIndex = 0;
|
||||
foreach (var b in s.ReadBytes(count))
|
||||
{
|
||||
if (b != 255)
|
||||
{
|
||||
s.Position = imgStart + b * width * height;
|
||||
tiles[tilesIndex++] = new TmpTile(s.ReadBytes(width * height), size);
|
||||
}
|
||||
else
|
||||
tiles[tilesIndex++] = new TmpTile(null, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class TmpTSTile : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get { return Size; } }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public TmpTSTile(Stream s, Size size)
|
||||
{
|
||||
Size = size;
|
||||
|
||||
// Ignore tile header for now
|
||||
s.Position += 52;
|
||||
|
||||
Data = new byte[size.Width * size.Height];
|
||||
|
||||
// Unpack tile data
|
||||
var width = 4;
|
||||
for (var i = 0; i < size.Height; i++)
|
||||
{
|
||||
var start = i * size.Width + (size.Width - width) / 2;
|
||||
for (var j = 0; j < width; j++)
|
||||
Data[start + j] = s.ReadUInt8();
|
||||
|
||||
width += (i < size.Height / 2 - 1 ? 1 : -1) * 4;
|
||||
}
|
||||
|
||||
// Ignore Z-data for now
|
||||
// Ignore extra data for now
|
||||
}
|
||||
}
|
||||
|
||||
public class TmpTSReader : ISpriteSource
|
||||
{
|
||||
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
|
||||
public bool CacheWhenLoadingTileset { get { return false; } }
|
||||
|
||||
public TmpTSReader(Stream s)
|
||||
{
|
||||
var templateWidth = s.ReadUInt32();
|
||||
var templateHeight = s.ReadUInt32();
|
||||
var tileWidth = s.ReadInt32();
|
||||
var tileHeight = s.ReadInt32();
|
||||
var size = new Size(tileWidth, tileHeight);
|
||||
var offsets = new uint[templateWidth * templateHeight];
|
||||
for (var i = 0; i < offsets.Length; i++)
|
||||
offsets[i] = s.ReadUInt32();
|
||||
|
||||
var tiles = new List<TmpTSTile>();
|
||||
for (var i = 0; i < offsets.Length; i++)
|
||||
{
|
||||
s.Position = offsets[i];
|
||||
tiles.Add(new TmpTSTile(s, size));
|
||||
}
|
||||
|
||||
Frames = tiles.ToArray().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace OpenRA.Graphics
|
||||
foreach (var p in nodesDict["Palettes"].Nodes)
|
||||
palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false);
|
||||
|
||||
var spriteLoader = new SpriteLoader(new string[0], new SheetBuilder(SheetType.Indexed));
|
||||
var spriteLoader = new SpriteLoader(modData.SpriteLoaders, new string[0], new SheetBuilder(SheetType.Indexed));
|
||||
foreach (var s in nodesDict["Cursors"].Nodes)
|
||||
LoadSequencesForCursor(spriteLoader, s.Key, s.Value);
|
||||
spriteLoader.SheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
this.modData = modData;
|
||||
|
||||
spriteLoader = Exts.Lazy(() => new SpriteLoader(tileSet.Extensions, new SheetBuilder(SheetType.Indexed)));
|
||||
spriteLoader = Exts.Lazy(() => new SpriteLoader(modData.SpriteLoaders, tileSet.Extensions, new SheetBuilder(SheetType.Indexed)));
|
||||
}
|
||||
|
||||
public Sequences LoadSequences(Map map)
|
||||
|
||||
@@ -8,35 +8,67 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteLoader
|
||||
{
|
||||
bool TryParseSprite(Stream s, out ISpriteFrame[] frames);
|
||||
}
|
||||
|
||||
public interface ISpriteFrame
|
||||
{
|
||||
Size Size { get; }
|
||||
Size FrameSize { get; }
|
||||
float2 Offset { get; }
|
||||
byte[] Data { get; }
|
||||
bool DisableExportPadding { get; }
|
||||
}
|
||||
|
||||
public class SpriteLoader
|
||||
{
|
||||
public readonly SheetBuilder SheetBuilder;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly Cache<string, Sprite[]> sprites;
|
||||
readonly Cache<string, ISpriteFrame[]> frames;
|
||||
readonly string[] exts;
|
||||
|
||||
public SpriteLoader(string[] exts, SheetBuilder sheetBuilder)
|
||||
public SpriteLoader(ISpriteLoader[] loaders, string[] exts, SheetBuilder sheetBuilder)
|
||||
{
|
||||
this.loaders = loaders;
|
||||
SheetBuilder = sheetBuilder;
|
||||
|
||||
// Include extension-less version
|
||||
this.exts = exts.Append("").ToArray();
|
||||
sprites = new Cache<string, Sprite[]>(CacheSpriteFrames);
|
||||
sprites = new Cache<string, Sprite[]>(CacheSprites);
|
||||
frames = new Cache<string, ISpriteFrame[]>(CacheFrames);
|
||||
}
|
||||
|
||||
Sprite[] CacheSpriteFrames(string filename)
|
||||
Sprite[] CacheSprites(string filename)
|
||||
{
|
||||
return frames[filename].Select(a => SheetBuilder.Add(a))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
ISpriteFrame[] CacheFrames(string filename)
|
||||
{
|
||||
using (var stream = GlobalFileSystem.OpenWithExts(filename, exts))
|
||||
return SpriteSource.LoadSpriteSource(stream, filename).Frames
|
||||
.Select(a => SheetBuilder.Add(a))
|
||||
.ToArray();
|
||||
{
|
||||
ISpriteFrame[] frames;
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, out frames))
|
||||
return frames;
|
||||
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file");
|
||||
}
|
||||
}
|
||||
|
||||
public Sprite[] LoadAllSprites(string filename) { return sprites[filename]; }
|
||||
public ISpriteFrame[] LoadAllFrames(string filename) { return frames[filename]; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ISpriteFrame
|
||||
{
|
||||
Size Size { get; }
|
||||
Size FrameSize { get; }
|
||||
float2 Offset { get; }
|
||||
byte[] Data { get; }
|
||||
}
|
||||
|
||||
public interface ISpriteSource
|
||||
{
|
||||
IReadOnlyList<ISpriteFrame> Frames { get; }
|
||||
bool CacheWhenLoadingTileset { get; }
|
||||
}
|
||||
|
||||
// TODO: Most of this should be moved into the format parsers themselves.
|
||||
public enum SpriteType { Unknown, ShpTD, ShpTS, ShpD2, TmpTD, TmpRA, TmpTS, R8 }
|
||||
public static class SpriteSource
|
||||
{
|
||||
static bool IsTmpRA(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
s.Position += 20;
|
||||
var a = s.ReadUInt32();
|
||||
s.Position += 2;
|
||||
var b = s.ReadUInt16();
|
||||
|
||||
s.Position = start;
|
||||
return a == 0 && b == 0x2c73;
|
||||
}
|
||||
|
||||
static bool IsTmpTD(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
s.Position += 16;
|
||||
var a = s.ReadUInt32();
|
||||
var b = s.ReadUInt32();
|
||||
|
||||
s.Position = start;
|
||||
return a == 0 && b == 0x0D1AFFFF;
|
||||
}
|
||||
|
||||
static bool IsTmpTS(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
s.Position += 8;
|
||||
var sx = s.ReadUInt32();
|
||||
var sy = s.ReadUInt32();
|
||||
|
||||
// Find the first frame
|
||||
var offset = s.ReadUInt32();
|
||||
|
||||
if (offset > s.Length - 52)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
s.Position = offset + 12;
|
||||
var test = s.ReadUInt32();
|
||||
|
||||
s.Position = start;
|
||||
return test == sx * sy / 2 + 52;
|
||||
}
|
||||
|
||||
static bool IsShpTS(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
// First word is zero
|
||||
if (s.ReadUInt16() != 0)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity Check the image count
|
||||
s.Position += 4;
|
||||
var imageCount = s.ReadUInt16();
|
||||
if (s.Position + 24 * imageCount > s.Length)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the size and format flag
|
||||
// Some files define bogus frames, so loop until we find a valid one
|
||||
s.Position += 4;
|
||||
ushort w, h, f = 0;
|
||||
byte type;
|
||||
do
|
||||
{
|
||||
w = s.ReadUInt16();
|
||||
h = s.ReadUInt16();
|
||||
type = s.ReadUInt8();
|
||||
}
|
||||
while (w == 0 && h == 0 && f++ < imageCount);
|
||||
|
||||
s.Position = start;
|
||||
return type < 4;
|
||||
}
|
||||
|
||||
static bool IsShpTD(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
// First word is the image count
|
||||
var imageCount = s.ReadUInt16();
|
||||
if (imageCount == 0)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Last offset should point to the end of file
|
||||
var finalOffset = start + 14 + 8 * imageCount;
|
||||
if (finalOffset > s.Length)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
s.Position = finalOffset;
|
||||
var eof = s.ReadUInt32();
|
||||
if (eof != s.Length)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the format flag on the first frame
|
||||
s.Position = start + 17;
|
||||
var b = s.ReadUInt8();
|
||||
|
||||
s.Position = start;
|
||||
return b == 0x20 || b == 0x40 || b == 0x80;
|
||||
}
|
||||
|
||||
static bool IsShpD2(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
// First word is the image count
|
||||
var imageCount = s.ReadUInt16();
|
||||
if (imageCount == 0)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test for two vs four byte offset
|
||||
var testOffset = s.ReadUInt32();
|
||||
var offsetSize = (testOffset & 0xFF0000) > 0 ? 2 : 4;
|
||||
|
||||
// Last offset should point to the end of file
|
||||
var finalOffset = start + 2 + offsetSize * imageCount;
|
||||
if (finalOffset > s.Length)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
s.Position = finalOffset;
|
||||
var eof = offsetSize == 2 ? s.ReadUInt16() : s.ReadUInt32();
|
||||
if (eof + 2 != s.Length)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the format flag on the first frame
|
||||
var b = s.ReadUInt16();
|
||||
s.Position = start;
|
||||
return b == 5 || b <= 3;
|
||||
}
|
||||
|
||||
static bool IsR8(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
|
||||
// First byte is nonzero
|
||||
if (s.ReadUInt8() == 0)
|
||||
{
|
||||
s.Position = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the format of the first frame
|
||||
s.Position = start + 25;
|
||||
var d = s.ReadUInt8();
|
||||
|
||||
s.Position = start;
|
||||
return d == 8;
|
||||
}
|
||||
|
||||
public static SpriteType DetectSpriteType(Stream s)
|
||||
{
|
||||
if (IsShpTD(s))
|
||||
return SpriteType.ShpTD;
|
||||
|
||||
if (IsShpTS(s))
|
||||
return SpriteType.ShpTS;
|
||||
|
||||
if (IsR8(s))
|
||||
return SpriteType.R8;
|
||||
|
||||
if (IsTmpRA(s))
|
||||
return SpriteType.TmpRA;
|
||||
|
||||
if (IsTmpTD(s))
|
||||
return SpriteType.TmpTD;
|
||||
|
||||
if (IsTmpTS(s))
|
||||
return SpriteType.TmpTS;
|
||||
|
||||
if (IsShpD2(s))
|
||||
return SpriteType.ShpD2;
|
||||
|
||||
return SpriteType.Unknown;
|
||||
}
|
||||
|
||||
public static ISpriteSource LoadSpriteSource(Stream s, string filename)
|
||||
{
|
||||
var type = DetectSpriteType(s);
|
||||
switch (type)
|
||||
{
|
||||
case SpriteType.ShpTD:
|
||||
return new ShpReader(s);
|
||||
case SpriteType.ShpTS:
|
||||
return new ShpTSReader(s);
|
||||
case SpriteType.R8:
|
||||
return new R8Reader(s);
|
||||
case SpriteType.TmpRA:
|
||||
return new TmpRAReader(s);
|
||||
case SpriteType.TmpTD:
|
||||
return new TmpTDReader(s);
|
||||
case SpriteType.TmpTS:
|
||||
return new TmpTSReader(s);
|
||||
case SpriteType.ShpD2:
|
||||
return new ShpD2Reader(s);
|
||||
case SpriteType.Unknown:
|
||||
default:
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,33 +22,6 @@ namespace OpenRA.Graphics
|
||||
Dictionary<ushort, Sprite[]> templates;
|
||||
Sprite missingTile;
|
||||
|
||||
Sprite[] LoadTemplate(string filename, string[] exts, Dictionary<string, ISpriteSource> sourceCache, int[] frames)
|
||||
{
|
||||
ISpriteSource source;
|
||||
if (!sourceCache.ContainsKey(filename))
|
||||
{
|
||||
using (var s = GlobalFileSystem.OpenWithExts(filename, exts))
|
||||
source = SpriteSource.LoadSpriteSource(s, filename);
|
||||
|
||||
if (source.CacheWhenLoadingTileset)
|
||||
sourceCache.Add(filename, source);
|
||||
}
|
||||
else
|
||||
source = sourceCache[filename];
|
||||
|
||||
if (frames != null)
|
||||
{
|
||||
var ret = new List<Sprite>();
|
||||
var srcFrames = source.Frames.ToArray();
|
||||
foreach (var i in frames)
|
||||
ret.Add(sheetBuilder.Add(srcFrames[i]));
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
return source.Frames.Select(f => sheetBuilder.Add(f)).ToArray();
|
||||
}
|
||||
|
||||
public Theater(TileSet tileset)
|
||||
{
|
||||
var allocated = false;
|
||||
@@ -61,11 +34,17 @@ namespace OpenRA.Graphics
|
||||
return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize), true);
|
||||
};
|
||||
|
||||
var sourceCache = new Dictionary<string, ISpriteSource>();
|
||||
templates = new Dictionary<ushort, Sprite[]>();
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
|
||||
templates = new Dictionary<ushort, Sprite[]>();
|
||||
|
||||
// We manage the SheetBuilder ourselves, to avoid loading all of the tileset images
|
||||
var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null);
|
||||
foreach (var t in tileset.Templates)
|
||||
templates.Add(t.Value.Id, LoadTemplate(t.Value.Image, tileset.Extensions, sourceCache, t.Value.Frames));
|
||||
{
|
||||
var allFrames = spriteLoader.LoadAllFrames(t.Value.Image);
|
||||
var frames = t.Value.Frames != null ? t.Value.Frames.Select(f => allFrames[f]).ToArray() : allFrames;
|
||||
templates.Add(t.Value.Id, frames.Select(f => sheetBuilder.Add(f)).ToArray());
|
||||
}
|
||||
|
||||
// 1x1px transparent tile
|
||||
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace OpenRA
|
||||
public readonly Size TileSize = new Size(24, 24);
|
||||
public readonly TileShape TileShape = TileShape.Rectangle;
|
||||
|
||||
public readonly string[] SpriteFormats = { };
|
||||
|
||||
[Desc("(x,y,z) offset of the full cell and each sub-cell", "X & Y should be between -512 ... 512 and Z >= 0")]
|
||||
public readonly WVec[] SubCellOffsets =
|
||||
{
|
||||
@@ -128,6 +130,9 @@ namespace OpenRA
|
||||
compat.Add(c.Trim());
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.ContainsKey("SpriteFormats"))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
|
||||
}
|
||||
|
||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
||||
|
||||
@@ -457,7 +457,7 @@ namespace OpenRA
|
||||
for (var i = 0; i < MapSize.X; i++)
|
||||
for (var j = 0; j < MapSize.Y; j++)
|
||||
{
|
||||
var tile = MapTiles.Value[new CPos(i, j)];
|
||||
var tile = MapTiles.Value[i, j];
|
||||
writer.Write(tile.Type);
|
||||
writer.Write(tile.Index);
|
||||
}
|
||||
@@ -467,7 +467,7 @@ namespace OpenRA
|
||||
{
|
||||
for (var j = 0; j < MapSize.Y; j++)
|
||||
{
|
||||
var tile = MapResources.Value[new CPos(i, j)];
|
||||
var tile = MapResources.Value[i, j];
|
||||
writer.Write(tile.Type);
|
||||
writer.Write(tile.Index);
|
||||
}
|
||||
@@ -654,9 +654,8 @@ namespace OpenRA
|
||||
{
|
||||
for (var i = Bounds.Left; i < Bounds.Right; i++)
|
||||
{
|
||||
var cell = new CPos(i, j);
|
||||
var type = MapTiles.Value[cell].Type;
|
||||
var index = MapTiles.Value[cell].Index;
|
||||
var type = MapTiles.Value[i, j].Type;
|
||||
var index = MapTiles.Value[i, j].Index;
|
||||
if (!tileset.Templates.ContainsKey(type))
|
||||
{
|
||||
Console.WriteLine("Unknown Tile ID {0}".F(type));
|
||||
@@ -668,7 +667,7 @@ namespace OpenRA
|
||||
continue;
|
||||
|
||||
index = (byte)r.Next(0, template.TilesCount);
|
||||
MapTiles.Value[cell] = new TerrainTile(type, index);
|
||||
MapTiles.Value[i, j] = new TerrainTile(type, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,7 +729,7 @@ namespace OpenRA
|
||||
for (var j = -max; j <= max; j++)
|
||||
for (var i = -max; i <= max; i++)
|
||||
if (max * max >= i * i + j * j)
|
||||
ts [Exts.ISqrt(i * i + j * j, Exts.ISqrtRoundMode.Ceiling)].Add(new CVec(i, j));
|
||||
ts[Exts.ISqrt(i * i + j * j, Exts.ISqrtRoundMode.Ceiling)].Add(new CVec(i, j));
|
||||
|
||||
// Sort each integer-distance group by the actual distance
|
||||
foreach (var list in ts)
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace OpenRA
|
||||
public readonly ObjectCreator ObjectCreator;
|
||||
public readonly WidgetLoader WidgetLoader;
|
||||
public readonly MapCache MapCache;
|
||||
public readonly ISpriteLoader[] SpriteLoaders;
|
||||
public ILoadScreen LoadScreen = null;
|
||||
public VoxelLoader VoxelLoader;
|
||||
public readonly RulesetCache RulesetCache;
|
||||
@@ -45,6 +46,18 @@ namespace OpenRA
|
||||
RulesetCache.LoadingProgress += HandleLoadingProgress;
|
||||
MapCache = new MapCache(this);
|
||||
|
||||
var loaders = new List<ISpriteLoader>();
|
||||
foreach (var format in Manifest.SpriteFormats)
|
||||
{
|
||||
var loader = ObjectCreator.FindType(format + "Loader");
|
||||
if (loader == null || !loader.GetInterfaces().Contains(typeof(ISpriteLoader)))
|
||||
throw new InvalidOperationException("Unable to find a sprite loader for type '{0}'.".F(format));
|
||||
|
||||
loaders.Add((ISpriteLoader)ObjectCreator.CreateBasic(loader));
|
||||
}
|
||||
|
||||
SpriteLoaders = loaders.ToArray();
|
||||
|
||||
// HACK: Mount only local folders so we have a half-working environment for the asset installer
|
||||
GlobalFileSystem.UnmountAll();
|
||||
foreach (var dir in Manifest.Folders)
|
||||
|
||||
@@ -277,13 +277,6 @@
|
||||
<Compile Include="FileFormats\XccLocalDatabase.cs" />
|
||||
<Compile Include="FileFormats\HvaReader.cs" />
|
||||
<Compile Include="FileFormats\PngLoader.cs" />
|
||||
<Compile Include="FileFormats\R8Reader.cs" />
|
||||
<Compile Include="FileFormats\ShpD2Reader.cs" />
|
||||
<Compile Include="FileFormats\ShpReader.cs" />
|
||||
<Compile Include="FileFormats\ShpTSReader.cs" />
|
||||
<Compile Include="FileFormats\TmpRAReader.cs" />
|
||||
<Compile Include="FileFormats\TmpTDReader.cs" />
|
||||
<Compile Include="FileFormats\TmpTSReader.cs" />
|
||||
<Compile Include="FileFormats\VqaReader.cs" />
|
||||
<Compile Include="FileFormats\VxlReader.cs" />
|
||||
<Compile Include="Primitives\ActionQueue.cs" />
|
||||
@@ -320,7 +313,6 @@
|
||||
<Compile Include="Map\ActorReference.cs" />
|
||||
<Compile Include="Support\Evaluator.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Graphics\SpriteSource.cs" />
|
||||
<Compile Include="Graphics\PlayerColorRemap.cs" />
|
||||
<Compile Include="Graphics\Palette.cs" />
|
||||
<Compile Include="FileSystem\GlobalFileSystem.cs" />
|
||||
|
||||
Reference in New Issue
Block a user