136 lines
5.5 KiB
C#

using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents an IGD image container.
/// </summary>
public class Igd : ILoadableFile
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Igd"/> class, loading data from the file with the given
/// <paramref name="fileName"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Igd(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="Igd"/> class, loading data from the given
/// <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Igd(Stream stream) => Load(stream);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public int UnknownA { get; set; }
public int UnknownB { get; set; }
public byte[] UnknownC { get; set; }
public Point Center { get; set; }
public Size Size { get; set; }
public IList<IgdImage> Images { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Load(Stream stream)
{
UnknownA = stream.ReadInt32();
UnknownB = stream.ReadInt32();
UnknownC = stream.ReadBytes(8);
Center = new Point(stream.ReadInt32(), stream.ReadInt32());
Size = new Size(stream.ReadInt32(), stream.ReadInt32());
// Load the palette.
int colorCount = stream.ReadInt32();
Color[] palette = new Color[colorCount];
for (int i = 0; i < colorCount; i++)
{
palette[i] = Color.FromArgb(stream.Read1Byte(), stream.Read1Byte(), stream.Read1Byte());
stream.Seek(1); // Ignore empty alpha.
}
// Load the images.
int imageCount = stream.ReadInt32();
Images = new List<IgdImage>(imageCount);
for (int i = 0; i < imageCount; i++)
{
IgdImage image = new IgdImage();
image.UnknownA = stream.ReadInt32();
int index = stream.ReadInt32();
if (index != i)
throw new InvalidDataException("Read index does not match image index.");
image.UnknownB = stream.ReadInt32();
image.UnknownC = stream.ReadInt32();
image.Size = new Size(stream.ReadInt32(), stream.ReadInt32());
image.Center = new Point(stream.ReadInt32(), stream.ReadInt32());
// Decompress the data.
int dataSize = stream.ReadInt32();
int dataSizeCompressed = stream.ReadInt32();
image.RawBitmap = new RawBitmap
{
Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height),
Palette = palette,
Data = Decompress(stream, dataSizeCompressed, dataSize)
};
Images.Add(image);
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static byte[] Decompress(Stream stream, int compressedSize, int decompressedSize)
{
// Each input byte is either a byte of decompressed data or a marker for a following command (0xFF).
// A command consists of 4 bytes which specify the range of bytes to copy from already decompressed data.
byte[] decompressed = new byte[decompressedSize];
int i = 0;
long endPosition = stream.Position + compressedSize - 2;
while (stream.Position < endPosition)
{
byte b = stream.Read1Byte();
if (b == 0xFF)
{
// Copy existing data.
byte mask1 = stream.Read1Byte();
byte mask2 = stream.Read1Byte();
int offset = mask2 & 0x0FFF | ((mask1 & 0x000F) << 8);
int bytesToCopy = stream.Read1Byte();
for (int j = 0; j < bytesToCopy; j++)
{
int outIndex = i + j;
decompressed[outIndex] = decompressed[outIndex - offset];
}
i += bytesToCopy;
}
else
{
// Raw transfer next byte.
decompressed[i++] = b;
}
}
// Validate remaining 2 check bytes and return the decompressed data if valid.
if (stream.ReadUInt16() != 0x0080)
throw new InvalidDataException("Invalid check bytes at end of compressed image data.");
return decompressed;
}
}
}