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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
{ {
WeaponAirstrikeSubstyle.Launcher => stream.ReadObject<LauncherStyle>(), WeaponAirstrikeSubstyle.Launcher => stream.ReadObject<LauncherStyle>(),
WeaponAirstrikeSubstyle.Mines => stream.ReadObject<MineStyle>(), 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; } public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))] [BinaryMember(Converter = typeof(ActionConverter))]
public IAction Action { get; set; } public IAction? Action { get; set; }
[BinaryMember(OffsetOrigin = OffsetOrigin.Begin, Offset = 180)] [BinaryMember(OffsetOrigin = OffsetOrigin.Begin, Offset = 180)]
public ExplosionTarget ExplosionTarget { get; set; } public ExplosionTarget ExplosionTarget { get; set; }
[BinaryMember(Offset = sizeof(int), Converter = typeof(TargetConverter))] [BinaryMember(Offset = sizeof(int), Converter = typeof(TargetConverter))]
public ITarget Target { get; set; } public ITarget? Target { get; set; }
} }
public enum ExplosionAction : int public enum ExplosionAction : int

View File

@ -3,9 +3,9 @@
<PropertyGroup> <PropertyGroup>
<AssemblyName>Syroot.Worms.Armageddon.ProjectX</AssemblyName> <AssemblyName>Syroot.Worms.Armageddon.ProjectX</AssemblyName>
<Description>.NET library for loading and modifying files of Worms Armageddon ProjectX.</Description> <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> <PackageTags>$(PackageTags);project x;worms armageddon</PackageTags>
<Version>3.2.0</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" /> <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; } public ExplosionAction ExplosionAction { get; set; }
[BinaryMember(Converter = typeof(ActionConverter))] [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) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public object Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute, public object? Read(Stream stream, object instance, BinaryMemberAttribute memberAttribute,
ByteConverter byteConverter) ByteConverter byteConverter)
{ {
ExplosionTarget explosionTarget = instance switch ExplosionTarget explosionTarget = instance switch
@ -17,10 +17,11 @@ namespace Syroot.Worms.Armageddon.ProjectX
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
return explosionTarget switch return explosionTarget switch
{ {
ExplosionTarget.None => null,
ExplosionTarget.Clusters => stream.ReadObject<ClusterTarget>(), ExplosionTarget.Clusters => stream.ReadObject<ClusterTarget>(),
ExplosionTarget.Fire => stream.ReadObject<FireTarget>(), ExplosionTarget.Fire => stream.ReadObject<FireTarget>(),
_ => null, _ => throw new NotImplementedException(),
}; };
} }

View File

@ -1,8 +1,9 @@
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon.ProjectX namespace Syroot.Worms.Armageddon.ProjectX
{ {
@ -49,7 +50,7 @@ namespace Syroot.Worms.Armageddon.ProjectX
public WeaponThrowAction ThrowAction { get; set; } public WeaponThrowAction ThrowAction { get; set; }
public IStyle Style { get; set; } public IStyle? Style { get; set; }
public bool AmmunitionOverride { get; set; } public bool AmmunitionOverride { get; set; }
@ -59,15 +60,15 @@ namespace Syroot.Worms.Armageddon.ProjectX
public int WeaponSprite { get; set; } 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; } public bool DelayOverride { get; set; }
@ -75,27 +76,27 @@ namespace Syroot.Worms.Armageddon.ProjectX
public bool UseLibrary { get; set; } 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; } public bool AimSpriteOverride { get; set; }
@ -243,8 +244,9 @@ namespace Syroot.Worms.Armageddon.ProjectX
NameLong = reader.ReadString(StringCoding.Int32CharCount); NameLong = reader.ReadString(StringCoding.Int32CharCount);
Name = reader.ReadString(StringCoding.Int32CharCount); Name = reader.ReadString(StringCoding.Int32CharCount);
GridImageFile = reader.ReadString(StringCoding.Int32CharCount); GridImageFile = reader.ReadString(StringCoding.Int32CharCount);
GfxDirectoryFile = reader.ReadString(StringCoding.Int32CharCount); GfxDirectoryFile = reader.ReadString(StringCoding.Int32CharCount);
SpriteNames = reader.ReadStrings(5, StringCoding.Int32CharCount); for (int i = 0; i < SpriteNames.Length; i++)
SpriteNames[i] = reader.ReadString(StringCoding.Int32CharCount);
DelayOverride = reader.ReadBoolean(BooleanCoding.Dword); DelayOverride = reader.ReadBoolean(BooleanCoding.Dword);
Delay = reader.ReadInt32(); Delay = reader.ReadInt32();

View File

@ -1,71 +1,175 @@
using System.IO; using System.IO;
using System.Text; using System.Runtime.InteropServices;
using Syroot.BinaryData; using System.Text;
using Syroot.Worms.Core.IO; using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon
{ namespace Syroot.Worms.Armageddon
/// <summary> {
/// Represents <see cref="MapGeneratorSettings"/> stored in a LEV file. /// <summary>
/// Used by WA and WWP. S. https://worms2d.info/Monochrome_map_(.bit,_.lev). /// Represents <see cref="MapGenSettings"/> stored in a LEV file.
/// </summary> /// Used by WA and WWP. S. https://worms2d.info/Monochrome_map_(.bit,_.lev).
public class GeneratedMap : ILoadableFile, ISaveableFile /// </summary>
{ public class GeneratedMap : ILoadableFile, ISaveableFile
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ {
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="GeneratedMap"/> class.
public GeneratedMap() { } /// </summary>
public GeneratedMap() { }
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedMap"/> class, loading the data from the given /// <summary>
/// <see cref="Stream"/>. /// Initializes a new instance of the <see cref="GeneratedMap"/> class, loading the data from the given
/// </summary> /// <see cref="Stream"/>.
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// </summary>
public GeneratedMap(Stream stream) => Load(stream); /// <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>
/// </summary> /// Initializes a new instance of the <see cref="GeneratedMap"/> class, loading the data from the given file.
/// <param name="fileName">The name of the file to load the data from.</param> /// </summary>
public GeneratedMap(string fileName) => Load(fileName); /// <param name="fileName">The name of the file to load the data from.</param>
public GeneratedMap(string fileName) => Load(fileName);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="MapGeneratorSettings"/>. /// <summary>
/// </summary> /// Gets or sets the <see cref="MapGenSettings"/>.
public MapGeneratorSettings Settings { get; set; } /// </summary>
public MapGenSettings Settings { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public void Load(Stream stream) /// <inheritdoc/>
{ public void Load(Stream stream)
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); {
Settings = reader.ReadStruct<MapGeneratorSettings>(); using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
} Settings = reader.ReadStruct<MapGenSettings>();
}
/// <inheritdoc/>>
public void Load(string fileName) /// <inheritdoc/>>
{ public void Load(string fileName)
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); {
Load(stream); using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
} Load(stream);
}
/// <inheritdoc/>
public void Save(Stream stream) /// <inheritdoc/>
{ public void Save(Stream stream)
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); {
writer.WriteStruct(Settings); using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
} writer.WriteStruct(Settings);
}
/// <inheritdoc/>
public void Save(string fileName) /// <inheritdoc/>
{ public void Save(string fileName)
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); {
Save(stream); 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;
using System.IO; using System.Collections.Generic;
using System.Text; using System.Drawing;
using Syroot.BinaryData; using System.IO;
using Syroot.Worms.Core.IO; using System.Runtime.CompilerServices;
using System.Text;
namespace Syroot.Worms.Armageddon using Syroot.BinaryData;
{ using Syroot.Worms.IO;
/// <summary>
/// Represents map configuration stored by the land generator in LAND.DAT files. namespace Syroot.Worms.Armageddon
/// Used by WA. S. https://worms2d.info/Land_Data_file. {
/// </summary> /// <summary>
public class LandData : ILoadableFile, ISaveableFile /// Represents map configuration stored by the land generator in LAND.DAT files.
{ /// Used by WA and WWP. S. https://worms2d.info/Land_Data_file.
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- /// </summary>
public class LandData : ILoadableFile, ISaveableFile
private const int _signature = 0x1A444E4C; // "LND", 0x1A {
// ---- FIELDS -------------------------------------------------------------------------------------------------
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
private bool _alignImgData;
/// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class. // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// </summary>
public LandData() { } /// <summary>
/// Initializes a new instance of the <see cref="LandData"/> class.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given public LandData() { }
/// <see cref="Stream"/>.
/// </summary> /// <summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given
public LandData(Stream stream) => Load(stream); /// <see cref="Stream"/>.
/// </summary>
/// <summary> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file. public LandData(Stream stream) => Load(stream);
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// <summary>
public LandData(string fileName) => Load(fileName); /// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary>
// ---- PROPERTIES --------------------------------------------------------------------------------------------- /// <param name="fileName">The name of the file to load the data from.</param>
public LandData(string fileName) => Load(fileName);
/// <summary>
/// Gets or sets the size of the landscape in pixels. // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// </summary>
public Size Size { get; set; } /// <summary>
/// Gets or sets the format version of the file.
/// <summary> /// </summary>
/// Gets or sets a value indicating whether an indestructible top border will be enabled. public LandDataVersion Version { get; set; }
/// </summary>
public bool TopBorder { 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> /// </summary>
/// Gets or sets the height of the water in pixels. public bool AlignImgData
/// </summary> {
public int WaterHeight { get; set; } get => _alignImgData;
set => _alignImgData = value;
/// <summary> }
/// Gets or sets an unknown value.
/// </summary> /// <summary>
public int Unknown { get; set; } /// Gets or sets the size of the landscape in pixels.
/// </summary>
/// <summary> public Size Size { get; set; }
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary> /// <summary>
public Point[] ObjectLocations { get; set; } /// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
/// <summary> public bool TopBorder { get; set; }
/// Gets or sets the visual foreground image.
/// </summary> /// <summary>
public Img Foreground { get; set; } /// Gets or sets the height of the water in pixels.
/// </summary>
/// <summary> public int WaterHeight { get; set; }
/// Gets or sets the collision mask of the landscape.
/// </summary> /// <summary>
public Img CollisionMask { get; set; } /// Gets or sets an unknown value only available in newer W:A versions (apparently since 3.6.28.0).
/// </summary>
/// <summary> public int? Unknown { get; set; }
/// Gets or sets the visual background image.
/// </summary> /// <summary>
public Img Background { get; set; } /// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
/// <summary> public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// Gets or sets the path to the land image file.
/// </summary> /// <summary>
public string LandTexturePath { get; set; } /// Gets or sets the visual foreground image.
/// </summary>
/// <summary> public Img Foreground { get; set; } = new Img();
/// Gets or sets the path to the Water.dir file.
/// </summary> /// <summary>
public string WaterDirPath { get; set; } /// Gets or sets the collision mask of the landscape.
/// </summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public Img CollisionMask { get; set; } = new Img();
/// <inheritdoc/> /// <summary>
public void Load(Stream stream) /// Gets or sets the visual background image.
{ /// </summary>
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); public Img Background { get; set; } = new Img();
// Read the header. /// <summary>
if (reader.ReadInt32() != _signature) /// Gets or sets the path to the land image file.
throw new InvalidDataException("Invalid LND file signature."); /// </summary>
int fileSize = reader.ReadInt32(); public string LandTexturePath { get; set; } = String.Empty;
// Read the data. /// <summary>
Size = reader.ReadStruct<Size>(); /// Gets or sets the path to the Water.dir file.
TopBorder = reader.ReadBoolean(BooleanCoding.Dword); /// </summary>
WaterHeight = reader.ReadInt32(); public string WaterDirPath { get; set; } = String.Empty;
Unknown = reader.ReadInt32();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// Read the possible object coordinate array.
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32()); /// <inheritdoc/>
public void Load(Stream stream)
// Read the image data. {
Foreground = reader.Load<Img>(); using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
CollisionMask = reader.Load<Img>();
Background = reader.Load<Img>(); int readLocationCount()
{
// Read the file paths. int oldLocationCount = reader.ReadInt32();
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount); int oldImgStart = oldLocationCount * Unsafe.SizeOf<Point>();
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
} // Check whether the old IMG start would be invalid.
bool isNew = false;
/// <inheritdoc/> using (reader.TemporarySeek(oldImgStart))
public void Load(string fileName) isNew = reader.EndOfStream || reader.ReadUInt32() != Img.Signature;
{ if (isNew)
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); {
Load(stream); Unknown = oldLocationCount;
} return reader.ReadInt32();
}
/// <inheritdoc/> else
public void Save(Stream stream) {
{ Unknown = null;
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); return oldLocationCount;
}
// Write the header. }
writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset(); Img readImage(ref bool aligned)
{
// Write the data. long imgStart = reader.Position;
writer.WriteStruct(Size); Img img = new Img(reader.BaseStream, aligned);
writer.Write(TopBorder, BooleanCoding.Dword);
writer.Write(WaterHeight); // If reading the next image fails, the previous data was aligned and must be reread.
writer.Write(Unknown); if (!aligned)
{
// Write the possible object coordinate array. if (reader.ReadUInt32() == Img.Signature)
writer.Write(ObjectLocations.Length); {
writer.WriteStructs(ObjectLocations); reader.Position -= sizeof(uint);
}
// Write the image data. else
Foreground.Save(writer.BaseStream); {
CollisionMask.Save(writer.BaseStream); reader.Position = imgStart;
Background.Save(writer.BaseStream); img = new Img(reader.BaseStream, true);
aligned = true;
// Write the file paths. }
writer.Write(LandTexturePath, StringCoding.ByteCharCount); }
writer.Write(WaterDirPath, StringCoding.ByteCharCount);
return img;
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position); }
}
// Read the header.
/// <inheritdoc/> Version = reader.ReadEnum<LandDataVersion>(true);
public void Save(string fileName) int fileSize = reader.ReadInt32();
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); // Read the data.
Save(stream); 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> /// <summary>
/// Represents the stockpiling mode of weapon armory between rounds. /// Represents the stockpiling mode of weapon armory between rounds.
/// </summary> /// </summary>
public enum StockpilingMode : byte public enum Stockpiling : byte
{ {
/// <summary>Each round starts with the exact amount of weapons set in the scheme.</summary> /// <summary>Each round starts with the exact amount of weapons set in the scheme.</summary>
Off, 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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -14,30 +15,32 @@ namespace Syroot.Worms.Armageddon
public class WeaponList : IReadOnlyCollection<SchemeWeapon> public class WeaponList : IReadOnlyCollection<SchemeWeapon>
{ {
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
private readonly SchemeWeapon[] _list = new SchemeWeapon[64]; private readonly SchemeWeapon[] _array = new SchemeWeapon[64];
/// <summary> /// <summary>
/// Gets a reference to the weapon with the given <paramref name="index"/>. /// Gets a reference to the weapon with the given <paramref name="index"/>.
/// </summary> /// </summary>
/// <param name="name">The index of the weapon to access.</param> /// <param name="name">The index of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns> /// <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> /// <summary>
/// Gets a reference to the weapon with the given <paramref name="name"/>. /// Gets a reference to the weapon with the given <paramref name="name"/>.
/// </summary> /// </summary>
/// <param name="name">The <see cref="Weapon"/> of the weapon to access.</param> /// <param name="name">The <see cref="Weapon"/> of the weapon to access.</param>
/// <returns>A reference to the <see cref="SchemeWeapon"/>.</returns> /// <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/> /// <inheritdoc/>
public int Count => _list.Length; public int Count => _array.Length;
/// <inheritdoc/> /// <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/> /// <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 System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core; using Syroot.Worms.Core;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.Armageddon namespace Syroot.Worms.Armageddon
{ {
@ -38,8 +38,8 @@ namespace Syroot.Worms.Armageddon
private byte _roundTimeMinutes; private byte _roundTimeMinutes;
private byte _roundTimeSeconds; private byte _roundTimeSeconds;
private SchemeExtendedOptions _extended = SchemeExtendedOptions.Default; private ExtendedOptions _extended = ExtendedOptions.Default;
private ushort _rwVersionOverride; private ushort _rwVersion;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -116,7 +116,7 @@ namespace Syroot.Worms.Armageddon
/// <summary> /// <summary>
/// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats. /// Gets or sets the delay in seconds between each team's turn to allow relaxed switching of seats.
/// </summary> /// </summary>
public byte HotSeatDelay { get; set; } public byte HotSeatTime { get; set; }
/// <summary> /// <summary>
/// Gets or sets the time in seconds available for a worm to retreat after using a weapon which ends the turn /// 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> /// <summary>
/// Gets or sets a value indicating whether significant turns will be replayed in offline games. /// Gets or sets a value indicating whether significant turns will be replayed in offline games.
/// </summary> /// </summary>
public bool AutomaticReplays { get; set; } public bool Replays { get; set; }
/// <summary> /// <summary>
/// Gets or sets the percentual amount of fall damage applied, relative to normal fall damage being 100%. Valid /// 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) if (value < 0 || value > 508)
throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508."); throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508.");
_fallDamage = value >> 2 << 2; _fallDamage = value >> 2 << 2;
} }
} }
@ -175,12 +174,12 @@ namespace Syroot.Worms.Armageddon
/// <summary> /// <summary>
/// Gets or sets a value indicating the stockpiling of armory between game rounds. /// Gets or sets a value indicating the stockpiling of armory between game rounds.
/// </summary> /// </summary>
public StockpilingMode StockpilingMode { get; set; } public Stockpiling Stockpiling { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played. /// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary> /// </summary>
public WormSelect WormSelectMode { get; set; } public WormSelect WormSelect { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating the action triggered when Sudden Death starts. /// 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 /// Gets or sets the percentual probability of a weapon crate to drop between turns. Negative values might crash
/// the game. /// the game.
/// </summary> /// </summary>
public sbyte WeaponCrateProbability { get; set; } public sbyte WeaponCrateProb { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether donor cards can spawn upon a worm's death. /// 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 /// Gets or sets the percentual probability of a health crate to drop between turns. Negative values might crash
/// the game. /// the game.
/// </summary> /// </summary>
public sbyte HealthCrateProbability { get; set; } public sbyte HealthCrateProb { get; set; }
/// <summary> /// <summary>
/// Gets or sets the amount of health included in a health crate added to the collecting worm's energy. /// 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 /// Gets or sets the percentual probability of a utility crate to drop between turns. Negative values might
/// crash the game. /// crash the game.
/// </summary> /// </summary>
public sbyte UtilityCrateProbability { get; set; } public sbyte UtilityCrateProb { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of objects which can be placed on the map. /// Gets or sets the type of objects which can be placed on the map.
@ -273,7 +272,7 @@ namespace Syroot.Worms.Armageddon
} }
/// <summary> /// <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> /// </summary>
/// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127 or 4.</exception> /// <exception cref="ArgumentOutOfRangeException">Value is not between 0-127 or 4.</exception>
public byte MineDelay public byte MineDelay
@ -289,7 +288,7 @@ namespace Syroot.Worms.Armageddon
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the mine fuse will be randomly chosen between fractions of 1 to 3 /// 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> /// </summary>
public bool MineDelayRandom { get; set; } public bool MineDelayRandom { get; set; }
@ -325,7 +324,7 @@ namespace Syroot.Worms.Armageddon
} }
/// <summary> /// <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. /// <see cref="TurnTime"/> setting will be ignored.
/// </summary> /// </summary>
public bool TurnTimeInfinite { get; set; } public bool TurnTimeInfinite { get; set; }
@ -393,40 +392,40 @@ namespace Syroot.Worms.Armageddon
/// <summary> /// <summary>
/// Gets or sets a value indicating whether terrain cannot be destroyed by explosions. /// Gets or sets a value indicating whether terrain cannot be destroyed by explosions.
/// </summary> /// </summary>
public bool IndestructibleLand { get; set; } public bool IndiLand { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the Grenade weapon is more powerful. /// Gets or sets a value indicating whether the Grenade weapon is more powerful.
/// </summary> /// </summary>
public bool UpgradedGrenade { get; set; } public bool UpgradeGrenade { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the Shotgun weapon shoots twice for each of the two shots. /// Gets or sets a value indicating whether the Shotgun weapon shoots twice for each of the two shots.
/// </summary> /// </summary>
public bool UpgradedShotgun { get; set; } public bool UpgradeShotgun { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether cluster weapon explode into more clusters. /// Gets or sets a value indicating whether cluster weapon explode into more clusters.
/// </summary> /// </summary>
public bool UpgradedCluster { get; set; } public bool UpgradeCluster { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the Longbow weapon is more powerful. /// Gets or sets a value indicating whether the Longbow weapon is more powerful.
/// </summary> /// </summary>
public bool UpgradedLongbow { get; set; } public bool UpgradeLongbow { get; set; }
// ---- Weapons ----
/// <summary> /// <summary>
/// Gets or sets a value indicating whether team weapons will be given to the teams, overriding the default /// Gets or sets a value indicating whether team weapons will be given to the teams, overriding the default
/// weapon settings for them. /// weapon settings for them.
/// </summary> /// </summary>
public bool EnableTeamWeapons { get; set; } public bool TeamWeapons { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether super weapons can be collected from crates. /// Gets or sets a value indicating whether super weapons can be collected from crates.
/// </summary> /// </summary>
public bool EnableSuperWeapons { get; set; } public bool SuperWeapons { get; set; }
// ---- Weapons ----
/// <summary> /// <summary>
/// Gets the list of <see cref="SchemeWeapon"/> instances, of which each weapon can be accessed by index or /// 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> /// <summary>
/// Gets or sets <see cref="SchemeVersion.Version3"/> or RubberWorm settings. /// Gets or sets <see cref="SchemeVersion.Version3"/> or RubberWorm settings.
/// </summary> /// </summary>
public ref SchemeExtendedOptions Extended => ref _extended; public ref ExtendedOptions Extended => ref _extended;
/// <summary> /// <summary>
/// Gets or sets trailing bytes which could not be parsed and will be attached when saving the scheme anew. /// 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> /// </summary>
/// <remarks>Configurable with the /version command in RubberWorm. /// <remarks>Configurable with the /version command in RubberWorm.
/// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions.</remarks> /// S. http://worms2d.info/List_of_Worms_Armageddon_logic_versions.</remarks>
public ushort RwVersionOverride public ushort RwVersion
{ {
get => _rwVersionOverride; get => _rwVersion;
set set
{ {
_rwVersionOverride = value; _rwVersion = value;
// Configure V3 settings according to selected version. // Configure V3 settings according to selected version.
void setBattyRope1To2() => Extended.BattyRope = true; void setBattyRope1To2() => Extended.BattyRope = true;
@ -535,7 +534,7 @@ namespace Syroot.Worms.Armageddon
Extended.WormSelectKeepHotSeat = true; Extended.WormSelectKeepHotSeat = true;
} }
switch (_rwVersionOverride) switch (_rwVersion)
{ {
case 11: // 3.5 Beta 3pre15[BattyRope1] case 11: // 3.5 Beta 3pre15[BattyRope1]
case 12: // 3.5 Beta 3pre15[BattyRope2] case 12: // 3.5 Beta 3pre15[BattyRope2]
@ -845,30 +844,29 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => Equals(obj as Scheme); public override bool Equals(object? obj) => Equals(obj as Scheme);
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Scheme other) public bool Equals(Scheme? other)
=> other == this => other != null
|| other != null
&& Version == other.Version && Version == other.Version
&& HotSeatDelay == other.HotSeatDelay && HotSeatTime == other.HotSeatTime
&& RetreatTime == other.RetreatTime && RetreatTime == other.RetreatTime
&& RetreatTimeRope == other.RetreatTimeRope && RetreatTimeRope == other.RetreatTimeRope
&& ShowRoundTime == other.ShowRoundTime && ShowRoundTime == other.ShowRoundTime
&& AutomaticReplays == other.AutomaticReplays && Replays == other.Replays
&& FallDamage == other.FallDamage && FallDamage == other.FallDamage
&& ArtilleryMode == other.ArtilleryMode && ArtilleryMode == other.ArtilleryMode
&& SchemeEditor == other.SchemeEditor && SchemeEditor == other.SchemeEditor
&& StockpilingMode == other.StockpilingMode && Stockpiling == other.Stockpiling
&& WormSelectMode == other.WormSelectMode && WormSelect == other.WormSelect
&& SuddenDeathEvent == other.SuddenDeathEvent && SuddenDeathEvent == other.SuddenDeathEvent
&& WaterRiseRate == other.WaterRiseRate && WaterRiseRate == other.WaterRiseRate
&& WeaponCrateProbability == other.WeaponCrateProbability && WeaponCrateProb == other.WeaponCrateProb
&& DonorCards == other.DonorCards && DonorCards == other.DonorCards
&& HealthCrateProbability == other.HealthCrateProbability && HealthCrateProb == other.HealthCrateProb
&& HealthCrateEnergy == other.HealthCrateEnergy && HealthCrateEnergy == other.HealthCrateEnergy
&& UtilityCrateProbability == other.UtilityCrateProbability && UtilityCrateProb == other.UtilityCrateProb
&& ObjectTypes == other.ObjectTypes && ObjectTypes == other.ObjectTypes
&& ObjectCount == other.ObjectCount && ObjectCount == other.ObjectCount
&& MineDelay == other.MineDelay && MineDelay == other.MineDelay
@ -885,40 +883,40 @@ namespace Syroot.Worms.Armageddon
&& AquaSheep == other.AquaSheep && AquaSheep == other.AquaSheep
&& SheepHeaven == other.SheepHeaven && SheepHeaven == other.SheepHeaven
&& GodWorms == other.GodWorms && GodWorms == other.GodWorms
&& IndestructibleLand == other.IndestructibleLand && IndiLand == other.IndiLand
&& UpgradedGrenade == other.UpgradedGrenade && UpgradeGrenade == other.UpgradeGrenade
&& UpgradedShotgun == other.UpgradedShotgun && UpgradeShotgun == other.UpgradeShotgun
&& UpgradedCluster == other.UpgradedCluster && UpgradeCluster == other.UpgradeCluster
&& UpgradedLongbow == other.UpgradedLongbow && UpgradeLongbow == other.UpgradeLongbow
&& EnableTeamWeapons == other.EnableTeamWeapons && TeamWeapons == other.TeamWeapons
&& EnableSuperWeapons == other.EnableSuperWeapons && SuperWeapons == other.SuperWeapons
&& Weapons.SequenceEqual(other.Weapons) && Weapons.SequenceEqual(other.Weapons)
&& Extended.Equals(ref other.Extended) && Extended.Equals(other.Extended)
&& Attachment.SequenceEqual(other.Attachment) && Attachment.SequenceEqual(other.Attachment)
&& RwVersionOverride == other.RwVersionOverride; && RwVersion == other.RwVersion;
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
HashCode hash = new HashCode(); HashCode hash = new HashCode();
hash.Add(Version); hash.Add(Version);
hash.Add(HotSeatDelay); hash.Add(HotSeatTime);
hash.Add(RetreatTime); hash.Add(RetreatTime);
hash.Add(RetreatTimeRope); hash.Add(RetreatTimeRope);
hash.Add(ShowRoundTime); hash.Add(ShowRoundTime);
hash.Add(AutomaticReplays); hash.Add(Replays);
hash.Add(FallDamage); hash.Add(FallDamage);
hash.Add(ArtilleryMode); hash.Add(ArtilleryMode);
hash.Add(SchemeEditor); hash.Add(SchemeEditor);
hash.Add(StockpilingMode); hash.Add(Stockpiling);
hash.Add(WormSelectMode); hash.Add(WormSelect);
hash.Add(SuddenDeathEvent); hash.Add(SuddenDeathEvent);
hash.Add(WaterRiseRate); hash.Add(WaterRiseRate);
hash.Add(WeaponCrateProbability); hash.Add(WeaponCrateProb);
hash.Add(DonorCards); hash.Add(DonorCards);
hash.Add(HealthCrateProbability); hash.Add(HealthCrateProb);
hash.Add(HealthCrateEnergy); hash.Add(HealthCrateEnergy);
hash.Add(UtilityCrateProbability); hash.Add(UtilityCrateProb);
hash.Add(ObjectTypes); hash.Add(ObjectTypes);
hash.Add(ObjectCount); hash.Add(ObjectCount);
hash.Add(MineDelay); hash.Add(MineDelay);
@ -935,19 +933,19 @@ namespace Syroot.Worms.Armageddon
hash.Add(AquaSheep); hash.Add(AquaSheep);
hash.Add(SheepHeaven); hash.Add(SheepHeaven);
hash.Add(GodWorms); hash.Add(GodWorms);
hash.Add(IndestructibleLand); hash.Add(IndiLand);
hash.Add(UpgradedGrenade); hash.Add(UpgradeGrenade);
hash.Add(UpgradedShotgun); hash.Add(UpgradeShotgun);
hash.Add(UpgradedCluster); hash.Add(UpgradeCluster);
hash.Add(UpgradedLongbow); hash.Add(UpgradeLongbow);
hash.Add(EnableTeamWeapons); hash.Add(TeamWeapons);
hash.Add(EnableSuperWeapons); hash.Add(SuperWeapons);
foreach (SchemeWeapon weapon in Weapons) foreach (SchemeWeapon weapon in Weapons)
hash.Add(weapon); hash.Add(weapon);
hash.Add(Extended); hash.Add(Extended);
foreach (byte attachmentByte in Attachment) foreach (byte attachmentByte in Attachment)
hash.Add(attachmentByte); hash.Add(attachmentByte);
hash.Add(RwVersionOverride); hash.Add(RwVersion);
return hash.ToHashCode(); return hash.ToHashCode();
} }
@ -1030,23 +1028,23 @@ namespace Syroot.Worms.Armageddon
Version = reader.ReadEnum<SchemeVersion>(true); Version = reader.ReadEnum<SchemeVersion>(true);
// Read the options. // Read the options.
HotSeatDelay = reader.Read1Byte(); HotSeatTime = reader.Read1Byte();
RetreatTime = reader.Read1Byte(); RetreatTime = reader.Read1Byte();
RetreatTimeRope = reader.Read1Byte(); RetreatTimeRope = reader.Read1Byte();
ShowRoundTime = reader.ReadBoolean(); ShowRoundTime = reader.ReadBoolean();
AutomaticReplays = reader.ReadBoolean(); Replays = reader.ReadBoolean();
FallDamage = reader.ReadByte() * 50 % 256 * 2; FallDamage = reader.ReadByte() * 50 % 256 * 2;
ArtilleryMode = reader.ReadBoolean(); ArtilleryMode = reader.ReadBoolean();
SchemeEditor = reader.ReadEnum<SchemeEditor>(false); SchemeEditor = reader.ReadEnum<SchemeEditor>(false);
StockpilingMode = reader.ReadEnum<StockpilingMode>(true); Stockpiling = reader.ReadEnum<Stockpiling>(true);
WormSelectMode = reader.ReadEnum<WormSelect>(true); WormSelect = reader.ReadEnum<WormSelect>(true);
SuddenDeathEvent = reader.ReadEnum<SuddenDeathEvent>(true); SuddenDeathEvent = reader.ReadEnum<SuddenDeathEvent>(true);
_waterRiseIndex = reader.Read1Byte(); _waterRiseIndex = reader.Read1Byte();
WeaponCrateProbability = reader.ReadSByte(); WeaponCrateProb = reader.ReadSByte();
DonorCards = reader.ReadBoolean(); DonorCards = reader.ReadBoolean();
HealthCrateProbability = reader.ReadSByte(); HealthCrateProb = reader.ReadSByte();
HealthCrateEnergy = reader.Read1Byte(); HealthCrateEnergy = reader.Read1Byte();
UtilityCrateProbability = reader.ReadSByte(); UtilityCrateProb = reader.ReadSByte();
readObjectCombo(); readObjectCombo();
readMineDelay(); readMineDelay();
DudMines = reader.ReadBoolean(); DudMines = reader.ReadBoolean();
@ -1059,19 +1057,17 @@ namespace Syroot.Worms.Armageddon
AquaSheep = reader.ReadBoolean(); AquaSheep = reader.ReadBoolean();
SheepHeaven = reader.ReadBoolean(); SheepHeaven = reader.ReadBoolean();
GodWorms = reader.ReadBoolean(); GodWorms = reader.ReadBoolean();
IndestructibleLand = reader.ReadBoolean(); IndiLand = reader.ReadBoolean();
UpgradedGrenade = reader.ReadBoolean(); UpgradeGrenade = reader.ReadBoolean();
UpgradedShotgun = reader.ReadBoolean(); UpgradeShotgun = reader.ReadBoolean();
UpgradedCluster = reader.ReadBoolean(); UpgradeCluster = reader.ReadBoolean();
UpgradedLongbow = reader.ReadBoolean(); UpgradeLongbow = reader.ReadBoolean();
EnableTeamWeapons = reader.ReadBoolean(); TeamWeapons = reader.ReadBoolean();
EnableSuperWeapons = reader.ReadBoolean(); SuperWeapons = reader.ReadBoolean();
// Read the weapons. // Read the weapons.
Weapons = new WeaponList(); Weapons = new WeaponList();
int weaponCount = GetWeaponCount(Version); reader.ReadStructs(Weapons.AsSpan(GetWeaponCount(Version)));
for (int i = 0; i < weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeapon>();
// Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities. // Read available extended settings or deserialize them from RubberWorm settings encoded in probabilities.
switch (Version) switch (Version)
@ -1163,23 +1159,23 @@ namespace Syroot.Worms.Armageddon
writer.WriteEnum(version); writer.WriteEnum(version);
// Write the options. // Write the options.
writer.Write(HotSeatDelay); writer.Write(HotSeatTime);
writer.Write(RetreatTime); writer.Write(RetreatTime);
writer.Write(RetreatTimeRope); writer.Write(RetreatTimeRope);
writer.Write(ShowRoundTime); writer.Write(ShowRoundTime);
writer.Write(AutomaticReplays); writer.Write(Replays);
writer.Write((byte)(FallDamage / 4 * 41 % 128)); writer.Write((byte)(FallDamage / 4 * 41 % 128));
writer.Write(ArtilleryMode); writer.Write(ArtilleryMode);
writer.WriteEnum(SchemeEditor, false); writer.WriteEnum(SchemeEditor, false);
writer.WriteEnum(StockpilingMode, true); writer.WriteEnum(Stockpiling, true);
writer.WriteEnum(WormSelectMode, true); writer.WriteEnum(WormSelect, true);
writer.WriteEnum(SuddenDeathEvent, true); writer.WriteEnum(SuddenDeathEvent, true);
writer.Write(_waterRiseIndex); writer.Write(_waterRiseIndex);
writer.Write(WeaponCrateProbability); writer.Write(WeaponCrateProb);
writer.Write(DonorCards); writer.Write(DonorCards);
writer.Write(HealthCrateProbability); writer.Write(HealthCrateProb);
writer.Write(HealthCrateEnergy); writer.Write(HealthCrateEnergy);
writer.Write(UtilityCrateProbability); writer.Write(UtilityCrateProb);
saveObjectTypesAndCount(); saveObjectTypesAndCount();
saveMineDelayConfig(); saveMineDelayConfig();
writer.Write(DudMines); writer.Write(DudMines);
@ -1192,22 +1188,20 @@ namespace Syroot.Worms.Armageddon
writer.Write(AquaSheep); writer.Write(AquaSheep);
writer.Write(SheepHeaven); writer.Write(SheepHeaven);
writer.Write(GodWorms); writer.Write(GodWorms);
writer.Write(IndestructibleLand); writer.Write(IndiLand);
writer.Write(UpgradedGrenade); writer.Write(UpgradeGrenade);
writer.Write(UpgradedShotgun); writer.Write(UpgradeShotgun);
writer.Write(UpgradedCluster); writer.Write(UpgradeCluster);
writer.Write(UpgradedLongbow); writer.Write(UpgradeLongbow);
writer.Write(EnableTeamWeapons); writer.Write(TeamWeapons);
writer.Write(EnableSuperWeapons); writer.Write(SuperWeapons);
// Serialize RubberWorm settings encoded in weapon probabilities. // Serialize RubberWorm settings encoded in weapon probabilities.
if (version == SchemeVersion.Version2) if (version == SchemeVersion.Version2)
SaveRubberWorm(); SaveRubberWorm();
// Write the weapons. // Write the weapons.
int weaponCount = GetWeaponCount(version); writer.WriteStructs<SchemeWeapon>(Weapons.AsSpan(GetWeaponCount(version)));
for (int i = 0; i < weaponCount; i++)
writer.WriteStruct(Weapons[i]);
// Clear RubberWorm probabilities again or write available extended settings. // Clear RubberWorm probabilities again or write available extended settings.
switch (version) switch (version)
@ -1228,10 +1222,10 @@ namespace Syroot.Worms.Armageddon
{ {
// Reset all super weapon probabilities to remove any settings made by RubberWorm. // Reset all super weapon probabilities to remove any settings made by RubberWorm.
for (Weapon superWeapon = Weapon.Freeze; superWeapon <= Weapon.Armageddon; superWeapon++) 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) private unsafe void LoadExtendedOptions(BinaryStream reader)
{ {
@ -1242,7 +1236,7 @@ namespace Syroot.Worms.Armageddon
else else
{ {
Span<byte> bytes = new Span<byte>( Span<byte> bytes = new Span<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>()); Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0 #if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0. // Cannot prevent copy in .NET Standard 2.0.
byte[] buffer = new byte[(int)Math.Min(bytes.Length, reader.Length - reader.Position)]; byte[] buffer = new byte[(int)Math.Min(bytes.Length, reader.Length - reader.Position)];
@ -1259,7 +1253,7 @@ namespace Syroot.Worms.Armageddon
unchecked unchecked
{ {
// Earthquake flags. // Earthquake flags.
byte prob = (byte)Weapons[Weapon.Earthquake].Crates; byte prob = (byte)Weapons[Weapon.Earthquake].Prob;
Extended.AutoReaim = prob.GetBit(0); Extended.AutoReaim = prob.GetBit(0);
Extended.CircularAim = prob.GetBit(1); Extended.CircularAim = prob.GetBit(1);
Extended.AntiLockPower = prob.GetBit(2); Extended.AntiLockPower = prob.GetBit(2);
@ -1267,19 +1261,19 @@ namespace Syroot.Worms.Armageddon
Extended.KaosMod = prob.DecodeByte(4, 4); Extended.KaosMod = prob.DecodeByte(4, 4);
// Antisink. // Antisink.
Extended.AntiSink = Weapons[Weapon.SheepStrike].Crates != 0; Extended.AntiSink = Weapons[Weapon.SheepStrike].Prob != 0;
// Crate limit and rate. // Crate limit and rate.
prob = (byte)Weapons[Weapon.MagicBullet].Crates; prob = (byte)Weapons[Weapon.MagicBullet].Prob;
Extended.CrateLimit = prob switch Extended.CrateLimit = prob switch
{ {
0 => SchemeExtendedOptions.Default.CrateLimit, 0 => ExtendedOptions.Default.CrateLimit,
_ => prob _ => prob
}; };
Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Crates; Extended.CrateRate = (byte)Weapons[Weapon.NuclearTest].Prob;
// Mole squadron flags. // Mole squadron flags.
prob = (byte)Weapons[Weapon.MoleSquadron].Crates; prob = (byte)Weapons[Weapon.MoleSquadron].Prob;
Extended.ShotDoesntEndTurn = prob.GetBit(0); Extended.ShotDoesntEndTurn = prob.GetBit(0);
Extended.LoseControlDoesntEndTurn = prob.GetBit(1); Extended.LoseControlDoesntEndTurn = prob.GetBit(1);
Extended.FiringPausesTimer = !prob.GetBit(2); Extended.FiringPausesTimer = !prob.GetBit(2);
@ -1290,21 +1284,21 @@ namespace Syroot.Worms.Armageddon
Extended.ExtendedFuse = prob.GetBit(7); Extended.ExtendedFuse = prob.GetBit(7);
// Flame limit. // Flame limit.
prob = (byte)Weapons[Weapon.ScalesOfJustice].Crates; prob = (byte)Weapons[Weapon.ScalesOfJustice].Prob;
if (prob > 0) if (prob > 0)
Extended.FlameLimit = (ushort)(prob * 100); Extended.FlameLimit = (ushort)(prob * 100);
// Friction. // Friction.
prob = (byte)Weapons[Weapon.SalvationArmy].Crates; prob = (byte)Weapons[Weapon.SalvationArmy].Prob;
if (prob > 0) if (prob > 0)
Extended.Friction = prob / 100f; Extended.Friction = prob / 100f;
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity. // 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) if (prob == 0)
{ {
Extended.RwGravityType = RwGravityType.None; Extended.RwGravityType = RwGravityType.None;
Extended.RwGravity = SchemeExtendedOptions.Default.RwGravity; Extended.RwGravity = ExtendedOptions.Default.RwGravity;
} }
else if (prob.GetBit(7) && prob.GetBit(6)) else if (prob.GetBit(7) && prob.GetBit(6))
{ {
@ -1323,7 +1317,7 @@ namespace Syroot.Worms.Armageddon
} }
// Rope-knocking force. // Rope-knocking force.
prob = (byte)Weapons[Weapon.SuperBananaBomb].Crates; prob = (byte)Weapons[Weapon.SuperBananaBomb].Prob;
Extended.RopeKnockForce = prob switch Extended.RopeKnockForce = prob switch
{ {
0 => null, 0 => null,
@ -1332,35 +1326,35 @@ namespace Syroot.Worms.Armageddon
}; };
// Maximum rope speed. // Maximum rope speed.
prob = (byte)Weapons[Weapon.MineStrike].Crates; prob = (byte)Weapons[Weapon.MineStrike].Prob;
Extended.RopeMaxSpeed = prob switch Extended.RopeMaxSpeed = prob switch
{ {
0 => SchemeExtendedOptions.Default.RopeMaxSpeed, 0 => ExtendedOptions.Default.RopeMaxSpeed,
Byte.MaxValue => 0, Byte.MaxValue => 0,
_ => prob _ => prob
}; };
// Select worm any time. // Select worm any time.
Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Crates).GetBit(0); Extended.WormSelectAnytime = ((byte)Weapons[Weapon.MBBomb].Prob).GetBit(0);
// Viscosity. // Viscosity.
prob = (byte)Weapons[Weapon.ConcreteDonkey].Crates; prob = (byte)Weapons[Weapon.ConcreteDonkey].Prob;
Extended.Viscosity = prob / 255f; Extended.Viscosity = prob / 255f;
Extended.ViscosityWorms = (prob & 1) == 1; Extended.ViscosityWorms = (prob & 1) == 1;
// Wind. // Wind.
prob = (byte)Weapons[Weapon.SuicideBomber].Crates; prob = (byte)Weapons[Weapon.SuicideBomber].Prob;
Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Crates / 255f; Extended.RwWind = (byte)Weapons[Weapon.SuicideBomber].Prob / 255f;
Extended.RwWindWorms = (prob & 1) == 1; Extended.RwWindWorms = (prob & 1) == 1;
// Worm bounce. // Worm bounce.
Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Crates / 255f; Extended.WormBounce = (byte)Weapons[Weapon.Armageddon].Prob / 255f;
// Version override. // Version override.
RwVersionOverride = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[] RwVersion = BinaryPrimitives.ReadUInt16BigEndian(stackalloc[]
{ {
(byte)Weapons[Weapon.SelectWorm].Crates, (byte)Weapons[Weapon.SelectWorm].Prob,
(byte)Weapons[Weapon.Freeze].Crates (byte)Weapons[Weapon.Freeze].Prob
}); });
} }
@ -1371,7 +1365,7 @@ namespace Syroot.Worms.Armageddon
private unsafe void SaveExtendedOptions(BinaryStream writer) private unsafe void SaveExtendedOptions(BinaryStream writer)
{ {
ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>( ReadOnlySpan<byte> bytes = new ReadOnlySpan<byte>(
Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<SchemeExtendedOptions>()); Unsafe.AsPointer(ref _extended), Unsafe.SizeOf<ExtendedOptions>());
#if NETSTANDARD2_0 #if NETSTANDARD2_0
// Cannot prevent copy in .NET Standard 2.0. // Cannot prevent copy in .NET Standard 2.0.
writer.Write(bytes.ToArray()); writer.Write(bytes.ToArray());
@ -1391,15 +1385,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(2, Extended.AntiLockPower); prob = prob.SetBit(2, Extended.AntiLockPower);
prob = prob.SetBit(3, Extended.ShotDoesntEndTurnAll); prob = prob.SetBit(3, Extended.ShotDoesntEndTurnAll);
prob = prob.Encode(Extended.KaosMod, 4, 4); prob = prob.Encode(Extended.KaosMod, 4, 4);
Weapons[Weapon.Earthquake].Crates = (sbyte)prob; Weapons[Weapon.Earthquake].Prob = (sbyte)prob;
// Antisink // Antisink
Weapons[Weapon.SheepStrike].Crates = (sbyte)(Extended.AntiSink ? 1 : 0); Weapons[Weapon.SheepStrike].Prob = (sbyte)(Extended.AntiSink ? 1 : 0);
// Crate limit and rate. // Crate limit and rate.
if (Extended.CrateLimit != SchemeExtendedOptions.Default.CrateLimit) if (Extended.CrateLimit != ExtendedOptions.Default.CrateLimit)
Weapons[Weapon.MagicBullet].Crates = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit); Weapons[Weapon.MagicBullet].Prob = (sbyte)Math.Min(Byte.MaxValue, Extended.CrateLimit);
Weapons[Weapon.NuclearTest].Crates = (sbyte)Extended.CrateRate; Weapons[Weapon.NuclearTest].Prob = (sbyte)Extended.CrateRate;
// Mole squadron flags. // Mole squadron flags.
prob = 0; prob = 0;
@ -1411,15 +1405,15 @@ namespace Syroot.Worms.Armageddon
prob = prob.SetBit(5, Extended.ObjectPushByExplosion == true); prob = prob.SetBit(5, Extended.ObjectPushByExplosion == true);
prob = prob.SetBit(6, Extended.WeaponsDontChange); prob = prob.SetBit(6, Extended.WeaponsDontChange);
prob = prob.SetBit(7, Extended.ExtendedFuse); prob = prob.SetBit(7, Extended.ExtendedFuse);
Weapons[Weapon.MoleSquadron].Crates = (sbyte)prob; Weapons[Weapon.MoleSquadron].Prob = (sbyte)prob;
// Flame limit. // Flame limit.
if (Extended.FlameLimit != SchemeExtendedOptions.Default.FlameLimit) if (Extended.FlameLimit != ExtendedOptions.Default.FlameLimit)
Weapons[Weapon.ScalesOfJustice].Crates = (sbyte)(Extended.FlameLimit / 100); Weapons[Weapon.ScalesOfJustice].Prob = (sbyte)(Extended.FlameLimit / 100);
// Friction. // Friction.
if (Extended.Friction != SchemeExtendedOptions.Default.Friction) if (Extended.Friction != ExtendedOptions.Default.Friction)
Weapons[Weapon.SalvationArmy].Crates = (sbyte)Math.Round(Extended.Friction * 100, 0); Weapons[Weapon.SalvationArmy].Prob = (sbyte)Math.Round(Extended.Friction * 100, 0);
// Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity. // Gravity - 8th and 7th bit control constant / proportional black hole, otherwise normal gravity.
prob = 0; prob = 0;
@ -1438,10 +1432,10 @@ namespace Syroot.Worms.Armageddon
prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6); prob = prob.Encode((sbyte)(Extended.RwGravity / 512), 6);
break; break;
} }
Weapons[Weapon.MailStrike].Crates = (sbyte)prob; Weapons[Weapon.MailStrike].Prob = (sbyte)prob;
// Rope-knocking force. // Rope-knocking force.
Weapons[Weapon.SuperBananaBomb].Crates = (sbyte)(Extended.RopeKnockForce switch Weapons[Weapon.SuperBananaBomb].Prob = (sbyte)(Extended.RopeKnockForce switch
{ {
null => 0, null => 0,
0 => Byte.MaxValue, 0 => Byte.MaxValue,
@ -1449,7 +1443,7 @@ namespace Syroot.Worms.Armageddon
}); });
// Maximum rope speed. // Maximum rope speed.
Weapons[Weapon.MineStrike].Crates = (sbyte)(Extended.RopeMaxSpeed switch Weapons[Weapon.MineStrike].Prob = (sbyte)(Extended.RopeMaxSpeed switch
{ {
16/*SchemeExtendedOptions.Default.RopeMaxSpeed*/ => 0, 16/*SchemeExtendedOptions.Default.RopeMaxSpeed*/ => 0,
0 => Byte.MaxValue, 0 => Byte.MaxValue,
@ -1457,27 +1451,27 @@ namespace Syroot.Worms.Armageddon
}); });
// Select worm any time. // Select worm any time.
prob = ((byte)Weapons[Weapon.MBBomb].Crates).SetBit(0, Extended.WormSelectAnytime); prob = ((byte)Weapons[Weapon.MBBomb].Prob).SetBit(0, Extended.WormSelectAnytime);
Weapons[Weapon.MBBomb].Crates = (sbyte)prob; Weapons[Weapon.MBBomb].Prob = (sbyte)prob;
// Viscosity. // Viscosity.
prob = (byte)Math.Round(Extended.Viscosity * 255, 0); prob = (byte)Math.Round(Extended.Viscosity * 255, 0);
prob.SetBit(0, Extended.ViscosityWorms); prob.SetBit(0, Extended.ViscosityWorms);
Weapons[Weapon.ConcreteDonkey].Crates = (sbyte)prob; Weapons[Weapon.ConcreteDonkey].Prob = (sbyte)prob;
// Wind. // Wind.
prob = (byte)Math.Round(Extended.RwWind * 255, 0); prob = (byte)Math.Round(Extended.RwWind * 255, 0);
prob.SetBit(0, Extended.RwWindWorms); prob.SetBit(0, Extended.RwWindWorms);
Weapons[Weapon.SuicideBomber].Crates = (sbyte)prob; Weapons[Weapon.SuicideBomber].Prob = (sbyte)prob;
// Worm bounce. // 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. // Version override.
Span<byte> versionBytes = stackalloc byte[sizeof(ushort)]; Span<byte> versionBytes = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersionOverride); BinaryPrimitives.WriteUInt16BigEndian(versionBytes, RwVersion);
Weapons[Weapon.SelectWorm].Crates = (sbyte)versionBytes[0]; Weapons[Weapon.SelectWorm].Prob = (sbyte)versionBytes[0];
Weapons[Weapon.Freeze].Crates = (sbyte)versionBytes[1]; 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> /// values represent infinity.</summary>
public sbyte Delay; public sbyte Delay;
/// <summary>Probability of this weapon to appear in crates. Has no effect on super weapons.</summary> /// <summary>Probability of this weapon to appear in crates. Has no effect on super weapons.</summary>
public sbyte Crates; public sbyte Prob;
// ---- OPERATORS ---------------------------------------------------------------------------------------------- // ---- OPERATORS ----------------------------------------------------------------------------------------------
@ -39,9 +39,9 @@ namespace Syroot.Worms.Armageddon
=> Ammo == other.Ammo => Ammo == other.Ammo
&& Power == other.Power && Power == other.Power
&& Delay == other.Delay && Delay == other.Delay
&& Crates == other.Crates; && Prob == other.Prob;
/// <inheritdoc/> /// <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> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms.Armageddon</AssemblyName> <AssemblyName>Syroot.Worms.Armageddon</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description> <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> <PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>3.3.0-beta001</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" /> <ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

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

View File

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

View File

@ -1,135 +1,132 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core; using Syroot.Worms.Core;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.Mgame namespace Syroot.Worms.Mgame
{ {
/// <summary> /// <summary>
/// Represents an IGD image container. /// Represents an IGD image container.
/// </summary> /// </summary>
public class Igd : ILoadableFile public class Igd : ILoadableFile
{ {
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Igd"/> class, loading data from the file with the given /// Initializes a new instance of the <see cref="Igd"/> class, loading data from the file with the given
/// <paramref name="fileName"/>. /// <paramref name="fileName"/>.
/// </summary> /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// <param name="fileName">The name of the file to load the data from.</param>
public Igd(string fileName) => Load(fileName); public Igd(string fileName) => Load(fileName);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Igd"/> class, loading data from the given /// Initializes a new instance of the <see cref="Igd"/> class, loading data from the given
/// <paramref name="stream"/>. /// <paramref name="stream"/>.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Igd(Stream stream) => Load(stream); public Igd(Stream stream) => Load(stream);
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
public int UnknownA { get; set; } public int UnknownA { get; set; }
public int UnknownB { get; set; } public int UnknownB { get; set; }
public byte[] UnknownC { get; set; } public byte[] UnknownC { get; set; } = new byte[8];
public Point Center { get; set; } public Point Center { get; set; }
public Size Size { get; set; } public Size Size { get; set; }
public IList<IgdImage> Images { get; set; } public IList<IgdImage> Images { get; set; } = new List<IgdImage>();
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/> /// <inheritdoc/>
public void Load(string fileName) public void Load(string fileName)
{ {
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream); Load(stream);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Load(Stream stream) public void Load(Stream stream)
{ {
UnknownA = stream.ReadInt32(); UnknownA = stream.ReadInt32();
UnknownB = stream.ReadInt32(); UnknownB = stream.ReadInt32();
UnknownC = stream.ReadBytes(8); UnknownC = stream.ReadBytes(8);
Center = new Point(stream.ReadInt32(), stream.ReadInt32()); Center = new Point(stream.ReadInt32(), stream.ReadInt32());
Size = new Size(stream.ReadInt32(), stream.ReadInt32()); Size = new Size(stream.ReadInt32(), stream.ReadInt32());
// Load the palette. // Load the palette.
int colorCount = stream.ReadInt32(); int colorCount = stream.ReadInt32();
Color[] palette = new Color[colorCount]; Color[] palette = new Color[colorCount];
for (int i = 0; i < colorCount; i++) for (int i = 0; i < colorCount; i++)
{ {
palette[i] = Color.FromArgb(stream.Read1Byte(), stream.Read1Byte(), stream.Read1Byte()); palette[i] = Color.FromArgb(stream.Read1Byte(), stream.Read1Byte(), stream.Read1Byte());
stream.Seek(1); // Ignore empty alpha. stream.Seek(1); // Ignore empty alpha.
} }
// Load the images. // Load the images.
int imageCount = stream.ReadInt32(); int imageCount = stream.ReadInt32();
Images = new List<IgdImage>(imageCount); Images = new List<IgdImage>(imageCount);
for (int i = 0; i < imageCount; i++) for (int i = 0; i < imageCount; i++)
{ {
IgdImage image = new IgdImage(); IgdImage image = new IgdImage();
image.UnknownA = stream.ReadInt32(); image.UnknownA = stream.ReadInt32();
int index = stream.ReadInt32(); int index = stream.ReadInt32();
if (index != i) if (index != i)
throw new InvalidDataException("Read index does not match image index."); throw new InvalidDataException("Read index does not match image index.");
image.UnknownB = stream.ReadInt32(); image.UnknownB = stream.ReadInt32();
image.UnknownC = stream.ReadInt32(); image.UnknownC = stream.ReadInt32();
image.Size = new Size(stream.ReadInt32(), stream.ReadInt32()); image.Size = new Size(stream.ReadInt32(), stream.ReadInt32());
image.Center = new Point(stream.ReadInt32(), stream.ReadInt32()); image.Center = new Point(stream.ReadInt32(), stream.ReadInt32());
// Decompress the data. // Decompress the data.
int dataSize = stream.ReadInt32(); int dataSize = stream.ReadInt32();
int dataSizeCompressed = stream.ReadInt32(); int dataSizeCompressed = stream.ReadInt32();
image.RawBitmap = new RawBitmap image.RawBitmap.Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height);
{ image.RawBitmap.Palette = palette;
Size = new Size(Algebra.NextMultiple(image.Size.Width, 4), image.Size.Height), image.RawBitmap.Data = Decompress(stream, dataSizeCompressed, dataSize);
Palette = palette, Images.Add(image);
Data = Decompress(stream, dataSizeCompressed, dataSize) }
}; }
Images.Add(image);
} // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
}
private static byte[] Decompress(Stream stream, int compressedSize, int decompressedSize)
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- {
// Each input byte is either a byte of decompressed data or a marker for a following command (0xFF).
private static byte[] Decompress(Stream stream, int compressedSize, int decompressedSize) // A command consists of 4 bytes which specify the range of bytes to copy from already decompressed data.
{ byte[] decompressed = new byte[decompressedSize];
// Each input byte is either a byte of decompressed data or a marker for a following command (0xFF). int i = 0;
// A command consists of 4 bytes which specify the range of bytes to copy from already decompressed data.
byte[] decompressed = new byte[decompressedSize]; long endPosition = stream.Position + compressedSize - 2;
int i = 0; while (stream.Position < endPosition)
{
long endPosition = stream.Position + compressedSize - 2; byte b = stream.Read1Byte();
while (stream.Position < endPosition) if (b == 0xFF)
{ {
byte b = stream.Read1Byte(); // Copy existing data.
if (b == 0xFF) byte mask1 = stream.Read1Byte();
{ byte mask2 = stream.Read1Byte();
// Copy existing data. int offset = mask2 & 0x0FFF | ((mask1 & 0x000F) << 8);
byte mask1 = stream.Read1Byte(); int bytesToCopy = stream.Read1Byte();
byte mask2 = stream.Read1Byte(); for (int j = 0; j < bytesToCopy; j++)
int offset = mask2 & 0x0FFF | ((mask1 & 0x000F) << 8); {
int bytesToCopy = stream.Read1Byte(); int outIndex = i + j;
for (int j = 0; j < bytesToCopy; j++) decompressed[outIndex] = decompressed[outIndex - offset];
{ }
int outIndex = i + j; i += bytesToCopy;
decompressed[outIndex] = decompressed[outIndex - offset]; }
} else
i += bytesToCopy; {
} // Raw transfer next byte.
else decompressed[i++] = b;
{ }
// 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.");
// Validate remaining 2 check bytes and return the decompressed data if valid. return decompressed;
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 int UnknownC { get; set; }
public Size Size { get; set; } public Size Size { get; set; }
public Point Center { 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.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using Syroot.BinaryData; using Syroot.BinaryData;
namespace Syroot.Worms.Mgame namespace Syroot.Worms.Mgame
{ {
/// <summary> /// <summary>
/// Represents a KSF image container. /// Represents a KSF image container.
/// </summary> /// </summary>
public class Ksf // TODO: Implement ILoadableFile public class Ksf // TODO: Implement ILoadableFile
{ {
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class. /// Initializes a new instance of the <see cref="Ksf"/> class.
/// </summary> /// </summary>
public Ksf() { } public Ksf() { }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the file with the given /// 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"/>. /// <paramref name="fileName"/> and the colors in the provided <paramref name="palette"/>.
/// </summary> /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// <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> /// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(string fileName, Palette palette) => Load(fileName, palette); public Ksf(string fileName, KsfPalette palette) => Load(fileName, palette);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Ksf"/> class, loading data from the given /// 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"/>. /// <paramref name="stream"/> and the colors in the provided <paramref name="palette"/>.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <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> /// <param name="palette">The color palette which is indexed by the image data.</param>
public Ksf(Stream stream, Palette palette) => Load(stream, palette); public Ksf(Stream stream, KsfPalette palette) => Load(stream, palette);
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Gets or sets the list of converted <see cref="Bitmap"/> instances stored in this KSF. /// Gets or sets the list of converted <see cref="Bitmap"/> instances stored in this KSF.
/// </summary> /// </summary>
public IList<KsfImage> Images { get; set; } = new List<KsfImage>(); public IList<KsfImage> Images { get; set; } = new List<KsfImage>();
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Loads the data from the file with the given <paramref name="fileName"/> and the colors in the provided /// Loads the data from the file with the given <paramref name="fileName"/> and the colors in the provided
/// <paramref name="palette"/>. /// <paramref name="palette"/>.
/// </summary> /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// <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> /// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(string fileName, Palette palette) public void Load(string fileName, KsfPalette palette)
{ {
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream, palette); Load(stream, palette);
} }
/// <summary> /// <summary>
/// Loads the data from the given <paramref name="stream"/> and the colors in the provided /// Loads the data from the given <paramref name="stream"/> and the colors in the provided
/// <paramref name="palette"/>. /// <paramref name="palette"/>.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <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> /// <param name="palette">The color palette which is indexed by the image data.</param>
public void Load(Stream stream, Palette palette) public void Load(Stream stream, KsfPalette palette)
{ {
int imageCount = stream.ReadInt32(); // Includes terminator. int imageCount = stream.ReadInt32(); // Includes terminator.
_ = stream.ReadInt32(); // data size _ = stream.ReadInt32(); // data size
// Read image headers. Terminating image is of 0 size and data offset at end of data block. // Read image headers. Terminating image is of 0 size and data offset at end of data block.
KsfImage[] images = new KsfImage[imageCount]; KsfImage[] images = new KsfImage[imageCount];
int[] offsets = new int[imageCount]; int[] offsets = new int[imageCount];
Size[] sizes = new Size[imageCount]; Size[] sizes = new Size[imageCount];
for (int i = 0; i < imageCount; i++) for (int i = 0; i < imageCount; i++)
{ {
KsfImage image = new KsfImage(); KsfImage image = new KsfImage();
offsets[i] = stream.ReadInt32(); offsets[i] = stream.ReadInt32();
image.Center = new Point(stream.ReadInt32(), stream.ReadInt32()); image.Center = new Point(stream.ReadInt32(), stream.ReadInt32());
sizes[i] = new Size(stream.ReadInt16(), stream.ReadInt16()); sizes[i] = new Size(stream.ReadInt16(), stream.ReadInt16());
images[i] = image; images[i] = image;
} }
// Convert images. // Convert images.
Images = new List<KsfImage>(imageCount); Images = new List<KsfImage>(imageCount);
long dataStart = stream.Position; long dataStart = stream.Position;
for (int i = 0; i < imageCount - 1; i++) for (int i = 0; i < imageCount - 1; i++)
{ {
int offset = offsets[i]; int offset = offsets[i];
stream.Position = dataStart + offset; stream.Position = dataStart + offset;
int dataLength = offsets[i + 1] - offset; int dataLength = offsets[i + 1] - offset;
Size size = sizes[i]; Size size = sizes[i];
if (!size.IsEmpty) if (!size.IsEmpty)
{ {
images[i].RawBitmap = new RawBitmap images[i].RawBitmap = new RawBitmap
{ {
Size = size, BitsPerPixel = 8,
Palette = palette.Colors, Size = size,
Data = stream.ReadBytes(dataLength) Palette = palette.Colors,
}; Data = stream.ReadBytes(dataLength)
} };
}
Images.Add(images[i]);
} Images.Add(images[i]);
} }
} }
} }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@
<PropertyGroup> <PropertyGroup>
<AssemblyName>Syroot.Worms.Mgame</AssemblyName> <AssemblyName>Syroot.Worms.Mgame</AssemblyName>
<Description>.NET library for loading and modifying files of Mgame Worms clients.</Description> <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> <PackageTags>$(PackageTags);online worms;worms world party aqua</PackageTags>
<Version>3.2.0</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" /> <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> <PropertyGroup>
<AssemblyName>Syroot.Worms.WorldParty</AssemblyName> <AssemblyName>Syroot.Worms.WorldParty</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms World Party.</Description> <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> <PackageTags>$(PackageTags);worms world party</PackageTags>
<Version>3.2.0</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" /> <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.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.WorldParty namespace Syroot.Worms.WorldParty
{ {
/// <summary> /// <summary>
/// Represents the list of teams and unlocked game features stored in WGT files. /// Represents the list of teams and unlocked game features stored in WGT files.
/// Used by WWP. See https://worms2d.info/File_formats. /// Used by WWP. See https://worms2d.info/File_formats.
/// </summary> /// </summary>
public class TeamContainer : ILoadableFile, ISaveableFile public class TeamContainer : ILoadableFile, ISaveableFile
{ {
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- // ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WWP"; // 0-terminated. private const uint _signature = 0x00505757; // "WWP\0"
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class. /// Initializes a new instance of the <see cref="TeamContainer"/> class.
/// </summary> /// </summary>
public TeamContainer() => Teams = new List<Team>(); public TeamContainer() { }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given /// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given
/// <see cref="Stream"/>. /// <see cref="Stream"/>.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public TeamContainer(Stream stream) => Load(stream); public TeamContainer(Stream stream) => Load(stream);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file. /// Initializes a new instance of the <see cref="TeamContainer"/> class, loading the data from the given file.
/// </summary> /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// <param name="fileName">The name of the file to load the data from.</param>
public TeamContainer(string fileName) => Load(fileName); public TeamContainer(string fileName) => Load(fileName);
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Gets or sets a value possibly indicating a version of the file format. /// Gets or sets a value possibly indicating a version of the file format.
/// </summary> /// </summary>
public byte Version { get; set; } public byte Version { get; set; }
/// <summary> public byte Unknown1 { get; set; }
/// Gets or sets an unknown value.
/// </summary> public byte Unknown2 { get; set; }
public byte Unknown1 { get; set; }
/// <summary>
/// <summary> /// Gets or sets 840 unknown bytes, all possibly 0.
/// Gets or sets an unknown value. /// </summary>
/// </summary> public byte[] Unknown3 { get; set; } = new byte[840];
public byte Unknown2 { get; set; }
/// <summary>
/// <summary> /// Gets or sets the list of <see cref="Team"/> instances stored.
/// Gets or sets 840 unknown bytes, all possibly 0. /// </summary>
/// </summary> public IList<Team> Teams { get; set; } = new List<Team>();
public byte[] Unknown3 { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the list of <see cref="Team"/> instances stored. /// <inheritdoc/>
/// </summary> public void Load(Stream stream)
public List<Team> Teams { get; set; } {
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// Read the header.
/// <inheritdoc/> if (reader.ReadUInt32() != _signature)
public void Load(Stream stream) throw new InvalidDataException("Invalid WWP file signature.");
{ Version = reader.Read1Byte(); // Really version?
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Read global settings.
// Read the header. byte teamCount = reader.Read1Byte();
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature) Unknown1 = reader.Read1Byte();
throw new InvalidDataException("Invalid WWP file signature."); Unknown2 = reader.Read1Byte();
Version = reader.Read1Byte(); // Really version? Unknown3 = reader.ReadBytes(840);
// Read global settings. // Read the teams.
byte teamCount = reader.Read1Byte(); Teams = new List<Team>(teamCount);
Unknown1 = reader.Read1Byte(); while (teamCount-- > 0)
Unknown2 = reader.Read1Byte(); Teams.Add(reader.Load<Team>());
Unknown3 = reader.ReadBytes(840); }
// Read the teams. /// <inheritdoc/>
Teams = new List<Team>(reader.Load<Team>(teamCount)); public void Load(string fileName)
} {
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
/// <inheritdoc/> Load(stream);
public void Load(string fileName) }
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); /// <inheritdoc/>
Load(stream); public void Save(Stream stream)
} {
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
/// <inheritdoc/>
public void Save(Stream stream) // Write the header.
{ writer.Write(_signature);
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); writer.Write(Version);
// Write the header. // Write global settings.
writer.Write(_signature, StringCoding.ZeroTerminated); writer.Write((byte)Teams.Count);
writer.Write(Version); writer.Write(Unknown1);
writer.Write(Unknown2);
// Write global settings. writer.Write(Unknown3);
writer.Write((byte)Teams.Count);
writer.Write(Unknown1); // Write the teams.
writer.Write(Unknown2); foreach (Team team in Teams)
writer.Write(Unknown3); team.Save(writer.BaseStream);
}
// Write the teams.
foreach (Team team in Teams) /// <inheritdoc/>
team.Save(writer.BaseStream); public void Save(string fileName)
} {
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
/// <inheritdoc/> Save(stream);
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;
using System.IO; using System.Collections.Generic;
using System.Text; using System.Drawing;
using Syroot.BinaryData; using System.IO;
using Syroot.Worms.Core.IO; using System.Text;
using Syroot.BinaryData;
namespace Syroot.Worms.Worms2 using Syroot.Worms.IO;
{
/// <summary> namespace Syroot.Worms.Worms2
/// Represents map configuration stored by the land generator in LAND.DAT files. {
/// Used by W2. S. https://worms2d.info/Land_Data_file. /// <summary>
/// </summary> /// Represents map configuration stored by the land generator in LAND.DAT files.
public class LandData : ILoadableFile, ISaveableFile /// Used by W2 and OW. S. https://worms2d.info/Land_Data_file.
{ /// </summary>
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- public class LandData : ILoadableFile, ISaveableFile
{
private const int _signature = 0x1A444E4C; // "LND", 0x1A // ---- CONSTANTS ----------------------------------------------------------------------------------------------
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ private const int _signature = 0x1A444E4C; // "LND\x1A"
/// <summary> // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary> /// <summary>
public LandData() { } /// Initializes a new instance of the <see cref="LandData"/> class.
/// </summary>
/// <summary> public LandData() { }
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given
/// <see cref="Stream"/>. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <see cref="Stream"/>.
public LandData(Stream stream) => Load(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <summary> public LandData(Stream stream) => Load(stream);
/// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// Initializes a new instance of the <see cref="LandData"/> class, loading the data from the given file.
public LandData(string fileName) => Load(fileName); /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
// ---- PROPERTIES --------------------------------------------------------------------------------------------- public LandData(string fileName) => Load(fileName);
/// <summary> // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// Gets or sets the size of the landscape in pixels.
/// </summary> /// <summary>
public Size Size { get; set; } /// Gets or sets the size of the landscape in pixels.
/// </summary>
/// <summary> public Size Size { get; set; }
/// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary> /// <summary>
public bool TopBorder { get; set; } /// Gets or sets a value indicating whether an indestructible top border will be enabled.
/// </summary>
/// <summary> public bool TopBorder { get; set; }
/// Gets or sets an array of coordinates at which objects can be placed.
/// </summary> /// <summary>
public Point[] ObjectLocations { get; set; } /// Gets or sets an array of coordinates at which objects can be placed.
/// </summary>
/// <summary> public IList<Point> ObjectLocations { get; set; } = new List<Point>();
/// Gets or sets an unknown value, seeming to be 0 most of the time.
/// </summary> /// <summary>
public int Unknown { get; set; } /// Gets or sets an unknown value, seeming to be 0 most of the time.
/// </summary>
/// <summary> public int Unknown { get; set; }
/// Gets or sets the visual foreground image.
/// </summary> /// <summary>
public Img Foreground { get; set; } /// Gets or sets the visual foreground image.
/// </summary>
/// <summary> public Img Foreground { get; set; } = new Img();
/// Gets or sets the collision mask of the landscape.
/// </summary> /// <summary>
public Img CollisionMask { get; set; } /// Gets or sets the collision mask of the landscape.
/// </summary>
/// <summary> public Img CollisionMask { get; set; } = new Img();
/// Gets or sets the visual background image.
/// </summary> /// <summary>
public Img Background { get; set; } /// Gets or sets the visual background image.
/// </summary>
/// <summary> public Img Background { get; set; } = new Img();
/// Gets or sets an image of unknown use.
/// </summary> /// <summary>
public Img UnknownImage { get; set; } /// Gets or sets an image of unknown use.
/// </summary>
/// <summary> public Img UnknownImage { get; set; } = new Img();
/// Gets or sets the path to the land image file.
/// </summary> /// <summary>
public string LandTexturePath { get; set; } /// Gets or sets the path to the land image file.
/// </summary>
/// <summary> public string LandTexturePath { get; set; } = String.Empty;
/// Gets or sets the path to the Water.dir file.
/// </summary> /// <summary>
public string WaterDirPath { get; set; } /// Gets or sets the path to the Water.dir file.
/// </summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public string WaterDirPath { get; set; } = String.Empty;
/// <inheritdoc/> // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void Load(Stream stream)
{ /// <inheritdoc/>
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); public void Load(Stream stream)
{
// Read the header. using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature."); // Read the header.
int fileSize = reader.ReadInt32(); if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid LND file signature.");
// Read the data. int fileSize = reader.ReadInt32();
Size = reader.ReadStruct<Size>();
TopBorder = reader.ReadBoolean(BooleanCoding.Dword); // Read the data.
Size = reader.ReadStruct<Size>();
// Read the possible object coordinate array. TopBorder = reader.ReadBoolean(BooleanCoding.Dword);
ObjectLocations = reader.ReadStructs<Point>(reader.ReadInt32());
Unknown = reader.ReadInt32(); // Read the possible object coordinate array.
ObjectLocations = new List<Point>();
// Read the image data. int locationCount = reader.ReadInt32();
Foreground = reader.Load<Img>(); for (int i = 0; i < locationCount; i++)
CollisionMask = reader.Load<Img>(); ObjectLocations.Add(reader.ReadStruct<Point>());
Background = reader.Load<Img>(); Unknown = reader.ReadInt32();
UnknownImage = reader.Load<Img>();
// Read the image data.
// Read the file paths. Foreground = reader.Load<Img>();
LandTexturePath = reader.ReadString(StringCoding.ByteCharCount); CollisionMask = reader.Load<Img>();
WaterDirPath = reader.ReadString(StringCoding.ByteCharCount); Background = reader.Load<Img>();
} UnknownImage = reader.Load<Img>();
/// <inheritdoc/> // Read the file paths.
public void Load(string fileName) LandTexturePath = reader.ReadString(StringCoding.ByteCharCount);
{ WaterDirPath = reader.ReadString(StringCoding.ByteCharCount);
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); }
Load(stream);
} /// <inheritdoc/>
public void Load(string fileName)
/// <inheritdoc/> {
public void Save(Stream stream) using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
{ Load(stream);
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); }
// Write the header. /// <inheritdoc/>
writer.Write(_signature); public void Save(Stream stream)
uint fileSizeOffset = writer.ReserveOffset(); {
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
// Write the data.
writer.WriteStruct(Size); // Write the header.
writer.Write(TopBorder, BooleanCoding.Dword); writer.Write(_signature);
uint fileSizeOffset = writer.ReserveOffset();
// Write the possible object coordinate array.
writer.Write(ObjectLocations.Length); // Write the data.
writer.WriteStructs(ObjectLocations); writer.WriteStruct(Size);
writer.Write(Unknown); writer.Write(TopBorder, BooleanCoding.Dword);
// Write the image data. // Write the possible object coordinate array.
Foreground.Save(writer.BaseStream); writer.Write(ObjectLocations.Count);
CollisionMask.Save(writer.BaseStream); for (int i = 0; i < ObjectLocations.Count; i++)
Background.Save(writer.BaseStream); writer.WriteStruct(ObjectLocations[i]);
UnknownImage.Save(writer.BaseStream); writer.Write(Unknown);
// Write the file paths. // Write the image data.
writer.Write(LandTexturePath, StringCoding.ByteCharCount); Foreground.Save(writer.BaseStream);
writer.Write(WaterDirPath, StringCoding.ByteCharCount); CollisionMask.Save(writer.BaseStream);
Background.Save(writer.BaseStream);
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position); UnknownImage.Save(writer.BaseStream);
}
// Write the file paths.
/// <inheritdoc/> writer.Write(LandTexturePath, StringCoding.ByteCharCount);
public void Save(string fileName) writer.Write(WaterDirPath, StringCoding.ByteCharCount);
{
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
Save(stream); }
}
} /// <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> /// <summary>
/// Represents the method to determine the next turn's worm. /// Represents the method to determine the next turn's worm.
/// </summary> /// </summary>
public enum SchemeWormSelect : int public enum WormSelect : int
{ {
/// <summary> /// <summary>Worms are selected in the order in which they appear in the team.</summary>
/// Worms are selected in the order in which they appear in the team. Sequential,
/// </summary> /// <summary>Worms are selected randomly.</summary>
Sequential = 0, Random,
/// <summary>Worms are selected by a computed rating system.</summary>
/// <summary> Intelligent,
/// Worms are selected randomly. /// <summary>Worms are selected by the player.</summary>
/// </summary> Manual
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> /// <summary>
/// Represents the weapons in the game. /// Represents the weapons in the game.
/// </summary> /// </summary>
public enum SchemeWeapon public enum Weapon
{ {
/// <summary> /// <summary>The Bazooka weapon.</summary>
/// The Bazooka weapon.
/// </summary>
Bazooka, Bazooka,
/// <summary>The Homing Missile weapon.</summary>
/// <summary>
/// The Homing Missile weapon.
/// </summary>
HomingMissile, HomingMissile,
/// <summary>The Grenade weapon.</summary>
/// <summary>
/// The Grenade weapon.
/// </summary>
Grenade, Grenade,
/// <summary>The Cluster Bomb weapon.</summary>
/// <summary>
/// The Cluster Bomb weapon.
/// </summary>
ClusterBomb, ClusterBomb,
/// <summary>The Banana Bomb weapon.</summary>
/// <summary>
/// The Banana Bomb weapon.
/// </summary>
BananaBomb, BananaBomb,
/// <summary>The Holy Hand Grenade weapon.</summary>
/// <summary>
/// The Holy Hand Grenade weapon.
/// </summary>
HolyHandGrenade, HolyHandGrenade,
/// <summary>The Homing Cluster Bomb weapon.</summary>
/// <summary>
/// The Homing Cluster Bomb weapon.
/// </summary>
HomingClusterBomb, HomingClusterBomb,
/// <summary>The Petrol Bomb weapon.</summary>
/// <summary>
/// The Petrol Bomb weapon.
/// </summary>
PetrolBomb, PetrolBomb,
/// <summary>The Shotgun weapon.</summary>
/// <summary>
/// The Shotgun weapon.
/// </summary>
Shotgun, Shotgun,
/// <summary>The Handgun weapon.</summary>
/// <summary>
/// The Handgun weapon.
/// </summary>
Handgun, Handgun,
/// <summary>The Uzi weapon.</summary>
/// <summary>
/// The Uzi weapon.
/// </summary>
Uzi, Uzi,
/// <summary>The Minigun weapon.</summary>
/// <summary>
/// The Minigun weapon.
/// </summary>
Minigun, Minigun,
/// <summary>The Firepunch weapon.</summary>
/// <summary>
/// The Firepunch weapon.
/// </summary>
Firepunch, Firepunch,
/// <summary>The Dragonball weapon.</summary>
/// <summary>
/// The Dragonball weapon.
/// </summary>
Dragonball, Dragonball,
/// <summary>The Kamikaze weapon.</summary>
/// <summary>
/// The Kamikaze weapon.
/// </summary>
Kamikaze, Kamikaze,
/// <summary>The Dynamite weapon.</summary>
/// <summary>
/// The Dynamite weapon.
/// </summary>
Dynamite, Dynamite,
/// <summary>The Mine weapon.</summary>
/// <summary>
/// The Mine weapon.
/// </summary>
Mine, Mine,
/// <summary>The Ming Vase weapon.</summary>
/// <summary>
/// The Ming Vase weapon.
/// </summary>
MingVase, MingVase,
/// <summary>The Airstrike weapon.</summary>
/// <summary>
/// The Airstrike weapon.
/// </summary>
Airstrike, Airstrike,
/// <summary>The Homing Airstrike weapon.</summary>
/// <summary>
/// The Homing Airstrike weapon.
/// </summary>
HomingAirstrike, HomingAirstrike,
/// <summary>The Napalm Strike weapon.</summary>
/// <summary>
/// The Napalm Strike weapon.
/// </summary>
NapalmStrike, NapalmStrike,
/// <summary>The Mail Strike weapon.</summary>
/// <summary>
/// The Mail Strike weapon.
/// </summary>
MailStrike, MailStrike,
/// <summary>The Girder weapon.</summary>
/// <summary>
/// The Girder weapon.
/// </summary>
Girder, Girder,
/// <summary>The Pneumatic Drill weapon.</summary>
/// <summary>
/// The Pneumatic Drill weapon.
/// </summary>
PneumaticDrill, PneumaticDrill,
/// <summary>The Baseball Bat weapon.</summary>
/// <summary>
/// The Baseball Bat weapon.
/// </summary>
BaseballBat, BaseballBat,
/// <summary>The Prod weapon.</summary>
/// <summary>
/// The Prod weapon.
/// </summary>
Prod, Prod,
/// <summary>The Teleport weapon.</summary>
/// <summary>
/// The Teleport weapon.
/// </summary>
Teleport, Teleport,
/// <summary>The Ninja Rope weapon.</summary>
/// <summary>
/// The Ninja Rope weapon.
/// </summary>
NinjaRope, NinjaRope,
/// <summary>The Bungee weapon.</summary>
/// <summary>
/// The Bungee weapon.
/// </summary>
Bungee, Bungee,
/// <summary>The Parachute weapon.</summary>
/// <summary>
/// The Parachute weapon.
/// </summary>
Parachute, Parachute,
/// <summary>The Sheep weapon.</summary>
/// <summary>
/// The Sheep weapon.
/// </summary>
Sheep, Sheep,
/// <summary>The Mad Cow weapon.</summary>
/// <summary>
/// The Mad Cow weapon.
/// </summary>
MadCow, MadCow,
/// <summary>The Old Woman weapon.</summary>
/// <summary>
/// The Old Woman weapon.
/// </summary>
OldWoman, OldWoman,
/// <summary>The Mortar weapon.</summary>
/// <summary>
/// The Mortar weapon.
/// </summary>
Mortar, Mortar,
/// <summary>The Blowtorch weapon.</summary>
/// <summary>
/// The Blowtorch weapon.
/// </summary>
Blowtorch, Blowtorch,
/// <summary>The Homing Pigeon weapon.</summary>
/// <summary>
/// The Homing Pigeon weapon.
/// </summary>
HomingPigeon, HomingPigeon,
/// <summary>The Super Sheep weapon.</summary>
/// <summary>
/// The Super Sheep weapon.
/// </summary>
SuperSheep, SuperSheep,
/// <summary>The Super Banana Bomb weapon.</summary>
/// <summary>
/// The Super Banana Bomb weapon.
/// </summary>
SuperBananaBomb SuperBananaBomb
} }
} }

View File

@ -1,7 +1,7 @@
using System.IO; using System.IO;
using System.Text; using System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.Worms2 namespace Syroot.Worms.Worms2
{ {
@ -140,7 +140,7 @@ namespace Syroot.Worms.Worms2
/// <summary> /// <summary>
/// Gets or sets a value indicating the worm selection order determining the next worm to be played. /// Gets or sets a value indicating the worm selection order determining the next worm to be played.
/// </summary> /// </summary>
public SchemeWormSelect WormSelectMode { get; set; } public WormSelect WormSelectMode { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the chat box will be closed upon starting to move a worm or stays /// 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); SuddenDeathHealthDrop = reader.ReadBoolean(BooleanCoding.Dword);
IndestructibleBorder = reader.ReadBoolean(BooleanCoding.Dword); IndestructibleBorder = reader.ReadBoolean(BooleanCoding.Dword);
RestrictGirders = reader.ReadBoolean(BooleanCoding.Dword); RestrictGirders = reader.ReadBoolean(BooleanCoding.Dword);
WormSelectMode = reader.ReadEnum<SchemeWormSelect>(true); WormSelectMode = reader.ReadEnum<WormSelect>(true);
ExtendedChatControls = reader.ReadBoolean(BooleanCoding.Dword); ExtendedChatControls = reader.ReadBoolean(BooleanCoding.Dword);
HotSeatDelay = reader.ReadInt32(); HotSeatDelay = reader.ReadInt32();
EnableStockpiling = reader.ReadBoolean(BooleanCoding.Dword); 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;
using System.Text; using System.IO;
using Syroot.BinaryData; using System.Linq;
using Syroot.Worms.Core.IO; using System.Text;
using Syroot.BinaryData;
namespace Syroot.Worms.Worms2 using Syroot.Worms.IO;
{
/// <summary> namespace Syroot.Worms.Worms2
/// Represents scheme weapons stored in an WEP file which contains armory configuration. {
/// Used by W2. S. https://worms2d.info/Weapons_file. /// <summary>
/// </summary> /// Represents scheme weapons stored in an WEP file which contains armory configuration.
public class SchemeWeapons : ILoadableFile, ISaveableFile /// Used by W2. S. https://worms2d.info/Weapons_file.
{ /// </summary>
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- public class SchemeWeapons : ILoadableFile, ISaveableFile, IEquatable<SchemeWeapons?>
{
private const int _trashLength = 16; // ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "WEPFILE"; // Zero-terminated.
private const int _weaponCount = 38; private const int _trashLength = 16;
private const string _signature = "WEPFILE"; // Zero-terminated.
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ private const int _weaponCount = 38;
/// <summary> // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class.
/// </summary> /// <summary>
public SchemeWeapons() => Weapons = new SchemeWeaponSetting[_weaponCount]; /// Initializes a new instance of the <see cref="SchemeWeapons"/> class.
/// </summary>
/// <summary> public SchemeWeapons() { }
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given
/// <see cref="Stream"/>. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <see cref="Stream"/>.
public SchemeWeapons(Stream stream) => Load(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <summary> public SchemeWeapons(Stream stream) => Load(stream);
/// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// Initializes a new instance of the <see cref="SchemeWeapons"/> class, loading the data from the given file.
public SchemeWeapons(string fileName) => Load(fileName); /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
// ---- PROPERTIES --------------------------------------------------------------------------------------------- public SchemeWeapons(string fileName) => Load(fileName);
/// <summary> // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// Gets the array of <see cref="SchemeWeaponSetting"/> instances, each mapping to one weapon at the index of
/// the <see cref="SchemeWeapon"/> enumeration. /// <summary>
/// </summary> /// Gets the array of <see cref="SchemeWeapon"/> instances, each mapping to one weapon at the index of
public SchemeWeaponSetting[] Weapons { get; set; } /// the <see cref="Weapon"/> enumeration.
/// </summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public SchemeWeapon[] Weapons { get; set; } = new SchemeWeapon[_weaponCount];
/// <inheritdoc/> // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void Load(Stream stream)
{ /// <inheritdoc/>
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); public override bool Equals(object? obj) => Equals(obj as SchemeWeapons);
// Read the header. /// <inheritdoc/>
reader.Seek(_trashLength); public bool Equals(SchemeWeapons? other)
if (reader.ReadString(StringCoding.ZeroTerminated) != _signature) => other != null
throw new InvalidDataException("Invalid WEP file signature."); && Weapons.SequenceEqual(other.Weapons);
// Read the weapon settings. /// <inheritdoc/>
Weapons = new SchemeWeaponSetting[_weaponCount]; public override int GetHashCode() => HashCode.Combine(Weapons);
for (int i = 0; i < _weaponCount; i++)
Weapons[i] = reader.ReadStruct<SchemeWeaponSetting>(); /// <inheritdoc/>
} public void Load(Stream stream)
{
/// <inheritdoc/> using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
public void Load(string fileName)
{ // Read the header.
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); reader.Seek(_trashLength);
Load(stream); if (reader.ReadString(StringCoding.ZeroTerminated) != _signature)
} throw new InvalidDataException("Invalid WEP file signature.");
/// <inheritdoc/> // Read the weapon settings.
public void Save(Stream stream) Weapons = new SchemeWeapon[_weaponCount];
{ for (int i = 0; i < _weaponCount; i++)
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); Weapons[i] = reader.ReadStruct<SchemeWeapon>();
}
// Write the header.
writer.Write(new byte[_trashLength]); /// <inheritdoc/>
writer.Write(_signature, StringCoding.ZeroTerminated); public void Load(string fileName)
{
// Write the weapon settings. using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
foreach (SchemeWeaponSetting weapon in Weapons) Load(stream);
writer.WriteStruct(weapon); }
}
/// <inheritdoc/>
/// <inheritdoc/> public void Save(Stream stream)
public void Save(string fileName) {
{ using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream); // 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> <PropertyGroup>
<AssemblyName>Syroot.Worms.Worms2</AssemblyName> <AssemblyName>Syroot.Worms.Worms2</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms 2.</Description> <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> <PackageTags>$(PackageTags);worms 2</PackageTags>
<Version>3.2.0</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" /> <ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

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

View File

@ -1,204 +1,266 @@
using System; using System;
using System.Collections.Generic; using System.Collections;
using System.IO; using System.Collections.Generic;
using System.Text; using System.IO;
using Syroot.BinaryData; using System.Text;
using Syroot.Worms.Core.IO; using Syroot.BinaryData;
using Syroot.Worms.IO;
namespace Syroot.Worms
{ namespace Syroot.Worms
/// <summary> {
/// Represents a directory of files stored in a DIR file, mostly used to store graphics files. Due to the nowadays /// <summary>
/// small size of typical directories, the entries and data are loaded directly into a dictionary to profit from /// Represents a directory of files stored in a DIR file, mostly used to store graphics files. Due to the nowadays
/// optimal performance when accessing and manipulating the directory. /// small size of typical directories, the entries and data are loaded directly into a dictionary to profit from
/// Used by W2, WA and WWP. S. https://worms2d.info/Graphics_directory. /// optimal performance when accessing and manipulating the directory.
/// </summary> /// Used by W2, WA and WWP. S. https://worms2d.info/Graphics_directory.
public class Archive : Dictionary<string, byte[]>, ILoadableFile, ISaveableFile /// </summary>
{ public class Archive : IDictionary<string, byte[]>, ILoadableFile, ISaveableFile
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- {
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A524944; // "DIR", 0x1A
private const int _tocSignature = 0x0000000A; private const int _signature = 0x1A524944; // "DIR", 0x1A
private const int _tocSignature = 0x0000000A;
private const int _hashBits = 10;
private const int _hashSize = 1 << _hashBits; private const int _hashBits = 10;
private const int _hashSize = 1 << _hashBits;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Archive"/> class. private readonly IDictionary<string, byte[]> _entries = new Dictionary<string, byte[]>();
/// </summary>
public Archive() { } // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Archive"/> class, loading the data from the given /// Initializes a new instance of the <see cref="Archive"/> class.
/// <see cref="Stream"/>. /// </summary>
/// </summary> public Archive() { }
/// <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
/// <summary> /// <see cref="Stream"/>.
/// Initializes a new instance of the <see cref="Archive"/> class, loading the data from the given file. /// </summary>
/// </summary> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <param name="fileName">The name of the file to load the data from.</param> public Archive(Stream stream) => Load(stream);
public Archive(string fileName) => Load(fileName);
/// <summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- /// Initializes a new instance of the <see cref="Archive"/> class, loading the data from the given file.
/// </summary>
/// <summary> /// <param name="fileName">The name of the file to load the data from.</param>
/// Loads the data from the given <see cref="Stream"/>. public Archive(string fileName) => Load(fileName);
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> // ---- OPERATORS ----------------------------------------------------------------------------------------------
public void Load(Stream stream)
{ /// <inheritdoc/>
if (!stream.CanSeek) public byte[] this[string key]
throw new ArgumentException("Stream requires to be seekable.", nameof(stream)); {
get => _entries[key];
Clear(); set => _entries[key] = value;
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); }
// Read the header. // ---- PROPERTIES ---------------------------------------------------------------------------------------------
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid DIR file signature."); /// <inheritdoc/>
int fileSize = reader.ReadInt32(); public ICollection<string> Keys => _entries.Keys;
int tocOffset = reader.ReadInt32();
/// <inheritdoc/>
// Read the table of contents. public ICollection<byte[]> Values => _entries.Values;
reader.Position = tocOffset;
int tocSignature = reader.ReadInt32(); /// <inheritdoc/>
if (tocSignature != _tocSignature) public int Count => _entries.Count;
throw new InvalidDataException("Invalid DIR table of contents signature.");
// Generate a data dictionary out of the hash table and file entries. /// <inheritdoc/>
int[] hashTable = reader.ReadInt32s(_hashSize); bool ICollection<KeyValuePair<string, byte[]>>.IsReadOnly => false;
foreach (int entryOffset in hashTable)
{ // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
// 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) /// <inheritdoc/>
{ public void Add(string key, byte[] value) => _entries.Add(key, value);
int nextEntryOffset = entryOffset;
do /// <inheritdoc/>
{ public void Clear() => _entries.Clear();
reader.Position = tocOffset + nextEntryOffset; /// <inheritdoc/>
nextEntryOffset = reader.ReadInt32(); public bool ContainsKey(string key) => _entries.ContainsKey(key);
int offset = reader.ReadInt32();
int length = reader.ReadInt32(); /// <inheritdoc/>
string name = reader.ReadString(StringCoding.ZeroTerminated); public IEnumerator<KeyValuePair<string, byte[]>> GetEnumerator() => _entries.GetEnumerator();
using (reader.TemporarySeek(offset, SeekOrigin.Begin))
Add(name, reader.ReadBytes(length)); /// <summary>
} while (nextEntryOffset != 0); /// 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)
{
/// <summary> if (!stream.CanSeek)
/// Loads the data from the given file. throw new ArgumentException("Stream requires to be seekable.", nameof(stream));
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param> Clear();
public void Load(string fileName) using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); // Read the header.
Load(stream); if (reader.ReadInt32() != _signature)
} throw new InvalidDataException("Invalid DIR file signature.");
int fileSize = reader.ReadInt32();
/// <summary> int tocOffset = reader.ReadInt32();
/// Saves the data into the given <paramref name="stream"/>.
/// </summary> // Read the table of contents.
/// <param name="stream">The <see cref="Stream"/> to save the data in.</param> reader.Position = tocOffset;
public void Save(Stream stream) int tocSignature = reader.ReadInt32();
{ if (tocSignature != _tocSignature)
using BinaryStream writer = new BinaryStream(stream, leaveOpen: true); throw new InvalidDataException("Invalid DIR table of contents signature.");
// Generate a data dictionary out of the hash table and file entries.
// Write the header. int[] hashTable = reader.ReadInt32s(_hashSize);
writer.Write(_signature); foreach (int entryOffset in hashTable)
uint fileSizeOffset = writer.ReserveOffset(); {
uint tocOffset = writer.ReserveOffset(); // 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)
// Write the data and build the hash table and file entries. {
List<HashTableEntry>[] hashTable = new List<HashTableEntry>[_hashSize]; int nextEntryOffset = entryOffset;
foreach (KeyValuePair<string, byte[]> item in this) do
{ {
HashTableEntry entry = new HashTableEntry() reader.Position = tocOffset + nextEntryOffset;
{ nextEntryOffset = reader.ReadInt32();
Name = item.Key, int offset = reader.ReadInt32();
Offset = (int)writer.Position, int length = reader.ReadInt32();
Length = item.Value.Length string name = reader.ReadString(StringCoding.ZeroTerminated);
}; using (reader.TemporarySeek(offset, SeekOrigin.Begin))
writer.Write(item.Value); Add(name, reader.ReadBytes(length));
} while (nextEntryOffset != 0);
int hash = CalculateHash(item.Key); }
if (hashTable[hash] == null) }
hashTable[hash] = new List<HashTableEntry>(); }
hashTable[hash].Add(entry);
} /// <summary>
/// Loads the data from the given file.
// Write the hash table and file entries. /// </summary>
int tocStart = (int)writer.Position; /// <param name="fileName">The name of the file to load the data from.</param>
int fileEntryOffset = sizeof(int) + _hashSize * sizeof(int); public void Load(string fileName)
writer.SatisfyOffset(tocOffset, tocStart); {
writer.Write(_tocSignature); using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
for (int i = 0; i < _hashSize; i++) Load(stream);
{ }
List<HashTableEntry> entries = hashTable[i];
if (entries == null) /// <inheritdoc/>
{ public bool Remove(string key) => _entries.Remove(key);
writer.Write(0);
} /// <summary>
else /// Saves the data into the given <paramref name="stream"/>.
{ /// </summary>
// Write the entries resolving to the current hash. /// <param name="stream">The <see cref="Stream"/> to save the data in.</param>
writer.Write(fileEntryOffset); public void Save(Stream stream)
using (writer.TemporarySeek(tocStart + fileEntryOffset, SeekOrigin.Begin)) {
{ using BinaryStream writer = new BinaryStream(stream, leaveOpen: true);
for (int j = 0; j < entries.Count; j++)
{ // Write the header.
HashTableEntry entry = entries[j]; writer.Write(_signature);
uint nextEntryOffset = writer.ReserveOffset(); uint fileSizeOffset = writer.ReserveOffset();
writer.Write(entry.Offset); uint tocOffset = writer.ReserveOffset();
writer.Write(entry.Length);
writer.Write(entry.Name, StringCoding.ZeroTerminated); // Write the data and build the hash table and file entries.
writer.Align(4); List<HashTableEntry>[] hashTable = new List<HashTableEntry>[_hashSize];
if (j < entries.Count - 1) foreach (KeyValuePair<string, byte[]> item in this)
writer.SatisfyOffset(nextEntryOffset, (int)writer.Position - tocStart); {
} HashTableEntry entry = new HashTableEntry()
fileEntryOffset = (int)writer.Position - tocStart; {
} Name = item.Key,
} Offset = (int)writer.Position,
} Length = item.Value.Length
};
writer.SatisfyOffset(fileSizeOffset, tocStart + fileEntryOffset - 1); writer.Write(item.Value);
}
int hash = CalculateHash(item.Key);
/// <summary> if (hashTable[hash] == null)
/// Saves the data in the file with the given <paramref name="fileName"/>. hashTable[hash] = new List<HashTableEntry>();
/// </summary> hashTable[hash].Add(entry);
/// <param name="fileName">The name of the file to save the data in.</param> }
public void Save(string fileName)
{ // Write the hash table and file entries.
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); uint tocStart = (uint)writer.Position;
Save(stream); uint fileEntryOffset = sizeof(int) + _hashSize * sizeof(int);
} writer.SatisfyOffset(tocOffset, tocStart);
writer.Write(_tocSignature);
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- for (int i = 0; i < _hashSize; i++)
{
private int CalculateHash(string name) List<HashTableEntry> entries = hashTable[i];
{ if (entries == null)
int hash = 0; {
for (int i = 0; i < name.Length; i++) writer.Write(0);
{ }
hash = ((hash << 1) % _hashSize) | (hash >> (_hashBits - 1) & 1); else
hash += name[i]; {
hash %= _hashSize; // Write the entries resolving to the current hash.
} writer.Write(fileEntryOffset);
return hash; using (writer.TemporarySeek(tocStart + fileEntryOffset, SeekOrigin.Begin))
} {
for (int j = 0; j < entries.Count; j++)
// ---- STRUCTURES --------------------------------------------------------------------------------------------- {
HashTableEntry entry = entries[j];
private struct HashTableEntry uint nextEntryOffset = writer.ReserveOffset();
{ writer.Write(entry.Offset);
internal string Name; writer.Write(entry.Length);
internal int Offset; writer.Write(entry.Name, StringCoding.ZeroTerminated);
internal int Length; 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> /// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param> /// <param name="self">The extended <see cref="Byte"/> instance.</param>
/// <param name="index">The 0-based index of the bit to check.</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; public static bool GetBit(this byte self, int index) => (self & (1 << index)) != 0;
/// <summary> /// <summary>
@ -62,7 +62,7 @@ namespace Syroot.Worms.Core
/// </summary> /// </summary>
/// <param name="self">The extended <see cref="Byte"/> instance.</param> /// <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="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> /// <returns>The current byte with the bit enabled or disabled.</returns>
public static byte SetBit(this byte self, int index, bool enable) public static byte SetBit(this byte self, int index, bool enable)
=> enable ? EnableBit(self, index) : DisableBit(self, index); => 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; using System.Collections.Generic;
using System.Drawing;
namespace Syroot.Worms.Core.Graphics
{ namespace Syroot.Worms.Graphics
/// <summary> {
/// Represents an interface for any class storing indexed image palette colors. /// <summary>
/// </summary> /// Represents an interface for any class storing indexed image palette colors.
public interface IPalette /// </summary>
{ public interface IPalette
// ---- PROPERTIES --------------------------------------------------------------------------------------------- {
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="Color"/> values stored by this palette. /// <summary>
/// </summary> /// Gets or sets the <see cref="Color"/> values stored by this palette.
Color[] Colors { get; set; } /// </summary>
} IList<Color> Colors { get; set; }
} }
}

View File

@ -1,61 +1,85 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
namespace Syroot.Worms namespace Syroot.Worms
{ {
/// <summary> /// <summary>
/// Represents a pixel-based 2D image in different color formats. /// Represents a pixel-based 2D image in different color formats.
/// </summary> /// </summary>
public class RawBitmap public class RawBitmap
{ {
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Gets or sets the number of bits required to describe a color per pixel. /// Gets or sets the number of bits required to describe a color per pixel.
/// </summary> /// </summary>
public byte BitsPerPixel { get; set; } public byte BitsPerPixel { get; set; }
/// <summary> /// <summary>
/// Gets or sets the colors in the palette of the bitmap, if it has one. /// Gets or sets the colors in the palette of the bitmap, if it has one.
/// </summary> /// </summary>
public IList<Color> Palette { get; set; } public IList<Color>? Palette { get; set; }
/// <summary> /// <summary>
/// Gets or sets the size of the image in pixels. /// Gets or sets the size of the image in pixels.
/// </summary> /// </summary>
public Size Size { get; set; } public Size Size { get; set; }
/// <summary> /// <summary>
/// Gets or sets the data of the image pixels. /// Gets or sets the data of the image pixels.
/// </summary> /// </summary>
public byte[] Data { get; set; } public byte[] Data { get; set; } = Array.Empty<byte>();
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Creates a <see cref="Bitmap"/> from the raw data. /// Creates a <see cref="Bitmap"/> from the raw data.
/// </summary> /// </summary>
/// <returns>The <see cref="Bitmap"/> created from the raw data.</returns> /// <returns>The <see cref="Bitmap"/> created from the raw data.</returns>
public unsafe Bitmap ToBitmap() public unsafe Bitmap ToBitmap()
{ {
// Transfer the pixel data, respecting power-of-2 strides. // Create bitmap with appropriate pixel format.
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, PixelFormat.Format8bppIndexed); PixelFormat pixelFormat = BitsPerPixel switch
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height), {
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); 1 => PixelFormat.Format1bppIndexed,
Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height); 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++) for (int y = 0; y < Size.Height; y++)
Data.AsSpan(y * Size.Width, Size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride)); {
bitmap.UnlockBits(bitmapData); Data.AsSpan((int)(y * Size.Width * bytesPerPixel), (int)(Size.Width * bytesPerPixel))
.CopyTo(bitmapSpan.Slice((int)(y * bitmapData.Stride * bytesPerPixel)));
// Transfer the palette. }
ColorPalette bitmapPalette = bitmap.Palette; bitmap.UnlockBits(bitmapData);
for (int i = 0; i < Palette.Count; i++)
bitmapPalette.Entries[i] = Palette[i]; // Transfer any palette.
bitmap.Palette = bitmapPalette; switch (pixelFormat)
{
return bitmap; 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;
using System.IO; using System.IO;
namespace Syroot.Worms.Core namespace Syroot.Worms.Graphics
{ {
/// <summary> /// <summary>
/// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for /// Represents methods to decompress data which is compressed using Team17's internal compression algorithm for
/// graphic file formats. /// graphic file formats.
/// S. http://worms2d.info/Team17_compression. /// S. http://worms2d.info/Team17_compression.
/// </summary> /// </summary>
internal static class Team17Compression internal static class Team17Compression
{ {
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary> /// <summary>
/// Returns the data available in <paramref name="bytes"/> in compressed format. /// Returns the data available in <paramref name="bytes"/> in compressed format.
/// </summary> /// </summary>
/// <param name="bytes">The data to compress.</param> /// <param name="bytes">The data to compress.</param>
/// <returns>The compressed data.</returns> /// <returns>The compressed data.</returns>
internal static byte[] Compress(byte[] bytes) internal static byte[] Compress(ReadOnlySpan<byte> bytes)
=> throw new NotImplementedException("Compressing data has not been implemented yet."); => throw new NotImplementedException("Compressing data has not been implemented yet.");
/// <summary> /// <summary>
/// Decompresses the data available in the given <paramref name="stream"/> into the provided /// Decompresses the data available in the given <paramref name="stream"/> into the provided
/// <paramref name="buffer"/>. /// <paramref name="buffer"/>.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the data from.</param> /// <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> /// <param name="buffer">The byte buffer to write the decompressed data to.</param>
internal static void Decompress(Stream stream, byte[] buffer) internal static void Decompress(Stream stream, Span<byte> buffer)
{ {
// TODO: This fails for compressed data in CD:\Data\Mission\Training0-9.img. int output = 0; // Offset of next write.
int output = 0; // Offset of next write. int cmd;
int cmd; while ((cmd = stream.ReadByte()) != -1)
while ((cmd = stream.ReadByte()) != -1) {
{ // Read a byte.
// Read a byte. if ((cmd & 0x80) == 0)
if ((cmd & 0x80) == 0) {
{ // Command: 1 byte (color)
// Command: 1 byte (color) buffer[output++] = (byte)cmd;
buffer[output++] = (byte)cmd; }
} else
else {
{ int arg1 = cmd >> 3 & 0b1111; // bits 2-5
// Arg1 = bits 2-5 int arg2 = stream.ReadByte();
int arg1 = (cmd >> 3) & 0xF; if (arg2 == -1)
int arg2 = stream.ReadByte(); return;
if (arg2 == -1) // Arg2 = bits 6-16
return; arg2 = ((cmd << 8) | arg2) & 0x7FF;
// Arg2 = bits 6-16 if (arg1 == 0)
arg2 = ((cmd << 8) | arg2) & 0x7FF; {
if (arg1 == 0) // Command: 0x80 0x00
{ if (arg2 == 0)
// Command: 0x80 0x00 return;
if (arg2 == 0) int arg3 = stream.ReadByte();
return; if (arg3 == -1)
int arg3 = stream.ReadByte(); return;
if (arg3 == -1) // Command: 3 bytes
return; output = CopyData(output, arg2, arg3 + 18, buffer);
// Command: 3 bytes }
output = CopyData(output, arg2, arg3 + 18, buffer); else
} {
else // Command: 2 bytes
{ output = CopyData(output, arg2 + 1, arg1 + 2, buffer);
// 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)
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- //{
// int srcPos = 0;
private static int CopyData(int offset, int compressedOffset, int count, byte[] buffer) // int dstPos = 0;
{ // int count, seek;
for (; count > 0; count--) //
buffer[offset] = buffer[offset++ - compressedOffset]; // while (true)
return offset; // {
} // 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; using System.IO;
namespace Syroot.Worms.Core.IO namespace Syroot.Worms.IO
{ {
/// <summary> /// <summary>
/// Represents data which can be loaded by providing a <see cref="Stream"/> to read from. /// 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> /// <summary>
/// Represents a file which can be loaded by providing a file name. /// Represents a file which can be loaded by providing a file name.

View File

@ -1,6 +1,6 @@
using System.IO; using System.IO;
namespace Syroot.Worms.Core.IO namespace Syroot.Worms.IO
{ {
/// <summary> /// <summary>
/// Represents data which can be saved by providing a <see cref="Stream "/>to write to. /// 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> /// <summary>
/// Represents a file which can be saved by providing a file name. /// 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.Reflection;
using System.Text; using System.Text;
using Syroot.BinaryData; using Syroot.BinaryData;
using Syroot.Worms.Core.IO; using Syroot.Worms.IO;
namespace Syroot.Worms.Core.Riff namespace Syroot.Worms.Core.Riff
{ {
@ -82,10 +82,10 @@ namespace Syroot.Worms.Core.Riff
chunkSaver.Value.Invoke(this, new object[] { writer }); 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) -------------------------------------------------------------------------------------- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
@ -102,8 +102,6 @@ namespace Syroot.Worms.Core.Riff
typeData.FileIdentifier = typeInfo.GetCustomAttribute<RiffFileAttribute>().Identifier; typeData.FileIdentifier = typeInfo.GetCustomAttribute<RiffFileAttribute>().Identifier;
// Get the chunk loading and saving handlers. // 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)) foreach (MethodInfo method in typeInfo.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
{ {
RiffChunkLoadAttribute loadAttribute = method.GetCustomAttribute<RiffChunkLoadAttribute>(); RiffChunkLoadAttribute loadAttribute = method.GetCustomAttribute<RiffChunkLoadAttribute>();
@ -137,9 +135,9 @@ namespace Syroot.Worms.Core.Riff
private class TypeData private class TypeData
{ {
internal string FileIdentifier; internal string FileIdentifier = String.Empty;
internal Dictionary<string, MethodInfo> ChunkLoaders; internal IDictionary<string, MethodInfo> ChunkLoaders = new Dictionary<string, MethodInfo>();
internal Dictionary<string, MethodInfo> ChunkSavers; 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;
using System.Drawing; using System.Collections.Generic;
using System.IO; using System.Drawing;
using System.Text; using System.IO;
using Syroot.BinaryData; using System.Text;
using Syroot.Worms.Core; using Syroot.BinaryData;
using Syroot.Worms.Core.IO; using Syroot.Worms.Graphics;
using Syroot.Worms.IO;
namespace Syroot.Worms
{ namespace Syroot.Worms
/// <summary> {
/// Represents a (palettized) graphical image stored in an IMG file, possibly compressed. /// <summary>
/// Used by W2, WA and WWP. S. https://worms2d.info/Image_file. /// Represents a (palletized) graphical image stored in an IMG file, possibly compressed.
/// </summary> /// Used by W2, WA and WWP. S. https://worms2d.info/Image_file.
public class Img : RawBitmap, ILoadableFile, ISaveableFile /// </summary>
{ public class Img : RawBitmap, ILoadableFile, ISaveableFile
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- {
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _signature = 0x1A474D49; // "IMG", 0x1A
/// <summary>Magic value identifying the start of IMG data.</summary>
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ public const uint Signature = 0x1A474D49; // "IMG\x1A"
/// <summary> // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// Initializes a new instance of the <see cref="Img"/> class.
/// </summary> /// <summary>
public Img() { } /// Initializes a new instance of the <see cref="Img"/> class.
/// </summary>
/// <summary> public Img() { }
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given
/// <see cref="Stream"/>. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <see cref="Stream"/>.
public Img(Stream stream) => Load(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <summary> public Img(Stream stream) => Load(stream);
/// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given file.
public Img(string fileName) => Load(fileName); /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <summary> public Img(string fileName) => Load(fileName);
/// 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>
/// </summary> /// Initializes a new instance of the <see cref="Img"/> class, loading the data from the given
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param> /// </summary>
public Img(Stream stream, bool alignData) => Load(stream, alignData); /// <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>
// ---- PROPERTIES --------------------------------------------------------------------------------------------- public Img(Stream stream, bool alignData) => Load(stream, alignData);
/// <summary> // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// Gets an optional description of the image contents.
/// </summary> /// <summary>
public string Description { get; private set; } /// Gets or sets a value indicating data was compressed or should be compressed when saving.
/// </summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public bool Compressed { get; private set; }
/// <summary> /// <summary>
/// Loads the data from the given <see cref="Stream"/>. /// Gets an optional description of the image contents.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> public string? Description { get; private set; }
public void Load(Stream stream) => Load(stream, false);
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given file. /// <summary>
/// </summary> /// Loads the data from the given <see cref="Stream"/>.
/// <param name="fileName">The name of the file to load the data from.</param> /// </summary>
public void Load(string fileName) /// <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.
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); /// This error can be ignored, the image has been completely read, and is only caused by a few files.</exception>
Load(stream); public void Load(Stream stream) => Load(stream, false);
}
/// <summary>
/// <summary> /// Loads the data from the given file.
/// Loads the data from the given <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary. /// </summary>
/// </summary> /// <param name="fileName">The name of the file to load the data from.</param>
/// <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.
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param> /// 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) public void Load(string fileName)
{ {
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
// Read the header. }
if (reader.ReadInt32() != _signature)
throw new InvalidDataException("Invalid IMG file signature."); /// <summary>
int fileSize = reader.ReadInt32(); /// Loads the data from the given <see cref="Stream"/>. The data block can be aligned to a 4-bte boundary.
/// </summary>
// Read an optional string describing the image contents and the bits per pixel. /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
BitsPerPixel = reader.Read1Byte(); /// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
if (BitsPerPixel == 0) /// <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>
Description = String.Empty; public void Load(Stream stream, bool alignData)
BitsPerPixel = reader.Read1Byte(); {
} using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
else if (BitsPerPixel > 32)
{ // Read the header.
Description = (char)BitsPerPixel + reader.ReadString(StringCoding.ZeroTerminated); if (reader.ReadUInt32() != Signature)
BitsPerPixel = reader.Read1Byte(); throw new InvalidDataException("Invalid IMG file signature.");
} int fileSize = reader.ReadInt32();
else
{ // Read an optional string describing the image contents and the bits per pixel.
Description = null; BitsPerPixel = reader.Read1Byte();
} if (BitsPerPixel == 0)
{
// Read image flags describing the format and availability of the following contents. Description = String.Empty;
Flags flags = (Flags)reader.ReadByte(); BitsPerPixel = reader.Read1Byte();
}
// Read the image palette if available. The first color of the palette is implicitly black. else if (BitsPerPixel > 32)
if (flags.HasFlag(Flags.Palettized)) {
{ Description = (char)BitsPerPixel + reader.ReadString(StringCoding.ZeroTerminated);
int colorCount = reader.ReadInt16(); BitsPerPixel = reader.Read1Byte();
Palette = new Color[colorCount + 1]; }
Palette[0] = Color.Black; else
for (int i = 1; i <= colorCount; i++) {
Palette[i] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()); Description = null;
} }
else
{ // Read image flags describing the format and availability of the following contents.
Palette = null; Flags flags = (Flags)reader.ReadByte();
}
// Read the image palette if available. The first color of the palette is implicitly black.
// Read the image size. if (flags.HasFlag(Flags.Palettized))
Size = new Size(reader.ReadInt16(), reader.ReadInt16()); {
int colorCount = reader.ReadInt16();
// Read the data byte array, which might be compressed or aligned. Palette = new List<Color>(colorCount + 1);
if (alignData) Palette.Add(Color.Black);
reader.Align(4); while (colorCount-- > 0)
byte[] data = new byte[Size.Width * Size.Height * BitsPerPixel / 8]; Palette.Add(Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()));
if (flags.HasFlag(Flags.Compressed)) }
Team17Compression.Decompress(reader.BaseStream, data); else
else {
data = reader.ReadBytes(data.Length); Palette = null;
}
Data = data;
} // Read the image size.
Size = new Size(reader.ReadInt16(), reader.ReadInt16());
/// <summary>
/// Loads the data from the given file. The data block can be aligned to a 4-bte boundary. // Read the data byte array, which might be compressed or aligned.
/// </summary> if (alignData)
/// <param name="fileName">The name of the file to load the data from.</param> reader.Align(4);
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param> int dataSize = (int)(BitsPerPixel / 8f * Size.Width * Size.Height);
public void Load(string fileName, bool alignData) if (flags.HasFlag(Flags.Compressed))
{ {
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); // Some shipped images require up to 3 bytes to not fail when decompressing with out-of-bounds access.
Load(stream, alignData); Data = new byte[dataSize];
} Team17Compression.Decompress(reader.BaseStream, Data);
}
/// <summary> else
/// Saves the data into the given <paramref name="stream"/>. {
/// </summary> Data = reader.ReadBytes(dataSize);
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param> }
public void Save(Stream stream) => Save(stream, false, false); }
/// <summary> /// <summary>
/// Saves the optionally compressed data into the given <paramref name="stream"/>. /// Loads the data from the given file. The data block can be aligned to a 4-bte boundary.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param> /// <param name="fileName">The name of the file to load the data from.</param>
/// <param name="compress"><c>true</c> to compress image data.</param> /// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool compress) => Save(stream, compress, false); /// <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>
/// <summary> public void Load(string fileName, bool alignData)
/// Saves the data in the given file. {
/// </summary> using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
/// <param name="fileName">The name of the file to save the data in.</param> Load(stream, alignData);
public void Save(string fileName) => Save(fileName, false, false); }
/// <summary> /// <summary>
/// Saves the optionally compressed data in the given file. /// Saves the data into the given <paramref name="stream"/>.
/// </summary> /// </summary>
/// <param name="fileName">The name of the file to save the data in.</param> /// <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) => Save(stream, false);
public void Save(string fileName, bool compress) => Save(fileName, compress, false);
/// <summary>
/// <summary> /// Saves the data in the given file.
/// Saves the optionally compressed data into the given <paramref name="stream"/>. The data block can be aligned /// </summary>
/// to a 4-bte boundary. /// <param name="fileName">The name of the file to save the data in.</param>
/// </summary> public void Save(string fileName) => Save(fileName, false);
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <param name="compress"><c>true</c> to compress image data.</param> /// <summary>
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param> /// Saves the data into the given <paramref name="stream"/>. The data block can be aligned to a 4-byte boundary.
public void Save(Stream stream, bool compress, bool alignData) /// </summary>
{ /// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true); /// <param name="alignData"><see langword="true"/> to align the data array by 4 bytes.</param>
public void Save(Stream stream, bool alignData)
// Write the header. {
writer.Write(_signature); using BinaryStream writer = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
uint fileSizeOffset = writer.ReserveOffset();
// Write the header.
// Write an optional string describing the image contents and the bits per pixel. writer.Write(Signature);
if (Description != null) uint fileSizeOffset = writer.ReserveOffset();
writer.Write(Description, StringCoding.ZeroTerminated);
writer.Write(BitsPerPixel); // Write an optional string describing the image contents and the bits per pixel.
if (Description != null)
// Write image flags describing the format and availability of the following contents. writer.Write(Description, StringCoding.ZeroTerminated);
Flags flags = Flags.None; writer.Write(BitsPerPixel);
if (Palette != null)
flags |= Flags.Palettized; // Write image flags describing the format and availability of the following contents.
if (compress) Flags flags = Flags.None;
flags |= Flags.Compressed; if (Palette != null)
writer.WriteEnum(flags, true); flags |= Flags.Palettized;
if (Compressed)
// Write the image palette if available. The first color of the palette is implicitly black. flags |= Flags.Compressed;
if (Palette != null) writer.WriteEnum(flags, true);
{
writer.Write((short)(Palette.Count - 1)); // Write the image palette if available. The first color of the palette is implicitly black.
for (int i = 1; i < Palette.Count; i++) if (Palette != null)
{ {
Color color = Palette[i]; writer.Write((short)(Palette.Count - 1));
writer.Write(color.R); for (int i = 1; i < Palette.Count; i++)
writer.Write(color.G); {
writer.Write(color.B); 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 image size.
// Write the data byte array, which might be compressed or aligned. writer.Write((short)Size.Width);
if (alignData) writer.Write((short)Size.Height);
writer.Align(4);
byte[] data = Data; // Write the data byte array, which might be compressed or aligned.
if (compress) if (alignData)
data = Team17Compression.Compress(data); writer.Align(4);
writer.Write(data); byte[] data = Data;
if (Compressed)
writer.SatisfyOffset(fileSizeOffset, (int)writer.Position); data = Team17Compression.Compress(data);
} writer.Write(data);
/// <summary> writer.SatisfyOffset(fileSizeOffset, (uint)writer.Position);
/// 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> /// <summary>
/// <param name="compress"><c>true</c> to compress image data.</param> /// Saves the data in the given file. The data block can be aligned to a 4-byte boundary.
/// <param name="alignData"><c>true</c> to align the data array by 4 bytes.</param> /// </summary>
public void Save(string fileName, bool compress, bool alignData) /// <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>
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); public void Save(string fileName, bool alignData)
Save(stream, compress, alignData); {
} using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream, alignData);
// ---- ENUMERATIONS ------------------------------------------------------------------------------------------- }
[Flags] // ---- ENUMERATIONS -------------------------------------------------------------------------------------------
private enum Flags : byte
{ [Flags]
None = 0, private enum Flags : byte
Compressed = 1 << 6, {
Palettized = 1 << 7 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;
using System.IO; using System.Collections.Generic;
using Syroot.BinaryData; using System.Drawing;
using Syroot.Worms.Core.Graphics; using System.IO;
using Syroot.Worms.Core.IO; using Syroot.BinaryData;
using Syroot.Worms.Core.Riff; using Syroot.Worms.Core.Riff;
using Syroot.Worms.Graphics;
namespace Syroot.Worms using Syroot.Worms.IO;
{
/// <summary> namespace Syroot.Worms
/// 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>
/// </summary> /// Represents a color palette stored in PAL files, following the RIFF format. It is used to index colors in images.
[RiffFile("PAL ")] /// Used by WA and WWP. S. http://worms2d.info/Palette_file.
public class RiffPalette : RiffFile, ILoadableFile, ISaveableFile, IPalette /// </summary>
{ [RiffFile("PAL ")]
// ---- CONSTANTS ---------------------------------------------------------------------------------------------- public class RiffPalette : RiffFile, ILoadableFile, ISaveableFile, IPalette
{
private const short _version = 0x0300; // ---- CONSTANTS ----------------------------------------------------------------------------------------------
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ private const short _version = 0x0300;
/// <summary> // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// Initializes a new instance of the <see cref="RiffPalette"/> class.
/// </summary> /// <summary>
public RiffPalette() => Version = _version; /// Initializes a new instance of the <see cref="RiffPalette"/> class.
/// </summary>
/// <summary> public RiffPalette() { }
/// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given
/// <see cref="Stream"/>. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <see cref="Stream"/>.
public RiffPalette(Stream stream) => Load(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <summary> public RiffPalette(Stream stream) => Load(stream);
/// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// Initializes a new instance of the <see cref="RiffPalette"/> class, loading the data from the given file.
public RiffPalette(string fileName) => Load(fileName); /// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
// ---- PROPERTIES --------------------------------------------------------------------------------------------- public RiffPalette(string fileName) => Load(fileName);
/// <summary> // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// Gets the version of the palette data.
/// </summary> /// <summary>
public int Version { get; set; } /// Gets the version of the palette data.
/// </summary>
/// <summary> public int Version { get; set; } = _version;
/// Gets the <see cref="Color"/> instances stored in this palette.
/// </summary> /// <summary>
public Color[] Colors { get; set; } /// Gets or sets the <see cref="Color"/> instances stored in this palette.
/// </summary>
/// <summary> public IList<Color> Colors { get; set; } = new List<Color>();
/// Gets the unknown data in the offl chunk.
/// </summary> /// <summary>
public byte[] OfflData { get; set; } /// Gets the unknown data in the offl chunk.
/// </summary>
/// <summary> public byte[] OfflData { get; set; } = Array.Empty<byte>();
/// Gets the unknown data in the tran chunk.
/// </summary> /// <summary>
public byte[] TranData { get; set; } /// Gets the unknown data in the tran chunk.
/// </summary>
/// <summary> public byte[] TranData { get; set; } = Array.Empty<byte>();
/// Gets the unknown data in the unde chunk.
/// </summary> /// <summary>
public byte[] UndeData { get; set; } /// Gets the unknown data in the unde chunk.
/// </summary>
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public byte[] UndeData { get; set; } = Array.Empty<byte>();
/// <summary> // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// Loads the data from the given <see cref="Stream"/>.
/// </summary> /// <summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// Loads the data from the given <see cref="Stream"/>.
public void Load(Stream stream) => LoadRiff(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <summary> public void Load(Stream stream) => LoadRiff(stream);
/// Loads the data from the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to load the data from.</param> /// Loads the data from the given file.
public void Load(string fileName) /// </summary>
{ /// <param name="fileName">The name of the file to load the data from.</param>
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); public void Load(string fileName)
Load(stream); {
} 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> /// <summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param> /// Saves the data into the given <paramref name="stream"/>.
public void Save(Stream stream) => SaveRiff(stream); /// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <summary> public void Save(Stream stream) => SaveRiff(stream);
/// Saves the data in the given file.
/// </summary> /// <summary>
/// <param name="fileName">The name of the file to save the data in.</param> /// Saves the data in the given file.
public void Save(string fileName) /// </summary>
{ /// <param name="fileName">The name of the file to save the data in.</param>
using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); public void Save(string fileName)
Save(stream); {
} using FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
Save(stream);
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- }
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
#pragma warning disable IDE0051 // Remove unused private members #pragma warning disable IDE0051 // Remove unused private members
[RiffChunkLoad("data")] [RiffChunkLoad("data")]
private void LoadDataChunk(BinaryStream reader, int length) private void LoadDataChunk(BinaryStream reader, int _)
{ {
// Read the PAL version. // Read the PAL version.
Version = reader.ReadInt16(); Version = reader.ReadInt16();
if (Version != _version) if (Version != _version)
throw new InvalidDataException("Unknown PAL version."); throw new InvalidDataException("Unknown PAL version.");
// Read the colors. // Read the colors.
Colors = new Color[reader.ReadInt16()]; short colorCount = reader.ReadInt16();
for (int i = 0; i < Colors.Length; i++) Colors = new List<Color>();
{ while (colorCount-- > 0)
Colors[i] = Color.FromArgb(reader.Read1Byte(), reader.Read1Byte(), reader.Read1Byte()); {
_ = reader.ReadByte(); // Dismiss alpha, as it is not used in WA. 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("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("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); [RiffChunkLoad("unde")]
private void LoadUndeChunk(BinaryStream reader, int length) => UndeData = reader.ReadBytes(length);
[RiffChunkSave("data")]
private void SaveDataChunk(BinaryStream writer) [RiffChunkSave("data")]
{ private void SaveDataChunk(BinaryStream writer)
// Write the PAL version. {
writer.Write(_version); // Write the PAL version.
writer.Write(_version);
// Write the colors.
writer.Write((short)Colors.Length); // Write the colors.
for (int i = 0; i < Colors.Length; i++) writer.Write((short)Colors.Count);
{ foreach (Color color in Colors)
Color color = Colors[i]; {
writer.Write(color.R); writer.Write(color.R);
writer.Write(color.G); writer.Write(color.G);
writer.Write(color.B); writer.Write(color.B);
writer.Write((byte)0); // Dismiss alpha, as it is not used in WA. writer.Write((byte)0); // Dismiss alpha, as it is not used in WA.
} }
} }
[RiffChunkSave("offl")] [RiffChunkSave("offl")]
private void SaveOfflChunk(BinaryStream writer) => writer.Write(OfflData); private void SaveOfflChunk(BinaryStream writer) => writer.Write(OfflData);
[RiffChunkSave("tran")] [RiffChunkSave("tran")]
private void SaveTranChunk(BinaryStream writer) => writer.Write(TranData); private void SaveTranChunk(BinaryStream writer) => writer.Write(TranData);
[RiffChunkSave("unde")] [RiffChunkSave("unde")]
private void SaveUndeChunk(BinaryStream writer) => writer.Write(UndeData); 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> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms</AssemblyName> <AssemblyName>Syroot.Worms</AssemblyName>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description> <Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Fix issues when loading and saving some formats.</PackageReleaseNotes> <PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<Version>3.2.0</Version> <Version>4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Syroot.BinaryData.Serialization" Version="5.2.0" /> <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