Overhaul implementations and documentation.

This commit is contained in:
Ray Koopa 2020-07-01 02:09:36 +02:00
parent 8b92e6a8be
commit 5321dfb49a
777 changed files with 7857 additions and 5718 deletions

7
.gitattributes vendored
View File

@ -1 +1,8 @@
*.dat filter=lfs diff=lfs merge=lfs -text
*.img filter=lfs diff=lfs merge=lfs -text
*.pxl filter=lfs diff=lfs merge=lfs -text
*.pxs filter=lfs diff=lfs merge=lfs -text
*.wgt filter=lfs diff=lfs merge=lfs -text
*.wsc filter=lfs diff=lfs merge=lfs -text
*.wwp filter=lfs diff=lfs merge=lfs -text
src/test/Syroot.Worms.Armageddon.Test/Files/Schemes/WA/Wsdb/dump.csv filter=lfs diff=lfs merge=lfs -text

8
src/010editor/Common.bt Normal file
View File

@ -0,0 +1,8 @@
void FAlign(byte alignment)
{
local int bytesToSkip <hidden=true> = (-FTell() % alignment + alignment) % alignment;
while (bytesToSkip--)
{
byte padding <fgcolor=0x808080, hidden=true>;
}
}

18
src/010editor/IMG.bt Normal file
View File

@ -0,0 +1,18 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: IMG.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon and Worms World Party image files.
// Category: Worms
// File Mask: *.img
// ID Bytes:
// History:
// 0.1.0 2020-06-29 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
Image image <open=true>;

74
src/010editor/Image.bt Normal file
View File

@ -0,0 +1,74 @@
#include "Common.bt"
typedef struct(char description, char align)
{
uint signature <format=hex>;
if (signature != 0x1A474D49) // "IMG\x1A"
{
Warning("Invalid IMG signature.");
Exit(-1);
}
uint fileSize;
if (description)
string description;
ubyte bpp;
enum <ubyte> Flags
{
IMG_COMPRESSED = 1 << 6,
IMG_PALETTIZED = 1 << 7
} flags;
if (flags & IMG_PALETTIZED)
{
ushort colorCount;
struct Color
{
ubyte r;
ubyte g;
ubyte b;
} colors[colorCount];
}
ushort width;
ushort height;
if (align)
FAlign(4);
if (flags & IMG_COMPRESSED)
char data[GetCompressedImgSize()];
else
char data[bpp / 8.0 * width * height];
} Image;
int GetCompressedImgSize()
{
local int pos = FTell();
local int start = pos;
local int cmd;
while ((cmd = ReadByte(pos++)) != -1)
{
// Read a byte.
if ((cmd & 0x80) != 0)
{
local int arg1 = cmd >> 3 & 0b1111; // bits 2-5
local int arg2 = ReadByte(pos++);
if (arg2 == -1)
break;
// Arg2 = bits 6-16
arg2 = ((cmd << 8) | arg2) & 0x7FF;
if (arg1 == 0)
{
// Command: 0x80 0x00
if (arg2 == 0)
break;
if (ReadByte(pos++) == -1)
break;
}
}
}
return pos - start;
}

20
src/010editor/OW_PAL.bt Normal file
View File

@ -0,0 +1,20 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: OW_PAL.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Online Worms PAL color palettes.
// Category: Worms
// File Mask: *.pal
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
LittleEndian();
struct Color
{
ubyte r, g, b;
} colors[256];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: W2_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms 2 land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
int unknown;
Image foregroundImage(true, false);
Image collisionImage(true, false);
Image backgroundImage(true, false);
Image unknownImage(true, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,43 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WA3.0_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon (older than 3.6.28.0) land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, false);
Image collisionImage(false, false);
Image backgroundImage(false, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WA3.6_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms Armageddon (3.6.28.0 and newer) land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int holeCount;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, false);
Image collisionImage(false, false);
Image backgroundImage(false, false);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,44 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WWPA_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms World Party Aqua land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1B444E4C) // "LND\x1B"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int holeCount;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, true);
Image collisionImage(false, true);
Image backgroundImage(false, true);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -0,0 +1,43 @@
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File: WWP_LandDAT.bt
// Authors: Syroot
// Version: 0.1.0
// Purpose: Parse Worms World Party land generator data files.
// Category: Worms
// File Mask: land.dat
// ID Bytes:
// History:
// 0.1.0 2020-06-30 Initial version.
//------------------------------------------------
#include "Image.bt"
LittleEndian();
uint signature <format=hex>;
if (signature != 0x1A444E4C) // "LND\x1A"
{
Warning("Invalid LND signature.");
Exit(-1);
}
int fileSize;
int mapWidth;
int mapHeight;
int hasCavernBorder;
int waterHeight;
int numObjectLocations;
struct ObjectLocation
{
int x;
int y;
} objectLocations[numObjectLocations];
Image foregroundImage(false, true);
Image collisionImage(false, true);
Image backgroundImage(false, true);
ubyte landTexPathLength;
char landTexPath[landTexPathLength];
ubyte waterDirPathLength;
char waterDirPath[waterDirPathLength];

View File

@ -44,8 +44,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = " Solution Items", " Solution Items", "{8A49F314-1011-4819-A8F6-0EDDA94A9C3D}"
ProjectSection(SolutionItems) = preProject
build.xml = build.xml
test.xml = test.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.SchemeEditor", "tool\Syroot.Worms.SchemeEditor\Syroot.Worms.SchemeEditor.csproj", "{4ACE4589-4478-4B4D-8179-87A491B79DAE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -116,6 +119,10 @@ Global
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.Build.0 = Release|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4ACE4589-4478-4B4D-8179-87A491B79DAE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -137,6 +144,7 @@ Global
{392E4CA2-61D9-4BE1-B065-8801A9F102B8} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{1FAB6B9F-2585-46DC-81C0-579DC808C389} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{4ACE4589-4478-4B4D-8179-87A491B79DAE} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}

View File

@ -18,7 +18,8 @@
<!-- Compilation -->
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>preview</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>

View File

@ -8,7 +8,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
public object? Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
ByteConverter byteConverter)
{
ExplosionAction explosionAction = instance switch
@ -18,12 +18,13 @@ namespace Syroot.Worms.Armageddon.ProjectX
_ => throw new NotImplementedException(),
};
return explosionAction switch
{
{
ExplosionAction.None => null,
ExplosionAction.Bounce => stream.ReadObject<BounceAction>(),
ExplosionAction.Dig => stream.ReadObject<DigAction>(),
ExplosionAction.Home => stream.ReadObject<HomeAction>(),
ExplosionAction.Roam => stream.ReadObject<RoamAction>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -1,250 +1,250 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
/// <summary>
/// Represents a library stored in a PXL file which contains reusable weapons, files and scripts.
/// Used by WA PX. S. https://worms2d.info/Project_X/Library_file.
/// </summary>
public class Library : List<LibraryItem>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1BCD0102;
private const byte _version = 0x00;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> class.
/// </summary>
public Library() { }
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> 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 Library(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Library(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public byte Version { get; set; }
// ---- OPERATORS ----------------------------------------------------------------------------------------------
/// <summary>
/// Gets the library entries matching the given key.
/// </summary>
/// <param name="key">The key of the entries to match.</param>
/// <returns>All matching entries.</returns>
public IEnumerable<LibraryItem> this[string key] => this.Where(x => x.Key == key);
// ---- 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)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid PXL file signature.");
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
/// <summary>
/// Represents a library stored in a PXL file which contains reusable weapons, files and scripts.
/// Used by WA PX. S. https://worms2d.info/Project_X/Library_file.
/// </summary>
public class Library : List<LibraryItem>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1BCD0102;
private const byte _version = 0x00;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> class.
/// </summary>
public Library() { }
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> 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 Library(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="Scheme"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Library(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public byte Version { get; set; }
// ---- OPERATORS ----------------------------------------------------------------------------------------------
/// <summary>
/// Gets the library entries matching the given key.
/// </summary>
/// <param name="key">The key of the entries to match.</param>
/// <returns>All matching entries.</returns>
public IEnumerable<LibraryItem> this[string key] => this.Where(x => x.Key == key);
// ---- 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)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid PXL file signature.");
Version = reader.Read1Byte();
if (Version != _version)
throw new InvalidDataException("Invalid PXL file version.");
// Read the items.
Clear();
int itemCount = reader.ReadInt32();
for (int i = 0; i < itemCount; i++)
{
LibraryItemType type = reader.ReadEnum<LibraryItemType>(true);
string name = reader.ReadString(StringCoding.Int32CharCount);
switch (type)
{
case LibraryItemType.File:
Add(new LibraryItem(name, reader.ReadBytes(reader.ReadInt32())));
break;
case LibraryItemType.Script:
Add(new LibraryItem(name, reader.ReadString(StringCoding.Int32CharCount)));
break;
case LibraryItemType.Weapon:
Add(new LibraryItem(name, reader.Load<Weapon>()));
break;
}
}
}
/// <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 to.</param>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
writer.Write(Version);
// Write the items.
writer.Write(Count);
foreach (LibraryItem item in this)
{
writer.WriteEnum(item.Type, true);
writer.Write(item.Key, StringCoding.Int32CharCount);
switch (item.Type)
{
case LibraryItemType.File:
byte[] value = (byte[])item.Value;
writer.Write(value.Length);
writer.Write(value);
break;
case LibraryItemType.Script:
writer.Write((string)item.Value, StringCoding.Int32CharCount);
break;
case LibraryItemType.Weapon:
writer.Save((Weapon)item.Value);
break;
}
}
}
/// <summary>
/// Saves the data in the given file.
/// </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);
}
/// <summary>
/// Gets all attached files.
/// </summary>
/// <returns>The enumeration of attached files.</returns>
public IEnumerable<byte[]> GetFiles()
=> this.Where(x => x.Type == LibraryItemType.File).Select(x => (byte[])x.Value);
/// <summary>
/// Gets all attached scripts.
/// </summary>
/// <returns>The enumeration of attached scripts.</returns>
public IEnumerable<string> GetScripts()
=> this.Where(x => x.Type == LibraryItemType.Script).Select(x => (string)x.Value);
/// <summary>
/// Gets all attached weapons.
/// </summary>
/// <returns>The enumeration of attached weapons.</returns>
public IEnumerable<Weapon> GetWeapons()
=> this.Where(x => x.Type == LibraryItemType.Weapon).Select(x => (Weapon)x.Value);
}
/// <summary>
/// Represents an entry in a Project X library.
/// </summary>
[DebuggerDisplay("LibraryItem Key={Key} Type={Type}")]
public class LibraryItem
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private object _value;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LibraryItem"/> class with the given <paramref name="key"/> and
/// <paramref name="value"/>.
/// </summary>
/// <param name="key">The key under which the item will be stored.</param>
/// <param name="value">The value which will be stored under the key. The type is inferred from this.</param>
public LibraryItem(string key, object value)
{
Key = key;
Value = value;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name under which this item is stored.
/// </summary>
public string Key { get; set; }
/// <summary>
/// Gets the type of the item.
/// </summary>
public LibraryItemType Type { get; private set; }
/// <summary>
/// Gets or sets the data of the item.
/// </summary>
public object Value
{
get => _value;
set
{
// Validate the type.
if (value.GetType() == typeof(byte[]))
Type = LibraryItemType.File;
else if (value.GetType() == typeof(string))
Type = LibraryItemType.Script;
else if (value.GetType() == typeof(Weapon))
Type = LibraryItemType.Weapon;
else
throw new ArgumentException("Invalid LibraryItemType.", nameof(value));
_value = value;
}
}
}
/// <summary>
/// Represents the possible type of a library entry.
/// </summary>
public enum LibraryItemType : byte
{
/// <summary>
/// The entry is a raw file in form of a byte array.
/// </summary>
File = 2,
/// <summary>
/// The entry is a script in form of a string.
/// </summary>
Script = 4,
/// <summary>
/// The entry is a weapon in form of a <see cref="Weapon"/> instance.
/// </summary>
Weapon = 8
}
}
throw new InvalidDataException("Invalid PXL file version.");
// Read the items.
Clear();
int itemCount = reader.ReadInt32();
for (int i = 0; i < itemCount; i++)
{
LibraryItemType type = reader.ReadEnum<LibraryItemType>(true);
string name = reader.ReadString(StringCoding.Int32CharCount);
switch (type)
{
case LibraryItemType.File:
Add(new LibraryItem(name, reader.ReadBytes(reader.ReadInt32())));
break;
case LibraryItemType.Script:
Add(new LibraryItem(name, reader.ReadString(StringCoding.Int32CharCount)));
break;
case LibraryItemType.Weapon:
Add(new LibraryItem(name, reader.Load<Weapon>()));
break;
}
}
}
/// <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 to.</param>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
writer.Write(Version);
// Write the items.
writer.Write(Count);
foreach (LibraryItem item in this)
{
writer.WriteEnum(item.Type, true);
writer.Write(item.Key, StringCoding.Int32CharCount);
switch (item.Type)
{
case LibraryItemType.File:
byte[] value = (byte[])item.Value;
writer.Write(value.Length);
writer.Write(value);
break;
case LibraryItemType.Script:
writer.Write((string)item.Value, StringCoding.Int32CharCount);
break;
case LibraryItemType.Weapon:
writer.Save((Weapon)item.Value);
break;
}
}
}
/// <summary>
/// Saves the data in the given file.
/// </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);
}
/// <summary>
/// Gets all attached files.
/// </summary>
/// <returns>The enumeration of attached files.</returns>
public IEnumerable<byte[]> GetFiles()
=> this.Where(x => x.Type == LibraryItemType.File).Select(x => (byte[])x.Value);
/// <summary>
/// Gets all attached scripts.
/// </summary>
/// <returns>The enumeration of attached scripts.</returns>
public IEnumerable<string> GetScripts()
=> this.Where(x => x.Type == LibraryItemType.Script).Select(x => (string)x.Value);
/// <summary>
/// Gets all attached weapons.
/// </summary>
/// <returns>The enumeration of attached weapons.</returns>
public IEnumerable<Weapon> GetWeapons()
=> this.Where(x => x.Type == LibraryItemType.Weapon).Select(x => (Weapon)x.Value);
}
/// <summary>
/// Represents an entry in a Project X library.
/// </summary>
[DebuggerDisplay("LibraryItem Key={Key} Type={Type}")]
public class LibraryItem
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private object _value = null!;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LibraryItem"/> class with the given <paramref name="key"/> and
/// <paramref name="value"/>.
/// </summary>
/// <param name="key">The key under which the item will be stored.</param>
/// <param name="value">The value which will be stored under the key. The type is inferred from this.</param>
public LibraryItem(string key, object value)
{
Key = key;
Value = value;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name under which this item is stored.
/// </summary>
public string Key { get; set; } = String.Empty;
/// <summary>
/// Gets the type of the item.
/// </summary>
public LibraryItemType Type { get; private set; }
/// <summary>
/// Gets or sets the data of the item.
/// </summary>
public object Value
{
get => _value;
set
{
// Validate the type.
if (value.GetType() == typeof(byte[]))
Type = LibraryItemType.File;
else if (value.GetType() == typeof(string))
Type = LibraryItemType.Script;
else if (value.GetType() == typeof(Weapon))
Type = LibraryItemType.Weapon;
else
throw new ArgumentException("Invalid LibraryItemType.", nameof(value));
_value = value;
}
}
}
/// <summary>
/// Represents the possible type of a library entry.
/// </summary>
public enum LibraryItemType : byte
{
/// <summary>
/// The entry is a raw file in form of a byte array.
/// </summary>
File = 2,
/// <summary>
/// The entry is a script in form of a string.
/// </summary>
Script = 4,
/// <summary>
/// The entry is a weapon in form of a <see cref="Weapon"/> instance.
/// </summary>
Weapon = 8
}
}

View File

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
@ -44,17 +45,17 @@ namespace Syroot.Worms.Armageddon.ProjectX
public SchemeFlags Flags { get; set; }
public List<Weapon[]> WeaponTables { get; set; }
public IList<Weapon[]> WeaponTables { get; set; } = new List<Weapon[]>();
public Dictionary<string, byte[]> Files { get; set; }
public IDictionary<string, byte[]> Files { get; set; } = new Dictionary<string, byte[]>();
public Dictionary<string, string> Scripts { get; set; }
public IDictionary<string, string> Scripts { get; set; } = new Dictionary<string, string>();
public List<string> Libraries { get; set; }
public IList<string> Libraries { get; set; } = new List<string>();
public string GameSchemeName { get; set; }
public string GameSchemeName { get; set; } = String.Empty;
public Armageddon.Scheme GameScheme { get; set; }
public Armageddon.Scheme? GameScheme { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -74,8 +75,13 @@ namespace Syroot.Worms.Armageddon.ProjectX
// Read the weapon tables.
int weaponTableCount = reader.ReadInt32();
WeaponTables = new List<Weapon[]>(weaponTableCount);
for (int i = 0; i < weaponTableCount; i++)
WeaponTables.Add(reader.Load<Weapon>(_weaponsPerTable));
for (int i = 0; i < weaponTableCount; i++)
{
Weapon[] weaponTable = new Weapon[_weaponsPerTable];
for (int j = 0; j < _weaponsPerTable; j++)
weaponTable[j] = reader.Load<Weapon>();
WeaponTables.Add(weaponTable);
}
// Read a placeholder array.
reader.Seek(sizeof(int));
@ -131,8 +137,9 @@ namespace Syroot.Worms.Armageddon.ProjectX
// Write the weapon tables.
writer.Write(WeaponTables.Count);
foreach (Weapon[] weaponTable in WeaponTables)
writer.Save(weaponTable);
foreach (Weapon[] weaponTable in WeaponTables)
for (int i = 0; i < _weaponsPerTable; i++)
writer.Save(weaponTable[i]);
// Write a placeholder array.
writer.Write(0);

View File

@ -19,7 +19,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
public WeaponAirstrikeSubstyle AirstrikeSubstyle;
[BinaryMember(Converter = typeof(AirstrikeSubstyleConverter))]
public IStyle Style;
public IStyle? Style;
}
public enum WeaponAirstrikeSubstyle : int

View File

@ -20,7 +20,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
WeaponAirstrikeSubstyle.Launcher => stream.ReadObject<LauncherStyle>(),
WeaponAirstrikeSubstyle.Mines => stream.ReadObject<MineStyle>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -47,13 +47,13 @@ namespace Syroot.Worms.Armageddon.ProjectX
public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))]
public IAction Action { get; set; }
public IAction? Action { get; set; }
[BinaryMember(OffsetOrigin = OffsetOrigin.Begin, Offset = 180)]
public ExplosionTarget ExplosionTarget { get; set; }
public ExplosionTarget ExplosionTarget { get; set; }
[BinaryMember(Offset = sizeof(int), Converter = typeof(TargetConverter))]
public ITarget Target { get; set; }
public ITarget? Target { get; set; }
}
public enum ExplosionAction : int

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Armageddon.ProjectX</AssemblyName>
<Description>.NET library for loading and modifying files of Worms Armageddon ProjectX.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);project x;worms armageddon</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />

View File

@ -50,6 +50,6 @@ namespace Syroot.Worms.Armageddon.ProjectX
public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))]
public IAction Action { get; set; }
public IAction? Action { get; set; }
}
}

View File

@ -8,7 +8,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
public object? Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
ByteConverter byteConverter)
{
ExplosionTarget explosionTarget = instance switch
@ -17,10 +17,11 @@ namespace Syroot.Worms.Armageddon.ProjectX
_ => throw new NotImplementedException(),
};
return explosionTarget switch
{
{
ExplosionTarget.None => null,
ExplosionTarget.Clusters => stream.ReadObject<ClusterTarget>(),
ExplosionTarget.Fire => stream.ReadObject<FireTarget>(),
_ => null,
_ => throw new NotImplementedException(),
};
}

View File

@ -1,8 +1,9 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX
{
@ -49,7 +50,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
public WeaponThrowAction ThrowAction { get; set; }
public IStyle Style { get; set; }
public IStyle? Style { get; set; }
public bool AmmunitionOverride { get; set; }
@ -59,15 +60,15 @@ namespace Syroot.Worms.Armageddon.ProjectX
public int WeaponSprite { get; set; }
public string NameLong { get; set; }
public string NameLong { get; set; } = String.Empty;
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
public string GridImageFile { get; set; }
public string GridImageFile { get; set; } = String.Empty;
public string GfxDirectoryFile { get; set; }
public string GfxDirectoryFile { get; set; } = String.Empty;
public string[] SpriteNames { get; set; }
public string[] SpriteNames { get; } = new string[5];
public bool DelayOverride { get; set; }
@ -75,27 +76,27 @@ namespace Syroot.Worms.Armageddon.ProjectX
public bool UseLibrary { get; set; }
public string LibraryName { get; set; }
public string LibraryName { get; set; } = String.Empty;
public string LibraryWeaponName { get; set; }
public string LibraryWeaponName { get; set; } = String.Empty;
public string AimSpriteEven { get; set; }
public string AimSpriteEven { get; set; } = String.Empty;
public string AimSpriteUphill { get; set; }
public string AimSpriteUphill { get; set; } = String.Empty;
public string AimSpriteDownhill { get; set; }
public string AimSpriteDownhill { get; set; } = String.Empty;
public string PickSpriteEven { get; set; }
public string PickSpriteEven { get; set; } = String.Empty;
public string PickSpriteUphill { get; set; }
public string PickSpriteUphill { get; set; } = String.Empty;
public string PickSpriteDownhill { get; set; }
public string PickSpriteDownhill { get; set; } = String.Empty;
public string FireSpriteEven { get; set; }
public string FireSpriteEven { get; set; } = String.Empty;
public string FireSpriteUphill { get; set; }
public string FireSpriteUphill { get; set; } = String.Empty;
public string FireSpriteDownhill { get; set; }
public string FireSpriteDownhill { get; set; } = String.Empty;
public bool AimSpriteOverride { get; set; }
@ -243,8 +244,9 @@ namespace Syroot.Worms.Armageddon.ProjectX
NameLong = reader.ReadString(StringCoding.Int32CharCount);
Name = reader.ReadString(StringCoding.Int32CharCount);
GridImageFile = reader.ReadString(StringCoding.Int32CharCount);
GfxDirectoryFile = reader.ReadString(StringCoding.Int32CharCount);
SpriteNames = reader.ReadStrings(5, StringCoding.Int32CharCount);
GfxDirectoryFile = reader.ReadString(StringCoding.Int32CharCount);
for (int i = 0; i < SpriteNames.Length; i++)
SpriteNames[i] = reader.ReadString(StringCoding.Int32CharCount);
DelayOverride = reader.ReadBoolean(BooleanCoding.Dword);
Delay = reader.ReadInt32();

View File

@ -1,71 +1,175 @@
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents <see cref="MapGeneratorSettings"/> stored in a LEV file.
/// Used by WA and WWP. S. https://worms2d.info/Monochrome_map_(.bit,_.lev).
/// </summary>
public class GeneratedMap : ILoadableFile, ISaveableFile
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class.
/// </summary>
public GeneratedMap() { }
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> 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 GeneratedMap(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public GeneratedMap(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="MapGeneratorSettings"/>.
/// </summary>
public MapGeneratorSettings Settings { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Settings = reader.ReadStruct<MapGeneratorSettings>();
}
/// <inheritdoc/>>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.WriteStruct(Settings);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents <see cref="MapGenSettings"/> stored in a LEV file.
/// Used by WA and WWP. S. https://worms2d.info/Monochrome_map_(.bit,_.lev).
/// </summary>
public class GeneratedMap : ILoadableFile, ISaveableFile
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class.
/// </summary>
public GeneratedMap() { }
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> 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 GeneratedMap(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public GeneratedMap(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="MapGenSettings"/>.
/// </summary>
public MapGenSettings Settings { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Settings = reader.ReadStruct<MapGenSettings>();
}
/// <inheritdoc/>>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.WriteStruct(Settings);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
/// <summary>
/// Represents the required configuration for the land generator to create a map. This structure appears in LEV
/// files (being their only content), in BIT files (being the header) and in PNG maps stored by WA (being the data
/// of the w2lv or waLV chunks).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MapGenSettings
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>Random seed to generate the land shape (no effect in BIT files).</summary>
public int LandSeed;
/// <summary>Random seed to generate object placements.</summary>
public int ObjectSeed;
/// <summary><see langword="true"/> treats the generated map as a cavern, otherwise as an island, depending on
/// <see cref="Style"/>.</summary>
public bool Cavern;
/// <summary>Style of the land generated, dependending on <see cref="Cavern"/>.</summary>
public MapGenStyle Style;
/// <summary><see langword="true"/> to disable an indestructible border being placed around the map.</summary>
public bool NoIndiBorders;
/// <summary>Probability percentage of map objects to appear on the land, from 0-100.</summary>
public int ObjectPercentage;
/// <summary>Probability percentage of bridges to appear on the land, from 0-100.</summary>
public int BridgePercentage;
/// <summary>Height of the water in percent, from 0-99.</summary>
public int WaterLevel;
/// <summary>Version of the structure, determining the available <see cref="SoilTextureIndex"/> values in Worms
/// Armageddon.</summary>
public MapGenVersion Version;
/// <summary>Texture index determining the style of the generated map.</summary>
public MapGenSoil SoilTextureIndex;
/// <summary>Water color used for the map (deprecated, used only in Worms Armageddon 1.0).</summary>
public int WaterColor;
}
/// <summary>
/// Represents the possible land styles.
/// </summary>
public enum MapGenStyle : int
{
/// <summary>A single island or a cavern.</summary>
OneIslandOrCavern,
/// <summary>Two islands or a double-layer cavern.</summary>
TwoIslandsOrCaverns,
/// <summary>One flat island or a cavern open at the bottom.</summary>
OneFlatIslandOrCavernOpenBottom,
/// <summary>Two flat islands or a cavern open at the left or right.</summary>
TwoFlatIslandsOrCavernOpenLeftRight
}
/// <summary>
/// Represents the possible versions which can be stored in the <see cref="MapGenSettings.Version"/> field.
/// </summary>
public enum MapGenVersion : short
{
/// <summary>Game version 3.0. Last soil texture indices are 26 = Tribal, 27 = Tribal, 28 = Urban. Additionally,
/// floor and ceiling trimming is more severe.</summary>
Version_3_0_0_0 = -1,
/// <summary>Game version 3.5. Last soil texture indices are 26 = Tribal, 27 = Urban, 28 = undefined.</summary>
Version_3_5_0_0 = 0,
/// <summary>Game version 3.6.26.4. Last soil texture indices are 26 = Tools, 27 = Tribal, Urban = 28 (same as
/// in WWP).</summary>
Version_3_6_26_4 = 1
}
/// <summary>
/// Represents the possible soil styles. In case of Worms Armageddon, this list matches
/// <see cref="MapGenVersion.Version_3_6_26_4"/>.
/// </summary>
public enum MapGenSoil : short
{
ClassicBeach,
ClassicDesert,
ClassicFarm,
ClassicForest,
ClassicHell,
Art,
Cheese,
Construction,
Desert,
Dungeon,
Easter,
Forest,
Fruit,
Gulf,
Hell,
Hospital,
Jungle,
Manhattan,
Medieval,
Music,
Pirate,
Snow,
Space,
Sports,
Tentacle,
Time,
Tools,
Tribal,
Urban
}
}

View File

@ -1,167 +1,246 @@
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by WA. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> 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 LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets the height of the water in pixels.
/// </summary>
public int WaterHeight { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public int Unknown { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
WaterHeight = reader.ReadInt32();
Unknown = reader.ReadInt32();
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
// Read the image data.
Foreground = reader.Load<Img>();
CollisionMask = reader.Load<Img>();
Background = reader.Load<Img>();
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight);
writer.Write(Unknown);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
// Write the image data.
Foreground.Save(writer.BaseStream);
CollisionMask.Save(writer.BaseStream);
Background.Save(writer.BaseStream);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by WA and WWP. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private bool _alignImgData;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> 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 LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the format version of the file.
/// </summary>
public LandDataVersion Version { get; set; }
/// <summary>
/// Gets or sets a value indicating whether image data is aligned by 4-bytes. This is the case only for WWP.
/// </summary>
public bool AlignImgData
{
get => _alignImgData;
set => _alignImgData = value;
}
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets the height of the water in pixels.
/// </summary>
public int WaterHeight { get; set; }
/// <summary>
/// Gets or sets an unknown value only available in newer W:A versions (apparently since 3.6.28.0).
/// </summary>
public int? Unknown { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; } = new Img();
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; } = new Img();
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; } = new Img();
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
int readLocationCount()
{
int oldLocationCount = reader.ReadInt32();
int oldImgStart = oldLocationCount * Unsafe.SizeOf<Point>();
// Check whether the old IMG start would be invalid.
bool isNew = false;
using (reader.TemporarySeek(oldImgStart))
isNew = reader.EndOfStream || reader.ReadUInt32() != Img.Signature;
if (isNew)
{
Unknown = oldLocationCount;
return reader.ReadInt32();
}
else
{
Unknown = null;
return oldLocationCount;
}
}
Img readImage(ref bool aligned)
{
long imgStart = reader.Position;
Img img = new Img(reader.BaseStream, aligned);
// If reading the next image fails, the previous data was aligned and must be reread.
if (!aligned)
{
if (reader.ReadUInt32() == Img.Signature)
{
reader.Position -= sizeof(uint);
}
else
{
reader.Position = imgStart;
img = new Img(reader.BaseStream, true);
aligned = true;
}
}
return img;
}
// Read the header.
Version = reader.ReadEnum<LandDataVersion>(true);
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
WaterHeight = reader.ReadInt32();
// Read the object locations.
int locationCount = readLocationCount();
ObjectLocations = new List<Point>(locationCount);
for (int i = 0; i < locationCount; i++)
ObjectLocations.Add(reader.ReadStruct<Point>());
// Read the image data, which alignment must be detected by trial-and-error.
_alignImgData = Version == LandDataVersion.WormsWorldPartyAqua;
long foreImgLocation = reader.Position;
Foreground = readImage(ref _alignImgData);
CollisionMask = readImage(ref _alignImgData);
Background = new Img(reader.BaseStream, _alignImgData);
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.WriteEnum(Version, true);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight);
if (Unknown.HasValue)
writer.Write(Unknown.Value);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Count);
for (int i = 0; i < ObjectLocations.Count; i++)
writer.WriteStruct(ObjectLocations[i]);
// Write the image data.
Foreground.Save(writer.BaseStream, _alignImgData);
CollisionMask.Save(writer.BaseStream, _alignImgData);
Background.Save(writer.BaseStream, _alignImgData);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>
/// Represents the version of <see cref="LandData"/> files, encoded as the signature.
/// </summary>
public enum LandDataVersion : uint
{
/// <summary>Found in W2, WA, WWP, and OW.</summary>
Team17 = 0x1A444E4C, // "LND\x1A"
/// <summary>Found only in WWPA.</summary>
WormsWorldPartyAqua = 0x1B444E4C // "LND\x1B"
}
}

View File

@ -1,158 +0,0 @@
using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the required configuration for the land generator to create a map. This structure appears in LEV
/// files (being their only content), in BIT files (being the header) and in PNG maps stored by WA (being the data
/// of the w2lv or waLV chunks).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MapGeneratorSettings
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>
/// The random seed to generate the land shape (no effect in BIT files).
/// </summary>
public int LandSeed;
/// <summary>
/// The random seed to generate object placements.
/// </summary>
public int ObjectSeed;
/// <summary>
/// <c>true</c> treats the generated map as a cavern, otherwise as an island, depending on <see cref="Style"/>.
/// </summary>
[MarshalAs(UnmanagedType.Bool)]
public bool Cavern;
/// <summary>
/// The style of the land generated, dependending on <see cref="Cavern"/>
/// </summary>
public MapGeneratorStyle Style;
/// <summary>
/// <c>true</c> to disable an indestructible border being placed around the map.
/// </summary>
[MarshalAs(UnmanagedType.Bool)]
public bool NoIndestructibleBorders;
/// <summary>
/// The probability percentage of map objects to appear on the land, from 0-100.
/// </summary>
public int ObjectPercentage;
/// <summary>
/// The probability percentage of bridges to appear on the land, from 0-100.
/// </summary>
public int BridgePercentage;
/// <summary>
/// The height of the water in percent, from 0-99.
/// </summary>
public int WaterLevel;
/// <summary>
/// The version of the structure, determining the available <see cref="SoilTextureIndex"/> values in Worms
/// Armageddon.
/// </summary>
public MapGeneratorVersion Version;
/// <summary>
/// Represents the texture index determining the style of the generated map.
/// </summary>
public MapGeneratorSoil SoilTextureIndex;
/// <summary>
/// Represents the water color used for the map (deprecated, used only in Worms Armageddon 1.0).
/// </summary>
public int WaterColor;
}
/// <summary>
/// Represents the possible land styles.
/// </summary>
public enum MapGeneratorStyle : int
{
/// <summary>
/// Represents a single island or a cavern.
/// </summary>
OneIslandOrCavern,
/// <summary>
/// Represents two islands or a double-layer cavern.
/// </summary>
TwoIslandsOrCaverns,
/// <summary>
/// Represents one flat island or a cavern open at the bottom.
/// </summary>
OneFlatIslandOrCavernOpenBottom,
/// <summary>
/// Represents two flat islands or a cavern open at the left or right.
/// </summary>
TwoFlatIslandsOrCavernOpenLeftRight
}
/// <summary>
/// Represents the possible versions which can be stored in the <see cref="MapGeneratorSettings.Version"/> field.
/// </summary>
public enum MapGeneratorVersion : short
{
/// <summary>
/// Game version 3.0. Last soil texture indices are 26 = Tribal, 27 = Tribal, 28 = Urban. Additionally, floor
/// and ceiling trimming is more severe.
/// </summary>
Version_3_0_0_0 = -1,
/// <summary>
/// Game version 3.5. Last soil texture indices are 26 = Tribal, 27 = Urban, 28 = undefined.
/// </summary>
Version_3_5_0_0 = 0,
/// <summary>
/// Game version 3.6.26.4. Last soil texture indices are 26 = Tools, 27 = Tribal, Urban = 28 (same as in WWP).
/// </summary>
Version_3_6_26_4 = 1
}
/// <summary>
/// Represents the possible soil styles. In case of Worms Armageddon, this list matches
/// <see cref="MapGeneratorVersion.Version_3_6_26_4"/>.
/// </summary>
public enum MapGeneratorSoil : short
{
ClassicBeach,
ClassicDesert,
ClassicFarm,
ClassicForest,
ClassicHell,
Art,
Cheese,
Construction,
Desert,
Dungeon,
Easter,
Forest,
Fruit,
Gulf,
Hell,
Hospital,
Jungle,
Manhattan,
Medieval,
Music,
Pirate,
Snow,
Space,
Sports,
Tentacle,
Time,
Tools,
Tribal,
Urban
}
}

View File

@ -145,7 +145,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Represents the stockpiling mode of weapon armory between rounds.
/// </summary>
public enum StockpilingMode : byte
public enum Stockpiling : byte
{
/// <summary>Each round starts with the exact amount of weapons set in the scheme.</summary>
Off,

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
@ -14,30 +15,32 @@ namespace Syroot.Worms.Armageddon
public class WeaponList : IReadOnlyCollection<SchemeWeapon>
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
private readonly SchemeWeapon[] _list = new SchemeWeapon[64];
private readonly SchemeWeapon[] _array = new SchemeWeapon[64];
/// <summary>
/// Gets a reference to the weapon with the given <paramref name="index"/>.
/// </summary>
/// <param name="name">The index of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns>
public ref SchemeWeapon this[int index] => ref _list[index];
public ref SchemeWeapon this[int index] => ref _array[index];
/// <summary>
/// Gets a reference to the weapon with the given <paramref name="name"/>.
/// </summary>
/// <param name="name">The <see cref="Weapon"/> of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns>
public ref SchemeWeapon this[Weapon name] => ref _list[(int)name];
public ref SchemeWeapon this[Weapon name] => ref _array[(int)name];
/// <inheritdoc/>
public int Count => _list.Length;
public int Count => _array.Length;
/// <inheritdoc/>
public IEnumerator<SchemeWeapon> GetEnumerator() => ((IEnumerable<SchemeWeapon>)_list).GetEnumerator();
public IEnumerator<SchemeWeapon> GetEnumerator() => ((IEnumerable<SchemeWeapon>)_array).GetEnumerator();
internal Span<SchemeWeapon> AsSpan(int length) => _array.AsSpan(0, length);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _array.GetEnumerator();
}
}
}

View File

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
@ -38,8 +38,8 @@ namespace Syroot.Worms.Armageddon
private byte _roundTimeMinutes;
private byte _roundTimeSeconds;
private SchemeExtendedOptions _extended = SchemeExtendedOptions.Default;
private ushort _rwVersionOverride;
private ExtendedOptions _extended = ExtendedOptions.Default;
private ushort _rwVersion;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -116,7 +116,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats.
/// </summary>
public byte HotSeatDelay { get; set; }
public byte HotSeatTime { get; set; }
/// <summary>
/// Gets or sets the time in seconds available for a worm to retreat after using a weapon which ends the turn
@ -139,7 +139,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether significant turns will be replayed in offline games.
/// </summary>
public bool AutomaticReplays { get; set; }
public bool Replays { get; set; }
/// <summary>
/// Gets or sets the percentual amount of fall damage applied, relative to normal fall damage being 100%. Valid
@ -155,7 +155,6 @@ namespace Syroot.Worms.Armageddon
{
if (value < 0 || value > 508)
throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508.");
_fallDamage = value >> 2 << 2;
}
}
@ -175,12 +174,12 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating the stockpiling of armory between game rounds.
/// </summary>
public StockpilingMode StockpilingMode { get; set; }
public Stockpiling Stockpiling { get; set; }
/// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary>
public WormSelect WormSelectMode { get; set; }
public WormSelect WormSelect { get; set; }
/// <summary>
/// Gets or sets a value indicating the action triggered when Sudden Death starts.
@ -218,7 +217,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a weapon crate to drop between turns. Negative values might crash
/// the game.
/// </summary>
public sbyte WeaponCrateProbability { get; set; }
public sbyte WeaponCrateProb { get; set; }
/// <summary>
/// Gets or sets a value indicating whether donor cards can spawn upon a worm's death.
@ -229,7 +228,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a health crate to drop between turns. Negative values might crash
/// the game.
/// </summary>
public sbyte HealthCrateProbability { get; set; }
public sbyte HealthCrateProb { get; set; }
/// <summary>
/// Gets or sets the amount of health included in a health crate added to the collecting worm's energy.
@ -240,7 +239,7 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the percentual probability of a utility crate to drop between turns. Negative values might
/// crash the game.
/// </summary>
public sbyte UtilityCrateProbability { get; set; }
public sbyte UtilityCrateProb { get; set; }
/// <summary>
/// Gets or sets the type of objects which can be placed on the map.
@ -273,7 +272,7 @@ namespace Syroot.Worms.Armageddon
}
/// <summary>
/// Gets or sets the number of seconds a mine requires to explode. Can be 1-3 and 5-127 seconds.
/// Gets or sets the number of seconds a mine requires to explode. Can be 0-3 and 5-127 seconds.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127 or 4.</exception>
public byte MineDelay
@ -289,7 +288,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether the mine fuse will be randomly chosen between fractions of 1 to 3
/// seconds. If <c>true</c>, the <see cref="MineDelay"/> setting will be ignored.
/// seconds. If <see langword="true"/>, the <see cref="MineDelay"/> setting will be ignored.
/// </summary>
public bool MineDelayRandom { get; set; }
@ -325,7 +324,7 @@ namespace Syroot.Worms.Armageddon
}
/// <summary>
/// Gets or sets a value indicating whether the turn time is unlimited. If <c>true</c>, the
/// Gets or sets a value indicating whether the turn time is unlimited. If <see langword="true"/>, the
/// <see cref="TurnTime"/> setting will be ignored.
/// </summary>
public bool TurnTimeInfinite { get; set; }
@ -393,40 +392,40 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets a value indicating whether terrain cannot be destroyed by explosions.
/// </summary>
public bool IndestructibleLand { get; set; }
public bool IndiLand { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Grenade weapon is more powerful.
/// </summary>
public bool UpgradedGrenade { get; set; }
public bool UpgradeGrenade { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Shotgun weapon shoots twice for each of the two shots.
/// </summary>
public bool UpgradedShotgun { get; set; }
public bool UpgradeShotgun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether cluster weapon explode into more clusters.
/// </summary>
public bool UpgradedCluster { get; set; }
public bool UpgradeCluster { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Longbow weapon is more powerful.
/// </summary>
public bool UpgradedLongbow { get; set; }
public bool UpgradeLongbow { get; set; }
// ---- Weapons ----
/// <summary>
/// Gets or sets a value indicating whether team weapons will be given to the teams, overriding the default
/// weapon settings for them.
/// </summary>
public bool EnableTeamWeapons { get; set; }
public bool TeamWeapons { get; set; }
/// <summary>
/// Gets or sets a value indicating whether super weapons can be collected from crates.
/// </summary>
public bool EnableSuperWeapons { get; set; }
// ---- Weapons ----
public bool SuperWeapons { get; set; }
/// <summary>
/// Gets the list of <see cref="SchemeWeapon"/> instances, of which each weapon can be accessed by index or
@ -440,7 +439,7 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets <see cref="SchemeVersion.Version3"/> or RubberWorm settings.
/// </summary>
public ref SchemeExtendedOptions Extended => ref _extended;
public ref ExtendedOptions Extended => ref _extended;
/// <summary>
/// Gets or sets trailing bytes which could not be parsed and will be attached when saving the scheme anew.
@ -454,12 +453,12 @@ namespace Syroot.Worms.Armageddon
/// </summary>
/// <remarks>Configurable with the /version command in RubberWorm.
/// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions.</remarks>
public ushort RwVersionOverride
public ushort RwVersion
{
get => _rwVersionOverride;
get => _rwVersion;
set
{
_rwVersionOverride = value;
_rwVersion = value;
// Configure V3 settings according to selected version.
void setBattyRope1To2() => Extended.BattyRope = true;
@ -535,7 +534,7 @@ namespace Syroot.Worms.Armageddon
Extended.WormSelectKeepHotSeat = true;
}
switch (_rwVersionOverride)
switch (_rwVersion)
{
case 11: // 3.5 Beta 3pre15[BattyRope1]
case 12: // 3.5 Beta 3pre15[BattyRope2]
@ -845,30 +844,29 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object obj) => Equals(obj as Scheme);
public override bool Equals(object? obj) => Equals(obj as Scheme);
/// <inheritdoc/>
public bool Equals(Scheme other)
=> other == this
|| other != null
public bool Equals(Scheme? other)
=> other != null
&& Version == other.Version
&& HotSeatDelay == other.HotSeatDelay
&& HotSeatTime == other.HotSeatTime
&& RetreatTime == other.RetreatTime
&& RetreatTimeRope == other.RetreatTimeRope
&& ShowRoundTime == other.ShowRoundTime
&& AutomaticReplays == other.AutomaticReplays
&& Replays == other.Replays
&& FallDamage == other.FallDamage
&& ArtilleryMode == other.ArtilleryMode
&& SchemeEditor == other.SchemeEditor
&& StockpilingMode == other.StockpilingMode
&& WormSelectMode == other.WormSelectMode
&& Stockpiling == other.Stockpiling
&& WormSelect == other.WormSelect
&& SuddenDeathEvent == other.SuddenDeathEvent
&& WaterRiseRate == other.WaterRiseRate
&& WeaponCrateProbability == other.WeaponCrateProbability
&& WeaponCrateProb == other.WeaponCrateProb
&& DonorCards == other.DonorCards
&& HealthCrateProbability == other.HealthCrateProbability
&& HealthCrateProb == other.HealthCrateProb
&& HealthCrateEnergy == other.HealthCrateEnergy
&& UtilityCrateProbability == other.UtilityCrateProbability
&& UtilityCrateProb == other.UtilityCrateProb
&& ObjectTypes == other.ObjectTypes
&& ObjectCount == other.ObjectCount
&& MineDelay == other.MineDelay
@ -885,40 +883,40 @@ namespace Syroot.Worms.Armageddon
&& AquaSheep == other.AquaSheep
&& SheepHeaven == other.SheepHeaven
&& GodWorms == other.GodWorms
&& IndestructibleLand == other.IndestructibleLand
&& UpgradedGrenade == other.UpgradedGrenade
&& UpgradedShotgun == other.UpgradedShotgun
&& UpgradedCluster == other.UpgradedCluster
&& UpgradedLongbow == other.UpgradedLongbow
&& EnableTeamWeapons == other.EnableTeamWeapons
&& EnableSuperWeapons == other.EnableSuperWeapons
&& IndiLand == other.IndiLand
&& UpgradeGrenade == other.UpgradeGrenade
&& UpgradeShotgun == other.UpgradeShotgun
&& UpgradeCluster == other.UpgradeCluster
&& UpgradeLongbow == other.UpgradeLongbow
&& TeamWeapons == other.TeamWeapons
&& SuperWeapons == other.SuperWeapons
&& Weapons.SequenceEqual(other.Weapons)
&& Extended.Equals(ref other.Extended)
&& Extended.Equals(other.Extended)
&& Attachment.SequenceEqual(other.Attachment)
&& RwVersionOverride == other.RwVersionOverride;
&& RwVersion == other.RwVersion;
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Version);
hash.Add(HotSeatDelay);
hash.Add(HotSeatTime);
hash.Add(RetreatTime);
hash.Add(RetreatTimeRope);
hash.Add(ShowRoundTime);
hash.Add(AutomaticReplays);
hash.Add(Replays);
hash.Add(FallDamage);
hash.Add(ArtilleryMode);
hash.Add(SchemeEditor);
hash.Add(StockpilingMode);
hash.Add(WormSelectMode);
hash.Add(Stockpiling);
hash.Add(WormSelect);
hash.Add(SuddenDeathEvent);
hash.Add(WaterRiseRate);
hash.Add(WeaponCrateProbability);
hash.Add(WeaponCrateProb);
hash.Add(DonorCards);
hash.Add(HealthCrateProbability);
hash.Add(HealthCrateProb);
hash.Add(HealthCrateEnergy);
hash.Add(UtilityCrateProbability);
hash.Add(UtilityCrateProb);
hash.Add(ObjectTypes);
hash.Add(ObjectCount);
hash.Add(MineDelay);
@ -935,19 +933,19 @@ namespace Syroot.Worms.Armageddon
hash.Add(AquaSheep);
hash.Add(SheepHeaven);
hash.Add(GodWorms);
hash.Add(IndestructibleLand);
hash.Add(UpgradedGrenade);
hash.Add(UpgradedShotgun);
hash.Add(UpgradedCluster);
hash.Add(UpgradedLongbow);
hash.Add(EnableTeamWeapons);
hash.Add(EnableSuperWeapons);
hash.Add(IndiLand);
hash.Add(UpgradeGrenade);
hash.Add(UpgradeShotgun);
hash.Add(UpgradeCluster);
hash.Add(UpgradeLongbow);
hash.Add(TeamWeapons);
hash.Add(SuperWeapons);
foreach (SchemeWeapon weapon in Weapons)
hash.Add(weapon);
hash.Add(Extended);
foreach (byte attachmentByte in Attachment)
hash.Add(attachmentByte);
hash.Add(RwVersionOverride);
hash.Add(RwVersion);
return hash.ToHashCode();
}
@ -1030,23 +1028,23 @@ namespace Syroot.Worms.Armageddon
Version = reader.ReadEnum<SchemeVersion>(true);
// Read the options.
HotSeatDelay = reader.Read1Byte();
HotSeatTime = reader.Read1Byte();
RetreatTime = reader.Read1Byte();
RetreatTimeRope = reader.Read1Byte();
ShowRoundTime = reader.ReadBoolean();
AutomaticReplays = reader.ReadBoolean();
Replays = reader.ReadBoolean();
FallDamage = reader.ReadByte() * 50 % 256 * 2;
ArtilleryMode = reader.ReadBoolean();
SchemeEditor = reader.ReadEnum<SchemeEditor>(false);
StockpilingMode = reader.ReadEnum<StockpilingMode>(true);
WormSelectMode = reader.ReadEnum<WormSelect>(true);
Stockpiling = reader.ReadEnum<Stockpiling>(true);
WormSelect = reader.ReadEnum<WormSelect>(true);
SuddenDeathEvent = reader.ReadEnum<SuddenDeathEvent>(true);
_waterRiseIndex = reader.Read1Byte();
WeaponCrateProbability = reader.ReadSByte();
WeaponCrateProb = reader.ReadSByte();
DonorCards = reader.ReadBoolean();
HealthCrateProbability = reader.ReadSByte();
HealthCrateProb = reader.ReadSByte();
HealthCrateEnergy = reader.Read1Byte();
UtilityCrateProbability = reader.ReadSByte();
UtilityCrateProb = reader.ReadSByte();
readObjectCombo();
readMineDelay();
DudMines = reader.ReadBoolean();
@ -1059,19 +1057,17 @@ namespace Syroot.Worms.Armageddon
AquaSheep = reader.ReadBoolean();
SheepHeaven = reader.ReadBoolean();
GodWorms = reader.ReadBoolean();
IndestructibleLand = reader.ReadBoolean();
UpgradedGrenade = reader.ReadBoolean();
UpgradedShotgun = reader.ReadBoolean();
UpgradedCluster = reader.ReadBoolean();
UpgradedLongbow = reader.ReadBoolean();
EnableTeamWeapons = reader.ReadBoolean();
EnableSuperWeapons = reader.ReadBoolean();
IndiLand = reader.ReadBoolean();
UpgradeGrenade = reader.ReadBoolean();
UpgradeShotgun = reader.ReadBoolean();
UpgradeCluster = reader.ReadBoolean();
UpgradeLongbow = reader.ReadBoolean();
TeamWeapons = reader.ReadBoolean();
SuperWeapons = reader.ReadBoolean();
// Read the weapons.
Weapons = new WeaponList();
int weaponCount = GetWeaponCount(Version);
for (int i = 0; i < weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeapon>();
reader.ReadStructs(Weapons.AsSpan(GetWeaponCount(Version)));
// Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities.
switch (Version)
@ -1163,23 +1159,23 @@ namespace Syroot.Worms.Armageddon
writer.WriteEnum(version);
// Write the options.
writer.Write(HotSeatDelay);
writer.Write(HotSeatTime);
writer.Write(RetreatTime);
writer.Write(RetreatTimeRope);
writer.Write(ShowRoundTime);
writer.Write(AutomaticReplays);
writer.Write(Replays);
writer.Write((byte)(FallDamage / 4 * 41 % 128));
writer.Write(ArtilleryMode);
writer.WriteEnum(SchemeEditor, false);
writer.WriteEnum(StockpilingMode, true);
writer.WriteEnum(WormSelectMode, true);
writer.WriteEnum(Stockpiling, true);
writer.WriteEnum(WormSelect, true);
writer.WriteEnum(SuddenDeathEvent, true);
writer.Write(_waterRiseIndex);
writer.Write(WeaponCrateProbability);
writer.Write(WeaponCrateProb);
writer.Write(DonorCards);
writer.Write(HealthCrateProbability);
writer.Write(HealthCrateProb);
writer.Write(HealthCrateEnergy);
writer.Write(UtilityCrateProbability);
writer.Write(UtilityCrateProb);
saveObjectTypesAndCount();
saveMineDelayConfig();
writer.Write(DudMines);
@ -1192,22 +1188,20 @@ namespace Syroot.Worms.Armageddon
writer.Write(AquaSheep);
writer.Write(SheepHeaven);
writer.Write(GodWorms);
writer.Write(IndestructibleLand);
writer.Write(UpgradedGrenade);
writer.Write(UpgradedShotgun);
writer.Write(UpgradedCluster);
writer.Write(UpgradedLongbow);
writer.Write(EnableTeamWeapons);
writer.Write(EnableSuperWeapons);
writer.Write(IndiLand);
writer.Write(UpgradeGrenade);
writer.Write(UpgradeShotgun);
writer.Write(UpgradeCluster);
writer.Write(UpgradeLongbow);
writer.Write(TeamWeapons);
writer.Write(SuperWeapons);
// Serialize RubberWorm settings encoded in weapon probabilities.
if (version == SchemeVersion.Version2)
SaveRubberWorm();
// Write the weapons.
int weaponCount = GetWeaponCount(version);
for (int i = 0; i < weaponCount; i++)
writer.WriteStruct(Weapons[i]);
writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(GetWeaponCount(version)));
// Clear RubberWorm probabilities again or write available extended settings.
switch (version)
@ -1228,10 +1222,10 @@ namespace Syroot.Worms.Armageddon
{
// Reset all super weapon probabilities to remove any settings made by RubberWorm.
for (Weapon superWeapon = Weapon.Freeze; superWeapon <= Weapon.Armageddon; superWeapon++)
Weapons[superWeapon].Crates = 0;
Weapons[superWeapon].Prob = 0;
}
private void ClearExtendedOptions() => Extended = SchemeExtendedOptions.Default;
private void ClearExtendedOptions() => Extended = ExtendedOptions.Default;
private unsafe void LoadExtendedOptions(BinaryStream reader)
{
@ -1242,7 +1236,7 @@ namespace Syroot.Worms.Armageddon
else
{
Span<byte> bytes = new Span<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>());
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
byte[] buffer = new byte[(int)Math.Min(bytes.Length, reader.Length - reader.Position)];
@ -1259,7 +1253,7 @@ namespace Syroot.Worms.Armageddon
unchecked
{
// Earthquake flags.
byte prob = (byte)Weapons[Weapon.Earthquake].Crates;
byte prob = (byte)Weapons[Weapon.Earthquake].Prob;
Extended.AutoReaim = prob.GetBit(0);
Extended.CircularAim = prob.GetBit(1);
Extended.AntiLockPower = prob.GetBit(2);
@ -1267,19 +1261,19 @@ namespace Syroot.Worms.Armageddon
Extended.KaosMod = prob.DecodeByte(4, 4);
// Antisink.
Extended.AntiSink = Weapons[Weapon.SheepStrike].Crates != 0;
Extended.AntiSink = Weapons[Weapon.SheepStrike].Prob != 0;
// Crate limit and rate.
prob = (byte)Weapons[Weapon.MagicBullet].Crates;
prob = (byte)Weapons[Weapon.MagicBullet].Prob;
Extended.CrateLimit = prob switch
{
0 => SchemeExtendedOptions.Default.CrateLimit,
0 => ExtendedOptions.Default.CrateLimit,
_ => prob
};
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Crates;
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Prob;
// Mole squadron flags.
prob = (byte)Weapons[Weapon.MoleSquadron].Crates;
prob = (byte)Weapons[Weapon.MoleSquadron].Prob;
Extended.ShotDoesntEndTurn = prob.GetBit(0);
Extended.LoseControlDoesntEndTurn = prob.GetBit(1);
Extended.FiringPausesTimer = !prob.GetBit(2);
@ -1290,21 +1284,21 @@ namespace Syroot.Worms.Armageddon
Extended.ExtendedFuse = prob.GetBit(7);
// Flame limit.
prob = (byte)Weapons[Weapon.ScalesOfJustice].Crates;
prob = (byte)Weapons[Weapon.ScalesOfJustice].Prob;
if (prob > 0)
Extended.FlameLimit = (ushort)(prob * 100);
// Friction.
prob = (byte)Weapons[Weapon.SalvationArmy].Crates;
prob = (byte)Weapons[Weapon.SalvationArmy].Prob;
if (prob > 0)
Extended.Friction = prob / 100f;
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity.
prob = (byte)Weapons[Weapon.MailStrike].Crates;
prob = (byte)Weapons[Weapon.MailStrike].Prob;
if (prob == 0)
{
Extended.RwGravityType = RwGravityType.None;
Extended.RwGravity = SchemeExtendedOptions.Default.RwGravity;
Extended.RwGravity = ExtendedOptions.Default.RwGravity;
}
else if (prob.GetBit(7) && prob.GetBit(6))
{
@ -1323,7 +1317,7 @@ namespace Syroot.Worms.Armageddon
}
// Rope-knocking force.
prob = (byte)Weapons[Weapon.SuperBananaBomb].Crates;
prob = (byte)Weapons[Weapon.SuperBananaBomb].Prob;
Extended.RopeKnockForce = prob switch
{
0 => null,
@ -1332,35 +1326,35 @@ namespace Syroot.Worms.Armageddon
};
// Maximum rope speed.
prob = (byte)Weapons[Weapon.MineStrike].Crates;
prob = (byte)Weapons[Weapon.MineStrike].Prob;
Extended.RopeMaxSpeed = prob switch
{
0 => SchemeExtendedOptions.Default.RopeMaxSpeed,
0 => ExtendedOptions.Default.RopeMaxSpeed,
Byte.MaxValue => 0,
_ => prob
};
// Select worm any time.
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Crates).GetBit(0);
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Prob).GetBit(0);
// Viscosity.
prob = (byte)Weapons[Weapon.ConcreteDonkey].Crates;
prob = (byte)Weapons[Weapon.ConcreteDonkey].Prob;
Extended.Viscosity = prob / 255f;
Extended.ViscosityWorms = (prob & 1) == 1;
// Wind.
prob = (byte)Weapons[Weapon.SuicideBomber].Crates;
Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Crates / 255f;
prob = (byte)Weapons[Weapon.SuicideBomber].Prob;
Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Prob / 255f;
Extended.RwWindWorms = (prob & 1) == 1;
// Worm bounce.
Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Crates / 255f;
Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Prob / 255f;
// Version override.
RwVersionOverride = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
RwVersion = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
{
(byte)Weapons[Weapon.SelectWorm].Crates,
(byte)Weapons[Weapon.Freeze].Crates
(byte)Weapons[Weapon.SelectWorm].Prob,
(byte)Weapons[Weapon.Freeze].Prob
});
}
@ -1371,7 +1365,7 @@ namespace Syroot.Worms.Armageddon
private unsafe void SaveExtendedOptions(BinaryStream writer)
{
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>());
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0.
writer.Write(bytes.ToArray());
@ -1391,15 +1385,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(2, Extended.AntiLockPower);
prob = prob.SetBit(3, Extended.ShotDoesntEndTurnAll);
prob = prob.Encode(Extended.KaosMod, 4, 4);
Weapons[Weapon.Earthquake].Crates = (sbyte)prob;
Weapons[Weapon.Earthquake].Prob = (sbyte)prob;
// Antisink
Weapons[Weapon.SheepStrike].Crates = (sbyte)(Extended.AntiSink ? 1 : 0);
Weapons[Weapon.SheepStrike].Prob = (sbyte)(Extended.AntiSink ? 1 : 0);
// Crate limit and rate.
if (Extended.CrateLimit != SchemeExtendedOptions.Default.CrateLimit)
Weapons[Weapon.MagicBullet].Crates = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit);
Weapons[Weapon.NuclearTest].Crates = (sbyte)Extended.CrateRate;
if (Extended.CrateLimit != ExtendedOptions.Default.CrateLimit)
Weapons[Weapon.MagicBullet].Prob = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit);
Weapons[Weapon.NuclearTest].Prob = (sbyte)Extended.CrateRate;
// Mole squadron flags.
prob = 0;
@ -1411,15 +1405,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(5, Extended.ObjectPushByExplosion == true);
prob = prob.SetBit(6, Extended.WeaponsDontChange);
prob = prob.SetBit(7, Extended.ExtendedFuse);
Weapons[Weapon.MoleSquadron].Crates = (sbyte)prob;
Weapons[Weapon.MoleSquadron].Prob = (sbyte)prob;
// Flame limit.
if (Extended.FlameLimit != SchemeExtendedOptions.Default.FlameLimit)
Weapons[Weapon.ScalesOfJustice].Crates = (sbyte)(Extended.FlameLimit / 100);
if (Extended.FlameLimit != ExtendedOptions.Default.FlameLimit)
Weapons[Weapon.ScalesOfJustice].Prob = (sbyte)(Extended.FlameLimit / 100);
// Friction.
if (Extended.Friction != SchemeExtendedOptions.Default.Friction)
Weapons[Weapon.SalvationArmy].Crates = (sbyte)Math.Round(Extended.Friction * 100, 0);
if (Extended.Friction != ExtendedOptions.Default.Friction)
Weapons[Weapon.SalvationArmy].Prob = (sbyte)Math.Round(Extended.Friction * 100, 0);
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity.
prob = 0;
@ -1438,10 +1432,10 @@ namespace Syroot.Worms.Armageddon
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
break;
}
Weapons[Weapon.MailStrike].Crates = (sbyte)prob;
Weapons[Weapon.MailStrike].Prob = (sbyte)prob;
// Rope-knocking force.
Weapons[Weapon.SuperBananaBomb].Crates = (sbyte)(Extended.RopeKnockForce switch
Weapons[Weapon.SuperBananaBomb].Prob = (sbyte)(Extended.RopeKnockForce switch
{
null => 0,
0 => Byte.MaxValue,
@ -1449,7 +1443,7 @@ namespace Syroot.Worms.Armageddon
});
// Maximum rope speed.
Weapons[Weapon.MineStrike].Crates = (sbyte)(Extended.RopeMaxSpeed switch
Weapons[Weapon.MineStrike].Prob = (sbyte)(Extended.RopeMaxSpeed switch
{
16/*SchemeExtendedOptions.Default.RopeMaxSpeed*/ => 0,
0 => Byte.MaxValue,
@ -1457,27 +1451,27 @@ namespace Syroot.Worms.Armageddon
});
// Select worm any time.
prob = ((byte)Weapons[Weapon.MBBomb].Crates).SetBit(0, Extended.WormSelectAnytime);
Weapons[Weapon.MBBomb].Crates = (sbyte)prob;
prob = ((byte)Weapons[Weapon.MBBomb].Prob).SetBit(0, Extended.WormSelectAnytime);
Weapons[Weapon.MBBomb].Prob = (sbyte)prob;
// Viscosity.
prob = (byte)Math.Round(Extended.Viscosity * 255, 0);
prob.SetBit(0, Extended.ViscosityWorms);
Weapons[Weapon.ConcreteDonkey].Crates = (sbyte)prob;
Weapons[Weapon.ConcreteDonkey].Prob = (sbyte)prob;
// Wind.
prob = (byte)Math.Round(Extended.RwWind * 255, 0);
prob.SetBit(0, Extended.RwWindWorms);
Weapons[Weapon.SuicideBomber].Crates = (sbyte)prob;
Weapons[Weapon.SuicideBomber].Prob = (sbyte)prob;
// Worm bounce.
Weapons[Weapon.Armageddon].Crates = (sbyte)Math.Round(Extended.WormBounce * 255, 0);
Weapons[Weapon.Armageddon].Prob = (sbyte)Math.Round(Extended.WormBounce * 255, 0);
// Version override.
Span<byte> versionBytes = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersionOverride);
Weapons[Weapon.SelectWorm].Crates = (sbyte)versionBytes[0];
Weapons[Weapon.Freeze].Crates = (sbyte)versionBytes[1];
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersion);
Weapons[Weapon.SelectWorm].Prob = (sbyte)versionBytes[0];
Weapons[Weapon.Freeze].Prob = (sbyte)versionBytes[1];
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ namespace Syroot.Worms.Armageddon
/// values represent infinity.</summary>
public sbyte Delay;
/// <summary>Probability of this weapon to appear in crates. Has no effect on super weapons.</summary>
public sbyte Crates;
public sbyte Prob;
// ---- OPERATORS ----------------------------------------------------------------------------------------------
@ -39,9 +39,9 @@ namespace Syroot.Worms.Armageddon
=> Ammo == other.Ammo
&& Power == other.Power
&& Delay == other.Delay
&& Crates == other.Crates;
&& Prob == other.Prob;
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(Ammo, Power, Delay, Crates);
public override int GetHashCode() => HashCode.Combine(Ammo, Power, Delay, Prob);
}
}

View File

@ -4,9 +4,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms.Armageddon</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats. Simplify Scheme usage.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation. Implement W:A V3 scheme format.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>3.3.0-beta001</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,9 +1,10 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
@ -17,33 +18,17 @@ namespace Syroot.Worms.Armageddon
private const int _missionCount = 33;
private const int _trainingMissionCount = 6;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Team"/> class.
/// </summary>
public Team()
{
WormNames = new string[8];
MissionStatuses = new TeamMissionStatus[_missionCount];
TrainingMissionTimes = new int[_trainingMissionCount];
Unknown1 = new int[10];
TrainingMissionMedals = new byte[_trainingMissionCount];
Unknown2 = new byte[10];
Unknown3 = new int[7];
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; }
public string Name { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the 8 worm names.
/// Gets the 8 worm names.
/// </summary>
public string[] WormNames { get; set; }
public string[] WormNames { get; } = new string[8];
/// <summary>
/// Gets or sets the AI intelligence difficulty level, from 0-5, where 0 is human-controlled.
@ -53,18 +38,18 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; }
public string SoundBankName { get; set; } = String.Empty;
public byte SoundBankLocation { get; set; }
/// <summary>
/// Gets or sets the name of the team fanfare.
/// </summary>
public string FanfareName { get; set; }
public string FanfareName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets a value indicating whether the fanfare with the name stored in <see cref="FanfareName"/>
/// (<c>true</c>) or the player's countries' fanfare should be played (<c>false</c>).
/// (<see langword="true"/>) or the player's countries' fanfare should be played (<see langword="false"/>).
/// </summary>
public byte UseCustomFanfare { get; set; }
@ -76,12 +61,12 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the file name of the team grave bitmap if it uses a custom one.
/// </summary>
public string GraveFileName { get; set; }
public string GraveFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the team grave bitmap if it uses a custom one.
/// </summary>
public RawBitmap Grave { get; set; }
public RawBitmap Grave { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets the team's special weapon.
@ -139,19 +124,19 @@ namespace Syroot.Worms.Armageddon
public int DeathmatchDeaths { get; set; }
/// <summary>
/// Gets or sets the array of 33 mission statuses.
/// Gets the array of 33 mission statuses.
/// </summary>
public TeamMissionStatus[] MissionStatuses { get; set; }
public TeamMissionStatus[] MissionStatuses { get; } = new TeamMissionStatus[_missionCount];
/// <summary>
/// Gets or sets the file name of the team flag.
/// </summary>
public string FlagFileName { get; set; }
public string FlagFileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the bitmap of the team flag.
/// </summary>
public RawBitmap Flag { get; set; }
public RawBitmap Flag { get; set; } = new RawBitmap();
/// <summary>
/// Gets or sets the deathmatch rank this team reached.
@ -161,31 +146,19 @@ namespace Syroot.Worms.Armageddon
/// <summary>
/// Gets or sets the seconds the team required to finish all 6 training missions.
/// </summary>
public int[] TrainingMissionTimes { get; set; }
public int[] TrainingMissionTimes { get; set; } = new int[_trainingMissionCount];
/// <summary>
/// Gets or sets 10 unknown integer values.
/// </summary>
public int[] Unknown1 { get; set; }
public int[] Unknown1 { get; set; } = new int[10];
/// <summary>
/// Gets or sets the medals the team achieved in all 6 training missions.
/// </summary>
public byte[] TrainingMissionMedals { get; set; }
public byte[] TrainingMissionMedals { get; set; } = new byte[_trainingMissionCount];
/// <summary>
/// Gets or sets 10 unknown bytes.
/// </summary>
public byte[] Unknown2 { get; set; }
public byte[] Unknown2 { get; set; } = new byte[10];
/// <summary>
/// Gets or sets 7 unknown integer values.
/// </summary>
public int[] Unknown3 { get; set; }
public int[] Unknown3 { get; set; } = new int[7];
/// <summary>
/// Gets or sets an unknown byte.
/// </summary>
public byte Unknown4 { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -196,7 +169,8 @@ namespace Syroot.Worms.Armageddon
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Name = reader.ReadString(17);
WormNames = reader.ReadStrings(8, 17);
for (int i = 0; i < WormNames.Length; i++)
WormNames[i] = reader.ReadString(17);
CpuLevel = reader.Read1Byte();
SoundBankName = reader.ReadString(0x20);
SoundBankLocation = reader.Read1Byte();
@ -227,7 +201,8 @@ namespace Syroot.Worms.Armageddon
DeathmatchKills = reader.ReadInt32();
Deaths = reader.ReadInt32();
DeathmatchDeaths = reader.ReadInt32();
MissionStatuses = reader.ReadStructs<TeamMissionStatus>(_missionCount);
for (int i = 0; i < _missionCount; i++)
MissionStatuses[i] = reader.ReadStruct<TeamMissionStatus>();
FlagFileName = reader.ReadString(0x20);
Flag = new RawBitmap()
@ -252,19 +227,20 @@ namespace Syroot.Worms.Armageddon
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.WriteString(Name, 17);
writer.WriteStrings(WormNames, 17);
writer.WriteFixedString(Name, 17);
for (int i = 0; i < WormNames.Length; i++)
writer.WriteFixedString(WormNames[i], 17);
writer.Write(CpuLevel);
writer.WriteString(SoundBankName, 0x20);
writer.WriteFixedString(SoundBankName, 0x20);
writer.Write(SoundBankLocation);
writer.WriteString(FanfareName, 0x20);
writer.WriteFixedString(FanfareName, 0x20);
writer.Write(UseCustomFanfare);
writer.Write(GraveSprite);
if (GraveSprite < 0)
{
writer.WriteString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette);
writer.WriteFixedString(GraveFileName, 0x20);
writer.WriteColors(Grave.Palette!);
writer.Write(Grave.Data);
}
@ -279,10 +255,11 @@ namespace Syroot.Worms.Armageddon
writer.Write(DeathmatchKills);
writer.Write(Deaths);
writer.Write(DeathmatchDeaths);
writer.WriteStructs(MissionStatuses);
for (int i = 0; i < MissionStatuses.Length; i++)
writer.WriteStruct(ref MissionStatuses[i]);
writer.WriteString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette);
writer.WriteFixedString(FlagFileName, 0x20);
writer.WriteColors(Flag.Palette!);
writer.Write(Flag.Data);
writer.Write(DeathmatchRank);

View File

@ -1,154 +1,156 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the list of teams and unlocked game features stored in WGT files.
/// Used by WA. S. https://worms2d.info/Team_file.
/// </summary>
public class TeamContainer : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WGT"; // 0-terminated.
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> 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 TeamContainer(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public TeamContainer(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value possibly indicating a version of the file format.
/// </summary>
public byte Version { get; set; }
/// <summary>
/// Gets or sets the unlocked utilities, weapon upgrades, and game cheats.
/// </summary>
public UnlockedFeatures UnlockedFeatures { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public IList<Team> Teams { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
throw new InvalidDataException("Invalid WGT file signature.");
Version = reader.Read1Byte(); // Really version?
// Read global settings.
byte teamCount = reader.Read1Byte();
UnlockedFeatures = reader.ReadEnum<UnlockedFeatures>(false);
Unknown = reader.Read1Byte();
// Read the teams.
Teams = new List<Team>(reader.Load<Team>(teamCount));
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature, StringCoding.ZeroTerminated);
writer.Write(Version);
// Write global settings.
writer.Write((byte)Teams.Count);
writer.WriteEnum(UnlockedFeatures, false);
writer.Write(Unknown);
// Write the teams.
foreach (Team team in Teams)
team.Save(writer.BaseStream);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
/// <summary>
/// Represents unlockable features of the game.
/// </summary>
[Flags]
public enum UnlockedFeatures : int
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the list of teams and unlocked game features stored in WGT files.
/// Used by WA. S. https://worms2d.info/Team_file.
/// </summary>
public class TeamContainer : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WGT"; // 0-terminated.
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> 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 TeamContainer(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public TeamContainer(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value possibly indicating a version of the file format.
/// </summary>
public byte Version { get; set; }
/// <summary>
/// Gets or sets the unlocked utilities, weapon upgrades, and game cheats.
/// </summary>
public UnlockedFeatures UnlockedFeatures { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
throw new InvalidDataException("Invalid WGT file signature.");
Version = reader.Read1Byte(); // Really version?
// Read global settings.
byte teamCount = reader.Read1Byte();
UnlockedFeatures = reader.ReadEnum<UnlockedFeatures>(false);
Unknown = reader.Read1Byte();
// Read the teams.
Teams = new List<Team>();
while (teamCount-- > 0)
Teams.Add(reader.Load<Team>());
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature, StringCoding.ZeroTerminated);
writer.Write(Version);
// Write global settings.
writer.Write((byte)Teams.Count);
writer.WriteEnum(UnlockedFeatures, false);
writer.Write(Unknown);
// Write the teams.
foreach (Team team in Teams)
team.Save(writer.BaseStream);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
/// <summary>
/// Represents unlockable features of the game.
/// </summary>
[Flags]
public enum UnlockedFeatures : int
{
/// <summary>No features have been unlocked yet.</summary>
None,
/// <summary>The utility weapon Laser Sight can be configured.</summary>
UtilityLaserSight = 1 << 0,
/// <summary>The utility weapon Fast Walk can be configured.</summary>
UtilityFastWalk = 1 << 1,
/// <summary>The utility weapon Invisibility can be configured.</summary>
UtilityInvisibility = 1 << 2,
/// <summary>The utility weapon Low Gravity can be configured.</summary>
UtilityLowGravity = 1 << 3,
/// <summary>The utility weapon Jetpack can be configured.</summary>
UtilityJetpack = 1 << 4,
/// <summary>The Grenade upgrade can be enabled.</summary>
UpgradedGrenade = 1 << 8,
/// <summary>The Shotgun upgrade can be enabled.</summary>
UpgradedShotgun = 1 << 9,
/// <summary>The cluster upgrade can be enabled.</summary>
UpgradedClusters = 1 << 10,
/// <summary>The Longbow upgrade can be enabled.</summary>
UpgradedLongbow = 1 << 11,
/// <summary>The upgrade of Super Sheeps to become Aqua Sheeps can be enabled.</summary>
AquaSheep = 1 << 12,
/// <summary>Worms can have infinite health and are killable only by drowning them.</summary>
GodWorms = 1 << 16,
/// <summary>Blood effects when hitting worms can be enabled.</summary>
BloodFx = 1 << 17,
/// <summary>Every crate explodes with a sheep.</summary>
SheepHeaven = 1 << 18,
/// <summary>Map terrain can be indestructible and Full Wormage scheme is accessible.</summary>
IndestructibleAndFullWormage = 1 << 24
}
}
None,
/// <summary>The utility weapon Laser Sight can be configured.</summary>
UtilityLaserSight = 1 << 0,
/// <summary>The utility weapon Fast Walk can be configured.</summary>
UtilityFastWalk = 1 << 1,
/// <summary>The utility weapon Invisibility can be configured.</summary>
UtilityInvisibility = 1 << 2,
/// <summary>The utility weapon Low Gravity can be configured.</summary>
UtilityLowGravity = 1 << 3,
/// <summary>The utility weapon Jetpack can be configured.</summary>
UtilityJetpack = 1 << 4,
/// <summary>The Grenade upgrade can be enabled.</summary>
UpgradedGrenade = 1 << 8,
/// <summary>The Shotgun upgrade can be enabled.</summary>
UpgradedShotgun = 1 << 9,
/// <summary>The cluster upgrade can be enabled.</summary>
UpgradedClusters = 1 << 10,
/// <summary>The Longbow upgrade can be enabled.</summary>
UpgradedLongbow = 1 << 11,
/// <summary>The upgrade of Super Sheeps to become Aqua Sheeps can be enabled.</summary>
AquaSheep = 1 << 12,
/// <summary>Worms can have infinite health and are killable only by drowning them.</summary>
GodWorms = 1 << 16,
/// <summary>Blood effects when hitting worms can be enabled.</summary>
BloodFx = 1 << 17,
/// <summary>Every crate explodes with a sheep.</summary>
SheepHeaven = 1 << 18,
/// <summary>Map terrain can be indestructible and Full Wormage scheme is accessible.</summary>
IndestructibleAndFullWormage = 1 << 24
}
}

View File

@ -1,135 +1,132 @@
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;
}
}
}
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.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; } = new byte[8];
public Point Center { get; set; }
public Size Size { get; set; }
public IList<IgdImage> Images { get; set; } = new List<IgdImage>();
// ---- 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.Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height);
image.RawBitmap.Palette = palette;
image.RawBitmap.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;
}
}
}

View File

@ -14,6 +14,6 @@ namespace Syroot.Worms.Mgame
public int UnknownC { get; set; }
public Size Size { get; set; }
public Point Center { get; set; }
public RawBitmap RawBitmap { get; set; }
public RawBitmap RawBitmap { get; } = new RawBitmap { BitsPerPixel = 8 };
}
}

View File

@ -1,104 +1,105 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a KSF image container.
/// </summary>
public class Ksf // TODO: Implement ILoadableFile
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class.
/// </summary>
public Ksf() { }
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the file with the given
/// <paramref name="fileName"/> and the colors in the provided <paramref name="palette"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(string fileName, Palette palette) => Load(fileName, palette);
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the given
/// <paramref name="stream"/> and the colors in the provided <paramref name="palette"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(Stream stream, Palette palette) => Load(stream, palette);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the list of converted <see cref="Bitmap"/> instances stored in this KSF.
/// </summary>
public IList<KsfImage> Images { get; set; } = new List<KsfImage>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the file with the given <paramref name="fileName"/> and the colors in the provided
/// <paramref name="palette"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(string fileName, Palette palette)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, palette);
}
/// <summary>
/// Loads the data from the given <paramref name="stream"/> and the colors in the provided
/// <paramref name="palette"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(Stream stream, Palette palette)
{
int imageCount = stream.ReadInt32(); // Includes terminator.
_ = stream.ReadInt32(); // data size
// Read image headers. Terminating image is of 0 size and data offset at end of data block.
KsfImage[] images = new KsfImage[imageCount];
int[] offsets = new int[imageCount];
Size[] sizes = new Size[imageCount];
for (int i = 0; i < imageCount; i++)
{
KsfImage image = new KsfImage();
offsets[i] = stream.ReadInt32();
image.Center = new Point(stream.ReadInt32(), stream.ReadInt32());
sizes[i] = new Size(stream.ReadInt16(), stream.ReadInt16());
images[i] = image;
}
// Convert images.
Images = new List<KsfImage>(imageCount);
long dataStart = stream.Position;
for (int i = 0; i < imageCount - 1; i++)
{
int offset = offsets[i];
stream.Position = dataStart + offset;
int dataLength = offsets[i + 1] - offset;
Size size = sizes[i];
if (!size.IsEmpty)
{
images[i].RawBitmap = new RawBitmap
{
Size = size,
Palette = palette.Colors,
Data = stream.ReadBytes(dataLength)
};
}
Images.Add(images[i]);
}
}
}
}
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a KSF image container.
/// </summary>
public class Ksf // TODO: Implement ILoadableFile
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class.
/// </summary>
public Ksf() { }
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the file with the given
/// <paramref name="fileName"/> and the colors in the provided <paramref name="palette"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(string fileName, KsfPalette palette) => Load(fileName, palette);
/// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the given
/// <paramref name="stream"/> and the colors in the provided <paramref name="palette"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(Stream stream, KsfPalette palette) => Load(stream, palette);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the list of converted <see cref="Bitmap"/> instances stored in this KSF.
/// </summary>
public IList<KsfImage> Images { get; set; } = new List<KsfImage>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the file with the given <paramref name="fileName"/> and the colors in the provided
/// <paramref name="palette"/>.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(string fileName, KsfPalette palette)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, palette);
}
/// <summary>
/// Loads the data from the given <paramref name="stream"/> and the colors in the provided
/// <paramref name="palette"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(Stream stream, KsfPalette palette)
{
int imageCount = stream.ReadInt32(); // Includes terminator.
_ = stream.ReadInt32(); // data size
// Read image headers. Terminating image is of 0 size and data offset at end of data block.
KsfImage[] images = new KsfImage[imageCount];
int[] offsets = new int[imageCount];
Size[] sizes = new Size[imageCount];
for (int i = 0; i < imageCount; i++)
{
KsfImage image = new KsfImage();
offsets[i] = stream.ReadInt32();
image.Center = new Point(stream.ReadInt32(), stream.ReadInt32());
sizes[i] = new Size(stream.ReadInt16(), stream.ReadInt16());
images[i] = image;
}
// Convert images.
Images = new List<KsfImage>(imageCount);
long dataStart = stream.Position;
for (int i = 0; i < imageCount - 1; i++)
{
int offset = offsets[i];
stream.Position = dataStart + offset;
int dataLength = offsets[i + 1] - offset;
Size size = sizes[i];
if (!size.IsEmpty)
{
images[i].RawBitmap = new RawBitmap
{
BitsPerPixel = 8,
Size = size,
Palette = palette.Colors,
Data = stream.ReadBytes(dataLength)
};
}
Images.Add(images[i]);
}
}
}
}

View File

@ -10,6 +10,6 @@ namespace Syroot.Worms.Mgame
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public Point Center { get; set; }
public RawBitmap RawBitmap { get; set; }
public RawBitmap? RawBitmap { get; set; } = null;
}
}

View File

@ -1,69 +1,68 @@
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.Graphics;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a PAL color palette.
/// </summary>
public class Palette : ILoadableFile, IPalette
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>
/// The number of colors stored in a palette.
/// </summary>
internal const int ColorCount = 256;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> class.
/// </summary>
public Palette() { }
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> 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 Palette(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="Palette"/> class, loading data from the given
/// <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Palette(Stream stream) => Load(stream);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of 256 colors stored in this palette.
/// </summary>
public Color[] Colors { get; set; } = new Color[ColorCount];
// ---- 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)
{
byte[] data = stream.ReadBytes(ColorCount * 3);
for (int i = 0; i < ColorCount; i++)
{
int offset = i * 3;
Colors[i] = Color.FromArgb(data[offset], data[offset + 1], data[offset + 2]);
}
}
}
}
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a color palette referenced by <see cref="KsfImage"/> data.
/// </summary>
public class KsfPalette : ILoadableFile, IPalette
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>Number of colors stored in a palette.</summary>
internal const int ColorCount = 256;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="KsfPalette"/> class.
/// </summary>
public KsfPalette() { }
/// <summary>
/// Initializes a new instance of the <see cref="KsfPalette"/> 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 KsfPalette(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="KsfPalette"/> class, loading data from the given
/// <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public KsfPalette(Stream stream) => Load(stream);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of 256 colors stored in this palette.
/// </summary>
public IList<Color> Colors { get; set; } = new Color[ColorCount];
// ---- 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)
{
byte[] data = stream.ReadBytes(ColorCount * 3);
for (int i = 0; i < ColorCount; i++)
{
int offset = i * 3;
Colors[i] = Color.FromArgb(data[offset], data[offset + 1], data[offset + 2]);
}
}
}
}

View File

@ -1,88 +1,86 @@
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Net;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents the memory mapped file passed to the game executable, configuring the server address.
/// </summary>
public class LaunchConfig
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private string _passwordString;
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name of the publisher providing the launch configuration. Must not exceed 15 characters.
/// </summary>
public string Publisher { get; set; } = "MGAME";
/// <summary>
/// Gets or sets an unknown value. Must not exceed 19 characters.
/// </summary>
public string Unknown { get; set; }
/// <summary>
/// Gets or sets the address of the game server.
/// </summary>
public IPEndPoint ServerEndPoint { get; set; }
/// <summary>
/// Gets or sets the initially entered user name. Must not exceed 250 characters.
/// </summary>
public string UserName { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="MemoryMappedFile"/> which is then read by the game client.
/// </summary>
/// <param name="mapName">The name under which the game client can access the mapping.</param>
/// <returns>The <see cref="MemoryMappedFile"/>.</returns>
public MemoryMappedFile CreateMappedFile(string mapName)
{
MemoryMappedFile mappedFile = MemoryMappedFile.CreateNew(mapName, 1266,
MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, HandleInheritability.Inheritable);
using (BinaryStream stream = new BinaryStream(mappedFile.CreateViewStream(),
encoding: Encodings.Korean, stringCoding: StringCoding.ZeroTerminated))
{
stream.WriteString(Publisher, 16);
stream.WriteString(Unknown, 20);
stream.WriteString(_passwordString, 30);
stream.WriteString($"UID={UserName}", 256);
stream.WriteString(ServerEndPoint.Address.ToString(), 30);
stream.WriteString(ServerEndPoint.Port.ToString(), 914);
}
return mappedFile;
}
/// <summary>
/// Decrypts the password stored in the configuration as previously set through
/// <see cref="SetPassword(String, Int32)"/>.
/// </summary>
public string GetPassword()
{
if (_passwordString == null)
return null;
string[] parts = _passwordString.Split(';');
return PasswordCrypto.Decrypt(parts[1], UInt32.Parse(parts[2]));
}
/// <summary>
/// Encrypts the given <paramref name="password"/> with the provided initial encryption <paramref name="key"/>
/// and stores the result in the launch config.
/// </summary>
/// <param name="password">The password to store encrypted.</param>
/// <param name="key">The key to encrypt with.</param>
public void SetPassword(string password, int key = 1000)
{
_passwordString = $";{PasswordCrypto.Encrypt(password, (uint)key)};{key}";
}
}
}
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Net;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents the memory mapped file passed to the game executable, configuring the server address.
/// </summary>
public class LaunchConfig
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private string _passwordString = String.Empty;
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the name of the publisher providing the launch configuration. Must not exceed 15 characters.
/// </summary>
public string Publisher { get; set; } = "MGAME";
/// <summary>
/// Gets or sets an unknown value. Must not exceed 19 characters.
/// </summary>
public string Unknown { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the address of the game server.
/// </summary>
public IPEndPoint ServerEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Gets or sets the initially entered user name. Must not exceed 250 characters.
/// </summary>
public string UserName { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="MemoryMappedFile"/> which is then read by the game client.
/// </summary>
/// <param name="mapName">The name under which the game client can access the mapping.</param>
/// <returns>The <see cref="MemoryMappedFile"/>.</returns>
public MemoryMappedFile CreateMappedFile(string mapName)
{
MemoryMappedFile mappedFile = MemoryMappedFile.CreateNew(mapName, 1266,
MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, HandleInheritability.Inheritable);
using (BinaryStream stream = new BinaryStream(mappedFile.CreateViewStream(),
encoding: Encodings.Korean, stringCoding: StringCoding.ZeroTerminated))
{
stream.WriteFixedString(Publisher, 16);
stream.WriteFixedString(Unknown, 20);
stream.WriteFixedString(_passwordString, 30);
stream.WriteFixedString($"UID={UserName}", 256);
stream.WriteFixedString(ServerEndPoint.Address.ToString(), 30);
stream.WriteFixedString(ServerEndPoint.Port.ToString(), 914);
}
return mappedFile;
}
/// <summary>
/// Decrypts the password stored in the configuration as previously set through
/// <see cref="SetPassword(String, Int32)"/>.
/// </summary>
public string GetPassword()
{
if (String.IsNullOrEmpty(_passwordString))
return String.Empty;
string[] parts = _passwordString.Split(';');
return PasswordCrypto.Decrypt(parts[1], UInt32.Parse(parts[2]));
}
/// <summary>
/// Encrypts the given <paramref name="password"/> with the provided initial encryption <paramref name="key"/>
/// and stores the result in the launch config.
/// </summary>
/// <param name="password">The password to store encrypted.</param>
/// <param name="key">The key to encrypt with.</param>
public void SetPassword(string password, int key = 1000)
=> _passwordString = $";{PasswordCrypto.Encrypt(password, (uint)key)};{key}";
}
}

View File

@ -2,7 +2,7 @@
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame
{
@ -32,7 +32,7 @@ namespace Syroot.Worms.Mgame
/// <summary>
/// Gets or sets the list of <see cref="LpdElement"/> instances stored by this class.
/// </summary>
public IList<LpdElement> Elements { get; set; }
public IList<LpdElement> Elements { get; set; } = new List<LpdElement>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -1,32 +1,30 @@
using System.Drawing;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a single UI element described in an <see cref="Lpd"/> file.
/// </summary>
public class LpdElement
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a the relative path to the IGD storing the image of this element.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// Gets or sets a the area at which the UI element appears.
/// </summary>
public Rectangle Rectangle { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public int Properties { get; set; }
/// <summary>
/// Gets or sets a value which must lie between 0-4 (inclusive) for the game to accept the element.
/// </summary>
public int Version { get; set; }
}
}
using System;
using System.Drawing;
namespace Syroot.Worms.Mgame
{
/// <summary>
/// Represents a single UI element described in an <see cref="Lpd"/> file.
/// </summary>
public class LpdElement
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a the relative path to the IGD storing the images of this element.
/// </summary>
public string FileName { get; set; } = String.Empty;
/// <summary>
/// Gets or sets a the area at which the UI element appears.
/// </summary>
public Rectangle Rectangle { get; set; }
public int Properties { get; set; }
/// <summary>
/// Gets or sets a value which must lie between 0-4 (inclusive) for the game to accept the element.
/// </summary>
public int Version { get; set; }
}
}

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Mgame</AssemblyName>
<Description>.NET library for loading and modifying files of Mgame Worms clients.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);online worms;worms world party aqua</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,160 +0,0 @@
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.WorldParty
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by WWP. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> 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 LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets the height of the water in pixels.
/// </summary>
public int WaterHeight { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
WaterHeight = reader.ReadInt32();
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
// Read the image data.
Foreground = new Img(stream, true);
CollisionMask = new Img(stream, true);
Background = new Img(stream, true);
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
// Write the image data.
Foreground.Save(writer.BaseStream, false, true);
CollisionMask.Save(writer.BaseStream, false, true);
Background.Save(writer.BaseStream, false, true);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.WorldParty</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms World Party.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms world party</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

File diff suppressed because it is too large Load Diff

View File

@ -1,122 +1,118 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.WorldParty
{
/// <summary>
/// Represents the list of teams and unlocked game features stored in WGT files.
/// Used by WWP. See https://worms2d.info/File_formats.
/// </summary>
public class TeamContainer : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WWP"; // 0-terminated.
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> 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 TeamContainer(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public TeamContainer(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value possibly indicating a version of the file format.
/// </summary>
public byte Version { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown1 { get; set; }
/// <summary>
/// Gets or sets an unknown value.
/// </summary>
public byte Unknown2 { get; set; }
/// <summary>
/// Gets or sets 840 unknown bytes, all possibly 0.
/// </summary>
public byte[] Unknown3 { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public List<Team> Teams { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
throw new InvalidDataException("Invalid WWP file signature.");
Version = reader.Read1Byte(); // Really version?
// Read global settings.
byte teamCount = reader.Read1Byte();
Unknown1 = reader.Read1Byte();
Unknown2 = reader.Read1Byte();
Unknown3 = reader.ReadBytes(840);
// Read the teams.
Teams = new List<Team>(reader.Load<Team>(teamCount));
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature, StringCoding.ZeroTerminated);
writer.Write(Version);
// Write global settings.
writer.Write((byte)Teams.Count);
writer.Write(Unknown1);
writer.Write(Unknown2);
writer.Write(Unknown3);
// Write the teams.
foreach (Team team in Teams)
team.Save(writer.BaseStream);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.WorldParty
{
/// <summary>
/// Represents the list of teams and unlocked game features stored in WGT files.
/// Used by WWP. See https://worms2d.info/File_formats.
/// </summary>
public class TeamContainer : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const uint _signature = 0x00505757; // "WWP\0"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> 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 TeamContainer(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public TeamContainer(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value possibly indicating a version of the file format.
/// </summary>
public byte Version { get; set; }
public byte Unknown1 { get; set; }
public byte Unknown2 { get; set; }
/// <summary>
/// Gets or sets 840 unknown bytes, all possibly 0.
/// </summary>
public byte[] Unknown3 { get; set; } = new byte[840];
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadUInt32() != _signature)
throw new InvalidDataException("Invalid WWP file signature.");
Version = reader.Read1Byte(); // Really version?
// Read global settings.
byte teamCount = reader.Read1Byte();
Unknown1 = reader.Read1Byte();
Unknown2 = reader.Read1Byte();
Unknown3 = reader.ReadBytes(840);
// Read the teams.
Teams = new List<Team>(teamCount);
while (teamCount-- > 0)
Teams.Add(reader.Load<Team>());
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
writer.Write(Version);
// Write global settings.
writer.Write((byte)Teams.Count);
writer.Write(Unknown1);
writer.Write(Unknown2);
writer.Write(Unknown3);
// Write the teams.
foreach (Team team in Teams)
team.Save(writer.BaseStream);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}

View File

@ -1,167 +1,173 @@
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by W2. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND", 0x1A
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> 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 LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public Point[] ObjectLocations { get; set; }
/// <summary>
/// Gets or sets an unknown value, seeming to be 0 most of the time.
/// </summary>
public int Unknown { get; set; }
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; }
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; }
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; }
/// <summary>
/// Gets or sets an image of unknown use.
/// </summary>
public Img UnknownImage { get; set; }
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; }
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
Unknown = reader.ReadInt32();
// Read the image data.
Foreground = reader.Load<Img>();
CollisionMask = reader.Load<Img>();
Background = reader.Load<Img>();
UnknownImage = reader.Load<Img>();
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length);
writer.WriteStructs(ObjectLocations);
writer.Write(Unknown);
// Write the image data.
Foreground.Save(writer.BaseStream);
CollisionMask.Save(writer.BaseStream);
Background.Save(writer.BaseStream);
UnknownImage.Save(writer.BaseStream);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files.
/// Used by W2 and OW. S. https://worms2d.info/Land_Data_file.
/// </summary>
public class LandData : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A444E4C; // "LND\x1A"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
public LandData() { }
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> 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 LandData(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the size of the landscape in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
public bool TopBorder { get; set; }
/// <summary>
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// <summary>
/// Gets or sets an unknown value, seeming to be 0 most of the time.
/// </summary>
public int Unknown { get; set; }
/// <summary>
/// Gets or sets the visual foreground image.
/// </summary>
public Img Foreground { get; set; } = new Img();
/// <summary>
/// Gets or sets the collision mask of the landscape.
/// </summary>
public Img CollisionMask { get; set; } = new Img();
/// <summary>
/// Gets or sets the visual background image.
/// </summary>
public Img Background { get; set; } = new Img();
/// <summary>
/// Gets or sets an image of unknown use.
/// </summary>
public Img UnknownImage { get; set; } = new Img();
/// <summary>
/// Gets or sets the path to the land image file.
/// </summary>
public string LandTexturePath { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the path to the Water.dir file.
/// </summary>
public string WaterDirPath { get; set; } = String.Empty;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
int fileSize = reader.ReadInt32();
// Read the data.
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
// Read the possible object coordinate array.
ObjectLocations = new List<Point>();
int locationCount = reader.ReadInt32();
for (int i = 0; i < locationCount; i++)
ObjectLocations.Add(reader.ReadStruct<Point>());
Unknown = reader.ReadInt32();
// Read the image data.
Foreground = reader.Load<Img>();
CollisionMask = reader.Load<Img>();
Background = reader.Load<Img>();
UnknownImage = reader.Load<Img>();
// Read the file paths.
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the data.
writer.WriteStruct(Size);
writer.Write(TopBorder, BooleanCoding.Dword);
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Count);
for (int i = 0; i < ObjectLocations.Count; i++)
writer.WriteStruct(ObjectLocations[i]);
writer.Write(Unknown);
// Write the image data.
Foreground.Save(writer.BaseStream);
CollisionMask.Save(writer.BaseStream);
Background.Save(writer.BaseStream);
UnknownImage.Save(writer.BaseStream);
// Write the file paths.
writer.Write(LandTexturePath, StringCoding.ByteCharCount);
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}

View File

@ -3,222 +3,98 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Represents the method to determine the next turn's worm.
/// </summary>
public enum SchemeWormSelect : int
public enum WormSelect : int
{
/// <summary>
/// Worms are selected in the order in which they appear in the team.
/// </summary>
Sequential = 0,
/// <summary>
/// Worms are selected randomly.
/// </summary>
Random = 1,
/// <summary>
/// Worms are selected by a computed rating system.
/// </summary>
Intelligent = 2,
/// <summary>
/// Worms are selected by the player.
/// </summary>
Manual = 3
/// <summary>Worms are selected in the order in which they appear in the team.</summary>
Sequential,
/// <summary>Worms are selected randomly.</summary>
Random,
/// <summary>Worms are selected by a computed rating system.</summary>
Intelligent,
/// <summary>Worms are selected by the player.</summary>
Manual
}
/// <summary>
/// Represents the weapons in the game.
/// </summary>
public enum SchemeWeapon
public enum Weapon
{
/// <summary>
/// The Bazooka weapon.
/// </summary>
/// <summary>The Bazooka weapon.</summary>
Bazooka,
/// <summary>
/// The Homing Missile weapon.
/// </summary>
/// <summary>The Homing Missile weapon.</summary>
HomingMissile,
/// <summary>
/// The Grenade weapon.
/// </summary>
/// <summary>The Grenade weapon.</summary>
Grenade,
/// <summary>
/// The Cluster Bomb weapon.
/// </summary>
/// <summary>The Cluster Bomb weapon.</summary>
ClusterBomb,
/// <summary>
/// The Banana Bomb weapon.
/// </summary>
/// <summary>The Banana Bomb weapon.</summary>
BananaBomb,
/// <summary>
/// The Holy Hand Grenade weapon.
/// </summary>
/// <summary>The Holy Hand Grenade weapon.</summary>
HolyHandGrenade,
/// <summary>
/// The Homing Cluster Bomb weapon.
/// </summary>
/// <summary>The Homing Cluster Bomb weapon.</summary>
HomingClusterBomb,
/// <summary>
/// The Petrol Bomb weapon.
/// </summary>
/// <summary>The Petrol Bomb weapon.</summary>
PetrolBomb,
/// <summary>
/// The Shotgun weapon.
/// </summary>
/// <summary>The Shotgun weapon.</summary>
Shotgun,
/// <summary>
/// The Handgun weapon.
/// </summary>
/// <summary>The Handgun weapon.</summary>
Handgun,
/// <summary>
/// The Uzi weapon.
/// </summary>
/// <summary>The Uzi weapon.</summary>
Uzi,
/// <summary>
/// The Minigun weapon.
/// </summary>
/// <summary>The Minigun weapon.</summary>
Minigun,
/// <summary>
/// The Firepunch weapon.
/// </summary>
/// <summary>The Firepunch weapon.</summary>
Firepunch,
/// <summary>
/// The Dragonball weapon.
/// </summary>
/// <summary>The Dragonball weapon.</summary>
Dragonball,
/// <summary>
/// The Kamikaze weapon.
/// </summary>
/// <summary>The Kamikaze weapon.</summary>
Kamikaze,
/// <summary>
/// The Dynamite weapon.
/// </summary>
/// <summary>The Dynamite weapon.</summary>
Dynamite,
/// <summary>
/// The Mine weapon.
/// </summary>
/// <summary>The Mine weapon.</summary>
Mine,
/// <summary>
/// The Ming Vase weapon.
/// </summary>
/// <summary>The Ming Vase weapon.</summary>
MingVase,
/// <summary>
/// The Airstrike weapon.
/// </summary>
/// <summary>The Airstrike weapon.</summary>
Airstrike,
/// <summary>
/// The Homing Airstrike weapon.
/// </summary>
/// <summary>The Homing Airstrike weapon.</summary>
HomingAirstrike,
/// <summary>
/// The Napalm Strike weapon.
/// </summary>
/// <summary>The Napalm Strike weapon.</summary>
NapalmStrike,
/// <summary>
/// The Mail Strike weapon.
/// </summary>
/// <summary>The Mail Strike weapon.</summary>
MailStrike,
/// <summary>
/// The Girder weapon.
/// </summary>
/// <summary>The Girder weapon.</summary>
Girder,
/// <summary>
/// The Pneumatic Drill weapon.
/// </summary>
/// <summary>The Pneumatic Drill weapon.</summary>
PneumaticDrill,
/// <summary>
/// The Baseball Bat weapon.
/// </summary>
/// <summary>The Baseball Bat weapon.</summary>
BaseballBat,
/// <summary>
/// The Prod weapon.
/// </summary>
/// <summary>The Prod weapon.</summary>
Prod,
/// <summary>
/// The Teleport weapon.
/// </summary>
/// <summary>The Teleport weapon.</summary>
Teleport,
/// <summary>
/// The Ninja Rope weapon.
/// </summary>
/// <summary>The Ninja Rope weapon.</summary>
NinjaRope,
/// <summary>
/// The Bungee weapon.
/// </summary>
/// <summary>The Bungee weapon.</summary>
Bungee,
/// <summary>
/// The Parachute weapon.
/// </summary>
/// <summary>The Parachute weapon.</summary>
Parachute,
/// <summary>
/// The Sheep weapon.
/// </summary>
/// <summary>The Sheep weapon.</summary>
Sheep,
/// <summary>
/// The Mad Cow weapon.
/// </summary>
/// <summary>The Mad Cow weapon.</summary>
MadCow,
/// <summary>
/// The Old Woman weapon.
/// </summary>
/// <summary>The Old Woman weapon.</summary>
OldWoman,
/// <summary>
/// The Mortar weapon.
/// </summary>
/// <summary>The Mortar weapon.</summary>
Mortar,
/// <summary>
/// The Blowtorch weapon.
/// </summary>
/// <summary>The Blowtorch weapon.</summary>
Blowtorch,
/// <summary>
/// The Homing Pigeon weapon.
/// </summary>
/// <summary>The Homing Pigeon weapon.</summary>
HomingPigeon,
/// <summary>
/// The Super Sheep weapon.
/// </summary>
/// <summary>The Super Sheep weapon.</summary>
SuperSheep,
/// <summary>
/// The Super Banana Bomb weapon.
/// </summary>
/// <summary>The Super Banana Bomb weapon.</summary>
SuperBananaBomb
}
}

View File

@ -1,7 +1,7 @@
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -140,7 +140,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary>
public SchemeWormSelect WormSelectMode { get; set; }
public WormSelect WormSelectMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the chat box will be closed upon starting to move a worm or stays
@ -236,7 +236,7 @@ namespace Syroot.Worms.Worms2
SuddenDeathHealthDrop = reader.ReadBoolean(BooleanCoding.Dword);
IndestructibleBorder = reader.ReadBoolean(BooleanCoding.Dword);
RestrictGirders = reader.ReadBoolean(BooleanCoding.Dword);
WormSelectMode = reader.ReadEnum<SchemeWormSelect>(true);
WormSelectMode = reader.ReadEnum<WormSelect>(true);
ExtendedChatControls = reader.ReadBoolean(BooleanCoding.Dword);
HotSeatDelay = reader.ReadInt32();
EnableStockpiling = reader.ReadBoolean(BooleanCoding.Dword);

View File

@ -0,0 +1,171 @@
using System;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeapon : IEquatable<SchemeWeapon>
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>Amount of this weapon with which a team is equipped at game start. 10 and negative values represent
/// infinity.</summary>
public int Ammo;
/// <summary>Number of turns required to be taken by each team before this weapon becomes available.</summary>
public int Delay;
/// <summary>Retreat time after using this weapon. 0 uses the setting from the game options.</summary>
public int RetreatTime;
/// <summary><see langword="true"/> to preselect this weapon in the next turn; otherwise <see langword="false"/>.</summary>
public bool Remember;
public int Unused1;
/// <summary>Amount of this weapon added to the team armory when collected from a crate.</summary>
public int CrateAmmo;
/// <summary>Amount of bullets shot at once.</summary>
public int BulletCount;
/// <summary>Percentual chance of this weapon to appear in crates.</summary>
public int Probability;
/// <summary>Damage measured in health points which also determines the blast radius.</summary>
public int Damage;
/// <summary>Pushing power measured in percent.</summary>
public int ExplosionPower;
/// <summary>Offset to the bottom of an explosion, measured in percent.</summary>
public int ExplosionBias;
/// <summary>Milliseconds required before this weapon starts flying towards its target.</summary>
public int HomingDelay;
/// <summary>Length in milliseconds this weapon flies towards its target before giving up.</summary>
public int HomingTime;
/// <summary>Percentual amount this weaopn is affected by wind.</summary>
public int WindResponse;
public int Unused2;
/// <summary>Number of clusters into which this weapon explodes.</summary>
public int ClusterCount;
/// <summary>Speed in which clusters are dispersed in percent.</summary>
public int ClusterLaunchPower;
/// <summary>Angle in which clusters are dispersed in degrees.</summary>
public int ClusterLaunchAngle;
/// <summary>Damage of clusters measured in health points which also determines the blast radius.</summary>
public int ClusterDamage;
/// <summary>Overrides the fuse of this weapon, 0 for default.</summary>
public int Fuse;
/// <summary>Amount of fire created.</summary>
public int FireAmount;
/// <summary>Speed in which fire spreads, measured in percent.</summary>
public int FireSpreadSpeed;
/// <summary>Period in which fire burns, measured in percent.</summary>
public int FireTime;
/// <summary>Melee impact force in percent.</summary>
public int MeleeForce;
/// <summary>Melee impact angle in degrees.</summary>
public int MeleeAngle;
/// <summary>Melee damage in health points.</summary>
public int MeleeDamage;
/// <summary>Height of the fire punch jump, measured in percent.</summary>
public int FirepunchHeight;
/// <summary>Damage a dragon ball causes, measured in health points.</summary>
public int DragonballDamage;
/// <summary>Power in which a dragon ball launches hit worms, measured in percent.</summary>
public int DragonballPower;
/// <summary>Angle in which a dragon ball launches hit worms, measured in degrees.</summary>
public int DragonballAngle;
/// <summary>Lifetime of a launched dragon ball measured in milliseconds.</summary>
public int DragonballTime;
/// <summary>Length of digging measured in milliseconds. Applies to Kamikaze and digging tools.</summary>
public int DiggingTime;
/// <summary>Amount of airstrike clusters thrown.</summary>
public int StrikeClusterCount;
/// <summary>Angle in which bullets are dispersed, measured in degrees.</summary>
public int BulletSpreadAngle;
// ---- OPERATORS ----------------------------------------------------------------------------------------------
public static bool operator ==(SchemeWeapon left, SchemeWeapon right) => left.Equals(right);
public static bool operator !=(SchemeWeapon left, SchemeWeapon right) => !(left == right);
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is SchemeWeapon weapon && Equals(weapon);
/// <inheritdoc/>
public bool Equals(SchemeWeapon other)
=> Ammo == other.Ammo
&& Delay == other.Delay
&& RetreatTime == other.RetreatTime
&& Remember == other.Remember
&& Unused1 == other.Unused1
&& CrateAmmo == other.CrateAmmo
&& BulletCount == other.BulletCount
&& Probability == other.Probability
&& Damage == other.Damage
&& ExplosionPower == other.ExplosionPower
&& ExplosionBias == other.ExplosionBias
&& HomingDelay == other.HomingDelay
&& HomingTime == other.HomingTime
&& WindResponse == other.WindResponse
&& Unused2 == other.Unused2
&& ClusterCount == other.ClusterCount
&& ClusterLaunchPower == other.ClusterLaunchPower
&& ClusterLaunchAngle == other.ClusterLaunchAngle
&& ClusterDamage == other.ClusterDamage
&& Fuse == other.Fuse
&& FireAmount == other.FireAmount
&& FireSpreadSpeed == other.FireSpreadSpeed
&& FireTime == other.FireTime
&& MeleeForce == other.MeleeForce
&& MeleeAngle == other.MeleeAngle
&& MeleeDamage == other.MeleeDamage
&& FirepunchHeight == other.FirepunchHeight
&& DragonballDamage == other.DragonballDamage
&& DragonballPower == other.DragonballPower
&& DragonballAngle == other.DragonballAngle
&& DragonballTime == other.DragonballTime
&& DiggingTime == other.DiggingTime
&& StrikeClusterCount == other.StrikeClusterCount
&& BulletSpreadAngle == other.BulletSpreadAngle;
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Ammo);
hash.Add(Delay);
hash.Add(RetreatTime);
hash.Add(Remember);
hash.Add(Unused1);
hash.Add(CrateAmmo);
hash.Add(BulletCount);
hash.Add(Probability);
hash.Add(Damage);
hash.Add(ExplosionPower);
hash.Add(ExplosionBias);
hash.Add(HomingDelay);
hash.Add(HomingTime);
hash.Add(WindResponse);
hash.Add(Unused2);
hash.Add(ClusterCount);
hash.Add(ClusterLaunchPower);
hash.Add(ClusterLaunchAngle);
hash.Add(ClusterDamage);
hash.Add(Fuse);
hash.Add(FireAmount);
hash.Add(FireSpreadSpeed);
hash.Add(FireTime);
hash.Add(MeleeForce);
hash.Add(MeleeAngle);
hash.Add(MeleeDamage);
hash.Add(FirepunchHeight);
hash.Add(DragonballDamage);
hash.Add(DragonballPower);
hash.Add(DragonballAngle);
hash.Add(DragonballTime);
hash.Add(DiggingTime);
hash.Add(StrikeClusterCount);
hash.Add(BulletSpreadAngle);
return hash.ToHashCode();
}
}
}

View File

@ -1,182 +0,0 @@
using System.Runtime.InteropServices;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeaponSetting
{
/// <summary>
/// The amount of this weapon with which a team is equipped at game start. 10 and negative values represent
/// infinity.
/// </summary>
public int Ammunition;
/// <summary>
/// The number of turns required to be taken by each team before this weapon becomes available.
/// </summary>
public int Delay;
/// <summary>
/// Retreat time after using this weapon. 0 uses the setting from the game options.
/// </summary>
public int RetreatTime;
/// <summary>
/// <c>true</c> to preselect this weapon in the next turn; otherwise <c>false</c>.
/// </summary>
public bool Remember;
/// <summary>
/// An unused field with unknown value.
/// </summary>
public int Unused1;
/// <summary>
/// The amount of this weapon added to the team armory when collected from a crate.
/// </summary>
public int CrateAmmunition;
/// <summary>
/// The amount of bullets shot at once.
/// </summary>
public int BulletCount;
/// <summary>
/// The percentual chance of this weapon to appear in crates.
/// </summary>
public int Probability;
/// <summary>
/// The damage measured in health points which also determines the blast radius.
/// </summary>
public int Damage;
/// <summary>
/// The pushing power measured in percent.
/// </summary>
public int ExplosionPower;
/// <summary>
/// The offset to the bottom of an explosion, measured in percent.
/// </summary>
public int ExplosionBias;
/// <summary>
/// The milliseconds required before this weapon starts flying towards its target.
/// </summary>
public int HomingDelay;
/// <summary>
/// The length in milliseconds this weapon flies towards its target before giving up.
/// </summary>
public int HomingTime;
/// <summary>
/// The percentual amount this weaopn is affected by wind.
/// </summary>
public int WindResponse;
/// <summary>
/// An unused field with unknown value.
/// </summary>
public int Unused2;
/// <summary>
/// The number of clusters into which this weapon explodes.
/// </summary>
public int ClusterCount;
/// <summary>
/// The speed in which clusters are dispersed in percent.
/// </summary>
public int ClusterLaunchPower;
/// <summary>
/// The angle in which clusters are dispersed in degrees.
/// </summary>
public int ClusterLaunchAngle;
/// <summary>
/// The damage of clusters measured in health points which also determines the blast radius.
/// </summary>
public int ClusterDamage;
/// <summary>
/// Overrides the fuse of this weapon, 0 for default.
/// </summary>
public int Fuse;
/// <summary>
/// The amount of fire created.
/// </summary>
public int FireAmount;
/// <summary>
/// The speed in which fire spreads, measured in percent.
/// </summary>
public int FireSpreadSpeed;
/// <summary>
/// The period in which fire burns, measured in percent.
/// </summary>
public int FireTime;
/// <summary>
/// The melee impact force in percent.
/// </summary>
public int MeleeForce;
/// <summary>
/// The melee impact angle in degrees.
/// </summary>
public int MeleeAngle;
/// <summary>
/// The melee damage in health points.
/// </summary>
public int MeleeDamage;
/// <summary>
/// The height of the fire punch jump, measured in percent.
/// </summary>
public int FirepunchHeight;
/// <summary>
/// The damage a dragon ball causes, measured in health points.
/// </summary>
public int DragonballDamage;
/// <summary>
/// The power in which a dragon ball launches hit worms, measured in percent.
/// </summary>
public int DragonballPower;
/// <summary>
/// The angle in which a dragon ball launches hit worms, measured in degrees.
/// </summary>
public int DragonballAngle;
/// <summary>
/// The life time of a launched dragon ball measured in milliseconds.
/// </summary>
public int DragonballTime;
/// <summary>
/// The length of digging measured in milliseconds. Applies to Kamikaze and digging tools.
/// </summary>
public int DiggingTime;
/// <summary>
/// The amount of airstrike clusters thrown.
/// </summary>
public int StrikeClusterCount;
/// <summary>
/// The angle in which bullets are dispersed, measured in degrees.
/// </summary>
public int BulletSpreadAngle;
}
}

View File

@ -1,94 +1,107 @@
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents scheme weapons stored in an WEP file which contains armory configuration.
/// Used by W2. S. https://worms2d.info/Weapons_file.
/// </summary>
public class SchemeWeapons : ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _trashLength = 16;
private const string _signature = "WEPFILE"; // Zero-terminated.
private const int _weaponCount = 38;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class.
/// </summary>
public SchemeWeapons() => Weapons = new SchemeWeaponSetting[_weaponCount];
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> 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 SchemeWeapons(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public SchemeWeapons(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of <see cref="SchemeWeaponSetting"/> instances, each mapping to one weapon at the index of
/// the <see cref="SchemeWeapon"/> enumeration.
/// </summary>
public SchemeWeaponSetting[] Weapons { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
reader.Seek(_trashLength);
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
throw new InvalidDataException("Invalid WEP file signature.");
// Read the weapon settings.
Weapons = new SchemeWeaponSetting[_weaponCount];
for (int i = 0; i < _weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeaponSetting>();
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(new byte[_trashLength]);
writer.Write(_signature, StringCoding.ZeroTerminated);
// Write the weapon settings.
foreach (SchemeWeaponSetting weapon in Weapons)
writer.WriteStruct(weapon);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents scheme weapons stored in an WEP file which contains armory configuration.
/// Used by W2. S. https://worms2d.info/Weapons_file.
/// </summary>
public class SchemeWeapons : ILoadableFile, ISaveableFile, IEquatable<SchemeWeapons?>
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _trashLength = 16;
private const string _signature = "WEPFILE"; // Zero-terminated.
private const int _weaponCount = 38;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class.
/// </summary>
public SchemeWeapons() { }
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> 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 SchemeWeapons(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public SchemeWeapons(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the array of <see cref="SchemeWeapon"/> instances, each mapping to one weapon at the index of
/// the <see cref="Weapon"/> enumeration.
/// </summary>
public SchemeWeapon[] Weapons { get; set; } = new SchemeWeapon[_weaponCount];
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object? obj) => Equals(obj as SchemeWeapons);
/// <inheritdoc/>
public bool Equals(SchemeWeapons? other)
=> other != null
&& Weapons.SequenceEqual(other.Weapons);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(Weapons);
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
reader.Seek(_trashLength);
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
throw new InvalidDataException("Invalid WEP file signature.");
// Read the weapon settings.
Weapons = new SchemeWeapon[_weaponCount];
for (int i = 0; i < _weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeapon>();
}
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(new byte[_trashLength]);
writer.Write(_signature, StringCoding.ZeroTerminated);
// Write the weapon settings.
foreach (SchemeWeapon weapon in Weapons)
writer.WriteStruct(weapon);
}
/// <inheritdoc/>
public void Save(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
}
}
}

View File

@ -3,9 +3,9 @@
<PropertyGroup>
<AssemblyName>Syroot.Worms.Worms2</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms 2.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms 2</PackageTags>
<Version>3.2.0</Version>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -1,202 +1,205 @@
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents a team stored in a <see cref="TeamContainer"/> file.
/// </summary>
public class Team : ILoadable, ISaveable
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public short Unknown1 { get; set; }
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; }
/// <summary>
/// Gets or sets the 8 worm names.
/// </summary>
public string[] WormNames { get; set; }
public int Unknown2 { get; set; }
public int Unknown3 { get; set; }
public int Unknown4 { get; set; }
public int Unknown5 { get; set; }
public int Unknown6 { get; set; }
public int Unknown7 { get; set; }
public int Unknown8 { get; set; }
public int Unknown9 { get; set; }
public int Unknown10 { get; set; }
public int Unknown11 { get; set; }
public int Unknown12 { get; set; }
public int Unknown13 { get; set; }
public int Unknown14 { get; set; }
public int Unknown15 { get; set; }
public int Unknown16 { get; set; }
public int Unknown17 { get; set; }
public int Unknown18 { get; set; }
public int Unknown19 { get; set; }
public int Unknown20 { get; set; }
public int Unknown21 { get; set; }
public int Unknown22 { get; set; }
public int Unknown23 { get; set; }
public int Unknown24 { get; set; }
public int Unknown25 { get; set; }
/// <summary>
/// Gets or sets the number of games lost.
/// </summary>
public int GamesLost { get; set; }
/// <summary>
/// Gets or sets the number of games won.
/// </summary>
public int GamesWon { get; set; }
public int Unknown26 { get; set; }
public int Unknown27 { get; set; }
/// <summary>
/// Gets or sets the number of opponent worms killed by this team.
/// </summary>
public int Kills { get; set; }
/// <summary>
/// Gets or sets the number of worms which got killed in this team.
/// </summary>
public int Deaths { get; set; }
/// <summary>
/// Gets or sets the AI intelligence difficulty level, from 0-100, where 0 is human-controlled.
/// </summary>
public int CpuLevel { get; set; }
public int Unknown28 { get; set; }
public int Unknown29 { get; set; }
public int Unknown30 { get; set; }
/// <summary>
/// Gets or sets the "difference" statistics value.
/// </summary>
public int Difference { get; set; }
/// <summary>
/// Gets or sets the number of games played, always being 0 for AI controlled teams.
/// </summary>
public int GamesPlayed { get; set; }
/// <summary>
/// Gets or sets the points gained by this team.
/// </summary>
public int Points { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Unknown1 = reader.ReadInt16();
Name = reader.ReadString(66);
SoundBankName = reader.ReadString(36);
WormNames = reader.ReadStrings(8, 20);
Unknown2 = reader.ReadInt32();
Unknown3 = reader.ReadInt32();
Unknown4 = reader.ReadInt32();
Unknown5 = reader.ReadInt32();
Unknown6 = reader.ReadInt32();
Unknown7 = reader.ReadInt32();
Unknown8 = reader.ReadInt32();
Unknown9 = reader.ReadInt32();
Unknown10 = reader.ReadInt32();
Unknown11 = reader.ReadInt32();
Unknown12 = reader.ReadInt32();
Unknown13 = reader.ReadInt32();
Unknown14 = reader.ReadInt32();
Unknown15 = reader.ReadInt32();
Unknown16 = reader.ReadInt32();
Unknown17 = reader.ReadInt32();
Unknown18 = reader.ReadInt32();
Unknown19 = reader.ReadInt32();
Unknown20 = reader.ReadInt32();
Unknown21 = reader.ReadInt32();
Unknown22 = reader.ReadInt32();
Unknown23 = reader.ReadInt32();
Unknown24 = reader.ReadInt32();
Unknown25 = reader.ReadInt32();
GamesLost = reader.ReadInt32();
GamesWon = reader.ReadInt32();
Unknown26 = reader.ReadInt32();
Unknown27 = reader.ReadInt32();
Kills = reader.ReadInt32();
Deaths = reader.ReadInt32();
CpuLevel = reader.ReadInt32();
Unknown28 = reader.ReadInt32();
Unknown29 = reader.ReadInt32();
Unknown30 = reader.ReadInt32();
Difference = reader.ReadInt32();
GamesPlayed = reader.ReadInt32();
Points = reader.ReadInt32();
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.Write(Unknown1);
writer.WriteString(Name, 66);
writer.WriteString(SoundBankName, 36);
writer.WriteStrings(WormNames, 20);
writer.Write(Unknown2);
writer.Write(Unknown3);
writer.Write(Unknown4);
writer.Write(Unknown5);
writer.Write(Unknown6);
writer.Write(Unknown7);
writer.Write(Unknown8);
writer.Write(Unknown9);
writer.Write(Unknown10);
writer.Write(Unknown11);
writer.Write(Unknown12);
writer.Write(Unknown13);
writer.Write(Unknown14);
writer.Write(Unknown15);
writer.Write(Unknown16);
writer.Write(Unknown17);
writer.Write(Unknown18);
writer.Write(Unknown19);
writer.Write(Unknown20);
writer.Write(Unknown21);
writer.Write(Unknown22);
writer.Write(Unknown23);
writer.Write(Unknown24);
writer.Write(Unknown25);
writer.Write(GamesLost);
writer.Write(GamesWon);
writer.Write(Unknown26);
writer.Write(Unknown27);
writer.Write(Kills);
writer.Write(Deaths);
writer.Write(CpuLevel);
writer.Write(Unknown28);
writer.Write(Unknown29);
writer.Write(Unknown30);
writer.Write(Kills);
writer.Write(Deaths);
writer.Write(Difference);
writer.Write(GamesPlayed);
writer.Write(Points);
}
}
}
using System;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
/// <summary>
/// Represents a team stored in a <see cref="TeamContainer"/> file.
/// </summary>
public class Team : ILoadable, ISaveable
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public short Unknown1 { get; set; }
/// <summary>
/// Gets or sets the name of the team.
/// </summary>
public string Name { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the name of soundbank for the voice of team worms.
/// </summary>
public string SoundBankName { get; set; } = String.Empty;
/// <summary>
/// Gets the 8 worm names.
/// </summary>
public string[] WormNames { get; } = new string[8];
public int Unknown2 { get; set; }
public int Unknown3 { get; set; }
public int Unknown4 { get; set; }
public int Unknown5 { get; set; }
public int Unknown6 { get; set; }
public int Unknown7 { get; set; }
public int Unknown8 { get; set; }
public int Unknown9 { get; set; }
public int Unknown10 { get; set; }
public int Unknown11 { get; set; }
public int Unknown12 { get; set; }
public int Unknown13 { get; set; }
public int Unknown14 { get; set; }
public int Unknown15 { get; set; }
public int Unknown16 { get; set; }
public int Unknown17 { get; set; }
public int Unknown18 { get; set; }
public int Unknown19 { get; set; }
public int Unknown20 { get; set; }
public int Unknown21 { get; set; }
public int Unknown22 { get; set; }
public int Unknown23 { get; set; }
public int Unknown24 { get; set; }
public int Unknown25 { get; set; }
/// <summary>
/// Gets or sets the number of games lost.
/// </summary>
public int GamesLost { get; set; }
/// <summary>
/// Gets or sets the number of games won.
/// </summary>
public int GamesWon { get; set; }
public int Unknown26 { get; set; }
public int Unknown27 { get; set; }
/// <summary>
/// Gets or sets the number of opponent worms killed by this team.
/// </summary>
public int Kills { get; set; }
/// <summary>
/// Gets or sets the number of worms which got killed in this team.
/// </summary>
public int Deaths { get; set; }
/// <summary>
/// Gets or sets the AI intelligence difficulty level, from 0-100, where 0 is human-controlled.
/// </summary>
public int CpuLevel { get; set; }
public int Unknown28 { get; set; }
public int Unknown29 { get; set; }
public int Unknown30 { get; set; }
/// <summary>
/// Gets or sets the "difference" statistics value.
/// </summary>
public int Difference { get; set; }
/// <summary>
/// Gets or sets the number of games played, always being 0 for AI controlled teams.
/// </summary>
public int GamesPlayed { get; set; }
/// <summary>
/// Gets or sets the points gained by this team.
/// </summary>
public int Points { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
Unknown1 = reader.ReadInt16();
Name = reader.ReadString(66);
SoundBankName = reader.ReadString(36);
for (int i = 0; i < WormNames.Length; i++)
WormNames[i] = reader.ReadString(20);
Unknown2 = reader.ReadInt32();
Unknown3 = reader.ReadInt32();
Unknown4 = reader.ReadInt32();
Unknown5 = reader.ReadInt32();
Unknown6 = reader.ReadInt32();
Unknown7 = reader.ReadInt32();
Unknown8 = reader.ReadInt32();
Unknown9 = reader.ReadInt32();
Unknown10 = reader.ReadInt32();
Unknown11 = reader.ReadInt32();
Unknown12 = reader.ReadInt32();
Unknown13 = reader.ReadInt32();
Unknown14 = reader.ReadInt32();
Unknown15 = reader.ReadInt32();
Unknown16 = reader.ReadInt32();
Unknown17 = reader.ReadInt32();
Unknown18 = reader.ReadInt32();
Unknown19 = reader.ReadInt32();
Unknown20 = reader.ReadInt32();
Unknown21 = reader.ReadInt32();
Unknown22 = reader.ReadInt32();
Unknown23 = reader.ReadInt32();
Unknown24 = reader.ReadInt32();
Unknown25 = reader.ReadInt32();
GamesLost = reader.ReadInt32();
GamesWon = reader.ReadInt32();
Unknown26 = reader.ReadInt32();
Unknown27 = reader.ReadInt32();
Kills = reader.ReadInt32();
Deaths = reader.ReadInt32();
CpuLevel = reader.ReadInt32();
Unknown28 = reader.ReadInt32();
Unknown29 = reader.ReadInt32();
Unknown30 = reader.ReadInt32();
Difference = reader.ReadInt32();
GamesPlayed = reader.ReadInt32();
Points = reader.ReadInt32();
}
/// <inheritdoc/>
public void Save(Stream stream)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
writer.Write(Unknown1);
writer.WriteFixedString(Name, 66);
writer.WriteFixedString(SoundBankName, 36);
for (int i = 0; i < 8; i++)
writer.WriteFixedString(WormNames[i], 20);
writer.Write(Unknown2);
writer.Write(Unknown3);
writer.Write(Unknown4);
writer.Write(Unknown5);
writer.Write(Unknown6);
writer.Write(Unknown7);
writer.Write(Unknown8);
writer.Write(Unknown9);
writer.Write(Unknown10);
writer.Write(Unknown11);
writer.Write(Unknown12);
writer.Write(Unknown13);
writer.Write(Unknown14);
writer.Write(Unknown15);
writer.Write(Unknown16);
writer.Write(Unknown17);
writer.Write(Unknown18);
writer.Write(Unknown19);
writer.Write(Unknown20);
writer.Write(Unknown21);
writer.Write(Unknown22);
writer.Write(Unknown23);
writer.Write(Unknown24);
writer.Write(Unknown25);
writer.Write(GamesLost);
writer.Write(GamesWon);
writer.Write(Unknown26);
writer.Write(Unknown27);
writer.Write(Kills);
writer.Write(Deaths);
writer.Write(CpuLevel);
writer.Write(Unknown28);
writer.Write(Unknown29);
writer.Write(Unknown30);
writer.Write(Kills);
writer.Write(Deaths);
writer.Write(Difference);
writer.Write(GamesPlayed);
writer.Write(Points);
}
}
}

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2
{
@ -17,7 +17,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary>
public TeamContainer() => Teams = new List<Team>();
public TeamContainer() { }
/// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given
@ -37,7 +37,7 @@ namespace Syroot.Worms.Worms2
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored.
/// </summary>
public List<Team> Teams { get; set; }
public IList<Team> Teams { get; set; } = new List<Team>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------

View File

@ -1,204 +1,266 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms
{
/// <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[]>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A524944; // "DIR", 0x1A
private const 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.
/// </summary>
public Archive() { }
/// <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 BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid DIR 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 DIR 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(StringCoding.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 BinaryStream writer = new BinaryStream(stream, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
uint 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);
writer.SatisfyOffset(tocOffset, 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];
uint nextEntryOffset = writer.ReserveOffset();
writer.Write(entry.Offset);
writer.Write(entry.Length);
writer.Write(entry.Name, StringCoding.ZeroTerminated);
writer.Align(4);
if (j < entries.Count - 1)
writer.SatisfyOffset(nextEntryOffset, (int)writer.Position - tocStart);
}
fileEntryOffset = (int)writer.Position - tocStart;
}
}
}
writer.SatisfyOffset(fileSizeOffset, 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;
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
/// <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 : IDictionary<string, byte[]>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A524944; // "DIR", 0x1A
private const int _tocSignature = 0x0000000A;
private const int _hashBits = 10;
private const int _hashSize = 1 << _hashBits;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly IDictionary<string, byte[]> _entries = new Dictionary<string, byte[]>();
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Archive"/> class.
/// </summary>
public Archive() { }
/// <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);
// ---- OPERATORS ----------------------------------------------------------------------------------------------
/// <inheritdoc/>
public byte[] this[string key]
{
get => _entries[key];
set => _entries[key] = value;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <inheritdoc/>
public ICollection<string> Keys => _entries.Keys;
/// <inheritdoc/>
public ICollection<byte[]> Values => _entries.Values;
/// <inheritdoc/>
public int Count => _entries.Count;
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.IsReadOnly => false;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Add(string key, byte[] value) => _entries.Add(key, value);
/// <inheritdoc/>
public void Clear() => _entries.Clear();
/// <inheritdoc/>
public bool ContainsKey(string key) => _entries.ContainsKey(key);
/// <inheritdoc/>
public IEnumerator<KeyValuePair<string, byte[]>> GetEnumerator() => _entries.GetEnumerator();
/// <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 BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid DIR 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 DIR 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(StringCoding.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);
}
/// <inheritdoc/>
public bool Remove(string key) => _entries.Remove(key);
/// <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 BinaryStream writer = new BinaryStream(stream, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
uint 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.
uint tocStart = (uint)writer.Position;
uint fileEntryOffset = sizeof(int) + _hashSize * sizeof(int);
writer.SatisfyOffset(tocOffset, 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];
uint nextEntryOffset = writer.ReserveOffset();
writer.Write(entry.Offset);
writer.Write(entry.Length);
writer.Write(entry.Name, StringCoding.ZeroTerminated);
writer.Align(4);
if (j < entries.Count - 1)
writer.SatisfyOffset(nextEntryOffset, (uint)writer.Position - tocStart);
}
fileEntryOffset = (uint)writer.Position - tocStart;
}
}
}
writer.SatisfyOffset(fileSizeOffset, 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);
}
/// <inheritdoc/>
public bool TryGetValue(string key, out byte[] value) => _entries.TryGetValue(key, out value);
// ---- 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;
}
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <inheritdoc/>
void ICollection<KeyValuePair<string, byte[]>>.Add(KeyValuePair<string, byte[]> item) => _entries.Add(item);
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.Contains(KeyValuePair<string, byte[]> item) => _entries.Contains(item);
/// <inheritdoc/>
void ICollection<KeyValuePair<string, byte[]>>.CopyTo(KeyValuePair<string, byte[]>[] array, int arrayIndex) => _entries.CopyTo(array, arrayIndex);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _entries.GetEnumerator();
/// <inheritdoc/>
bool ICollection<KeyValuePair<string, byte[]>>.Remove(KeyValuePair<string, byte[]> item) => _entries.Remove(item);
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
private struct HashTableEntry
{
internal string Name;
internal int Offset;
internal int Length;
}
}
}

View File

@ -31,7 +31,7 @@ namespace Syroot.Worms.Core
/// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param>
/// <param name="index">The 0-based index of the bit to check.</param>
/// <returns><c>true</c> when the bit is set; otherwise <c>false</c>.</returns>
/// <returns><see langword="true"/> when the bit is set; otherwise <see langword="false"/>.</returns>
public static bool GetBit(this byte self, int index) => (self & (1 << index)) != 0;
/// <summary>
@ -62,7 +62,7 @@ namespace Syroot.Worms.Core
/// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param>
/// <param name="index">The 0-based index of the bit to enable or disable.</param>
/// <param name="enable"><c>true</c> to enable the bit; otherwise <c>false</c>.</param>
/// <param name="enable"><see langword="true"/> to enable the bit; otherwise <see langword="false"/>.</param>
/// <returns>The current byte with the bit enabled or disabled.</returns>
public static byte SetBit(this byte self, int index, bool enable)
=> enable ? EnableBit(self, index) : DisableBit(self, index);

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace Syroot.Worms.Core.Graphics
{
/// <summary>
/// Represents a collection of methods helping with palettized images.
/// </summary>
public static class BitmapTools
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Creates an indexed <see cref="Bitmap"/> with the given <paramref name="size"/> and
/// <paramref name="palette"/> from the provided raw <paramref name="data"/> array.
/// </summary>
/// <param name="size">The dimensions of the image.</param>
/// <param name="palette">The palette as a <see cref="Color"/> array.</param>
/// <param name="data">The data array storing bytes indexing the palette color array.</param>
/// <returns>The <see cref="Bitmap"/> instance.</returns>
public static unsafe Bitmap CreateIndexed(Size size, IList<Color> palette, byte[] data)
{
// Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
for (int y = 0; y < size.Height; y++)
data.AsSpan(y * size.Width, size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
bitmap.UnlockBits(bitmapData);
// Transfer the palette.
ColorPalette bitmapPalette = bitmap.Palette;
for (int i = 0; i < palette.Count; i++)
bitmapPalette.Entries[i] = palette[i];
bitmap.Palette = bitmapPalette;
return bitmap;
}
}
}

View File

@ -1,265 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Syroot.BinaryData;
namespace Syroot.Worms.Core.IO
{
/// <summary>
/// Represents extension methods for <see cref="BinaryStream"/> instances.
/// </summary>
[DebuggerStepThrough]
public static partial class BinaryStreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// ---- Reading ----
/// <summary>
/// Reads an <see cref="ILoadable"/> instance from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The <see cref="ILoadable"/> instance.</returns>
public static T Load<T>(this BinaryStream self) where T : ILoadable, new()
{
T instance = new T();
instance.Load(self.BaseStream);
return instance;
}
/// <summary>
/// Reads <see cref="ILoadable"/> instances from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of instances to read.</param>
/// <returns>The <see cref="ILoadable"/> instances.</returns>
public static T[] Load<T>(this BinaryStream self, int count) where T : ILoadable, new()
{
T[] instances = new T[count];
for (int i = 0; i < count; i++)
instances[i] = Load<T>(self);
return instances;
}
/// <summary>
/// Reads an RGBA 32-bit color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The read color.</returns>
public static Color ReadColor(this BinaryStream self) => Color.FromArgb(self.ReadInt32());
/// <summary>
/// Reads <paramref name="count"/> RGBA 32-bit colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The read colors.</returns>
public static Color[] ReadColors(this BinaryStream self, int count)
{
Color[] values = new Color[count];
for (int i = 0; i < count; i++)
values[i] = Color.FromArgb(self.ReadInt32());
return values;
}
/// <summary>
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read string.</returns>
public static string ReadString(this BinaryStream self, int length)
{
// TODO: This may not work with multi-byte encodings.
// Ensure to not try to decode any bytes after the 0 termination.
byte[] bytes = self.ReadBytes(length);
while (bytes[--length] == 0 && length > 0) { }
return self.Encoding.GetString(bytes, 0, length + 1);
}
/// <summary>
/// Reads <paramref name="count"/> 0-terminated strings which are stored in a fixed-size block of
/// <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read strings.</returns>
public static string[] ReadStrings(this BinaryStream self, int count, int length)
{
string[] strings = new string[count];
for (int i = 0; i < count; i++)
strings[i] = ReadString(self, length);
return strings;
}
/// <summary>
/// Reads a raw byte structure from the current stream and returns it.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The structure of type <typeparamref name="T"/>.</returns>
public static T ReadStruct<T>(this BinaryStream self) where T : unmanaged
{
// Read the raw bytes of the structure.
#if NETSTANDARD2_0
byte[] bytes = new byte[Unsafe.SizeOf<T>()];
self.Read(bytes, 0, bytes.Length);
#else
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>()];
self.Read(bytes);
#endif
// Convert them to a structure instance and return it.
ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
return span[0];
}
/// <summary>
/// Reads raw byte structures from the current stream and returns them.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The structures of type <typeparamref name="T"/>.</returns>
public static T[] ReadStructs<T>(this BinaryStream self, int count) where T : unmanaged
{
// Read the raw bytes of the structures.
#if NETSTANDARD2_0
byte[] bytes = new byte[Unsafe.SizeOf<T>()];
self.Read(bytes, 0, bytes.Length);
#else
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>() * count];
self.Read(bytes);
#endif
// Convert them to a structure array and return it.
ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
return span.ToArray();
}
/// <summary>
/// Returns the current address of the stream to which a 4-byte placeholder has been written which can be filled
/// later.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The address at which a 4-byte placeholder has been written to.</returns>
public static uint ReserveOffset(this BinaryStream self)
{
uint offset = (uint)self.Position;
self.WriteUInt32(0);
return offset;
}
// ---- Writing ----
/// <summary>
/// Writes the given <see cref="ISaveable"/> instance into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, T value) where T : ISaveable
{
value.Save(self.BaseStream);
}
/// <summary>
/// Writes the given <see cref="ISaveable"/> instances into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The instances to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, IList<T> values) where T : ISaveable
{
foreach (T value in values)
Save(self, value);
}
public static void SatisfyOffset(this BinaryStream self, uint offset, int value)
{
using (self.TemporarySeek(offset, SeekOrigin.Begin))
self.WriteInt32(value);
}
/// <summary>
/// Writes the given color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The color to write.</param>
public static void WriteColor(this BinaryStream self, Color color)
=> self.Write(color.R << 24 | color.G << 16 | color.B << 8 | color.A);
/// <summary>
/// Writes the given colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The colors to write.</param>
public static void WriteColors(this BinaryStream self, IEnumerable<Color> colors)
{
foreach (Color color in colors)
WriteColor(self, color);
}
/// <summary>
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The string to write.</param>
/// <param name="length">The number of bytes the fixed-size block takes.</param>
public static void WriteString(this BinaryStream self, string value, int length)
{
// TODO: This may not work with multi-byte encodings.
byte[] bytes = new byte[length];
if (value != null)
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
self.WriteBytes(bytes);
}
/// <summary>
/// Writes the given strings into fixed-size blocks of <paramref name="length"/> bytes and 0-terminates them.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The strings to write.</param>
/// <param name="length">The number of bytes a fixed-size block takes.</param>
public static void WriteStrings(this BinaryStream self, IEnumerable<string> values, int length)
{
foreach (string value in values)
WriteString(self, value, length);
}
/// <summary>
/// Writes the bytes of a structure into the current stream.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The structure to write.</param>
public static unsafe void WriteStruct<T>(this BinaryStream self, T value) where T : unmanaged
{
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>());
#if NETSTANDARD2_0
self.Write(bytes.ToArray()); // cannot prevent copy in .NET Standard 2.0
#else
self.Write(bytes);
#endif
}
/// <summary>
/// Writes the bytes of structures into the current stream.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The structures to write.</param>
public static void WriteStructs<T>(this BinaryStream self, IEnumerable<T> values) where T : unmanaged
{
foreach (T value in values)
WriteStruct(self, value);
}
}
}

View File

@ -1,17 +1,18 @@
using System.Drawing;
namespace Syroot.Worms.Core.Graphics
{
/// <summary>
/// Represents an interface for any class storing indexed image palette colors.
/// </summary>
public interface IPalette
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="Color"/> values stored by this palette.
/// </summary>
Color[] Colors { get; set; }
}
}
using System.Collections.Generic;
using System.Drawing;
namespace Syroot.Worms.Graphics
{
/// <summary>
/// Represents an interface for any class storing indexed image palette colors.
/// </summary>
public interface IPalette
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="Color"/> values stored by this palette.
/// </summary>
IList<Color> Colors { get; set; }
}
}

View File

@ -1,61 +1,85 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace Syroot.Worms
{
/// <summary>
/// Represents a pixel-based 2D image in different color formats.
/// </summary>
public class RawBitmap
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the number of bits required to describe a color per pixel.
/// </summary>
public byte BitsPerPixel { get; set; }
/// <summary>
/// Gets or sets the colors in the palette of the bitmap, if it has one.
/// </summary>
public IList<Color> Palette { get; set; }
/// <summary>
/// Gets or sets the size of the image in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets the data of the image pixels.
/// </summary>
public byte[] Data { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="Bitmap"/> from the raw data.
/// </summary>
/// <returns>The <see cref="Bitmap"/> created from the raw data.</returns>
public unsafe Bitmap ToBitmap()
{
// Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace Syroot.Worms
{
/// <summary>
/// Represents a pixel-based 2D image in different color formats.
/// </summary>
public class RawBitmap
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the number of bits required to describe a color per pixel.
/// </summary>
public byte BitsPerPixel { get; set; }
/// <summary>
/// Gets or sets the colors in the palette of the bitmap, if it has one.
/// </summary>
public IList<Color>? Palette { get; set; }
/// <summary>
/// Gets or sets the size of the image in pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets the data of the image pixels.
/// </summary>
public byte[] Data { get; set; } = Array.Empty<byte>();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Creates a <see cref="Bitmap"/> from the raw data.
/// </summary>
/// <returns>The <see cref="Bitmap"/> created from the raw data.</returns>
public unsafe Bitmap ToBitmap()
{
// Create bitmap with appropriate pixel format.
PixelFormat pixelFormat = BitsPerPixel switch
{
1 => PixelFormat.Format1bppIndexed,
8 => PixelFormat.Format8bppIndexed,
32 => PixelFormat.Format32bppRgb,
_ => throw new NotSupportedException($"Cannot convert to {BitsPerPixel}bpp bitmap.")
};
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, pixelFormat);
// Transfer the pixel data, respecting power-of-2 strides.
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, pixelFormat);
float bytesPerPixel = BitsPerPixel / 8f;
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
for (int y = 0; y < Size.Height; y++)
Data.AsSpan(y * Size.Width, Size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
bitmap.UnlockBits(bitmapData);
// Transfer the palette.
ColorPalette bitmapPalette = bitmap.Palette;
for (int i = 0; i < Palette.Count; i++)
bitmapPalette.Entries[i] = Palette[i];
bitmap.Palette = bitmapPalette;
return bitmap;
}
}
}
{
Data.AsSpan((int)(y * Size.Width * bytesPerPixel), (int)(Size.Width * bytesPerPixel))
.CopyTo(bitmapSpan.Slice((int)(y * bitmapData.Stride * bytesPerPixel)));
}
bitmap.UnlockBits(bitmapData);
// Transfer any palette.
switch (pixelFormat)
{
case PixelFormat.Format1bppIndexed:
ColorPalette palette1bpp = bitmap.Palette;
palette1bpp.Entries[0] = Color.Black;
palette1bpp.Entries[1] = Color.White;
bitmap.Palette = palette1bpp;
break;
case PixelFormat.Format8bppIndexed:
ColorPalette palette8bpp = bitmap.Palette;
for (int i = 0; i < Palette!.Count; i++)
palette8bpp.Entries[i] = Palette[i];
bitmap.Palette = palette8bpp;
break;
}
return bitmap;
}
}
}

View File

@ -1,80 +1,115 @@
using System;
using System.IO;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for
/// graphic file formats.
/// S. http://worms2d.info/Team17_compression.
/// </summary>
internal static class Team17Compression
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Returns the data available in <paramref name="bytes"/> in compressed format.
/// </summary>
/// <param name="bytes">The data to compress.</param>
/// <returns>The compressed data.</returns>
internal static byte[] Compress(byte[] bytes)
=> throw new NotImplementedException("Compressing data has not been implemented yet.");
/// <summary>
/// Decompresses the data available in the given <paramref name="stream"/> into the provided
/// <paramref name="buffer"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the data from.</param>
/// <param name="buffer">The byte array buffer to write the decompressed data to.</param>
internal static void Decompress(Stream stream, byte[] buffer)
{
// TODO: This fails for compressed data in CD:\Data\Mission\Training0-9.img.
int output = 0; // Offset of next write.
int cmd;
while ((cmd = stream.ReadByte()) != -1)
{
// Read a byte.
if ((cmd & 0x80) == 0)
{
// Command: 1 byte (color)
buffer[output++] = (byte)cmd;
}
else
{
// Arg1 = bits 2-5
int arg1 = (cmd >> 3) & 0xF;
int arg2 = stream.ReadByte();
if (arg2 == -1)
return;
// Arg2 = bits 6-16
arg2 = ((cmd << 8) | arg2) & 0x7FF;
if (arg1 == 0)
{
// Command: 0x80 0x00
if (arg2 == 0)
return;
int arg3 = stream.ReadByte();
if (arg3 == -1)
return;
// Command: 3 bytes
output = CopyData(output, arg2, arg3 + 18, buffer);
}
else
{
// Command: 2 bytes
output = CopyData(output, arg2 + 1, arg1 + 2, buffer);
}
}
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static int CopyData(int offset, int compressedOffset, int count, byte[] buffer)
{
for (; count > 0; count--)
buffer[offset] = buffer[offset++ - compressedOffset];
return offset;
}
}
}
using System;
using System.IO;
namespace Syroot.Worms.Graphics
{
/// <summary>
/// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for
/// graphic file formats.
/// S. http://worms2d.info/Team17_compression.
/// </summary>
internal static class Team17Compression
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Returns the data available in <paramref name="bytes"/> in compressed format.
/// </summary>
/// <param name="bytes">The data to compress.</param>
/// <returns>The compressed data.</returns>
internal static byte[] Compress(ReadOnlySpan<byte> bytes)
=> throw new NotImplementedException("Compressing data has not been implemented yet.");
/// <summary>
/// Decompresses the data available in the given <paramref name="stream"/> into the provided
/// <paramref name="buffer"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the data from.</param>
/// <param name="buffer">The byte buffer to write the decompressed data to.</param>
internal static void Decompress(Stream stream, Span<byte> buffer)
{
int output = 0; // Offset of next write.
int cmd;
while ((cmd = stream.ReadByte()) != -1)
{
// Read a byte.
if ((cmd & 0x80) == 0)
{
// Command: 1 byte (color)
buffer[output++] = (byte)cmd;
}
else
{
int arg1 = cmd >> 3 & 0b1111; // bits 2-5
int arg2 = stream.ReadByte();
if (arg2 == -1)
return;
// Arg2 = bits 6-16
arg2 = ((cmd << 8) | arg2) & 0x7FF;
if (arg1 == 0)
{
// Command: 0x80 0x00
if (arg2 == 0)
return;
int arg3 = stream.ReadByte();
if (arg3 == -1)
return;
// Command: 3 bytes
output = CopyData(output, arg2, arg3 + 18, buffer);
}
else
{
// Command: 2 bytes
output = CopyData(output, arg2 + 1, arg1 + 2, buffer);
}
}
}
}
// Own Span reimplementation, has the same issues with some out-of-bounds-access maps. Supports color remap.
//internal static int Decompress(ReadOnlySpan<byte> src, Span<byte> dst, ReadOnlySpan<byte> colorMap)
//{
// int srcPos = 0;
// int dstPos = 0;
// int count, seek;
//
// while (true)
// {
// while (true)
// {
// while ((src[srcPos] & 0b1000_0000) == 0)
// dst[dstPos++] = colorMap[src[srcPos++]];
//
// if ((src[srcPos] & 0b0111_1000) == 0)
// break;
//
// count = (src[srcPos] >> 3 & 0b0000_1111) + 2;
// seek = (src[srcPos + 1] | ((src[srcPos] & 0b0000_0111) << 8)) + 1;
// while (count-- > 0)
// dst[dstPos] = dst[dstPos++ - seek];
// srcPos += 2;
// }
//
// seek = src[srcPos + 1] | (src[srcPos] & 0b0000_0111) << 8;
// if (seek == 0)
// break;
// count = src[srcPos + 2] + 18;
// while (count-- > 0)
// dst[dstPos] = dst[dstPos++ - seek];
//
// srcPos += 3;
// }
//
// return dstPos;
//}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static int CopyData(int offset, int compressedOffset, int count, Span<byte> buffer)
{
for (; count > 0; count--)
buffer[offset] = buffer[offset++ - compressedOffset];
return offset;
}
}
}

View File

@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents extension methods for <see cref="BinaryStream"/> instances.
/// </summary>
public static partial class BinaryStreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// ---- Reading ----
/// <summary>
/// Reads an <see cref="ILoadable"/> instance from the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The <see cref="ILoadable"/> instance.</returns>
public static T Load<T>(this BinaryStream self) where T : ILoadable, new()
{
T instance = new T();
instance.Load(self.BaseStream);
return instance;
}
/// <summary>
/// Reads an RGBA 32-bit color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The read color.</returns>
public static Color ReadColor(this BinaryStream self) => Color.FromArgb(self.ReadInt32());
/// <summary>
/// Reads <paramref name="count"/> RGBA 32-bit colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="count">The number of values to read.</param>
/// <returns>The read colors.</returns>
public static Color[] ReadColors(this BinaryStream self, int count)
{
Color[] values = new Color[count];
for (int i = 0; i < count; i++)
values[i] = Color.FromArgb(self.ReadInt32());
return values;
}
/// <summary>
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param>
/// <returns>The read string.</returns>
public static string ReadFixedString(this BinaryStream self, int length)
{
// TODO: This may not work with multi-byte encodings.
// Ensure to not try to decode any bytes after the 0 termination.
byte[] bytes = self.ReadBytes(length);
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
return self.Encoding.GetString(bytes, 0, length);
}
/// <summary>
/// Returns the current position of the stream at which a 4-byte placeholder has been written which can be
/// filled later with <see cref="SatisfyOffset"/>.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <returns>The position at which a 4-byte placeholder has been written to.</returns>
public static uint ReserveOffset(this BinaryStream self)
{
uint offset = (uint)self.Position;
self.WriteUInt32(0);
return offset;
}
// ---- Writing ----
/// <summary>
/// Writes the given <see cref="ISaveable"/> instance into the current stream.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this BinaryStream self, T value) where T : ISaveable
{
value.Save(self.BaseStream);
}
/// <summary>
/// Writes the given <paramref name="value"/> to the given <paramref name="offset"/>. This is meant to be used
/// in combination with <see cref="ReserveOffset"/>.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="offset">The position at which to write the value.</param>
/// <param name="value">The value to write.</param>
public static void SatisfyOffset(this BinaryStream self, uint offset, uint value)
{
using var _ = self.TemporarySeek(offset, SeekOrigin.Begin);
self.WriteUInt32(value);
}
/// <summary>
/// Writes the given color.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The color to write.</param>
public static void WriteColor(this BinaryStream self, Color color)
=> self.Write(color.A << 24 | color.R << 16 | color.G << 8 | color.B);
/// <summary>
/// Writes the given colors.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The colors to write.</param>
public static void WriteColors(this BinaryStream self, IEnumerable<Color> colors)
{
foreach (Color color in colors)
WriteColor(self, color);
}
/// <summary>
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
/// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The string to write.</param>
/// <param name="length">The number of bytes the fixed-size block takes.</param>
public static void WriteFixedString(this BinaryStream self, string value, int length)
{
// TODO: This may not work with multi-byte encodings.
byte[] bytes = new byte[length];
if (value != null)
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
self.WriteBytes(bytes);
}
}
}

View File

@ -1,6 +1,6 @@
using System.IO;
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents data which can be loaded by providing a <see cref="Stream"/> to read from.

View File

@ -1,4 +1,4 @@
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents a file which can be loaded by providing a file name.

View File

@ -1,6 +1,6 @@
using System.IO;
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents data which can be saved by providing a <see cref="Stream "/>to write to.

View File

@ -1,4 +1,4 @@
namespace Syroot.Worms.Core.IO
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents a file which can be saved by providing a file name.

View File

@ -4,7 +4,7 @@ using System.IO;
using System.Reflection;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core.IO;
using Syroot.Worms.IO;
namespace Syroot.Worms.Core.Riff
{
@ -82,10 +82,10 @@ namespace Syroot.Worms.Core.Riff
chunkSaver.Value.Invoke(this, new object[] { writer });
writer.SatisfyOffset(chunkSizeOffset, (int)(writer.Position - chunkSizeOffset - 4));
writer.SatisfyOffset(chunkSizeOffset, (uint)(writer.Position - chunkSizeOffset - 4));
}
writer.SatisfyOffset(fileSizeOffset, (int)(writer.Position - 8));
writer.SatisfyOffset(fileSizeOffset, (uint)(writer.Position - 8));
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
@ -102,8 +102,6 @@ namespace Syroot.Worms.Core.Riff
typeData.FileIdentifier = typeInfo.GetCustomAttribute<RiffFileAttribute>().Identifier;
// Get the chunk loading and saving handlers.
typeData.ChunkLoaders = new Dictionary<string, MethodInfo>();
typeData.ChunkSavers = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in typeInfo.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
{
RiffChunkLoadAttribute loadAttribute = method.GetCustomAttribute<RiffChunkLoadAttribute>();
@ -137,9 +135,9 @@ namespace Syroot.Worms.Core.Riff
private class TypeData
{
internal string FileIdentifier;
internal Dictionary<string, MethodInfo> ChunkLoaders;
internal Dictionary<string, MethodInfo> ChunkSavers;
internal string FileIdentifier = String.Empty;
internal IDictionary<string, MethodInfo> ChunkLoaders = new Dictionary<string, MethodInfo>();
internal IDictionary<string, MethodInfo> ChunkSavers = new Dictionary<string, MethodInfo>();
}
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Syroot.Worms.IO
{
/// <summary>
/// Represents extension methods for <see cref="Stream"/> instances.
/// </summary>
public static class StreamExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Reads the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to read.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
/// <returns>The read value.</returns>
public static T ReadStruct<T>(this Stream stream) where T : unmanaged
{
Span<T> span = stackalloc T[1];
stream.Read(MemoryMarshal.Cast<T, byte>(span));
return span[0];
}
/// <summary>
/// Reads the unmanaged representations of structs of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the structs to read.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to read with.</param>
/// <param name="span">A span receiving the read instances.</param>
public static void ReadStructs<T>(this Stream stream, Span<T> span) where T : unmanaged
=> stream.Read(MemoryMarshal.Cast<T, byte>(span));
/// <summary>
/// Writes the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="value">The value to write.</param>
public static unsafe void WriteStruct<T>(this Stream stream, T value) where T : unmanaged
=> stream.Write(new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>()));
/// <summary>
/// Writes the unmanaged representation of a struct of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="value">The value to write.</param>
public static unsafe void WriteStruct<T>(this Stream stream, ref T value) where T : unmanaged
=> stream.Write(new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>()));
/// <summary>
/// Writes the unmanaged representations of structs of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the struct to write.</typeparam>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="span">The values to write.</param>
public static unsafe void WriteStructs<T>(this Stream stream, ReadOnlySpan<T> span) where T : unmanaged
=> stream.Write(MemoryMarshal.Cast<T, byte>(span));
// ---- Backports ----
#if NETSTANDARD2_0
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the
/// position within the stream by the number of bytes read.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced
/// by the bytes read from the current source.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated
/// in the buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been
/// reached.</returns>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static int Read(this Stream stream, Span<byte> buffer)
{
byte[] bytes = new byte[buffer.Length];
int bytesRead = stream.Read(bytes);
bytes.AsSpan(0, bytesRead).CopyTo(buffer);
return bytesRead;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the
/// current position within this stream by the number of bytes written.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance.</param>
/// <param name="value">A region of memory. This method copies the contents of this region to the current
/// stream.</param>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static void Write(this Stream stream, ReadOnlySpan<byte> value) => stream.Write(value.ToArray());
#endif
}
}

View File

@ -1,252 +1,254 @@
using System;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Core;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms
{
/// <summary>
/// Represents a (palettized) graphical image stored in an IMG file, possibly compressed.
/// Used by W2, WA and WWP. S. https://worms2d.info/Image_file.
/// </summary>
public class Img : RawBitmap, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A474D49; // "IMG", 0x1A
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class.
/// </summary>
public Img() { }
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> 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 Img(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Img(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given
/// <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public Img(Stream stream, bool alignData) => Load(stream, alignData);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets an optional description of the image contents.
/// </summary>
public string Description { get; private set; }
// ---- 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) => Load(stream, false);
/// <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>
/// Loads the data from the given <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Load(Stream stream, bool alignData)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid IMG file signature.");
int fileSize = reader.ReadInt32();
// Read an optional string describing the image contents and the bits per pixel.
BitsPerPixel = reader.Read1Byte();
if (BitsPerPixel == 0)
{
Description = String.Empty;
BitsPerPixel = reader.Read1Byte();
}
else if (BitsPerPixel > 32)
{
Description = (char)BitsPerPixel + reader.ReadString(StringCoding.ZeroTerminated);
BitsPerPixel = reader.Read1Byte();
}
else
{
Description = null;
}
// 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] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte());
}
else
{
Palette = null;
}
// Read the image size.
Size = new Size(reader.ReadInt16(), reader.ReadInt16());
// Read the data byte array, which might be compressed or aligned.
if (alignData)
reader.Align(4);
byte[] data = new byte[Size.Width * Size.Height * BitsPerPixel / 8];
if (flags.HasFlag(Flags.Compressed))
Team17Compression.Decompress(reader.BaseStream, data);
else
data = reader.ReadBytes(data.Length);
Data = data;
}
/// <summary>
/// Loads the data from the given file. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Load(string fileName, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, alignData);
}
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
public void Save(Stream stream) => Save(stream, false, false);
/// <summary>
/// Saves the optionally compressed data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
public void Save(Stream stream, bool compress) => Save(stream, compress, false);
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
public void Save(string fileName) => Save(fileName, false, false);
/// <summary>
/// Saves the optionally compressed data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
public void Save(string fileName, bool compress) => Save(fileName, compress, false);
/// <summary>
/// Saves the optionally compressed data into the given <paramref name="stream"/>. The data block can be aligned
/// to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool compress, bool alignData)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write an optional string describing the image contents and the bits per pixel.
if (Description != null)
writer.Write(Description, StringCoding.ZeroTerminated);
writer.Write(BitsPerPixel);
// Write image flags describing the format and availability of the following contents.
Flags flags = Flags.None;
if (Palette != null)
flags |= Flags.Palettized;
if (compress)
flags |= Flags.Compressed;
writer.WriteEnum(flags, true);
// Write the image palette if available. The first color of the palette is implicitly black.
if (Palette != null)
{
writer.Write((short)(Palette.Count - 1));
for (int i = 1; i < Palette.Count; i++)
{
Color color = Palette[i];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
}
}
// Write the image size.
writer.Write((short)Size.Width);
writer.Write((short)Size.Height);
// Write the data byte array, which might be compressed or aligned.
if (alignData)
writer.Align(4);
byte[] data = Data;
if (compress)
data = Team17Compression.Compress(data);
writer.Write(data);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position);
}
/// <summary>
/// Saves the optionally compressed data in the given file. The data block can be aligned to a 4-byte boundary.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <param name="compress"><c>true</c> to compress image data.</param>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param>
public void Save(string fileName, bool compress, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream, compress, alignData);
}
// ---- ENUMERATIONS -------------------------------------------------------------------------------------------
[Flags]
private enum Flags : byte
{
None = 0,
Compressed = 1 << 6,
Palettized = 1 << 7
}
}
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
/// <summary>
/// Represents a (palletized) graphical image stored in an IMG file, possibly compressed.
/// Used by W2, WA and WWP. S. https://worms2d.info/Image_file.
/// </summary>
public class Img : RawBitmap, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
/// <summary>Magic value identifying the start of IMG data.</summary>
public const uint Signature = 0x1A474D49; // "IMG\x1A"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class.
/// </summary>
public Img() { }
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> 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 Img(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Img(string fileName) => Load(fileName);
/// <summary>
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given
/// <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public Img(Stream stream, bool alignData) => Load(stream, alignData);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value indicating data was compressed or should be compressed when saving.
/// </summary>
public bool Compressed { get; private set; }
/// <summary>
/// Gets an optional description of the image contents.
/// </summary>
public string? Description { get; private set; }
// ---- 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>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(Stream stream) => Load(stream, false);
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <summary>
/// Loads the data from the given <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(Stream stream, bool alignData)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read the header.
if (reader.ReadUInt32() != Signature)
throw new InvalidDataException("Invalid IMG file signature.");
int fileSize = reader.ReadInt32();
// Read an optional string describing the image contents and the bits per pixel.
BitsPerPixel = reader.Read1Byte();
if (BitsPerPixel == 0)
{
Description = String.Empty;
BitsPerPixel = reader.Read1Byte();
}
else if (BitsPerPixel > 32)
{
Description = (char)BitsPerPixel + reader.ReadString(StringCoding.ZeroTerminated);
BitsPerPixel = reader.Read1Byte();
}
else
{
Description = null;
}
// 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 List<Color>(colorCount + 1);
Palette.Add(Color.Black);
while (colorCount-- > 0)
Palette.Add(Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()));
}
else
{
Palette = null;
}
// Read the image size.
Size = new Size(reader.ReadInt16(), reader.ReadInt16());
// Read the data byte array, which might be compressed or aligned.
if (alignData)
reader.Align(4);
int dataSize = (int)(BitsPerPixel / 8f * Size.Width * Size.Height);
if (flags.HasFlag(Flags.Compressed))
{
// Some shipped images require up to 3 bytes to not fail when decompressing with out-of-bounds access.
Data = new byte[dataSize];
Team17Compression.Decompress(reader.BaseStream, Data);
}
else
{
Data = reader.ReadBytes(dataSize);
}
}
/// <summary>
/// Loads the data from the given file. The data block can be aligned to a 4-bte boundary.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
/// <exception cref="IndexOutOfRangeException">Compressed images required more bytes than usually to decompress.
/// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
public void Load(string fileName, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, alignData);
}
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
public void Save(Stream stream) => Save(stream, false);
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
public void Save(string fileName) => Save(fileName, false);
/// <summary>
/// Saves the data into the given <paramref name="stream"/>. The data block can be aligned to a 4-byte boundary.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool alignData)
{
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the header.
writer.Write(Signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write an optional string describing the image contents and the bits per pixel.
if (Description != null)
writer.Write(Description, StringCoding.ZeroTerminated);
writer.Write(BitsPerPixel);
// Write image flags describing the format and availability of the following contents.
Flags flags = Flags.None;
if (Palette != null)
flags |= Flags.Palettized;
if (Compressed)
flags |= Flags.Compressed;
writer.WriteEnum(flags, true);
// Write the image palette if available. The first color of the palette is implicitly black.
if (Palette != null)
{
writer.Write((short)(Palette.Count - 1));
for (int i = 1; i < Palette.Count; i++)
{
Color color = Palette[i];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
}
}
// Write the image size.
writer.Write((short)Size.Width);
writer.Write((short)Size.Height);
// Write the data byte array, which might be compressed or aligned.
if (alignData)
writer.Align(4);
byte[] data = Data;
if (Compressed)
data = Team17Compression.Compress(data);
writer.Write(data);
writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
}
/// <summary>
/// Saves the data in the given file. The data block can be aligned to a 4-byte boundary.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(string fileName, bool alignData)
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream, alignData);
}
// ---- ENUMERATIONS -------------------------------------------------------------------------------------------
[Flags]
private enum Flags : byte
{
None,
Compressed = 1 << 6,
Palettized = 1 << 7
}
}
}

View File

@ -1,42 +0,0 @@
namespace Syroot.Worms
{
/// <summary>
/// Represents the map terrain configuration found in LEV and BIT files.
/// </summary>
public class LevelInfo
{
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public uint LandSeed { get; set; }
public uint ObjectSeed { get; set; }
public bool Cavern { get; set; }
public LandStyle Style { get; set; }
public bool NoBorder { get; set; }
public int ObjectPercent { get; set; }
public int BridgePercent { get; set; }
public int WaterLevel { get; set; }
public int SoilIndex { get; set; }
public int WaterColor { get; set; }
}
public enum LandStyle : int
{
SingleIsland,
DoubleIsland,
SingleSmallIsland,
DoubleSmallIsland,
Cavern,
SingleTunnel,
CavernWater,
DoubleTunnel
}
}

View File

@ -1,159 +1,161 @@
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.Graphics;
using Syroot.Worms.Core.IO;
using Syroot.Worms.Core.Riff;
namespace Syroot.Worms
{
/// <summary>
/// Represents a color palette stored in PAL files, following the RIFF format. It is used to index colors in images.
/// Used by WA and WWP. S. http://worms2d.info/Palette_file.
/// </summary>
[RiffFile("PAL ")]
public class RiffPalette : RiffFile, ILoadableFile, ISaveableFile, IPalette
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const short _version = 0x0300;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class.
/// </summary>
public RiffPalette() => Version = _version;
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> 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 RiffPalette(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public RiffPalette(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the version of the palette data.
/// </summary>
public int Version { get; set; }
/// <summary>
/// Gets the <see cref="Color"/> instances stored in this palette.
/// </summary>
public Color[] Colors { get; set; }
/// <summary>
/// Gets the unknown data in the offl chunk.
/// </summary>
public byte[] OfflData { get; set; }
/// <summary>
/// Gets the unknown data in the tran chunk.
/// </summary>
public byte[] TranData { get; set; }
/// <summary>
/// Gets the unknown data in the unde chunk.
/// </summary>
public byte[] UndeData { get; set; }
// ---- 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) => LoadRiff(stream);
/// <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 to.</param>
public void Save(Stream stream) => SaveRiff(stream);
/// <summary>
/// Saves the data in the given file.
/// </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) --------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Syroot.BinaryData;
using Syroot.Worms.Core.Riff;
using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms
{
/// <summary>
/// Represents a color palette stored in PAL files, following the RIFF format. It is used to index colors in images.
/// Used by WA and WWP. S. http://worms2d.info/Palette_file.
/// </summary>
[RiffFile("PAL ")]
public class RiffPalette : RiffFile, ILoadableFile, ISaveableFile, IPalette
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const short _version = 0x0300;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class.
/// </summary>
public RiffPalette() { }
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> 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 RiffPalette(Stream stream) => Load(stream);
/// <summary>
/// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public RiffPalette(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the version of the palette data.
/// </summary>
public int Version { get; set; } = _version;
/// <summary>
/// Gets or sets the <see cref="Color"/> instances stored in this palette.
/// </summary>
public IList<Color> Colors { get; set; } = new List<Color>();
/// <summary>
/// Gets the unknown data in the offl chunk.
/// </summary>
public byte[] OfflData { get; set; } = Array.Empty<byte>();
/// <summary>
/// Gets the unknown data in the tran chunk.
/// </summary>
public byte[] TranData { get; set; } = Array.Empty<byte>();
/// <summary>
/// Gets the unknown data in the unde chunk.
/// </summary>
public byte[] UndeData { get; set; } = Array.Empty<byte>();
// ---- 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) => LoadRiff(stream);
/// <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 to.</param>
public void Save(Stream stream) => SaveRiff(stream);
/// <summary>
/// Saves the data in the given file.
/// </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) --------------------------------------------------------------------------------------
#pragma warning disable IDE0051 // Remove unused private members
[RiffChunkLoad("data")]
private void LoadDataChunk(BinaryStream reader, int length)
{
// Read the PAL version.
Version = reader.ReadInt16();
if (Version != _version)
throw new InvalidDataException("Unknown PAL version.");
// Read the colors.
Colors = new Color[reader.ReadInt16()];
for (int i = 0; i < Colors.Length; i++)
{
Colors[i] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte());
_ = reader.ReadByte(); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkLoad("offl")]
private void LoadOfflChunk(BinaryStream reader, int length) => OfflData = reader.ReadBytes(length);
[RiffChunkLoad("tran")]
private void LoadTranChunk(BinaryStream reader, int length) => TranData = reader.ReadBytes(length);
[RiffChunkLoad("unde")]
private void LoadUndeChunk(BinaryStream reader, int length) => UndeData = reader.ReadBytes(length);
[RiffChunkSave("data")]
private void SaveDataChunk(BinaryStream writer)
{
// Write the PAL version.
writer.Write(_version);
// Write the colors.
writer.Write((short)Colors.Length);
for (int i = 0; i < Colors.Length; i++)
{
Color color = Colors[i];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
writer.Write((byte)0); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkSave("offl")]
private void SaveOfflChunk(BinaryStream writer) => writer.Write(OfflData);
[RiffChunkSave("tran")]
private void SaveTranChunk(BinaryStream writer) => writer.Write(TranData);
[RiffChunkSave("unde")]
private void LoadDataChunk(BinaryStream reader, int _)
{
// Read the PAL version.
Version = reader.ReadInt16();
if (Version != _version)
throw new InvalidDataException("Unknown PAL version.");
// Read the colors.
short colorCount = reader.ReadInt16();
Colors = new List<Color>();
while (colorCount-- > 0)
{
Colors.Add(Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()));
_ = reader.ReadByte(); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkLoad("offl")]
private void LoadOfflChunk(BinaryStream reader, int length) => OfflData = reader.ReadBytes(length);
[RiffChunkLoad("tran")]
private void LoadTranChunk(BinaryStream reader, int length) => TranData = reader.ReadBytes(length);
[RiffChunkLoad("unde")]
private void LoadUndeChunk(BinaryStream reader, int length) => UndeData = reader.ReadBytes(length);
[RiffChunkSave("data")]
private void SaveDataChunk(BinaryStream writer)
{
// Write the PAL version.
writer.Write(_version);
// Write the colors.
writer.Write((short)Colors.Count);
foreach (Color color in Colors)
{
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
writer.Write((byte)0); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkSave("offl")]
private void SaveOfflChunk(BinaryStream writer) => writer.Write(OfflData);
[RiffChunkSave("tran")]
private void SaveTranChunk(BinaryStream writer) => writer.Write(TranData);
[RiffChunkSave("unde")]
private void SaveUndeChunk(BinaryStream writer) => writer.Write(UndeData);
#pragma warning restore IDE0051
}
}
#pragma warning restore IDE0051
}
}

View File

@ -4,8 +4,8 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms</AssemblyName>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes>
<Version>3.2.0</Version>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<Version>4.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData.Serialization" Version="5.2.0" />

26
src/test.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Compilation -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>netcoreapp3</TargetFramework>
</PropertyGroup>
<!-- References -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
</ItemGroup>
<!-- Files Linking -->
<Target Name="FilesRemove" AfterTargets="Build;Clean">
<Message Text="Removing Files" Importance="high" />
<Exec Command="RD &quot;$(OutputPath)Files&quot; /S/Q" />
</Target>
<Target Name="FilesCreate" AfterTargets="Build">
<Message Text="Linking Files" Importance="high" />
<Exec Command="MKLINK /D &quot;$(OutputPath)Files&quot; &quot;$(ProjectDir)Files&quot;" />
</Target>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More