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 { /// /// Represents an IGD image container. /// public class Igd : ILoadableFile { // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ /// /// Initializes a new instance of the class, loading data from the file with the given /// . /// /// The name of the file to load the data from. public Igd(string fileName) => Load(fileName); /// /// Initializes a new instance of the class, loading data from the given /// . /// /// The to load the data from. 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 Images { get; set; } // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- /// public void Load(string fileName) { using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); Load(stream); } /// 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(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; } } }