From 939288b10b9f8b30d46fdbc98bb042c210e2fe77 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Tue, 18 Apr 2017 14:32:43 +0200 Subject: [PATCH] Add support for loading IMG files. --- src/Syroot.Worms.Test/Program.cs | 41 ++++- src/Syroot.Worms.sln | 2 +- src/Syroot.Worms/Core/ByteArrayExtensions.cs | 8 +- src/Syroot.Worms/Core/Team17Compression.cs | 54 +++++++ src/Syroot.Worms/Gen2/Archive.cs | 8 +- src/Syroot.Worms/Gen2/Image.cs | 151 ++++++++++++++++++- src/Syroot.Worms/Syroot.Worms.csproj | 32 ++-- 7 files changed, 273 insertions(+), 23 deletions(-) create mode 100644 src/Syroot.Worms/Core/Team17Compression.cs diff --git a/src/Syroot.Worms.Test/Program.cs b/src/Syroot.Worms.Test/Program.cs index d529c54..359569f 100644 --- a/src/Syroot.Worms.Test/Program.cs +++ b/src/Syroot.Worms.Test/Program.cs @@ -1,17 +1,52 @@ using System; +using System.IO; using Syroot.Worms.Gen2; +using System.Linq; +using System.Collections; +using System.Collections.Generic; namespace Syroot.Worms.Test { + /// + /// Represents the main class of the application containing the program entry point. + /// internal class Program { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private static readonly string[] _testPaths = { @"C:\Games\Worms Armageddon 3.7.2.1", @"E:\" }; + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private static void Main(string[] args) { - Archive archive = new Archive(@"D:\Archive\Games\Worms\Worms Armageddon\DATA\Gfx\Gfx0.dir"); - archive.Save(@"D:\Pictures\test.dir"); - Archive archive2 = new Archive(@"D:\Pictures\test.dir"); + //foreach (string dirFile in GetFiles("*.dir")) + //{ + // Console.WriteLine($"Loading {dirFile}..."); + // Archive archive = new Archive(dirFile); + // foreach (string entry in archive.Keys) + // { + // Console.WriteLine($"\t{entry}"); + // } + //} + + foreach (string imgFile in GetFiles("*.img")) + { + Console.WriteLine("Loading {imgFile}..."); + Image image = new Image(imgFile); + } + + Console.ReadLine(); + } + + private static List GetFiles(string wildcard) + { + List files = new List(); + foreach (string testPath in _testPaths) + { + files.AddRange(Directory.GetFiles(testPath, wildcard, SearchOption.AllDirectories)); + } + return files; } } } \ No newline at end of file diff --git a/src/Syroot.Worms.sln b/src/Syroot.Worms.sln index b9a47e3..76d41e2 100644 --- a/src/Syroot.Worms.sln +++ b/src/Syroot.Worms.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.3 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms", "Syroot.Worms\Syroot.Worms.csproj", "{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}" EndProject diff --git a/src/Syroot.Worms/Core/ByteArrayExtensions.cs b/src/Syroot.Worms/Core/ByteArrayExtensions.cs index 6b6c2b5..7e5b142 100644 --- a/src/Syroot.Worms/Core/ByteArrayExtensions.cs +++ b/src/Syroot.Worms/Core/ByteArrayExtensions.cs @@ -1,17 +1,17 @@ namespace Syroot.Worms.Core { /// - /// Represents extension methods for instances. + /// Represents extension methods for byte array instances. /// internal static class ByteArrayExtensions { // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- /// - /// Returns true if the current instance holds the same values as the given one. + /// Returns true if the current byte array instance holds the same values as the given one. /// - /// The extended instance. - /// The instance to compare with. + /// The extended byte array instance. + /// The byte array instance to compare with. /// true if both instances hold the same values, otherwise false. internal static bool Compare(this byte[] self, byte[] other) { diff --git a/src/Syroot.Worms/Core/Team17Compression.cs b/src/Syroot.Worms/Core/Team17Compression.cs new file mode 100644 index 0000000..6b523f3 --- /dev/null +++ b/src/Syroot.Worms/Core/Team17Compression.cs @@ -0,0 +1,54 @@ +using System.IO; + +namespace Syroot.Worms.Core +{ + /// + /// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for + /// graphic file formats. + /// S. http://worms2d.info/Team17_compression. + /// + internal static class Team17Compression + { + public static bool Decompress(BinaryReader b, ref byte[] dStream) + { + int cmd; + int output = 0; //offset of next write + while ((cmd = b.ReadByte()) != -1) + { //read a byte + if ((cmd & 0x80) == 0) + { //command: 1 byte (color) + dStream[output++] = (byte)cmd; + } + else + { + int arg1 = (cmd >> 3) & 0xF; //arg1=bits 2-5 + int arg2 = b.ReadByte(); + if (arg2 == -1) + return false; + arg2 = ((cmd << 8) | arg2) & 0x7FF; //arg2=bits 6-16 + if (arg1 == 0) + { + if (arg2 == 0) //command: 0x80 0x00 + return false; + int arg3 = b.ReadByte(); + if (arg3 == -1) + return false; + output = CopyData(output, arg2, arg3 + 18, ref dStream); //command: 3 bytes + } + else + { + output = CopyData(output, arg2 + 1, arg1 + 2, ref dStream); //command: 2 bytes + } + } + } + return true; + } + + public static int CopyData(int dOffset, int cOffset, int Repeat, ref byte[] dStream) + { + for (; Repeat > 0; Repeat--) + dStream[dOffset] = dStream[dOffset++ - cOffset]; + return dOffset; + } + } +} diff --git a/src/Syroot.Worms/Gen2/Archive.cs b/src/Syroot.Worms/Gen2/Archive.cs index 2bfaf7c..1397f65 100644 --- a/src/Syroot.Worms/Gen2/Archive.cs +++ b/src/Syroot.Worms/Gen2/Archive.cs @@ -16,8 +16,8 @@ namespace Syroot.Worms.Gen2 { // ---- CONSTANTS ---------------------------------------------------------------------------------------------- - private static readonly int _signature = 0x1A524944; - private static readonly int _tocSignature = 0x0000000A; + private const int _signature = 0x1A524944; // "DIR", 0x1A + private const int _tocSignature = 0x0000000A; private const int _hashBits = 10; private const int _hashSize = 1 << _hashBits; @@ -63,7 +63,7 @@ namespace Syroot.Worms.Gen2 int signature = reader.ReadInt32(); if (signature != _signature) { - throw new InvalidDataException("Invalid file signature."); + throw new InvalidDataException("Invalid DIR file signature."); } int fileSize = reader.ReadInt32(); int tocOffset = reader.ReadInt32(); @@ -73,7 +73,7 @@ namespace Syroot.Worms.Gen2 int tocSignature = reader.ReadInt32(); if (tocSignature != _tocSignature) { - throw new InvalidDataException("Invalid table of contents signature."); + throw new InvalidDataException("Invalid DIR table of contents signature."); } // Generate a data dictionary out of the hash table and file entries. int[] hashTable = reader.ReadInt32s(_hashSize); diff --git a/src/Syroot.Worms/Gen2/Image.cs b/src/Syroot.Worms/Gen2/Image.cs index 72715ea..13a7aef 100644 --- a/src/Syroot.Worms/Gen2/Image.cs +++ b/src/Syroot.Worms/Gen2/Image.cs @@ -1,10 +1,157 @@ using System; -using System.Collections.Generic; +using System.IO; using System.Text; +using Syroot.IO; +using Syroot.Maths; +using Syroot.Worms.Core; namespace Syroot.Worms.Gen2 { - class Image + /// + /// Represents a (palettized) graphical image stored in an IMG file, possibly compressed. + /// Used by W2, WA and WWP. S. https://worms2d.info/Image_file. + /// + public class Image { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private const int _signature = 0x1A474D49; // "IMG", 0x1A + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class, loading the data from the given + /// . + /// + /// The to load the data from. + public Image(Stream stream) + { + Load(stream); + } + + /// + /// Initializes a new instance of the class, loading the data from the given file. + /// + /// The name of the file to load the data from. + public Image(string fileName) + { + Load(fileName); + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + /// + /// Gets an optional description of the image contents. + /// + public string Description { get; private set; } + + /// + /// Gets the number of bits required to describe a color per pixel. + /// + public int BitsPerPixel { get; private set; } + + /// + /// Gets the color palette of the image. The first color must always be black. + /// + public Color[] Palette { get; private set; } + + /// + /// Gets the width of the image in pixels. + /// + public int Width { get; private set; } + + /// + /// Gets the height of the image in pixels. + /// + public int Height { get; private set; } + + /// + /// Gets the data of the image pixels. + /// + public byte[] Data { get; private set; } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Loads the data from the given . + /// + /// The to load the data from. + public void Load(Stream stream) + { + using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII)) + { + // Read the header. + int signature = reader.ReadInt32(); + if (signature != _signature) + { + throw new InvalidDataException("Invalid IMG file signature."); + } + int fileSize = reader.ReadInt32(); + + // Read an optional string describing the image contents or the bits per pixel. + BitsPerPixel = reader.ReadByte(); + if (BitsPerPixel == 0) + { + Description = String.Empty; + BitsPerPixel = reader.ReadByte(); + } + else if (BitsPerPixel > 32) + { + Description = (char)BitsPerPixel + reader.ReadString(BinaryStringFormat.ZeroTerminated); + BitsPerPixel = reader.ReadByte(); + } + + // Read image flags describing the format and availability of the following contents. + Flags flags = (Flags)reader.ReadByte(); + + // Read the image palette if available. The first color of the palette is implicitly black. + if (flags.HasFlag(Flags.Palettized)) + { + int colorCount = reader.ReadInt16(); + Palette = new Color[colorCount + 1]; + Palette[0] = Color.Black; + for (int i = 1; i <= colorCount; i++) + { + Palette[i] = new Color(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); + } + } + + Width = reader.ReadInt16(); + Height = reader.ReadInt16(); + + // Read the image data, which might be compressed. + byte[] data = new byte[Width * Height * (BitsPerPixel / 8)]; + if (flags.HasFlag(Flags.Compressed)) + { + Team17Compression.Decompress(reader, ref data); + } + else + { + data = reader.ReadBytes(data.Length); + } + Data = data; + } + } + + /// + /// Loads the data from the given file. + /// + /// The name of the file to load the data from. + public void Load(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + Load(stream); + } + } + + // ---- ENUMERATIONS ------------------------------------------------------------------------------------------- + + [Flags] + private enum Flags + { + Compressed = 1 << 6, + Palettized = 1 << 7 + } } } diff --git a/src/Syroot.Worms/Syroot.Worms.csproj b/src/Syroot.Worms/Syroot.Worms.csproj index a5ed3fc..8d2376b 100644 --- a/src/Syroot.Worms/Syroot.Worms.csproj +++ b/src/Syroot.Worms/Syroot.Worms.csproj @@ -1,21 +1,35 @@  - netcoreapp1.1 - - - - bin\Release\netcoreapp1.1\Syroot.Worms.xml + .NET library to load and modify file formats of Team17 Worms games. + MIT + Syroot.Worms + Worms + Syroot + 0.1.0 + + Syroot.Worms + worms;team17 + Initial alpha release. + https://raw.githubusercontent.com/Syroot/Worms/master/res/Logo.png + https://github.com/Syroot/Worms + https://raw.githubusercontent.com/Syroot/Worms/master/LICENSE + git + https://github.com/Syroot/Worms + + netstandard1.6 + true + + + + + - - - - \ No newline at end of file