Add support for saving PAL files.

This commit is contained in:
Ray Koopa 2017-04-18 18:14:03 +02:00
parent e4512dbbd4
commit ddcd41df6f
13 changed files with 364 additions and 143 deletions

View File

@ -27,17 +27,21 @@ namespace Syroot.Worms.Test
// Console.WriteLine($"\t{entry}");
// }
//}
//foreach (string imgFile in GetFiles("*.img"))
//{
// Console.WriteLine("Loading {imgFile}...");
// Image image = new Image(imgFile);
//}
foreach (string palFile in GetFiles("*.pal"))
Palette pal = new Palette(@"C:\Games\Worms Armageddon 3.7.2.1\graphics\ServerLobby\flagsandwormnet.pal");
pal.Save(@"D:\Pictures\test.pal");
foreach (string palFile in GetFiles(" *.pal"))
{
Console.WriteLine($"Loading {palFile}...");
Palette palette = new Palette(palFile);
palette.Save(@"D:\Pictures\test.pal");
}
Console.ReadLine();

View File

@ -0,0 +1,18 @@
using System.IO;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents data which can be loaded by providing a <see cref="Stream"/> to read from.
/// </summary>
public interface ILoadable
{
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
void Load(Stream stream);
}
}

View File

@ -0,0 +1,16 @@
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents a file which can be loaded by providing a file name.
/// </summary>
public interface ILoadableFile : ILoadable
{
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
void Load(string fileName);
}
}

View File

@ -0,0 +1,18 @@
using System.IO;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents data which can be saved by providing a <see cref="Stream "/>to write to.
/// </summary>
public interface ISaveable
{
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
void Save(Stream stream);
}
}

View File

@ -0,0 +1,16 @@
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents a file which can be saved by providing a file name.
/// </summary>
public interface ISaveableFile : ISaveable
{
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
void Save(string fileName);
}
}

View File

@ -1,124 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Syroot.IO;
namespace Syroot.Worms.Core.Riff
{
/// <summary>
/// Represents the format of RIFF files, which manage their data in chunks.
/// </summary>
public abstract class RiffFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "RIFF";
// ---- MEMBERS ------------------------------------------------------------------------------------------------
private string _fileIdentifier;
private Dictionary<string, MethodInfo> _chunkLoaders;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
private RiffFile()
{
TypeInfo type = GetType().GetTypeInfo();
// Get the file identifier.
_fileIdentifier = type.GetCustomAttribute<RiffFileAttribute>().Identifier;
// Get the chunk loading handlers.
_chunkLoaders = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
{
RiffChunkLoaderAttribute attribute = method.GetCustomAttribute<RiffChunkLoaderAttribute>();
if (attribute != null)
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 2
&& parameters[0].ParameterType == typeof(BinaryDataReader)
&& parameters[1].ParameterType == typeof(int))
{
_chunkLoaders.Add(attribute.Identifier, method);
}
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RiffFile"/> 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 RiffFile(Stream stream)
: this()
{
Load(stream);
}
/// <summary>
/// Initializes a new instance of the <see cref="RiffFile"/> class, loading the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public RiffFile(string fileName)
: this()
{
Load(fileName);
}
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the RIFF data from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public void Load(Stream stream)
{
using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII))
{
// Read the file header.
string signature = reader.ReadString(4);
if (signature != _signature)
{
throw new InvalidDataException("Invalid RIFF file signature.");
}
int fileSize = reader.ReadInt32();
string fileIdentifier = reader.ReadString(4);
if (fileIdentifier != _fileIdentifier)
{
throw new InvalidDataException("Invalid RIFF file identifier.");
}
// Read the chunks.
while (!reader.EndOfStream)
{
string chunkIdentifier = reader.ReadString(4);
int chunkLength = reader.ReadInt32();
// Invoke a loader method if matching the identifier or skip the chunk.
if (_chunkLoaders.TryGetValue(chunkIdentifier, out MethodInfo loader))
{
loader.Invoke(this, new object[] { reader, chunkLength });
}
else
{
reader.Seek(chunkLength);
}
}
}
}
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public void Load(string fileName)
{
using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Load(stream);
}
}
}
}

View File

@ -1,13 +1,13 @@
using System;
namespace Syroot.Worms.Core.Riff
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents the attribute to decorate methods in <see cref="RiffFile"/> inheriting classes to provide methods
/// loading specific chunks according to their identifier.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
internal class RiffChunkLoaderAttribute : Attribute
[AttributeUsage(AttributeTargets.Method)]
internal class RiffChunkLoadAttribute : Attribute
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -16,7 +16,7 @@ namespace Syroot.Worms.Core.Riff
/// <paramref name="identifier"/>.
/// </summary>
/// <param name="identifier">The chunk identifier required to invoke this method for loading it.</param>
internal RiffChunkLoaderAttribute(string identifier)
internal RiffChunkLoadAttribute(string identifier)
{
Identifier = identifier;
}

View File

@ -0,0 +1,31 @@
using System;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents the attribute to decorate methods in <see cref="RiffFile"/> inheriting classes to provide methods
/// saving specific chunks with their identifier.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
internal class RiffChunkSaveAttribute : Attribute
{
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="RiffFileAttribute"/> class with the given
/// <paramref name="identifier"/>.
/// </summary>
/// <param name="identifier">The chunk identifier saved in the file.</param>
internal RiffChunkSaveAttribute(string identifier)
{
Identifier = identifier;
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the chunk identifier saved in the file.
/// </summary>
internal string Identifier { get; }
}
}

View File

@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Syroot.IO;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents the format of RIFF files, which manage their data in chunks.
/// </summary>
public abstract class RiffFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const string _signature = "RIFF";
// ---- MEMBERS ------------------------------------------------------------------------------------------------
private static readonly Dictionary<Type, TypeData> _typeDataCache = new Dictionary<Type, TypeData>();
private TypeData _typeData;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="RiffFile"/> class.
/// </summary>
protected RiffFile()
{
_typeData = GetTypeData();
}
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
/// <summary>
/// Loads the RIFF data from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the RIFF data from.</param>
protected void LoadRiff(Stream stream)
{
using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII))
{
// Read the file header.
string signature = reader.ReadString(4);
if (signature != _signature)
{
throw new InvalidDataException("Invalid RIFF file signature.");
}
int fileSize = reader.ReadInt32();
string fileIdentifier = reader.ReadString(4);
if (fileIdentifier != _typeData.FileIdentifier)
{
throw new InvalidDataException("Invalid RIFF file identifier.");
}
// Read the chunks.
while (!reader.EndOfStream)
{
string chunkIdentifier = reader.ReadString(4);
int chunkLength = reader.ReadInt32();
// Invoke a loader method if matching the identifier or skip the chunk.
if (_typeData.ChunkLoaders.TryGetValue(chunkIdentifier, out MethodInfo loader))
{
loader.Invoke(this, new object[] { reader, chunkLength });
}
else
{
reader.Seek(chunkLength);
}
}
}
}
/// <summary>
/// Saves the RIFF data in the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the RIFF data in.</param>
protected void SaveRiff(Stream stream)
{
using (BinaryDataWriter writer = new BinaryDataWriter(stream, Encoding.ASCII))
{
// Write the header.
writer.Write(_signature, BinaryStringFormat.NoPrefixOrTermination);
Offset fileSizeOffset = writer.ReserveOffset();
writer.Write(_typeData.FileIdentifier, BinaryStringFormat.NoPrefixOrTermination);
// Write the chunks.
foreach (KeyValuePair<string, MethodInfo> chunkSaver in _typeData.ChunkSavers)
{
writer.Write(chunkSaver.Key, BinaryStringFormat.NoPrefixOrTermination);
Offset chunkSizeOffset = writer.ReserveOffset();
chunkSaver.Value.Invoke(this, new object[] { writer });
chunkSizeOffset.Satisfy((int)(writer.Position - chunkSizeOffset.Position - 4));
}
fileSizeOffset.Satisfy((int)writer.Position - 8);
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private TypeData GetTypeData()
{
Type type = GetType();
if (!_typeDataCache.TryGetValue(type, out TypeData typeData))
{
typeData = new TypeData();
TypeInfo typeInfo = type.GetTypeInfo();
// Get the file identifier.
typeData.FileIdentifier = typeInfo.GetCustomAttribute<RiffFileAttribute>().Identifier;
// Get the chunk loading and saving handlers.
typeData.ChunkLoaders = new Dictionary<string, MethodInfo>();
typeData.ChunkSavers = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in typeInfo.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
{
RiffChunkLoadAttribute loadAttribute = method.GetCustomAttribute<RiffChunkLoadAttribute>();
if (loadAttribute != null)
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 2
&& parameters[0].ParameterType == typeof(BinaryDataReader)
&& parameters[1].ParameterType == typeof(int))
{
typeData.ChunkLoaders.Add(loadAttribute.Identifier, method);
}
continue;
}
RiffChunkSaveAttribute saveAttribute = method.GetCustomAttribute<RiffChunkSaveAttribute>();
if (saveAttribute != null)
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 1
&& parameters[0].ParameterType == typeof(BinaryDataWriter))
{
typeData.ChunkSavers.Add(saveAttribute.Identifier, method);
}
continue;
}
}
_typeDataCache.Add(type, typeData);
}
return typeData;
}
// ---- CLASSES ------------------------------------------------------------------------------------------------
private class TypeData
{
internal string FileIdentifier;
internal Dictionary<string, MethodInfo> ChunkLoaders;
internal Dictionary<string, MethodInfo> ChunkSavers;
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Syroot.Worms.Core.Riff
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents the attribute to decorate <see cref="RiffFile"/> inheriting classes to provide their file identifier.

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using Syroot.IO;
using Syroot.Worms.Core;
namespace Syroot.Worms.Gen2
{
@ -12,7 +13,7 @@ namespace Syroot.Worms.Gen2
/// optimal performance when accessing and manipulating the directory.
/// Used by W2, WA and WWP. S. https://worms2d.info/Graphics_directory.
/// </summary>
public class Archive : Dictionary<string, byte[]>
public class Archive : Dictionary<string, byte[]>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------

View File

@ -11,7 +11,7 @@ namespace Syroot.Worms.Gen2
/// Represents a (palettized) graphical image stored in an IMG file, possibly compressed.
/// Used by W2, WA and WWP. S. https://worms2d.info/Image_file.
/// </summary>
public class Image
public class Image : ILoadableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
using System.IO;
using Syroot.IO;
using Syroot.Maths;
using Syroot.Worms.Core.Riff;
using Syroot.Worms.Core;
namespace Syroot.Worms.Gen2
{
@ -10,11 +10,11 @@ namespace Syroot.Worms.Gen2
/// Used by WA and WWP. S. http://worms2d.info/Palette_file.
/// </summary>
[RiffFile("PAL ")]
public class Palette : RiffFile
public class Palette : RiffFile, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _version = 0x0300;
private const short _version = 0x0300;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -24,8 +24,8 @@ namespace Syroot.Worms.Gen2
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public Palette(Stream stream)
: base(stream)
{
Load(stream);
}
/// <summary>
@ -33,8 +33,8 @@ namespace Syroot.Worms.Gen2
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public Palette(string fileName)
: base(fileName)
{
Load(fileName);
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -64,9 +64,53 @@ namespace Syroot.Worms.Gen2
/// </summary>
public byte[] UndeData { get; set; }
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
public void Load(Stream stream)
{
LoadRiff(stream);
}
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
public void Load(string fileName)
{
using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Load(stream);
}
}
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
public void Save(Stream stream)
{
SaveRiff(stream);
}
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
public void Save(string fileName)
{
using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
Save(stream);
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
[RiffChunkLoader("data")]
[RiffChunkLoad("data")]
private void LoadDataChunk(BinaryDataReader reader, int length)
{
// Read the PAL version.
@ -81,26 +125,62 @@ namespace Syroot.Worms.Gen2
for (int i = 0; i < Colors.Length; i++)
{
Colors[i] = new Color(reader.ReadByte(), reader.ReadByte(), reader.ReadByte());
int alpha = reader.ReadByte(); // Dismiss alpha, as it is not used in W:A.
int alpha = reader.ReadByte(); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkLoader("offl")]
[RiffChunkLoad("offl")]
private void LoadOfflChunk(BinaryDataReader reader, int length)
{
OfflData = reader.ReadBytes(length);
}
[RiffChunkLoader("tran")]
[RiffChunkLoad("tran")]
private void LoadTranChunk(BinaryDataReader reader, int length)
{
TranData = reader.ReadBytes(length);
}
[RiffChunkLoader("unde")]
[RiffChunkLoad("unde")]
private void LoadUndeChunk(BinaryDataReader reader, int length)
{
UndeData = reader.ReadBytes(length);
}
[RiffChunkSave("data")]
private void SaveDataChunk(BinaryDataWriter writer)
{
// Write the PAL version.
writer.Write(_version);
// Write the colors.
writer.Write((short)Colors.Length);
for (int i = 0; i < Colors.Length; i++)
{
Color color = Colors[i];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
writer.Write((byte)0); // Dismiss alpha, as it is not used in WA.
}
}
[RiffChunkSave("offl")]
private void SaveOfflChunk(BinaryDataWriter writer)
{
writer.Write(OfflData);
}
[RiffChunkSave("tran")]
private void SaveTranChunk(BinaryDataWriter writer)
{
writer.Write(TranData);
}
[RiffChunkSave("unde")]
private void SaveUndeChunk(BinaryDataWriter writer)
{
writer.Write(UndeData);
}
}
}