From 9fe108f7caccfe82bfde3581e4bfca055fd49225 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Mon, 17 Apr 2017 22:01:27 +0200 Subject: [PATCH] Initial commit. --- README.md | 39 ++- src/Syroot.Worms.Test/Program.cs | 17 ++ .../Syroot.Worms.Test.csproj | 12 + src/Syroot.Worms.sln | 28 +++ src/Syroot.Worms/Core/ByteArrayExtensions.cs | 34 +++ src/Syroot.Worms/Gen2/Archive.cs | 223 ++++++++++++++++++ .../Gen2/Armageddon/GameScheme.cs | 10 + src/Syroot.Worms/Gen2/Armageddon/Mission.cs | 10 + src/Syroot.Worms/Gen2/Armageddon/MonoMap.cs | 10 + src/Syroot.Worms/Gen2/Armageddon/Replay.cs | 10 + .../Gen2/Armageddon/TeamContainer.cs | 10 + src/Syroot.Worms/Gen2/Image.cs | 10 + src/Syroot.Worms/Gen2/Palette.cs | 10 + src/Syroot.Worms/Gen2/Worms2/Mission.cs | 10 + src/Syroot.Worms/Gen2/Worms2/MonoMap.cs | 10 + src/Syroot.Worms/Gen2/Worms2/OptionsScheme.cs | 10 + src/Syroot.Worms/Gen2/Worms2/TeamContainer.cs | 10 + src/Syroot.Worms/Gen2/Worms2/WeaponsScheme.cs | 10 + src/Syroot.Worms/Syroot.Worms.csproj | 21 ++ 19 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 src/Syroot.Worms.Test/Program.cs create mode 100644 src/Syroot.Worms.Test/Syroot.Worms.Test.csproj create mode 100644 src/Syroot.Worms.sln create mode 100644 src/Syroot.Worms/Core/ByteArrayExtensions.cs create mode 100644 src/Syroot.Worms/Gen2/Archive.cs create mode 100644 src/Syroot.Worms/Gen2/Armageddon/GameScheme.cs create mode 100644 src/Syroot.Worms/Gen2/Armageddon/Mission.cs create mode 100644 src/Syroot.Worms/Gen2/Armageddon/MonoMap.cs create mode 100644 src/Syroot.Worms/Gen2/Armageddon/Replay.cs create mode 100644 src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs create mode 100644 src/Syroot.Worms/Gen2/Image.cs create mode 100644 src/Syroot.Worms/Gen2/Palette.cs create mode 100644 src/Syroot.Worms/Gen2/Worms2/Mission.cs create mode 100644 src/Syroot.Worms/Gen2/Worms2/MonoMap.cs create mode 100644 src/Syroot.Worms/Gen2/Worms2/OptionsScheme.cs create mode 100644 src/Syroot.Worms/Gen2/Worms2/TeamContainer.cs create mode 100644 src/Syroot.Worms/Gen2/Worms2/WeaponsScheme.cs create mode 100644 src/Syroot.Worms/Syroot.Worms.csproj diff --git a/README.md b/README.md index 037e31e..e72c44b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ # Worms -.NET library to load and modify file formats of Team17 Worms games. +The goal of this .NET library is to provide managed language access to game specific file formats of the Worms game series by Team17. + +Right now, the following file formats are supported: + +## First Generation (2D) +Support for first generation 2D games is planned at a later stage. + +| Description | Extension | Games | Load | Save | +|----------------|-----------|:-------:|:----:|:----:| +| Color Map | WRM | W1 | No | No | +| DIY Terrain | DIY | DC | No | No | +| Monochrome Map | GFT | DC | No | No | +| Mountain Set | MNT | DC | No | No | +| Option Scheme | OPT | DC | No | No | +| Team Container | - | DC | No | No | + +## Second Generation (2D) +Formats of the second generation 2D games are mostly focused right now. + +| Description | Extension | Games | Load | Save | +|-------------------|:---------:|:-----------:|:----:|:----:| +| Archive | DIR | W2, WA, WWP | Yes | Yes | +| Image | IMG | W2, WA, WWP | No | No | +| Mission | DAT | W2 | No | No | +| Mission | WAM | WA, WWP | No | No | +| Monochrome Map | LEV | W2 | No | No | +| Monochrome Map | LEV, BIT | WA, WWP | No | No | +| Option Scheme | OPT | W2 | No | No | +| Palette | PAL | W2, WA, WWP | No | No | +| Project X Library | PXL | WA+PX | No | No | +| Project X Scheme | PXS | WA+PX | No | No | +| Replay | WAGAME | WA | No | No | +| Team Container | ST1 | W2 | No | No | +| Team Container | WGT | WA, WWP | No | No | +| Weapon Scheme | WEP | W2 | No | No | + +## Third Generation (3D) +Third generation file formats used by the 3D games have not yet been looked into. \ No newline at end of file diff --git a/src/Syroot.Worms.Test/Program.cs b/src/Syroot.Worms.Test/Program.cs new file mode 100644 index 0000000..d529c54 --- /dev/null +++ b/src/Syroot.Worms.Test/Program.cs @@ -0,0 +1,17 @@ +using System; +using Syroot.Worms.Gen2; + +namespace Syroot.Worms.Test +{ + internal class Program + { + // ---- 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"); + } + } +} \ No newline at end of file diff --git a/src/Syroot.Worms.Test/Syroot.Worms.Test.csproj b/src/Syroot.Worms.Test/Syroot.Worms.Test.csproj new file mode 100644 index 0000000..2ab1ed5 --- /dev/null +++ b/src/Syroot.Worms.Test/Syroot.Worms.Test.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + \ No newline at end of file diff --git a/src/Syroot.Worms.sln b/src/Syroot.Worms.sln new file mode 100644 index 0000000..b9a47e3 --- /dev/null +++ b/src/Syroot.Worms.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms", "Syroot.Worms\Syroot.Worms.csproj", "{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syroot.Worms.Test", "Syroot.Worms.Test\Syroot.Worms.Test.csproj", "{9F7486C2-C30E-4457-B528-F4467486E6D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.Build.0 = Release|Any CPU + {9F7486C2-C30E-4457-B528-F4467486E6D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F7486C2-C30E-4457-B528-F4467486E6D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F7486C2-C30E-4457-B528-F4467486E6D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F7486C2-C30E-4457-B528-F4467486E6D8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Syroot.Worms/Core/ByteArrayExtensions.cs b/src/Syroot.Worms/Core/ByteArrayExtensions.cs new file mode 100644 index 0000000..6b6c2b5 --- /dev/null +++ b/src/Syroot.Worms/Core/ByteArrayExtensions.cs @@ -0,0 +1,34 @@ +namespace Syroot.Worms.Core +{ + /// + /// Represents extension methods for instances. + /// + internal static class ByteArrayExtensions + { + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Returns true if the current instance holds the same values as the given one. + /// + /// The extended instance. + /// The instance to compare with. + /// true if both instances hold the same values, otherwise false. + internal static bool Compare(this byte[] self, byte[] other) + { + if (self.Length != other.Length) + { + return false; + } + + for (int i = 0; i < self.Length; i++) + { + if (self[i] != other[i]) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Syroot.Worms/Gen2/Archive.cs b/src/Syroot.Worms/Gen2/Archive.cs new file mode 100644 index 0000000..2bfaf7c --- /dev/null +++ b/src/Syroot.Worms/Gen2/Archive.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Syroot.IO; + +namespace Syroot.Worms.Gen2 +{ + /// + /// Represents a directory of files stored in a DIR file, mostly used to store graphics files. Due to the nowadays + /// small size of typical directories, the entries and data are loaded directly into a dictionary to profit from + /// optimal performance when accessing and manipulating the directory. + /// Used by W2, WA and WWP. S. https://worms2d.info/Graphics_directory. + /// + public class Archive : Dictionary + { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private static readonly int _signature = 0x1A524944; + private static readonly int _tocSignature = 0x0000000A; + + private const int _hashBits = 10; + private const int _hashSize = 1 << _hashBits; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class, loading the data from the given + /// . + /// + /// The to load the data from. + public Archive(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 Archive(string fileName) + { + Load(fileName); + } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Loads the data from the given . + /// + /// The to load the data from. + public void Load(Stream stream) + { + if (!stream.CanSeek) + { + throw new ArgumentException("Stream requires to be seekable.", nameof(stream)); + } + + Clear(); + using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII)) + { + // Read the header. + int signature = reader.ReadInt32(); + if (signature != _signature) + { + throw new InvalidDataException("Invalid file signature."); + } + int fileSize = reader.ReadInt32(); + int tocOffset = reader.ReadInt32(); + + // Read the table of contents. + reader.Position = tocOffset; + int tocSignature = reader.ReadInt32(); + if (tocSignature != _tocSignature) + { + throw new InvalidDataException("Invalid table of contents signature."); + } + // Generate a data dictionary out of the hash table and file entries. + int[] hashTable = reader.ReadInt32s(_hashSize); + foreach (int entryOffset in hashTable) + { + // If the hash is not 0, it points to a list of files which have a hash being the hash table index. + if (entryOffset > 0) + { + int nextEntryOffset = entryOffset; + do + { + reader.Position = tocOffset + nextEntryOffset; + nextEntryOffset = reader.ReadInt32(); + int offset = reader.ReadInt32(); + int length = reader.ReadInt32(); + string name = reader.ReadString(BinaryStringFormat.ZeroTerminated); + using (reader.TemporarySeek(offset, SeekOrigin.Begin)) + { + Add(name, reader.ReadBytes(length)); + } + } while (nextEntryOffset != 0); + } + } + } + } + + /// + /// 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); + } + } + + /// + /// Saves the data into the given . + /// + /// The to save the data in. + public void Save(Stream stream) + { + using (BinaryDataWriter writer = new BinaryDataWriter(stream)) + { + // Write the header. + writer.Write(_signature); + Offset fileSizeOffset = writer.ReserveOffset(); + Offset tocOffset = writer.ReserveOffset(); + + // Write the data and build the hash table and file entries. + List[] hashTable = new List[_hashSize]; + foreach (KeyValuePair item in this) + { + HashTableEntry entry = new HashTableEntry() + { + Name = item.Key, + Offset = (int)writer.Position, + Length = item.Value.Length + }; + writer.Write(item.Value); + + int hash = CalculateHash(item.Key); + if (hashTable[hash] == null) + { + hashTable[hash] = new List(); + } + hashTable[hash].Add(entry); + } + + // Write the hash table and file entries. + int tocStart = (int)writer.Position; + int fileEntryOffset = sizeof(int) + _hashSize * sizeof(int); + tocOffset.Satisfy(tocStart); + writer.Write(_tocSignature); + for (int i = 0; i < _hashSize; i++) + { + List entries = hashTable[i]; + if (entries == null) + { + writer.Write(0); + } + else + { + // Write the entries resolving to the current hash. + writer.Write(fileEntryOffset); + using (writer.TemporarySeek(tocStart + fileEntryOffset, SeekOrigin.Begin)) + { + for (int j = 0; j < entries.Count; j++) + { + HashTableEntry entry = entries[j]; + Offset nextEntryOffset = writer.ReserveOffset(); + writer.Write(entry.Offset); + writer.Write(entry.Length); + writer.Write(entry.Name, BinaryStringFormat.ZeroTerminated); + writer.Align(4); + if (j < entries.Count - 1) + { + nextEntryOffset.Satisfy((int)writer.Position - tocStart); + } + } + fileEntryOffset = (int)writer.Position - tocStart; + } + } + } + + fileSizeOffset.Satisfy(tocStart + fileEntryOffset - 1); + } + } + + /// + /// Saves the data in the file with the given . + /// + /// The name of the file to save the data in. + public void Save(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Save(stream); + } + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private int CalculateHash(string name) + { + int hash = 0; + for (int i = 0; i < name.Length; i++) + { + hash = ((hash << 1) % _hashSize) | (hash >> (_hashBits - 1) & 1); + hash += name[i]; + hash %= _hashSize; + } + return hash; + } + + // ---- STRUCTURES --------------------------------------------------------------------------------------------- + + private struct HashTableEntry + { + internal string Name; + internal int Offset; + internal int Length; + } + } +} diff --git a/src/Syroot.Worms/Gen2/Armageddon/GameScheme.cs b/src/Syroot.Worms/Gen2/Armageddon/GameScheme.cs new file mode 100644 index 0000000..f71d920 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Armageddon/GameScheme.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Armageddon +{ + class GameScheme + { + } +} diff --git a/src/Syroot.Worms/Gen2/Armageddon/Mission.cs b/src/Syroot.Worms/Gen2/Armageddon/Mission.cs new file mode 100644 index 0000000..05646fd --- /dev/null +++ b/src/Syroot.Worms/Gen2/Armageddon/Mission.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Armageddon +{ + class Mission + { + } +} diff --git a/src/Syroot.Worms/Gen2/Armageddon/MonoMap.cs b/src/Syroot.Worms/Gen2/Armageddon/MonoMap.cs new file mode 100644 index 0000000..417c490 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Armageddon/MonoMap.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Armageddon +{ + class MonoMap + { + } +} diff --git a/src/Syroot.Worms/Gen2/Armageddon/Replay.cs b/src/Syroot.Worms/Gen2/Armageddon/Replay.cs new file mode 100644 index 0000000..4b147d8 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Armageddon/Replay.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Armageddon +{ + class Replay + { + } +} diff --git a/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs b/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs new file mode 100644 index 0000000..ff1f463 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Armageddon +{ + class TeamContainer + { + } +} diff --git a/src/Syroot.Worms/Gen2/Image.cs b/src/Syroot.Worms/Gen2/Image.cs new file mode 100644 index 0000000..72715ea --- /dev/null +++ b/src/Syroot.Worms/Gen2/Image.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2 +{ + class Image + { + } +} diff --git a/src/Syroot.Worms/Gen2/Palette.cs b/src/Syroot.Worms/Gen2/Palette.cs new file mode 100644 index 0000000..88aa46f --- /dev/null +++ b/src/Syroot.Worms/Gen2/Palette.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2 +{ + class Palette + { + } +} diff --git a/src/Syroot.Worms/Gen2/Worms2/Mission.cs b/src/Syroot.Worms/Gen2/Worms2/Mission.cs new file mode 100644 index 0000000..5edf405 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Worms2/Mission.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Worms2 +{ + class Mission + { + } +} diff --git a/src/Syroot.Worms/Gen2/Worms2/MonoMap.cs b/src/Syroot.Worms/Gen2/Worms2/MonoMap.cs new file mode 100644 index 0000000..c0679a5 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Worms2/MonoMap.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Worms2 +{ + class MonoMap + { + } +} diff --git a/src/Syroot.Worms/Gen2/Worms2/OptionsScheme.cs b/src/Syroot.Worms/Gen2/Worms2/OptionsScheme.cs new file mode 100644 index 0000000..b5254c8 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Worms2/OptionsScheme.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Worms2 +{ + class OptionsScheme + { + } +} diff --git a/src/Syroot.Worms/Gen2/Worms2/TeamContainer.cs b/src/Syroot.Worms/Gen2/Worms2/TeamContainer.cs new file mode 100644 index 0000000..34410df --- /dev/null +++ b/src/Syroot.Worms/Gen2/Worms2/TeamContainer.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Worms2 +{ + class TeamContainer + { + } +} diff --git a/src/Syroot.Worms/Gen2/Worms2/WeaponsScheme.cs b/src/Syroot.Worms/Gen2/Worms2/WeaponsScheme.cs new file mode 100644 index 0000000..9ed2258 --- /dev/null +++ b/src/Syroot.Worms/Gen2/Worms2/WeaponsScheme.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Syroot.Worms.Gen2.Worms2 +{ + class WeaponsScheme + { + } +} diff --git a/src/Syroot.Worms/Syroot.Worms.csproj b/src/Syroot.Worms/Syroot.Worms.csproj new file mode 100644 index 0000000..a5ed3fc --- /dev/null +++ b/src/Syroot.Worms/Syroot.Worms.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp1.1 + + + + bin\Release\netcoreapp1.1\Syroot.Worms.xml + + + + + + + + + + + + + \ No newline at end of file