Initial commit.

This commit is contained in:
Ray Koopa 2017-04-17 22:01:27 +02:00
parent 0a457a47d2
commit 9fe108f7ca
19 changed files with 493 additions and 1 deletions

View File

@ -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.

View File

@ -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");
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>
</Project>

28
src/Syroot.Worms.sln Normal file
View File

@ -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

View File

@ -0,0 +1,34 @@
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents extension methods for <see cref="byte[]"/> instances.
/// </summary>
internal static class ByteArrayExtensions
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Returns <c>true</c> if the current <see cref="byte[]"/> instance holds the same values as the given one.
/// </summary>
/// <param name="self">The extended <see cref="byte[]"/> instance.</param>
/// <param name="other">The <see cref="byte[]"/> instance to compare with.</param>
/// <returns><c>true</c> if both instances hold the same values, otherwise <c>false</c>.</returns>
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;
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.IO;
namespace Syroot.Worms.Gen2
{
/// <summary>
/// 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.
/// </summary>
public class Archive : Dictionary<string, byte[]>
{
// ---- 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 ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Archive"/> class, loading the data from the given
/// <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Archive(Stream stream)
{
Load(stream);
}
/// <summary>
/// Initializes a new instance of the <see cref="Archive"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Archive(string fileName)
{
Load(fileName);
}
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
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);
}
}
}
}
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public void Load(string fileName)
{
using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Load(stream);
}
}
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data in.</param>
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<HashTableEntry>[] hashTable = new List<HashTableEntry>[_hashSize];
foreach (KeyValuePair<string, byte[]> 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<HashTableEntry>();
}
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<HashTableEntry> 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);
}
}
/// <summary>
/// Saves the data in the file with the given <paramref name="fileName"/>.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
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;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Armageddon
{
class GameScheme
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Armageddon
{
class Mission
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Armageddon
{
class MonoMap
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Armageddon
{
class Replay
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Armageddon
{
class TeamContainer
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2
{
class Image
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2
{
class Palette
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Worms2
{
class Mission
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Worms2
{
class MonoMap
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Worms2
{
class OptionsScheme
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Worms2
{
class TeamContainer
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Syroot.Worms.Gen2.Worms2
{
class WeaponsScheme
{
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp1.1\Syroot.Worms.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Folder Include="Gen1\" />
<Folder Include="Gen2\Armageddon\ProjectX\" />
<Folder Include="Gen3\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Syroot.IO.BinaryData" Version="1.1.0" />
</ItemGroup>
</Project>