From ff03f73af6915c2412f4af8ce3e2e7a35baf65f6 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Tue, 25 Apr 2017 20:12:09 +0200 Subject: [PATCH] Add support for WWP team files. --- src/Syroot.Worms.Test/Program.cs | 6 +- src/Syroot.Worms/Gen2/Armageddon/Team.cs | 22 +- .../Gen2/Armageddon/TeamContainer.cs | 15 +- src/Syroot.Worms/Gen2/WorldParty/Team.cs | 306 ++++++++++++++++++ .../Gen2/WorldParty/TeamContainer.cs | 160 +++++++++ 5 files changed, 495 insertions(+), 14 deletions(-) create mode 100644 src/Syroot.Worms/Gen2/WorldParty/Team.cs create mode 100644 src/Syroot.Worms/Gen2/WorldParty/TeamContainer.cs diff --git a/src/Syroot.Worms.Test/Program.cs b/src/Syroot.Worms.Test/Program.cs index 41d1cda..64df96a 100644 --- a/src/Syroot.Worms.Test/Program.cs +++ b/src/Syroot.Worms.Test/Program.cs @@ -2,7 +2,7 @@ using System; using System.IO; using System.Collections.Generic; using Syroot.Worms.Gen2; -using Syroot.Worms.Gen2.Armageddon; +using Syroot.Worms.Gen2.WorldParty; namespace Syroot.Worms.Test { @@ -19,8 +19,8 @@ namespace Syroot.Worms.Test private static void Main(string[] args) { - TeamContainer teams = new TeamContainer(@"D:\Archive\Games\Worms\Worms Armageddon\3.7.2.1\User\Teams\WG.WGT"); - teams.Save(@"D:\Pictures\test.wgt"); + TeamContainer teams = new TeamContainer(@"C:\Games\Worms World Party\User\Teams\Wg.wwp"); + teams.Save(@"D:\Pictures\test.wwp"); Console.WriteLine("Done."); Console.ReadLine(); diff --git a/src/Syroot.Worms/Gen2/Armageddon/Team.cs b/src/Syroot.Worms/Gen2/Armageddon/Team.cs index fcb6e59..9f5f86b 100644 --- a/src/Syroot.Worms/Gen2/Armageddon/Team.cs +++ b/src/Syroot.Worms/Gen2/Armageddon/Team.cs @@ -12,6 +12,26 @@ namespace Syroot.Worms.Gen2.Armageddon /// public class Team : ILoadable, ISaveable { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private const int _missionCount = 33; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class. + /// + public Team() + { + WormNames = new string[8]; + MissionStatuses = new TeamMissionStatus[_missionCount]; + TrainingMissionTimes = new int[6]; + Unknown1 = new int[10]; + TrainingMissionMedals = new byte[6]; + Unknown2 = new byte[10]; + Unknown3 = new int[7]; + } + // ---- PROPERTIES --------------------------------------------------------------------------------------------- /// @@ -209,7 +229,7 @@ namespace Syroot.Worms.Gen2.Armageddon DeathmatchKills = reader.ReadInt32(); Deaths = reader.ReadInt32(); DeathmatchDeaths = reader.ReadInt32(); - MissionStatuses = reader.ReadStructs(33); + MissionStatuses = reader.ReadStructs(_missionCount); FlagFileName = reader.ReadFixedString(0x20); Flag = new Bitmap() diff --git a/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs b/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs index 4573300..2372899 100644 --- a/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs +++ b/src/Syroot.Worms/Gen2/Armageddon/TeamContainer.cs @@ -9,7 +9,7 @@ namespace Syroot.Worms.Gen2.Armageddon { /// /// Represents the list of teams and unlocked game features stored in WGT files. - /// Used by WA and WWP. S. https://worms2d.info/Team_file. + /// Used by WA. S. https://worms2d.info/Team_file. /// public class TeamContainer : ILoadableFile, ISaveableFile { @@ -87,7 +87,7 @@ namespace Syroot.Worms.Gen2.Armageddon // Read global settings. byte teamCount = reader.ReadByte(); - UnlockedFeatures = reader.ReadEnum(true); + UnlockedFeatures = reader.ReadEnum(false); Unknown = reader.ReadByte(); // Read the teams. @@ -127,7 +127,7 @@ namespace Syroot.Worms.Gen2.Armageddon // Write global settings. writer.Write((byte)Teams.Count); - writer.Write(UnlockedFeatures, true); + writer.Write(UnlockedFeatures, false); writer.Write(Unknown); // Write the teams. @@ -223,13 +223,8 @@ namespace Syroot.Worms.Gen2.Armageddon SheepHeaven = 1 << 18, /// - /// Map terrain becomes indestructible. Has to be set together with . + /// Map terrain can be indestructible and Full Wormage scheme is accessible. /// - IndestructibleMap1 = 1 << 24, - - /// - /// Map terrain becomes indestructible. Has to be set together with . - /// - IndestructibleMap2 = 1 << 25 + IndestructibleAndFullWormage = 1 << 24 } } diff --git a/src/Syroot.Worms/Gen2/WorldParty/Team.cs b/src/Syroot.Worms/Gen2/WorldParty/Team.cs new file mode 100644 index 0000000..f5a377c --- /dev/null +++ b/src/Syroot.Worms/Gen2/WorldParty/Team.cs @@ -0,0 +1,306 @@ +using System.Diagnostics; +using System.IO; +using System.Text; +using Syroot.IO; +using Syroot.Maths; +using Syroot.Worms.Core; + +namespace Syroot.Worms.Gen2.WorldParty +{ + /// + /// Represents a team stored in a file. + /// + public class Team : ILoadable, ISaveable + { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private const int _missionCount = 45; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class. + /// + public Team() + { + WormNames = new string[8]; + MissionStatuses = new TeamMissionStatus[_missionCount]; + TrainingMissionTimes = new int[6]; + WeaponPoints = new byte[46]; + Unknown2 = new int[7]; + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + /// + /// Gets or sets the name of the team. + /// + public string Name { get; set; } + + /// + /// Gets or sets the 8 worm names. + /// + public string[] WormNames { get; set; } + + /// + /// Gets or sets the AI intelligence difficulty level, from 0-5, where 0 is human-controlled. + /// + public byte CpuLevel { get; set; } + + /// + /// Gets or sets the name of soundbank for the voice of team worms. + /// + public string SoundBankName { get; set; } + + public byte SoundBankLocation { get; set; } + + /// + /// Gets or sets the name of the team fanfare. + /// + public string FanfareName { get; set; } + + /// + /// Gets or sets a value indicating whether the fanfare with the name stored in + /// (true) or the player's countries' fanfare should be played (false). + /// + public byte UseCustomFanfare { get; set; } + + /// + /// Gets or sets the sprite index of the team grave, -1 being a custom bitmap grave. + /// + public sbyte GraveSprite { get; set; } + + /// + /// Gets or sets the file name of the team grave bitmap if it uses a custom one. + /// + public string GraveFileName { get; set; } + + /// + /// Gets or sets the team grave bitmap if it uses a custom one. + /// + public Bitmap Grave { get; set; } + + /// + /// Gets or sets the team's special weapon. + /// + public byte TeamWeapon { get; set; } + + /// + /// Gets or sets the number of games lost. + /// + public int GamesLost { get; set; } + + /// + /// Gets or sets the number of deathmatch games lost. + /// + public int DeathmatchesLost { get; set; } + + /// + /// Gets or sets the number of games won. + /// + public int GamesWon { get; set; } + + /// + /// Gets or sets the number of deathmatch games won. + /// + public int DeathmatchesWon { get; set; } + + /// + /// Gets or sets the number of games drawn. + /// + public int GamesDrawn { get; set; } + + /// + /// Gets or sets the number of deathmatch games drawn. + /// + public int DeathmatchesDrawn { get; set; } + + /// + /// Gets or sets the number of opponent worms killed by this team. + /// + public int Kills { get; set; } + + /// + /// Gets or sets the number of opponent worms killed by this team in deathmatches. + /// + public int DeathmatchKills { get; set; } + + /// + /// Gets or sets the number of worms which got killed in this team. + /// + public int Deaths { get; set; } + + /// + /// Gets or sets the number of worms which got killed in this team in deathmatches. + /// + public int DeathmatchDeaths { get; set; } + + /// + /// Gets or sets the array of 33 mission statuses. + /// + public TeamMissionStatus[] MissionStatuses { get; set; } + + /// + /// Gets or sets the file name of the team flag. + /// + public string FlagFileName { get; set; } + + /// + /// Gets or sets the bitmap of the team flag. + /// + public Bitmap Flag { get; set; } + + public byte Unknown1 { get; set; } + + /// + /// Gets or sets the deathmatch rank this team reached. + /// + public byte DeathmatchRank { get; set; } + + /// + /// Gets or sets the seconds the team required to finish all 6 training missions. + /// + public int[] TrainingMissionTimes { get; set; } + + /// + /// Gets or sets a possibly unused training mission time for a 35th mission. + /// + public int UnknownTrainingMissionTime { get; set; } + + /// + /// Gets or sets the 46 weapons which were bought for points. + /// + public byte[] WeaponPoints { get; set; } + + /// + /// Gets or sets the fort of the team. + /// + public byte Fort { get; set; } + + /// + /// Gets or sets 7 unknown integer values. + /// + public int[] Unknown2 { get; set; } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Loads the data from the given . + /// + /// The to load the data from. + public void Load(Stream stream) + { + using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII, true)) + { + Name = reader.ReadFixedString(17); + WormNames = reader.ReadFixedStrings(8, 17); + CpuLevel = reader.ReadByte(); + SoundBankName = reader.ReadFixedString(0x20); + SoundBankLocation = reader.ReadByte(); + FanfareName = reader.ReadFixedString(0x20); + UseCustomFanfare = reader.ReadByte(); + + GraveSprite = reader.ReadSByte(); + if (GraveSprite < 0) + { + GraveFileName = reader.ReadFixedString(0x20); + Grave = new Bitmap() + { + BitsPerPixel = 8, + Size = new Vector2(24, 32), + Palette = reader.ReadStructs(256), + Data = reader.ReadBytes(24 * 32) + }; + } + + TeamWeapon = reader.ReadByte(); + GamesLost = reader.ReadInt32(); + DeathmatchesLost = reader.ReadInt32(); + GamesWon = reader.ReadInt32(); + DeathmatchesWon = reader.ReadInt32(); + GamesDrawn = reader.ReadInt32(); + DeathmatchesDrawn = reader.ReadInt32(); + Kills = reader.ReadInt32(); + DeathmatchKills = reader.ReadInt32(); + Deaths = reader.ReadInt32(); + DeathmatchDeaths = reader.ReadInt32(); + MissionStatuses = reader.ReadStructs(_missionCount); + + FlagFileName = reader.ReadFixedString(0x20); + Flag = new Bitmap() + { + BitsPerPixel = 8, + Size = new Vector2(20, 17), + Palette = reader.ReadStructs(256), + Data = reader.ReadBytes(20 * 17) + }; + + Unknown1 = reader.ReadByte(); + DeathmatchRank = reader.ReadByte(); + TrainingMissionTimes = reader.ReadInt32s(34); + UnknownTrainingMissionTime = reader.ReadInt32(); // Possibly an unused time. + WeaponPoints = reader.ReadBytes(46); + Fort = reader.ReadByte(); + Unknown2 = reader.ReadInt32s(7); + } + } + + /// + /// Saves the data into the given . + /// + /// The to save the data to. + public void Save(Stream stream) + { + using (BinaryDataWriter writer = new BinaryDataWriter(stream, Encoding.ASCII, true)) + { + writer.Write(Name, 17); + writer.Write(WormNames, 17); + writer.Write(CpuLevel); + writer.Write(SoundBankName, 0x20); + writer.Write(SoundBankLocation); + writer.Write(FanfareName, 0x20); + writer.Write(UseCustomFanfare); + + writer.Write(GraveSprite); + if (GraveSprite < 0) + { + writer.Write(GraveFileName, 0x20); + writer.Write(Grave.Palette); + writer.Write(Grave.Data); + } + + writer.Write(TeamWeapon); + writer.Write(GamesLost); + writer.Write(DeathmatchesLost); + writer.Write(GamesWon); + writer.Write(DeathmatchesWon); + writer.Write(GamesDrawn); + writer.Write(DeathmatchesDrawn); + writer.Write(Kills); + writer.Write(DeathmatchKills); + writer.Write(Deaths); + writer.Write(DeathmatchDeaths); + writer.Write(MissionStatuses); + + writer.Write(FlagFileName, 0x20); + writer.Write(Flag.Palette); + writer.Write(Flag.Data); + + writer.Write(Unknown1); + writer.Write(DeathmatchRank); + writer.Write(TrainingMissionTimes); + writer.Write(UnknownTrainingMissionTime); + writer.Write(WeaponPoints); + writer.Write(Fort); + writer.Write(Unknown2); + } + } + } + + [DebuggerDisplay("TeamMissionStatus Attemps={Attempts} Medal={Medal}")] + public struct TeamMissionStatus + { + public int Attempts; + public int Medal; + } +} diff --git a/src/Syroot.Worms/Gen2/WorldParty/TeamContainer.cs b/src/Syroot.Worms/Gen2/WorldParty/TeamContainer.cs new file mode 100644 index 0000000..404968c --- /dev/null +++ b/src/Syroot.Worms/Gen2/WorldParty/TeamContainer.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Syroot.IO; +using Syroot.Worms.Core; + +namespace Syroot.Worms.Gen2.WorldParty +{ + /// + /// Represents the list of teams and unlocked game features stored in WGT files. + /// Used by WWP. See https://worms2d.info/File_formats. + /// + public class TeamContainer : ILoadableFile, ISaveableFile + { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private const string _signature = "WWP"; // 0-terminated. + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class. + /// + public TeamContainer() + { + Teams = new List(); + } + + /// + /// Initializes a new instance of the class, loading the data from the given + /// . + /// + /// The to load the data from. + public TeamContainer(Stream stream) + { + Load(stream); + } + + /// + /// Initializes a new instance of the class, loading the data from the given file. + /// + /// The name of the file to load the data from. + public TeamContainer(string fileName) + { + Load(fileName); + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + /// + /// Gets or sets a value possibly indicating a version of the file format. + /// + public byte Version { get; set; } + + /// + /// Gets or sets an unknown value. + /// + public byte Unknown1 { get; set; } + + /// + /// Gets or sets an unknown value. + /// + public byte Unknown2 { get; set; } + + /// + /// Gets or sets 840 unknown bytes, all possibly 0. + /// + public byte[] Unknown3 { get; set; } + + /// + /// Gets or sets the list of instances stored. + /// + public List Teams { get; set; } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Loads the data from the given . + /// + /// The to load the data from. + public void Load(Stream stream) + { + using (BinaryDataReader reader = new BinaryDataReader(stream, Encoding.ASCII, true)) + { + // Read the header. + if (reader.ReadString(BinaryStringFormat.ZeroTerminated) != _signature) + { + throw new InvalidDataException("Invalid WWP file signature."); + } + Version = reader.ReadByte(); // Really version? + + // Read global settings. + byte teamCount = reader.ReadByte(); + Unknown1 = reader.ReadByte(); + Unknown2 = reader.ReadByte(); + Unknown3 = reader.ReadBytes(840); + + // Read the teams. + Teams = new List(teamCount); + for (int i = 0; i < teamCount; i++) + { + Team team = new Team(); + team.Load(reader.BaseStream); + Teams.Add(team); + } + } + } + + /// + /// Loads the data from the given file. + /// + /// The name of the file to load the data from. + public void Load(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + Load(stream); + } + } + + /// + /// Saves the data into the given . + /// + /// The to save the data to. + public void Save(Stream stream) + { + using (BinaryDataWriter writer = new BinaryDataWriter(stream, Encoding.ASCII)) + { + // Write the header. + writer.Write(_signature, BinaryStringFormat.ZeroTerminated); + writer.Write(Version); + + // Write global settings. + writer.Write((byte)Teams.Count); + writer.Write(Unknown1); + writer.Write(Unknown2); + writer.Write(Unknown3); + + // Write the teams. + foreach (Team team in Teams) + { + team.Save(writer.BaseStream); + } + } + } + + /// + /// Saves the data in the given file. + /// + /// The name of the file to save the data in. + public void Save(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Save(stream); + } + } + } +}