diff --git a/OpenKh.Game/Field/Kh2Field.cs b/OpenKh.Game/Field/Kh2Field.cs index dcb10b250..601214eb6 100644 --- a/OpenKh.Game/Field/Kh2Field.cs +++ b/OpenKh.Game/Field/Kh2Field.cs @@ -133,11 +133,11 @@ public void LoadArea(int world, int area) if (area >= 0 && area < worldInfos.Count) { var areaInfo = worldInfos[area]; - var isKnown = (areaInfo.Flags & 1) != 0; - var isInDoor = (areaInfo.Flags & 2) != 0; - var isMonochrome = (areaInfo.Flags & 4) != 0; - var hasNoShadow = (areaInfo.Flags & 8) != 0; - var hasGlow = (areaInfo.Flags & 16) != 0; + var isKnown = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.IsKnownArea); + var isInDoor = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.IndoorArea); + var isMonochrome = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.Monochrome); + var hasNoShadow = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.NoShadow); + var hasGlow = areaInfo.Flags.HasFlag(Kh2.SystemData.Arif.ArifFlags.HasGlow); _targetCamera.Type = isInDoor ? 1 : 0; } diff --git a/OpenKh.Kh2/BaseTable.cs b/OpenKh.Kh2/BaseTable.cs index 77d8db9d5..c0de1a628 100644 --- a/OpenKh.Kh2/BaseTable.cs +++ b/OpenKh.Kh2/BaseTable.cs @@ -31,7 +31,9 @@ public static void Write(Stream stream, int version, IEnumerable items) foreach (var item in itemList) BinaryMapping.WriteObject(stream, item); } - } + } + + public class BaseShortTable where T : class @@ -56,6 +58,34 @@ public static void Write(Stream stream, int id, IEnumerable items) Count = (short)itemList.Count, }); + foreach (var item in itemList) + BinaryMapping.WriteObject(stream, item); + } + } + + + //Tables that only have a Count of entries; like soundinfo, etc. + public class BaseTableCountOnly + where T : class + { + [Data] public int Count { get; set; } + + public static List Read(Stream stream) + { + var header = BinaryMapping.ReadObject>(stream); + return Enumerable.Range(0, header.Count) + .Select(_ => BinaryMapping.ReadObject(stream)) + .ToList(); + } + + public static void Write(Stream stream, IEnumerable items) + { + var itemList = items as IList ?? items.ToList(); + BinaryMapping.WriteObject(stream, new BaseTableCountOnly + { + Count = (short)itemList.Count, + }); + foreach (var item in itemList) BinaryMapping.WriteObject(stream, item); } diff --git a/OpenKh.Kh2/Battle/Atkp.cs b/OpenKh.Kh2/Battle/Atkp.cs index 8224c72fe..7f421d565 100644 --- a/OpenKh.Kh2/Battle/Atkp.cs +++ b/OpenKh.Kh2/Battle/Atkp.cs @@ -110,14 +110,42 @@ public enum AttackKind : byte 0x66, 0x5F, 0x6D, 0x6F, 0x76, 0x65, 0x00, 0x63, 0x72, 0x61, 0x73, 0x68, 0x00 }; - public static List Read(Stream stream) => BaseTable.Read(stream); - - public static void Write(Stream stream, IEnumerable items) - { - BaseTable.Write(stream, 6, items); - stream.Position = 130232; - stream.Write(endBytes,0,277); - } - + public static List Read(Stream stream) => BaseTable.Read(stream); + + //New ATKP: Extra byte addition is no longer hardcoded to add to a specific position. + //Rather, they're now added at the end. + //The bytes seem to not be important, the strings resemble ones found in AI. + //However, to prevent messing with the ATKP offset unless explicity adding attack hitboxes, they'll be appended to the end of the file. + public static void Write(Stream stream, IEnumerable items) + { + // Get the initial length of the stream + long initialLength = stream.Length; + + // Write the items to the stream + BaseTable.Write(stream, 6, items); + + // Check if the stream length has increased + if (stream.Length > initialLength) + { + + // Seek to the end of the stream + stream.Seek(0, SeekOrigin.End); + + // Append the bytes to the end of the stream + stream.Write(endBytes, 0, endBytes.Length); + } + if (stream.Length == initialLength) + { + // Seek to the end of the stream + stream.Seek(0, SeekOrigin.End); + } + //Currently if you're just editing hitboxes, nothing changes. No offset differences occur. + //If you add one hitbox, it overwrites endbytes until it reaches the end of the file, and starts appending new bytes AND endbytes after. + + else + { + // If no new data was written, do nothing + } + } } } diff --git a/OpenKh.Kh2/Battle/Limt.cs b/OpenKh.Kh2/Battle/Limt.cs index d7288161a..34f20c44e 100644 --- a/OpenKh.Kh2/Battle/Limt.cs +++ b/OpenKh.Kh2/Battle/Limt.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Xe.BinaryMapper; @@ -5,18 +6,51 @@ namespace OpenKh.Kh2.Battle { public class Limt - { + { + + + [Flags] + public enum Characters : byte + { + None = 0, + Sora = 1, + Donald = 2, + Goofy = 3, + Mickey = 4, + Auron = 5, + Mulan = 6, + Aladdin = 7, + JackSparrow = 8, + Beast = 9, + JackSkellington = 10, + Simba = 11, + Tron = 12, + Riku = 13, + Roxas = 14, + Ping = 15, + Stitch = 200, + Genie = 201, + PeterPan = 202, + ChickenLittle = 204, + } + + + [Data] public byte Id { get; set; } - [Data] public byte Character { get; set; } - [Data] public byte Summon { get; set; } + [Data] public Characters Character { get; set; } + [Data] public Characters Summon { get; set; } [Data] public byte Group { get; set; } [Data(Count = 32)] public string FileName { get; set; } [Data] public uint SpawnId { get; set; } [Data] public ushort Command { get; set; } [Data] public ushort Limit { get; set; } - [Data] public byte World { get; set; } - [Data(Count = 19)] public byte[] Padding { get; set; } - + [Data] public ushort World { get; set; } + [Data(Count = 18)] public byte[] Padding { get; set; } + + public override string ToString() + { + return FileName; + } public static List Read(Stream stream) => BaseTable.Read(stream); public static void Write(Stream stream, IEnumerable items) => diff --git a/OpenKh.Kh2/Battle/Stop.cs b/OpenKh.Kh2/Battle/Stop.cs new file mode 100644 index 000000000..140c44743 --- /dev/null +++ b/OpenKh.Kh2/Battle/Stop.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Battle +{ + public class Stop + { + [Flags] + public enum Flag : uint + { + Exist = 0x01, + DisableDamageReaction = 0x02, + Star = 0x04, + DisableDraw = 0x08, + } + [Data] public ushort Id { get; set; } + [Data] public Flag Flags { get; set; } + + public static List Read(Stream stream) => BaseTable.Read(stream); + + public static void Write(Stream stream, IEnumerable items) => + BaseTable.Write(stream, 2, items); + } +} diff --git a/OpenKh.Kh2/Jigsaw.cs b/OpenKh.Kh2/Jigsaw.cs index ffc68a57e..da382c244 100644 --- a/OpenKh.Kh2/Jigsaw.cs +++ b/OpenKh.Kh2/Jigsaw.cs @@ -1,15 +1,48 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using Xe.BinaryMapper; namespace OpenKh.Kh2 { public class Jigsaw - { - [Data] public byte Picture { get; set; } + { + public enum PictureName : byte + { + Awakening = 0, + Heart = 1, + Duality = 2, + Frontier = 3, + Daylight = 4, + Sunset = 5 + } + + public enum WorldList : byte + { + ZZ = 0, + EndofSea = 1, + TwilightTown = 2, + DestinyIsland = 3, + HollowBastion = 4, + BeastsCastle = 5, + OlympusColiseum = 6, + Agrabah = 7, + TheLandOfDragons = 8, + HundredAcreWood = 9, + PrideLand = 10, + Atlantica = 11, + DisneyCastle = 12, + TimelessRiver = 13, + HalloweenTown = 14, + WorldMap = 15, + PortRoyal = 16, + SpaceParanoids = 17, + TheWorldThatNeverWas = 18, + } + + [Data] public PictureName Picture { get; set; } [Data] public byte Part { get; set; } [Data] public ushort Text { get; set; } //z_un_002a2de8, binary addition 0x8000 - [Data] public byte World { get; set; } + [Data] public WorldList World { get; set; } [Data] public byte Room { get; set; } [Data] public byte JigsawIdWorld { get; set; } [Data] public byte Unk07 { get; set; } //has also something to do with pos and orientation diff --git a/OpenKh.Kh2/Libretto.cs b/OpenKh.Kh2/Libretto.cs new file mode 100644 index 000000000..cf1fb2a39 --- /dev/null +++ b/OpenKh.Kh2/Libretto.cs @@ -0,0 +1,145 @@ +using OpenKh.Common; +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; +namespace OpenKh.Kh2 +//Can properly read/write and update. Can insert new entries between. +{ + public class Libretto + { + [Data] public int MagicCode { get; set; } + [Data] public int Count { get; set; } + [Data] public List Definitions { get; set; } + [Data] public List> Contents { get; set; } + + public class TalkMessageDefinition + { + [Data] public ushort TalkMessageId { get; set; } + [Data] public ushort Unknown { get; set; } + [Data] public uint ContentPointer { get; set; } + } + + public class TalkMessageContent + { + [Data] public uint Unknown1 { get; set; } + [Data] public uint TextId { get; set; } + } + + public class TalkMessagePatch + { + [Data] public ushort TalkMessageId { get; set; } + [Data] public ushort Unknown { get; set; } + [Data] public List Contents { get; set; } + } + + public class ContentPatch + { + [Data] public uint Unknown1 { get; set; } + [Data] public uint TextId { get; set; } + } + + public static Libretto Read(Stream stream) + { + //Store initial position of stream. + var basePosition = stream.Position; + //Create new Libretto object, then read MagicCode & Count from stream. + var libretto = new Libretto + { + MagicCode = stream.ReadInt32(), + Count = stream.ReadInt32() + }; + + //Initialize definitions/contents list w/ capacity equal to count + libretto.Definitions = new List(libretto.Count); + libretto.Contents = new List>(libretto.Count); + + //Loop over number of definitions specified by count. + for (int i = 0; i < libretto.Count; i++) + { + //Read TalkMessageDefinition from the stream, add to Definitions list. + libretto.Definitions.Add(new TalkMessageDefinition + { + TalkMessageId = stream.ReadUInt16(), + Unknown = stream.ReadUInt16(), + ContentPointer = stream.ReadUInt32() + }); + } + + //Loop over each definition in the Definitions list. + foreach (var definition in libretto.Definitions) + { + //Set stream position to the Content Pointer for the current definition. + stream.Position = basePosition + definition.ContentPointer; + //Create a new list to hold our TalkMessageContent objects for the current definition. + var contents = new List(); + + //Read all TalkMessageContents for current definition until Terminating Condition is met. + //while (true) //CAn set to while (true)... + //var content = BinaryMapping.ReadObject(stream); + //while (content.Unknown1 != 0) + + //Literally had to do both a while loop and manually change the content check. + //Also changed the way content is read, from being read like this: + // var content = BinaryMapping.ReadObject(stream); + //And changed the condition. So, might be times where you need to read like this. + //Yea. issue is down to using the BinaryMapper for this. + while (true) + { + // Read a TalkMessageContent object manually from the stream + var content = new TalkMessageContent + { + Unknown1 = stream.ReadUInt32(), + TextId = stream.ReadUInt32() + }; + + // Check for the termination condition: Unknown1 == 0 and TextId == 0 + if (content.Unknown1 == 0 || content.Unknown1 == null) + { + break; + } + + // Add content to the contents list + contents.Add(content); + } + //ADd list of contents for the current definition to the Contents list. + libretto.Contents.Add(contents); + } + + return libretto; + } + + + public static void Write(Stream stream, Libretto libretto) + { + var basePosition = stream.Position; + + stream.Write(libretto.MagicCode); + stream.Write(libretto.Count); + + var offset = 8 + libretto.Definitions.Count * 8; //Set offset variable; start AFTER magiccode+count and update the offset later. Offset = 8 + # of Definitions*8. + + foreach (var definition in libretto.Definitions) + { + stream.Write(definition.TalkMessageId); //Write the TalkMessage for each Definition. + stream.Write(definition.Unknown); //Write the Unknown for each Definition. + stream.Write(offset); //Write the offset for each Definition. + offset += libretto.Contents[libretto.Definitions.IndexOf(definition)].Count * 8 + 4; //Update the Offset in each definition. + } + + stream.Position = basePosition + 8 + libretto.Definitions.Count * 8; + foreach (var contents in libretto.Contents) + { + foreach (var content in contents) + { + stream.Write(content.Unknown1); + stream.Write(content.TextId); + //if (content.Unknown1 == 0) + // break; + } + //Break this until we figure out why it isn't reading. + stream.Write(0); // Write the padding (0x00000000) + } + } + } +} diff --git a/OpenKh.Kh2/Localset.cs b/OpenKh.Kh2/Localset.cs new file mode 100644 index 000000000..01d707668 --- /dev/null +++ b/OpenKh.Kh2/Localset.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Localset + { + [Data] public ushort ProgramId { get; set; } + [Data] public ushort MapNumber { get; set; } + + public static List Read(Stream stream) => BaseTable.Read(stream); + public static void Write(Stream stream, IEnumerable entries) => + BaseTable.Write(stream, 1, entries); + } +} diff --git a/OpenKh.Kh2/Mixdata/Cond_LP.cs b/OpenKh.Kh2/Mixdata/Cond_LP.cs new file mode 100644 index 000000000..9fed483b9 --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Cond_LP.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class CondLP + //LP for Listpatch version of the file. Currently the BaseMixdata Read/Write seems to not work with reading/writing these files? Tests seemed to fail. + //LP versions of the files exist so as to not ruin the original, cleaner version of the code, however if the original version of the code is used for the Listpatch then current mods wouldn't break. + { + private const int MagicCode = 0x4F43494D; + + public enum RewardType + { + Item = 0, + ShopUpgrade = 1 + } + + public enum CollectionType + { + Stack = 0, + Unique = 1 + } + + [Data] public ushort TextId { get; set; } + [Data] public short Reward { get; set; } //either item from 03system or shop upgrades + [Data] public RewardType Type { get; set; } + [Data] public byte MaterialType { get; set; } + [Data] public byte MaterialRank { get; set; } + [Data] public CollectionType ItemCollect { get; set; } + [Data] public short Count { get; set; } + [Data] public short ShopUnlock { get; set; } + + public static List Read(Stream stream) + { + var condLPList = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var condLP = new CondLP + { + TextId = reader.ReadUInt16(), + Reward = reader.ReadInt16(), + Type = (CondLP.RewardType)reader.ReadByte(), + MaterialType = reader.ReadByte(), + MaterialRank = reader.ReadByte(), + ItemCollect = (CondLP.CollectionType)reader.ReadByte(), + Count = reader.ReadInt16(), + ShopUnlock = reader.ReadInt16() + }; + condLPList.Add(condLP); + } + } + return condLPList; + } + public static void Write(Stream stream, List condLPList) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(condLPList.Count); + writer.Write(0); // Padding + + foreach (var condLP in condLPList) + { + writer.Write(condLP.TextId); + writer.Write(condLP.Reward); + writer.Write((byte)condLP.Type); + writer.Write(condLP.MaterialType); + writer.Write(condLP.MaterialRank); + writer.Write((byte)condLP.ItemCollect); + writer.Write(condLP.Count); + writer.Write(condLP.ShopUnlock); + } + } + } + } +} diff --git a/OpenKh.Kh2/Mixdata/Leve_LP.cs b/OpenKh.Kh2/Mixdata/Leve_LP.cs new file mode 100644 index 000000000..5ef327aa5 --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Leve_LP.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class LeveLP + { + private const int MagicCode = 0x564C494D; + + [Data] public ushort Title { get; set; } //Names from Sys.bar like "Amateur Moogle" + [Data] public ushort Stat { get; set; } //Another text ID. + [Data] public short Enable { get; set; } + [Data] public ushort Padding { get; set; } + [Data] public int Exp { get; set; } + + public static List Read(Stream stream) + { + var leveLPList = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var leveLP = new LeveLP + { + Title = reader.ReadUInt16(), + Stat = reader.ReadUInt16(), + Enable = reader.ReadInt16(), + Padding = reader.ReadUInt16(), + Exp = reader.ReadInt32() + }; + leveLPList.Add(leveLP); + } + } + return leveLPList; + } + public static void Write(Stream stream, List leveLPList) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(leveLPList.Count); + writer.Write(0); // Padding + + foreach (var leveLP in leveLPList) + { + writer.Write(leveLP.Title); + writer.Write(leveLP.Stat); + writer.Write(leveLP.Enable); + writer.Write(leveLP.Padding); + writer.Write(leveLP.Exp); + } + } + } + } +} diff --git a/OpenKh.Kh2/Mixdata/Reci_LP.cs b/OpenKh.Kh2/Mixdata/Reci_LP.cs new file mode 100644 index 000000000..a712c51ee --- /dev/null +++ b/OpenKh.Kh2/Mixdata/Reci_LP.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.Mixdata +{ + public class ReciLP + { + public const int MagicCode = 0x4552494D; + + public enum UnlockType + { + Recipe = 0, + FreeDevelopment1 = 1, + FreeDevelopment2 = 2, + FreeDevelopment3 = 3, + } + + [Data] public ushort Id { get; set; } //03system -> item + [Data] public UnlockType Unlock { get; set; } + [Data] public byte Rank { get; set; } + [Data] public ushort Item { get; set; } + [Data] public ushort UpgradedItem { get; set; } + [Data] public ushort Ingredient1 { get; set; } + [Data] public ushort Ingredient1Amount { get; set; } + [Data] public ushort Ingredient2 { get; set; } + [Data] public ushort Ingredient2Amount { get; set; } + [Data] public ushort Ingredient3 { get; set; } + [Data] public ushort Ingredient3Amount { get; set; } + [Data] public ushort Ingredient4 { get; set; } + [Data] public ushort Ingredient4Amount { get; set; } + [Data] public ushort Ingredient5 { get; set; } + [Data] public ushort Ingredient5Amount { get; set; } + [Data] public ushort Ingredient6 { get; set; } + [Data] public ushort Ingredient6Amount { get; set; } + + public static List Read(Stream stream) + { + var recipes = new List(); + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + int magicCode = reader.ReadInt32(); + int version = reader.ReadInt32(); + int count = reader.ReadInt32(); + reader.ReadInt32(); // Skip padding + + for (int i = 0; i < count; i++) + { + var recipe = new ReciLP + { + Id = reader.ReadUInt16(), + Unlock = (UnlockType)reader.ReadByte(), + Rank = reader.ReadByte(), + Item = reader.ReadUInt16(), + UpgradedItem = reader.ReadUInt16(), + Ingredient1 = reader.ReadUInt16(), + Ingredient1Amount = reader.ReadUInt16(), + Ingredient2 = reader.ReadUInt16(), + Ingredient2Amount = reader.ReadUInt16(), + Ingredient3 = reader.ReadUInt16(), + Ingredient3Amount = reader.ReadUInt16(), + Ingredient4 = reader.ReadUInt16(), + Ingredient4Amount = reader.ReadUInt16(), + Ingredient5 = reader.ReadUInt16(), + Ingredient5Amount = reader.ReadUInt16(), + Ingredient6 = reader.ReadUInt16(), + Ingredient6Amount = reader.ReadUInt16() + }; + recipes.Add(recipe); + } + } + return recipes; + } + + public static void Write(Stream stream, List recipes) + { + stream.Position = 0; + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + writer.Write(MagicCode); + writer.Write(2); // Version number, hardcoded for example + writer.Write(recipes.Count); + writer.Write(0); // Padding + + foreach (var recipe in recipes) + { + writer.Write(recipe.Id); + writer.Write((byte)recipe.Unlock); + writer.Write(recipe.Rank); + writer.Write(recipe.Item); + writer.Write(recipe.UpgradedItem); + writer.Write(recipe.Ingredient1); + writer.Write(recipe.Ingredient1Amount); + writer.Write(recipe.Ingredient2); + writer.Write(recipe.Ingredient2Amount); + writer.Write(recipe.Ingredient3); + writer.Write(recipe.Ingredient3Amount); + writer.Write(recipe.Ingredient4); + writer.Write(recipe.Ingredient4Amount); + writer.Write(recipe.Ingredient5); + writer.Write(recipe.Ingredient5Amount); + writer.Write(recipe.Ingredient6); + writer.Write(recipe.Ingredient6Amount); + } + } + } + + + } +} diff --git a/OpenKh.Kh2/Place.cs b/OpenKh.Kh2/Place.cs new file mode 100644 index 000000000..0be12ff9c --- /dev/null +++ b/OpenKh.Kh2/Place.cs @@ -0,0 +1,48 @@ +using OpenKh.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Places + { + [Data] public ushort MessageId { get; set; } + [Data] public ushort Padding { get; set; } + + //Two bytes after MessageId don't seem to matter. + + public class PlacePatch + { + [Data] public int Index { get; set; } + [Data] public ushort MessageId { get; set; } + [Data] public ushort Padding { get; set; } + } + public static List Read(Stream stream) + { + long count = stream.Length / 4; // Each entry is 4 bytes + var placesList = new List((int)count); + + for (long i = 0; i < count; i++) + { + placesList.Add(new Places + { + MessageId = stream.ReadUInt16(), + Padding = stream.ReadUInt16() + }); + } + + return placesList; + } + + public static void Write(Stream stream, List placesList) + { + foreach (var place in placesList) + { + stream.Write(place.MessageId); + stream.Write(place.Padding); + } + } + } +} diff --git a/OpenKh.Kh2/Soundinfo.cs b/OpenKh.Kh2/Soundinfo.cs new file mode 100644 index 000000000..e2196f829 --- /dev/null +++ b/OpenKh.Kh2/Soundinfo.cs @@ -0,0 +1,49 @@ +using OpenKh.Common; +using System; +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; +namespace OpenKh.Kh2 +//Can properly read/write and update. Can insert new entries between. +{ + public class Soundinfo + { + //[Data] public int Count { get; set; } + + [Data] public short Reverb { get; set; } + [Data] public short Rate { get; set; } + [Data] public short EnvironmentWAV { get; set; } + [Data] public short EnvironmentSEB { get; set; } + [Data] public short EnvironmentNUMBER { get; set; } + [Data] public short EnvironmentSPOT { get; set; } + [Data] public short FootstepWAV { get; set; } + [Data] public short FootstepSORA{ get; set; } + [Data] public short FootstepDONALD { get; set; } + [Data] public short FootstepGOOFY { get; set; } + [Data] public short FootstepWORLDFRIEND { get; set; } + [Data] public short FootstepOTHER { get; set; } + + + public class SoundinfoPatch + { + [Data] public int Index { get; set; } + [Data] public short Reverb { get; set; } + [Data] public short Rate { get; set; } + [Data] public short EnvironmentWAV { get; set; } + [Data] public short EnvironmentSEB { get; set; } + [Data] public short EnvironmentNUMBER { get; set; } + [Data] public short EnvironmentSPOT { get; set; } + [Data] public short FootstepWAV { get; set; } + [Data] public short FootstepSORA { get; set; } + [Data] public short FootstepDONALD { get; set; } + [Data] public short FootstepGOOFY { get; set; } + [Data] public short FootstepWORLDFRIEND { get; set; } + [Data] public short FootstepOTHER { get; set; } + } + + public static List Read(Stream stream) => BaseTableCountOnly.Read(stream); + public static void Write(Stream stream, IEnumerable entries) => + BaseTableCountOnly.Write(stream, entries); + + } +} diff --git a/OpenKh.Kh2/SystemData/Arif.cs b/OpenKh.Kh2/SystemData/Arif.cs index 29e52264f..eb2561a87 100644 --- a/OpenKh.Kh2/SystemData/Arif.cs +++ b/OpenKh.Kh2/SystemData/Arif.cs @@ -1,4 +1,5 @@ using OpenKh.Common; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -17,7 +18,17 @@ public class BgmSet /// public class Arif { - [Data] public uint Flags { get; set; } + [Flags] + public enum ArifFlags: uint + { + IsKnownArea = 0x01, + IndoorArea = 0x02, + Monochrome = 0x04, + NoShadow = 0x08, + HasGlow = 0x10 + } + + [Data] public ArifFlags Flags { get; set; } //Originally a uint. Flags are now defined here. YMLs can use either flag names or values [Data] public int Reverb { get; set; } [Data] public int SoundEffectBank1 { get; set; } [Data] public int SoundEffectBank2 { get; set; } diff --git a/OpenKh.Kh2/SystemData/Memt.cs b/OpenKh.Kh2/SystemData/Memt.cs index 257a132ef..c71bbdfae 100644 --- a/OpenKh.Kh2/SystemData/Memt.cs +++ b/OpenKh.Kh2/SystemData/Memt.cs @@ -1,158 +1,188 @@ -using OpenKh.Common; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xe.BinaryMapper; - -namespace OpenKh.Kh2.SystemData -{ - public enum MemberVanilla - { - Sora, - Donald, - Goofy, - WorldCharacter, - FormValor, - FormWisdom, - FormTrinity, - FormFinal, - Antiform, - Mickey, - SoraHighPoly, - FormValorHighPoly, - FormWisdomHighPoly, - FormTrinityHighPoly, - FormFinalHighPoly, - AntiformHighPoly, - } - - public enum MemberFinalMix - { - Sora, - Donald, - Goofy, - WorldCharacter, - FormValor, - FormWisdom, - FormLimit, - FormTrinity, - FormFinal, - Antiform, - Mickey, - SoraHighPoly, - FormValorHighPoly, - FormWisdomHighPoly, - FormLimitHighPoly, - FormTrinityHighPoly, - FormFinalHighPoly, - AntiformHighPoly, - } - - public class Memt - { - private const int Version = 5; - public const int MemberCountVanilla = 16; - public const int MemberCountFinalMix = 18; - - public interface IEntry - { - short CheckStoryFlag { get; set; } - short CheckStoryFlagNegation { get; set; } - short[] Members { get; set; } - short Unk06 { get; set; } - short Unk08 { get; set; } - short Unk0A { get; set; } - short Unk0C { get; set; } - short Unk0E { get; set; } - short WorldId { get; set; } - } - - public class EntryVanilla : IEntry - { - [Data] public short WorldId { get; set; } - [Data] public short CheckStoryFlag { get; set; } - [Data] public short CheckStoryFlagNegation { get; set; } - [Data] public short Unk06 { get; set; } - [Data] public short Unk08 { get; set; } - [Data] public short Unk0A { get; set; } - [Data] public short Unk0C { get; set; } - [Data] public short Unk0E { get; set; } - [Data(Count = MemberCountVanilla)] public short[] Members { get; set; } - } - - public class EntryFinalMix : IEntry - { - [Data] public short WorldId { get; set; } - [Data] public short CheckStoryFlag { get; set; } - [Data] public short CheckStoryFlagNegation { get; set; } - [Data] public short Unk06 { get; set; } - [Data] public short Unk08 { get; set; } - [Data] public short Unk0A { get; set; } - [Data] public short Unk0C { get; set; } - [Data] public short Unk0E { get; set; } - [Data(Count = MemberCountFinalMix)] public short[] Members { get; set; } - } - - public class MemberIndices - { - [Data] public byte Player { get; set; } - [Data] public byte Friend1 { get; set; } - [Data] public byte Friend2 { get; set; } - [Data] public byte FriendWorld { get; set; } - } - - public List Entries { get; } - public MemberIndices[] MemberIndexCollection { get; } - - public Memt() - { - Entries = new List().Cast().ToList(); - MemberIndexCollection = new MemberIndices[1] - { - new MemberIndices - { - Player = 0, - Friend1 = 1, - Friend2 = 2, - FriendWorld = 3 - } - }; - } - - internal Memt(Stream stream) - { - const int HeaderSize = 8; - const int StructLengthVanilla = 48; - const int MemberIndicesExpected = 7; - - var previousPosition = stream.Position; - var version = stream.ReadInt32(); - var count = stream.ReadInt32(); - stream.Position = previousPosition; - - if (stream.Length - previousPosition == HeaderSize + - count * StructLengthVanilla + MemberIndicesExpected * 4) - Entries = BaseTable.Read(stream).Cast().ToList(); - else - Entries = BaseTable.Read(stream).Cast().ToList(); - - MemberIndexCollection = Enumerable.Range(0, MemberIndicesExpected) - .Select(x => BinaryMapping.ReadObject(stream)) - .ToArray(); - } - - public static Memt Read(Stream stream) => new Memt(stream); - - public static void Write(Stream stream, Memt memt) - { - var firstElement = memt.Entries.FirstOrDefault(); - if (firstElement is EntryVanilla) - BaseTable.Write(stream, Version, memt.Entries.Cast()); - else if (firstElement is EntryFinalMix) - BaseTable.Write(stream, Version, memt.Entries.Cast()); - - foreach (var item in memt.MemberIndexCollection) - BinaryMapping.WriteObject(stream, item); - } - } -} +using OpenKh.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public enum MemberVanilla + { + Sora, + Donald, + Goofy, + WorldCharacter, + FormValor, + FormWisdom, + FormTrinity, + FormFinal, + Antiform, + Mickey, + SoraHighPoly, + FormValorHighPoly, + FormWisdomHighPoly, + FormTrinityHighPoly, + FormFinalHighPoly, + AntiformHighPoly, + } + + public enum MemberFinalMix + { + Sora, + Donald, + Goofy, + WorldCharacter, + FormValor, + FormWisdom, + FormLimit, + FormTrinity, + FormFinal, + Antiform, + Mickey, + SoraHighPoly, + FormValorHighPoly, + FormWisdomHighPoly, + FormLimitHighPoly, + FormTrinityHighPoly, + FormFinalHighPoly, + AntiformHighPoly, + } + + public class Memt + { + private const int Version = 5; + public const int MemberCountVanilla = 16; + public const int MemberCountFinalMix = 18; + + public interface IEntry + { + short CheckStoryFlag { get; set; } + short CheckStoryFlagNegation { get; set; } + short[] Members { get; set; } + short Unk06 { get; set; } + short Unk08 { get; set; } + short Unk0A { get; set; } + short Unk0C { get; set; } + short Unk0E { get; set; } + short WorldId { get; set; } + } + + public class EntryVanilla : IEntry + { + [Data] public short WorldId { get; set; } + [Data] public short CheckStoryFlag { get; set; } + [Data] public short CheckStoryFlagNegation { get; set; } + [Data] public short Unk06 { get; set; } + [Data] public short Unk08 { get; set; } + [Data] public short Unk0A { get; set; } + [Data] public short Unk0C { get; set; } + [Data] public short Unk0E { get; set; } + [Data(Count = MemberCountVanilla)] public short[] Members { get; set; } + } + + public class EntryFinalMix : IEntry + { + [Data] public short WorldId { get; set; } + [Data] public short CheckStoryFlag { get; set; } + [Data] public short CheckStoryFlagNegation { get; set; } + [Data] public short Unk06 { get; set; } + [Data] public short Unk08 { get; set; } + [Data] public short Unk0A { get; set; } + [Data] public short Unk0C { get; set; } + [Data] public short Unk0E { get; set; } + [Data(Count = MemberCountFinalMix)] public short[] Members { get; set; } + } + + public class MemberIndices + { + [Data] public byte Player { get; set; } + [Data] public byte Friend1 { get; set; } + [Data] public byte Friend2 { get; set; } + [Data] public byte FriendWorld { get; set; } + } + + public List Entries { get; } + public MemberIndices[] MemberIndexCollection { get; } + + public class MemtEntryPatch + { + public int Index { get; set; } + public short WorldId { get; set; } + public short CheckStoryFlag { get; set; } + public short CheckStoryFlagNegation { get; set; } + public short Unk06 { get; set; } + public short Unk08 { get; set; } + public short Unk0A { get; set; } + public short Unk0C { get; set; } + public short Unk0E { get; set; } + public List Members { get; set; } + } + + public class MemberIndicesPatch + { + public int Index { get; set; } + public byte Player { get; set; } + public byte Friend1 { get; set; } + public byte Friend2 { get; set; } + public byte FriendWorld { get; set; } + } + + public class MemtPatches + { + public List MemtEntries { get; set; } + public List MemberIndices { get; set; } + } + + public Memt() + { + Entries = new List(); + MemberIndexCollection = new MemberIndices[7]; + for (int i = 0; i < MemberIndexCollection.Length; i++) + { + MemberIndexCollection[i] = new MemberIndices + { + Player = 0, + Friend1 = 0, + Friend2 = 0, + FriendWorld = 0 + }; + } + } + + internal Memt(Stream stream) + { + const int HeaderSize = 8; + const int StructLengthVanilla = 48; + const int MemberIndicesExpected = 7; + + var previousPosition = stream.Position; + var version = stream.ReadInt32(); + var count = stream.ReadInt32(); + stream.Position = previousPosition; + + if (stream.Length - previousPosition == HeaderSize + + count * StructLengthVanilla + MemberIndicesExpected * 4) + Entries = BaseTable.Read(stream).Cast().ToList(); + else + Entries = BaseTable.Read(stream).Cast().ToList(); + + MemberIndexCollection = Enumerable.Range(0, MemberIndicesExpected) + .Select(x => BinaryMapping.ReadObject(stream)) + .ToArray(); + } + + public static Memt Read(Stream stream) => new Memt(stream); + + public static void Write(Stream stream, Memt memt) + { + var firstElement = memt.Entries.FirstOrDefault(); + if (firstElement is EntryVanilla) + BaseTable.Write(stream, Version, memt.Entries.Cast()); + else if (firstElement is EntryFinalMix) + BaseTable.Write(stream, Version, memt.Entries.Cast()); + + foreach (var item in memt.MemberIndexCollection) + BinaryMapping.WriteObject(stream, item); + } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs b/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs new file mode 100644 index 000000000..43be455db --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/BasePrefTable.cs @@ -0,0 +1,182 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class BaseTableOffsetWithPaddings + where T : class + { + [Data] public int Count { get; set; } + + public static (List Items, List Offsets) ReadWithOffsets(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableOffsetWithPaddings + { + Count = reader.ReadInt32(), + }; + + var offsets = new List(); + for (int i = 0; i < header.Count; i++) + { + offsets.Add(reader.ReadInt32()); + } + + var items = new List(); + foreach (var offset in offsets.Distinct()) + { + stream.Position = offset; + items.Add(BinaryMapping.ReadObject(stream)); + } + + return (items, offsets); + } + } + + public static void WriteWithOffsets(Stream stream, IEnumerable items, List originalOffsets) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + var offsets = new List(); + + var header = new BaseTableOffsetWithPaddings + { + Count = originalOffsets.Count + }; + writer.Write(header.Count); + + foreach (var offset in originalOffsets) + { + writer.Write(offset); // Write original offsets + } + + var offsetMap = originalOffsets.Distinct().ToDictionary(offset => offset, offset => (int)stream.Position); + + foreach (var offset in offsetMap.Values) + { + stream.Position = offset; + BinaryMapping.WriteObject(stream, itemList[offsetMap.Keys.ToList().IndexOf(offset)]); + } + } + } + } + + + + public class BaseTableOffsets + where T : class + { + [Data] public int Count { get; set; } + + public static List Read(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableOffsets + { + Count = reader.ReadInt32() + }; + + var offsets = new int[header.Count]; + for (int i = 0; i < header.Count; i++) + { + offsets[i] = reader.ReadInt32(); + } + + var items = new List(); + foreach (var offset in offsets) + { + stream.Position = offset; + items.Add(BinaryMapping.ReadObject(stream)); + } + return items; + } + } + + public static void Write(Stream stream, IEnumerable items) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + var offsets = new List(); + + var header = new BaseTableOffsets + { + Count = itemList.Count + }; + writer.Write(header.Count); + + var offsetPosition = stream.Position; + for (int i = 0; i < itemList.Count; i++) + { + writer.Write(0); // Placeholder for offset + } + + for (int i = 0; i < itemList.Count; i++) + { + offsets.Add((int)stream.Position); + BinaryMapping.WriteObject(stream, itemList[i]); + } + + var currentPosition = stream.Position; + stream.Position = offsetPosition; + foreach (var offset in offsets) + { + writer.Write(offset); + } + stream.Position = currentPosition; + } + } + } + + public class BaseTableSstm + where T : class + { + [Data] public int Version { get; set; } + [Data] public int MagicCode { get; set; } + + public static List Read(Stream stream) + { + using (var reader = new BinaryReader(stream, System.Text.Encoding.Default, true)) + { + var header = new BaseTableSstm + { + Version = reader.ReadInt32(), + MagicCode = reader.ReadInt32() + }; + + var items = new List(); + for (int i = 0; i < 95; i++) // 95 float values + { + items.Add(BinaryMapping.ReadObject(stream)); + } + return items; + } + } + + public static void Write(Stream stream, IEnumerable items) + { + using (var writer = new BinaryWriter(stream, System.Text.Encoding.Default, true)) + { + var itemList = items as IList ?? items.ToList(); + + var header = new BaseTableSstm + { + Version = 6, + MagicCode = 0 + }; + writer.Write(header.Version); + writer.Write(header.MagicCode); + + foreach (var item in itemList) + { + BinaryMapping.WriteObject(stream, item); + } + } + } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Fmab.cs b/OpenKh.Kh2/SystemData/Pref/Fmab.cs new file mode 100644 index 000000000..29c8eda26 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Fmab.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Fmab + { + //public int Index { get; set; } + [Data] public float HighJumpHeight { get; set; } + [Data] public float AirDodgeHeight { get; set; } + [Data] public float AirDodgeSpeed { get; set; } + [Data] public float AirSlideTime { get; set; } + [Data] public float AirSlideSpeed { get; set; } + [Data] public float AirSlideBrake { get; set; } + [Data] public float AirSlideStopBrake { get; set; } + [Data] public float AirSlideInvulnerableFrames { get; set; } + [Data] public float GlideSpeed { get; set; } + [Data] public float GlideFallRatio { get; set; } + [Data] public float GlideFallHeight { get; set; } + [Data] public float GlideFallMax { get; set; } + [Data] public float GlideAcceleration { get; set; } + [Data] public float GlideStartHeight { get; set; } + [Data] public float GlideEndHeight { get; set; } + [Data] public float GlideTurnSpeed { get; set; } + [Data] public float DodgeRollInvulnerableFrames { get; set; } + + public class FmabEntries + { + [Data] public Dictionary Entries { get; set; } + } + + public static List Read(Stream stream) => BaseTableOffsets.Read(stream); + + public static void Write(Stream stream, IEnumerable entries) => BaseTableOffsets.Write(stream, entries); + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Magi.cs b/OpenKh.Kh2/SystemData/Pref/Magi.cs new file mode 100644 index 000000000..7406150e7 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Magi.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Magi + { + //[Data] public uint Id { get; set; } + [Data] public float FireRadius { get; set; } + [Data] public float FireHeight { get; set; } + [Data] public float FireTime { get; set; } + [Data] public float BlizzardFadeTime { get; set; } + [Data] public float BlizzardTime { get; set; } + [Data] public float BlizzardSpeed { get; set; } + [Data] public float BlizzardRadius { get; set; } + [Data] public float BlizzardHitBack { get; set; } + [Data] public float ThunderNoTargetDistance { get; set; } + [Data] public float ThunderBorderHeight { get; set; } + [Data] public float ThunderCheckHeight { get; set; } + [Data] public float ThunderBurstRadius { get; set; } + [Data] public float ThunderHeight { get; set; } + [Data] public float ThunderRadius { get; set; } + [Data] public float ThunderAttackWait { get; set; } + [Data] public float ThunderTime { get; set; } + [Data] public float CureRange { get; set; } + [Data] public float MagnetMinYOffset { get; set; } + [Data] public float MagnetLargeTime { get; set; } + [Data] public float MagnetStayTime { get; set; } + [Data] public float MagnetSmallTime { get; set; } + [Data] public float MagnetRadius { get; set; } + [Data] public float ReflectRadius { get; set; } + [Data] public float ReflectLaserTime { get; set; } + [Data] public float ReflectFinishTime { get; set; } + [Data] public float ReflectLv1Radius { get; set; } + [Data] public float ReflectLv1Height { get; set; } + [Data] public int ReflectLv2Count { get; set; } + [Data] public float ReflectLv2Radius { get; set; } + [Data] public float ReflectLv2Height { get; set; } + [Data] public int ReflectLv3Count { get; set; } + [Data] public float ReflectLv3Radius { get; set; } + [Data] public float ReflectLv3Height { get; set; } + + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Plyr.cs b/OpenKh.Kh2/SystemData/Pref/Plyr.cs new file mode 100644 index 000000000..2fa563260 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Plyr.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Plyr + { + [Data] public float AttackYOffset { get; set; } + [Data] public float AttackRadius { get; set; } + [Data] public float AttackMinHeight { get; set; } + [Data] public float AttackMaxHeight { get; set; } + [Data] public float AttackVAngle { get; set; } + [Data] public float AttackVRange { get; set; } + [Data] public float AttackSRange { get; set; } + [Data] public float AttackHAngle { get; set; } + [Data] public float AttackUMinHeight { get; set; } + [Data] public float AttackUMaxHeight { get; set; } + [Data] public float AttackURange { get; set; } + [Data] public float AttackJFront { get; set; } + [Data] public float AttackAirMinHeight { get; set; } + [Data] public float AttackAirMaxHeight { get; set; } + [Data] public float AttackAirBigHeightOffset { get; set; } + [Data] public float AttackUV0 { get; set; } + [Data] public float AttackJV0 { get; set; } + [Data] public float AttackFirstV0 { get; set; } + [Data] public float AttackComboV0 { get; set; } + [Data] public float AttackFinishV0 { get; set; } + [Data] public float BlowRecoveryH { get; set; } + [Data] public float BlowRecoveryV { get; set; } + [Data] public float BlowRecoveryTime { get; set; } + [Data] public float AutoLockonRange { get; set; } + [Data] public float AutoLockonMinHeight { get; set; } + [Data] public float AutoLockonMaxHeight { get; set; } + [Data] public float AutoLockonTime { get; set; } + [Data] public float AutoLockonHeightAdjust { get; set; } + [Data] public float AutoLockonInnerAdjust { get; set; } + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Prty.cs b/OpenKh.Kh2/SystemData/Pref/Prty.cs new file mode 100644 index 000000000..477805dc1 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Prty.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Prty + { + //Structure: 0x4 bytes for Count, then for Count, offsets pointing to different characters in the file. + //0x00000000 seems to be some filler data? Unknown what purpose it serves. + //Offsets also repeat. This is because the index of an offset is used for the NeoMoveset of a character. + //Unsure why, but worlds with different costumes (NM, WI, TR, etc.) need to use different NeoMoveset values even if they read the exact same values. + //For Sora, NeoMoveset also is linked to the PTYA entry to use. + [Data] public float WalkSpeed { get; set; } + [Data] public float RunSpeed { get; set; } + [Data] public float JumpHeight { get; set; } + [Data] public float TurnSpeed { get; set; } + [Data] public float HangHeight { get; set; } + [Data] public float HangMargin { get; set; } + [Data] public float StunTime { get; set; } + [Data] public float MpChargeTime { get; set; } + [Data] public float UpDownSpeed { get; set; } + [Data] public float DashSpeed { get; set; } + [Data] public float Acceleration { get; set; } + [Data] public float Brake { get; set; } + [Data] public float Subjective { get; set; } + + public class PrtyEntries + { + [Data] public Dictionary Entries { get; set; } + } + public static readonly Dictionary CharacterMap = new Dictionary + { + {"Sora", 0}, + {"ValorForm", 1}, + {"WisdomForm", 2}, + {"MasterForm", 3}, + {"FinalForm", 4}, + {"AntiForm", 5}, + {"SoraLK", 6}, + {"SoraLM", 7}, + {"Donald", 8}, + {"DonaldLK", 9}, + {"DonaldLM", 10}, + {"Goofy", 11}, + {"GoofyLK", 12}, + {"Aladdin", 13}, + {"Auron", 14}, + {"Mulan", 15}, + {"Ping", 16}, + {"Tron", 17}, + {"Mickey", 18}, + {"Beast", 19}, + {"Jack", 20}, + {"Simba", 21}, + {"Sparrow", 22}, + {"Riku", 23}, + {"MagicCarpet", 24}, + {"LightCycle", 25}, + {"SoraDie", 26}, + {"Unknown1", 27}, + {"Unknown2", 28}, + {"GummiShip", 29}, + {"RedRocket", 30}, + {"Neverland", 31}, + {"Session", 32}, + {"LimitForm", 33} + }; + + //Read/Write doesn't currently work. + + public static (List Items, List Offsets) Read(Stream stream) + { + return BaseTableOffsetWithPaddings.ReadWithOffsets(stream); + } + + public static void Write(Stream stream, IEnumerable entries, List originalOffsets) + { + BaseTableOffsetWithPaddings.WriteWithOffsets(stream, entries, originalOffsets); + } + + } +} diff --git a/OpenKh.Kh2/SystemData/Pref/Sstm.cs b/OpenKh.Kh2/SystemData/Pref/Sstm.cs new file mode 100644 index 000000000..110041728 --- /dev/null +++ b/OpenKh.Kh2/SystemData/Pref/Sstm.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.IO; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2.SystemData +{ + public class Sstm + { + //[Data] public uint Id { get; set; } + [Data] public float CeilingStop { get; set; } + [Data] public float CeilingDisableCommandTime { get; set; } + [Data] public float HangRangeH { get; set; } + [Data] public float HangRangeL { get; set; } + [Data] public float HangRangeXZ { get; set; } + [Data] public float FallMax { get; set; } + [Data] public float BlowBrakeXZ { get; set; } + [Data] public float BlowMinXZ { get; set; } + [Data] public float BlowBrakeUp { get; set; } + [Data] public float BlowUp { get; set; } + [Data] public float BlowSpeed { get; set; } + [Data] public float BlowToHitBack { get; set; } + [Data] public float HitBack { get; set; } + [Data] public float HitBackSmall { get; set; } + [Data] public float HitBackToJump { get; set; } + [Data] public float FlyBlowBrake { get; set; } + [Data] public float FlyBlowStop { get; set; } + [Data] public float FlyBlowUpAdjust { get; set; } + [Data] public float MagicJump { get; set; } + [Data] public float LockOnRange { get; set; } + [Data] public float LockOnReleaseRange { get; set; } + [Data] public float StunRecovery { get; set; } + [Data] public float StunRecoveryHp { get; set; } + [Data] public float StunRelax { get; set; } + [Data] public float DriveEnemy { get; set; } + [Data] public float ChangeTimeEnemy { get; set; } + [Data] public float DriveTime { get; set; } + [Data] public float DriveTimeRelax { get; set; } + [Data] public float ChangeTimeAddRate { get; set; } + [Data] public float ChangeTimeSubRate { get; set; } + [Data] public float MpDriveRate { get; set; } + [Data] public float MpToMpDrive { get; set; } + [Data] public float SummonTimeRelax { get; set; } + [Data] public float SummonPrayTime { get; set; } + [Data] public float SummonPrayTimeSkip { get; set; } + [Data] public int AntiFormDriveCount { get; set; } + [Data] public int AntiFormSubCount { get; set; } + [Data] public float AntiFormDamageRate { get; set; } + [Data] public float FinalFormRate { get; set; } + [Data] public float FinalFormMulRate { get; set; } + [Data] public float FinalFormMaxRate { get; set; } + [Data] public int FinalFormSubCount { get; set; } + [Data] public float AttackDistanceToSpeed { get; set; } + [Data] public float AlCarpetDashInner { get; set; } + [Data] public float AlCarpetDashDelay { get; set; } + [Data] public float AlCarpetDashAcceleration { get; set; } + [Data] public float AlCarpetDashBrake { get; set; } + [Data] public float LkDashDriftInner { get; set; } + [Data] public float LkDashDriftTime { get; set; } + [Data] public float LkDashAccelerationDrift { get; set; } + [Data] public float LkDashAccelerationStop { get; set; } + [Data] public float LkDashDriftSpeed { get; set; } + [Data] public float LkMagicJump { get; set; } + [Data] public float MickeyChargeWait { get; set; } + [Data] public float MickeyDownRate { get; set; } + [Data] public float MickeyMinRate { get; set; } + [Data] public float LmSwimSpeed { get; set; } + [Data] public float LmSwimControl { get; set; } + [Data] public float LmSwimAcceleration { get; set; } + [Data] public float LmDolphinAcceleration { get; set; } + [Data] public float LmDolphinSpeedMax { get; set; } + [Data] public float LmDolphinSpeedMin { get; set; } + [Data] public float LmDolphinSpeedMaxDistance { get; set; } + [Data] public float LmDolphinSpeedMinDistance { get; set; } + [Data] public float LmDolphinRotationMax { get; set; } + [Data] public float LmDolphinRotationDistance { get; set; } + [Data] public float LmDolphinRotationMaxDistance { get; set; } + [Data] public float LmDolphinDistanceToTime { get; set; } + [Data] public float LmDolphinTimeMax { get; set; } + [Data] public float LmDolphinTimeMin { get; set; } + [Data] public float LmDolphinNearSpeed { get; set; } + [Data] public int DriveBerserkAttack { get; set; } + [Data] public float MpHaste { get; set; } + [Data] public float MpHastera { get; set; } + [Data] public float MpHastega { get; set; } + [Data] public float DrawRange { get; set; } + [Data] public int ComboDamageUp { get; set; } + [Data] public int ReactionDamageUp { get; set; } + [Data] public float DamageDrive { get; set; } + [Data] public float DriveBoost { get; set; } + [Data] public float FormBoost { get; set; } + [Data] public float ExpChance { get; set; } + [Data] public int Defender { get; set; } + [Data] public int ElementUp { get; set; } + [Data] public float DamageAspir { get; set; } + [Data] public float HyperHeal { get; set; } + [Data] public float CombinationBoost { get; set; } + [Data] public float PrizeUp { get; set; } + [Data] public float LuckUp { get; set; } + [Data] public int ItemUp { get; set; } + [Data] public float AutoHeal { get; set; } + [Data] public float SummonBoost { get; set; } + [Data] public float DriveConvert { get; set; } + [Data] public float DefenseMaster { get; set; } + [Data] public int DefenseMasterRatio { get; set; } + + public class SstmPatch + { + public Dictionary Properties { get; set; } = new Dictionary(); + } + + //Read/Write doesn't currently work. + public static List Read(Stream stream) => BaseTableSstm.Read(stream); + + public static void Write(Stream stream, IEnumerable entries) => BaseTableSstm.Write(stream, entries); + + } + +} diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index c1da57177..c6bb4be9d 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -1,70 +1,113 @@ -using OpenKh.Kh2; -using System.Collections.Generic; -using System.IO; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace OpenKh.Patcher -{ - public class Metadata - { - public class Dependency - { - public string Name { get; set; } - } - - public string Title { get; set; } - public string OriginalAuthor { get; set; } - public string Description { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Game { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Specifications { get; set; } - public List Dependencies { get; set; } - public List Assets { get; set; } - - private static readonly IDeserializer deserializer = - new DeserializerBuilder() - .IgnoreFields() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - private static readonly ISerializer serializer = - new SerializerBuilder() - .IgnoreFields() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - public static Metadata Read(Stream stream) => - deserializer.Deserialize(new StreamReader(stream)); +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace OpenKh.Patcher +{ + public class Metadata + { + public class Dependency + { + public string Name { get; set; } + } + + public string Title { get; set; } + public string OriginalAuthor { get; set; } + public string Description { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Game { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Specifications { get; set; } + public List Dependencies { get; set; } + public List Assets { get; set; } + + private static readonly IDeserializer deserializer = + new DeserializerBuilder() + .IgnoreFields() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + private static readonly ISerializer serializer = + new SerializerBuilder() + .IgnoreFields() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + + public static Metadata Read(Stream stream) + { + try + { + return deserializer.Deserialize(new StreamReader(stream)); //Simplifed way of doing this. Cuts out the "deserializer" redundancies and variable for StreamReader(stream)) + } + + catch (YamlDotNet.Core.YamlException ex) + { + // Handle YAML parsing errors + Debug.WriteLine($"Error deserializing YAML: {ex.Message}"); + + string originalTitle = string.Empty; + + // Extract title using regex + stream.Position = 0; // Reset stream position + using (var reader = new StreamReader(stream)) + { + string yamlContent = reader.ReadToEnd(); + var match = Regex.Match(yamlContent, @"(?<=title:).*"); + if (match.Success) + { + originalTitle = match.Value.Trim(); + } + } + + var metadata = new Metadata + { + Title = $"{originalTitle}* \nMOD YML ERROR DETECTED: CHECK FORMATTING" + }; + + return metadata; // Return modified metadata indicating failure + } + catch (Exception ex) + { + // Handle other unexpected errors + Debug.WriteLine($"Unexpected error: {ex.Message}"); + throw; // Rethrow other exceptions for further investigation + } + } + public void Write(Stream stream) { using (var writer = new StreamWriter(stream)) { serializer.Serialize(writer, this); } - } - public override string ToString() => - serializer.Serialize(this); - } - - public class AssetFile - { - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Name { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Method { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Platform { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Package { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Multi { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Source { get; set; } - - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool Required { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Type { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public Bar.MotionsetType MotionsetType { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Language { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsSwizzled { get; set; } - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Index { get; set; } - } - - public class Multi - { - public string Name { get; set; } - } -} + } + public override string ToString() => + serializer.Serialize(this); + } + + public class AssetFile + { + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Name { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Method { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Platform { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Package { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Multi { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public List Source { get; set; } + + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool Required { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Type { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public Bar.MotionsetType MotionsetType { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public string Language { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsSwizzled { get; set; } + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public int Index { get; set; } + } + + public class Multi + { + public string Name { get; set; } + } +} diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 918bc401d..47f6a8bca 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -13,6 +13,7 @@ using YamlDotNet.Serialization; using OpenKh.Bbs; + namespace OpenKh.Patcher { public class PatcherProcessor @@ -82,11 +83,11 @@ public void Patch(string originalAssets, string outputDir, string modFilePath) public void Patch(string originalAssets, string outputDir, Metadata metadata, string modBasePath, int platform = 1, bool fastMode = false, IDictionary packageMap = null, string LaunchGame = null) { - + var context = new Context(metadata, originalAssets, modBasePath, outputDir); try { - + if (metadata.Assets == null) throw new Exception("No assets found."); if (metadata.Game != null && GamesList.Contains(metadata.Game.ToLower()) && metadata.Game.ToLower() != LaunchGame.ToLower()) @@ -117,8 +118,8 @@ public void Patch(string originalAssets, string outputDir, Metadata metadata, st _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "bbs_first"; break; case "Recom": - if (assetFile!= null) - _packageFile = "Recom"; + if (assetFile != null) + _packageFile = "Recom"; break; default: _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "kh2_first"; @@ -181,15 +182,19 @@ public void Patch(string originalAssets, string outputDir, Metadata metadata, st context.EnsureDirectoryExists(dstFile); + //Update: Prevent from copying a blank file should it not exist. try { - context.CopyOriginalFile(name, dstFile); + if (((assetFile.Type == "internal" || assetFile.Source[0].Type == "internal") && File.Exists(context.GetOriginalAssetPath(assetFile.Source[0].Name))) || assetFile.Type != "internal" && assetFile.Source[0].Type != "internal") + { + context.CopyOriginalFile(name, dstFile); - using var _stream = File.Open(dstFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); - PatchFile(context, assetFile, _stream); + using var _stream = File.Open(dstFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); + PatchFile(context, assetFile, _stream); - _stream.Close(); - _stream.Dispose(); + _stream.Close(); + _stream.Dispose(); + } } catch (IOException) { } @@ -247,6 +252,9 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea case "listpatch": PatchList(context, assetFile.Source, stream); break; + case "synthpatch": + PatchSynth(context, assetFile.Source, stream); + break; default: Log.Warn($"Method '{assetFile.Method}' not recognized for '{assetFile.Name}'. Falling back to 'copy'"); CopyFile(context, assetFile, stream); @@ -263,7 +271,7 @@ private static void CopyFile(Context context, AssetFile assetFile, Stream stream if (assetFile.Source == null || assetFile.Source.Count == 0) throw new Exception($"File '{assetFile.Name}' does not contain any source"); - string srcFile; + string srcFile; if (assetFile.Source[0].Type == "internal") { @@ -279,6 +287,10 @@ private static void CopyFile(Context context, AssetFile assetFile, Stream stream srcStream.CopyTo(stream); } + + + + //Binarc Update: Specify by Name OR Index. Some files in BARS may have the same name but different indexes, and you want to patch a later index only. private static void PatchBinarc(Context context, AssetFile assetFile, Stream stream) { var binarc = Bar.IsValid(stream) ? Bar.Read(stream) : @@ -292,7 +304,20 @@ private static void PatchBinarc(Context context, AssetFile assetFile, Stream str if (!Enum.TryParse(file.Type, true, out var barEntryType)) throw new Exception($"BinArc type {file.Type} not recognized"); - var entry = binarc.FirstOrDefault(x => x.Name == file.Name && x.Type == barEntryType); + Bar.Entry entry = null; + + // Check if the name is specified + if (!string.IsNullOrEmpty(file.Name)) + { + entry = binarc.FirstOrDefault(x => x.Name == file.Name && x.Type == barEntryType); + } + // If name is not specified but index is + else if (file.Index >= 0 && file.Index < binarc.Count) + { + entry = binarc[file.Index]; + } + + // If entry is not found by name or index, create a new one if (entry == null) { entry = new Bar.Entry @@ -466,7 +491,7 @@ private static void PatchBdscript(Context context, AssetFile assetFile, Stream s if (!File.Exists(srcFile)) throw new FileNotFoundException($"The mod does not contain the file {scriptName}", srcFile); - + var programsInput = File.ReadAllText(context.GetSourceModAssetPath(scriptName)); var ascii = BdxAsciiModel.ParseText(programsInput); var decoder = new BdxEncoder( @@ -494,14 +519,22 @@ private static void PatchSpawnPoint(Context context, AssetFile assetFile, Stream if (!File.Exists(srcFile)) throw new FileNotFoundException($"The mod does not contain the file {assetFile.Source[0].Name}", srcFile); - var spawnPoint = Helpers.YamlDeserialize>(File.ReadAllText(srcFile)); + var spawnPoint = OpenKh.Common.Helpers.YamlDeserialize>(File.ReadAllText(srcFile)); Kh2.Ard.SpawnPoint.Write(stream.SetPosition(0), spawnPoint); } private static readonly Dictionary characterMap = new Dictionary(){ - { "Sora", 1 }, { "Donald", 2 }, { "Goofy", 3 }, { "Mickey", 4 }, { "Auron", 5 }, { "PingMulan",6 }, { "Aladdin", 7 }, { "Sparrow", 8 }, { "Beast", 9 }, { "Jack", 10 }, { "Simba", 11 }, { "Tron", 12 }, { "Riku", 13 }, { "Roxas", 14}, {"Ping", 15} + { "Sora", 1 }, { "Donald", 2 }, { "Goofy", 3 }, { "Mickey", 4 }, { "Auron", 5 }, { "PingMulan",6 }, { "Aladdin", 7 }, { "Sparrow", 8 }, { "Beast", 9 }, { "Jack", 10 }, { "Simba", 11 }, { "Tron", 12 }, { "Riku", 13 }, { "Roxas", 14}, {"Ping", 15} }; + + private static readonly Dictionary worldIndexMap = new Dictionary(StringComparer.OrdinalIgnoreCase){ + { "worldzz", 0 }, { "endofsea", 1 }, { "twilighttown", 2 }, { "destinyisland", 3 }, { "hollowbastion", 4 }, { "beastscastle", 5 }, { "olympuscoliseum", 6 }, { "agrabah", 7 }, { "thelandofdragons", 8 }, { "100acrewood", 9 }, { "prideland", 10 }, { "atlantica", 11 }, { "disneycastle", 12 }, { "timelessriver", 13}, {"halloweentown", 14}, { "worldmap", 15 }, { "portroyal", 16 }, { "spaceparanoids", 17 }, { "theworldthatneverwas", 18 } + }; + + + + private static readonly IDeserializer deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); @@ -573,6 +606,7 @@ private static void PatchList(Context context, List sources, Stream s itemList.Write(stream.SetPosition(0)); break; + case "fmlv": var formRaw = Kh2.Battle.Fmlv.Read(stream).ToList(); var formList = new Dictionary>(); @@ -652,11 +686,25 @@ private static void PatchList(Context context, List sources, Stream s case "atkp": var atkpList = Kh2.Battle.Atkp.Read(stream); var moddedAtkp = deserializer.Deserialize>(sourceText); + foreach (var attack in moddedAtkp) { - var oldAtkp = atkpList.First(x => x.Id == attack.Id && x.SubId == attack.SubId && x.Switch == attack.Switch); - atkpList[atkpList.IndexOf(oldAtkp)] = attack; + //Same general template used for cmd, enmp, and przt. + // Check if the attack exists in atkpList based on Id, SubId, and Switch + var existingAttack = atkpList.FirstOrDefault(x => x.Id == attack.Id && x.SubId == attack.SubId && x.Switch == attack.Switch); + + if (existingAttack != null) + { + // Update existing attack in atkpList + atkpList[atkpList.IndexOf(existingAttack)] = attack; + } + else + { + // Add the attack to atkpList if it doesn't exist + atkpList.Add(attack); + } } + Kh2.Battle.Atkp.Write(stream.SetPosition(0), atkpList); break; @@ -683,70 +731,593 @@ private static void PatchList(Context context, List sources, Stream s foreach (var plrp in moddedPlrp) { var oldPlrp = plrpList.First(x => x.Character == plrp.Character && x.Id == plrp.Id); - plrpList[plrpList.IndexOf(oldPlrp)] = plrp; + if (oldPlrp != null) + { + plrpList[plrpList.IndexOf(oldPlrp)] = plrp; + } + else + { + plrpList.Add(plrp); + } } Kh2.Battle.Plrp.Write(stream.SetPosition(0), plrpList); break; case "cmd": - var cmdList = Kh2.SystemData.Cmd.Read(stream); - var moddedCmd = deserializer.Deserialize>(sourceText); - foreach (var commands in moddedCmd) + var cmdList = Kh2.SystemData.Cmd.Read(stream); + var moddedCmd = deserializer.Deserialize>(sourceText); + + foreach (var commands in moddedCmd) { - var oldCommands = cmdList.First(x => x.Id == commands.Id && x.Id == commands.Id); - cmdList[cmdList.IndexOf(oldCommands)] = commands; + var existingCommand = cmdList.FirstOrDefault(x => x.Id == commands.Id); + + if (existingCommand != null) + { + cmdList[cmdList.IndexOf(existingCommand)] = commands; + } + else + { + cmdList.Add(commands); + } } + Kh2.SystemData.Cmd.Write(stream.SetPosition(0), cmdList); break; + case "localset": + var localList = Kh2.Localset.Read(stream); + var moddedLocal = deserializer.Deserialize>(sourceText); + + foreach (var set in moddedLocal) + { + var existingSet = localList.FirstOrDefault(x => x.ProgramId == set.ProgramId); + + if (existingSet != null) + { + localList[localList.IndexOf(existingSet)] = set; + } + else + { + localList.Add(set); + } + } + + Kh2.Localset.Write(stream.SetPosition(0), localList); + break; + + case "jigsaw": + var jigsawList = Kh2.Jigsaw.Read(stream); + var moddedJigsaw = deserializer.Deserialize>(sourceText); + + foreach (var piece in moddedJigsaw) + { + //Allow variations of capitalizations with World spellings; can't handle an extra space unfortunately, making it inconsistent with Arif. + //Can just use ID's for worlds, though. + if (worldIndexMap.TryGetValue(piece.World.ToString().Replace(" ", "").ToLower(), out var worldValue)) + { + piece.World = (Kh2.Jigsaw.WorldList)worldValue; + } + + var existingPiece = jigsawList.FirstOrDefault(x => x.Picture == piece.Picture && x.Part == piece.Part); //Identify a puzzle by its Picture+Part. + + if (existingPiece != null) + { + jigsawList[jigsawList.IndexOf(existingPiece)] = piece; + } + else + { + jigsawList.Add(piece); + } + } + + Kh2.Jigsaw.Write(stream.SetPosition(0), jigsawList); + break; + + + case "enmp": var enmpList = Kh2.Battle.Enmp.Read(stream); var moddedEnmp = deserializer.Deserialize>(sourceText); + foreach (var enmp in moddedEnmp) { - var oldEnmp = enmpList.First(x => x.Id == enmp.Id); - enmpList[enmpList.IndexOf(oldEnmp)] = enmp; + var existingEnmp = enmpList.FirstOrDefault(x => x.Id == enmp.Id); + + if (existingEnmp != null) + { + enmpList[enmpList.IndexOf(existingEnmp)] = enmp; + } + else + { + enmpList.Add(enmp); + } } + Kh2.Battle.Enmp.Write(stream.SetPosition(0), enmpList); break; + case "sklt": var skltList = Kh2.SystemData.Sklt.Read(stream); var moddedSklt = deserializer.Deserialize>(sourceText); + foreach (var sklt in moddedSklt) { - var oldSklt = skltList.First(x => x.CharacterId == sklt.CharacterId); - skltList[skltList.IndexOf(oldSklt)] = sklt; + var existingSklt = skltList.FirstOrDefault(x => x.CharacterId == sklt.CharacterId); + + if (existingSklt != null) + { + skltList[skltList.IndexOf(existingSklt)] = sklt; + } + else + { + skltList.Add(sklt); + } } + Kh2.SystemData.Sklt.Write(stream.SetPosition(0), skltList); break; - + case "przt": var prztList = Kh2.Battle.Przt.Read(stream); var moddedPrzt = deserializer.Deserialize>(sourceText); + foreach (var przt in moddedPrzt) { - var oldPrzt = prztList.First(x => x.Id == przt.Id); - prztList[prztList.IndexOf(oldPrzt)] = przt; + var existingPrzt = prztList.FirstOrDefault(x => x.Id == przt.Id); + + if (existingPrzt != null) + { + prztList[prztList.IndexOf(existingPrzt)] = przt; + } + else + { + prztList.Add(przt); + } } Kh2.Battle.Przt.Write(stream.SetPosition(0), prztList); break; case "magc": - var magcList = Kh2.Battle.Magc.Read(stream); - var moddedMagc = deserializer.Deserialize>(sourceText); + var magcList = Kh2.Battle.Magc.Read(stream); + var moddedMagc = deserializer.Deserialize>(sourceText); foreach (var magc in moddedMagc) { - var oldMagc = magcList.First(x => x.Id == magc.Id && x.Level == magc.Level); - magcList[magcList.IndexOf(oldMagc)] = magc; + var existingMagc = magcList.First(x => x.Id == magc.Id && x.Level == magc.Level); + if (existingMagc != null) + { + //existingMagc = MergeHelper.Merge(existingMagc, magc); + magcList[magcList.IndexOf(existingMagc)] = magc; + } + else + { + magcList.Add(magc); + } } Kh2.Battle.Magc.Write(stream.SetPosition(0), magcList); break; + case "btlv": + var btlvList = Kh2.Battle.Btlv.Read(stream); + var moddedBtlv = deserializer.Deserialize>(sourceText); + + foreach (var btlv in moddedBtlv) + { + var existingBtlv = btlvList.FirstOrDefault(x => x.Id == btlv.Id); + + if (existingBtlv != null) + { + btlvList[btlvList.IndexOf(existingBtlv)] = btlv; + } + else + { + btlvList.Add(btlv); + } + } + + Kh2.Battle.Btlv.Write(stream.SetPosition(0), btlvList); + break; + + case "vtbl": + var vtblList = Kh2.Battle.Vtbl.Read(stream); + var moddedVtbl = deserializer.Deserialize>(sourceText); + + foreach (var vtbl in moddedVtbl) + { + var existingVtbl = vtblList.FirstOrDefault(x => x.Id == vtbl.Id && x.CharacterId == vtbl.CharacterId); + //Search for CharacterID & "Action" ID. + if (existingVtbl != null) + { + vtblList[vtblList.IndexOf(existingVtbl)] = vtbl; + + } + else + { + vtblList.Add(vtbl); + } + } + + Kh2.Battle.Vtbl.Write(stream.SetPosition(0), vtblList); + break; + + //Add new limits. ID found -> Continue. + case "limt": + var limtList = Kh2.Battle.Limt.Read(stream); + var moddedLimt = deserializer.Deserialize>(sourceText); + + foreach (var limt in moddedLimt) + { + var existingLimt = limtList.FirstOrDefault(x => x.Id == limt.Id); + + if (existingLimt != null) + { + limtList[limtList.IndexOf(existingLimt)] = limt; + } + else + { + limtList.Add(limt); + } + } + Kh2.Battle.Limt.Write(stream.SetPosition(0), limtList); + break; + + //Listpatch for Arif. Takes a string as an input for the WorldIndex to use, and ints for the roomIndex to edit. + //Strings do not need to worry about capitalization, etc. so long as they have the same characters. + case "arif": + var originalData = Kh2.SystemData.Arif.Read(stream); + var patches = deserializer.Deserialize>>(sourceText); + + foreach (var worldPatch in patches) + { + if (!worldIndexMap.TryGetValue(worldPatch.Key.ToLower().Replace(" ", ""), out var worldIndex)) + { + Log.Warn($"Invalid world index: {worldPatch.Key}"); + } + + if (worldIndex >= 0 && worldIndex < originalData.Count) + { + var worldData = originalData[worldIndex]; + + foreach (var areaPatch in worldPatch.Value) + { + int areaIndex = areaPatch.Key; + var patch = areaPatch.Value; + + // Add new areas. + while (areaIndex >= worldData.Count) + { + worldData.Add(new Kh2.SystemData.Arif + { + Bgms = new Kh2.SystemData.BgmSet[8], + Reserved = new byte[11] + }); + + // Initialize the BgmSet elements within the Bgms array + for (int i = 0; i < 8; i++) + { + worldData[worldData.Count - 1].Bgms[i] = new Kh2.SystemData.BgmSet(); + } + } + // End of adding new areas. + + if (areaIndex >= 0 && areaIndex < worldData.Count) + { + var areaData = worldData[areaIndex]; + //Below: Compares each field to see if it's specified in the YML. + //If yes, update w/ YML value. + //If no, retain original value. + areaData.Flags = patch.Flags != 0 ? patch.Flags : areaData.Flags; + areaData.Reverb = patch.Reverb != 0 ? patch.Reverb : areaData.Reverb; + areaData.SoundEffectBank1 = patch.SoundEffectBank1 != 0 ? patch.SoundEffectBank1 : areaData.SoundEffectBank1; + areaData.SoundEffectBank2 = patch.SoundEffectBank2 != 0 ? patch.SoundEffectBank2 : areaData.SoundEffectBank2; + for (int i = 0; i < patch.Bgms.Length && i < areaData.Bgms.Length; i++) + { + areaData.Bgms[i].BgmField = patch.Bgms[i].BgmField != 0 ? (ushort)patch.Bgms[i].BgmField : areaData.Bgms[i].BgmField; + areaData.Bgms[i].BgmBattle = patch.Bgms[i].BgmBattle != 0 ? (ushort)patch.Bgms[i].BgmBattle : areaData.Bgms[i].BgmBattle; + } + areaData.Voice = patch.Voice != 0 ? patch.Voice : areaData.Voice; + areaData.NavigationMapItem = patch.NavigationMapItem != 0 ? patch.NavigationMapItem : areaData.NavigationMapItem; + areaData.Command = patch.Command != 0 ? patch.Command : areaData.Command; + areaData.Reserved = patch.Reserved != null ? patch.Reserved : areaData.Reserved; + } + } + } + } + + + Kh2.SystemData.Arif.Write(stream.SetPosition(0), originalData); + break; + + case "place": + var originalPlace = Kh2.Places.Read(stream); + var moddedPlace = deserializer.Deserialize>(sourceText); + + foreach (var place in moddedPlace) + { + if (place.Index >= 0 && place.Index < originalPlace.Count) + { + // Update existing entry + originalPlace[place.Index].MessageId = place.MessageId; + originalPlace[place.Index].Padding = place.Padding; + } + else if (place.Index == originalPlace.Count) + { + // Add new entry + originalPlace.Add(new Places { MessageId = place.MessageId, Padding = place.Padding }); + } + else + { + // Expand the list and add the new entry at the specified index + while (originalPlace.Count < place.Index) + { + originalPlace.Add(new Places { MessageId = 0, Padding = 0 }); + } + originalPlace.Add(new Places { MessageId = place.MessageId, Padding = place.Padding }); + } + } + + Kh2.Places.Write(stream.SetPosition(0), originalPlace); + break; + + case "soundinfo": + var originalSoundInfo = Kh2.Soundinfo.Read(stream); + var moddedSoundInfo = deserializer.Deserialize>(sourceText); + + foreach (var info in moddedSoundInfo) + { + while (originalSoundInfo.Count <= info.Index) + { + originalSoundInfo.Add(new Soundinfo + { + Reverb = 0, + Rate = 0, + EnvironmentWAV = 0, + EnvironmentSEB = 0, + EnvironmentNUMBER = 0, + EnvironmentSPOT = 0, + FootstepWAV = 0, + FootstepSORA = 0, + FootstepDONALD = 0, + FootstepGOOFY = 0, + FootstepWORLDFRIEND = 0, + FootstepOTHER = 0 + }); + } + + originalSoundInfo[info.Index].Reverb = info.Reverb; + originalSoundInfo[info.Index].Rate = info.Rate; + originalSoundInfo[info.Index].EnvironmentWAV = info.EnvironmentWAV; + originalSoundInfo[info.Index].EnvironmentSEB = info.EnvironmentSEB; + originalSoundInfo[info.Index].EnvironmentNUMBER = info.EnvironmentNUMBER; + originalSoundInfo[info.Index].EnvironmentSPOT = info.EnvironmentSPOT; + originalSoundInfo[info.Index].FootstepWAV = info.FootstepWAV; + originalSoundInfo[info.Index].FootstepSORA = info.FootstepSORA; + originalSoundInfo[info.Index].FootstepDONALD = info.FootstepDONALD; + originalSoundInfo[info.Index].FootstepGOOFY = info.FootstepGOOFY; + originalSoundInfo[info.Index].FootstepWORLDFRIEND = info.FootstepWORLDFRIEND; + originalSoundInfo[info.Index].FootstepOTHER = info.FootstepOTHER; + } + + // Write the updated list back to the stream + Kh2.Soundinfo.Write(stream.SetPosition(0), originalSoundInfo); + break; + + case "libretto": + var originalLibretto = Kh2.Libretto.Read(stream); + + var patches2 = deserializer.Deserialize>(sourceText); + + foreach (var patch in patches2) + { + var definition = originalLibretto.Definitions.FirstOrDefault(def => def.TalkMessageId == patch.TalkMessageId); + + if (definition != null) + { + definition.Unknown = patch.Unknown; + + var contentList = new List(); + foreach (var contentPatch in patch.Contents) + { + contentList.Add(new Libretto.TalkMessageContent + { + Unknown1 = contentPatch.Unknown1, + TextId = contentPatch.TextId + }); + } + originalLibretto.Contents[originalLibretto.Definitions.IndexOf(definition)] = contentList; + } + else + { + var newDefinition = new Libretto.TalkMessageDefinition + { + TalkMessageId = patch.TalkMessageId, + Unknown = patch.Unknown, + ContentPointer = 0 // Will update this later after adding content entries + }; + + originalLibretto.Definitions.Add(newDefinition); + originalLibretto.Count++; + + var contentList = new List(); + foreach (var contentPatch in patch.Contents) + { + contentList.Add(new Libretto.TalkMessageContent + { + Unknown1 = contentPatch.Unknown1, + TextId = contentPatch.TextId + }); + } + originalLibretto.Contents.Add(contentList); + } + } + + stream.Position = 0; + Kh2.Libretto.Write(stream, originalLibretto); + break; + + + case "memt": + var memt = Kh2.SystemData.Memt.Read(stream); + var memtEntries = memt.Entries.Cast().ToList(); + var memtPatches = deserializer.Deserialize(sourceText); + + if (memtPatches.MemtEntries != null) + { + foreach (var patch in memtPatches.MemtEntries) + { + if (patch.Index < 0) + throw new IndexOutOfRangeException($"Invalid index {patch.Index} for Memt."); + + if (patch.Index >= memtEntries.Count) + { + // Index is beyond current entries, append new entries up to the patch index + while (memtEntries.Count <= patch.Index) + { + memtEntries.Add(new Kh2.SystemData.Memt.EntryFinalMix()); + } + } + + var memtEntry = memtEntries[patch.Index]; + memtEntry.WorldId = patch.WorldId; + memtEntry.CheckStoryFlag = patch.CheckStoryFlag; + memtEntry.CheckStoryFlagNegation = patch.CheckStoryFlagNegation; + memtEntry.Unk06 = patch.Unk06; + memtEntry.Unk08 = patch.Unk08; + memtEntry.Unk0A = patch.Unk0A; + memtEntry.Unk0C = patch.Unk0C; + memtEntry.Unk0E = patch.Unk0E; + memtEntry.Members = patch.Members.ToArray(); + + memtEntries[patch.Index] = memtEntry; + } + + memt.Entries.Clear(); + memt.Entries.AddRange(memtEntries); + + stream.Position = 0; + Kh2.SystemData.Memt.Write(stream, memt); + } + + if (memtPatches.MemberIndices != null) + { + foreach (var patch in memtPatches.MemberIndices) + { + if (patch.Index < 0 || patch.Index >= memt.MemberIndexCollection.Length) + throw new IndexOutOfRangeException($"Invalid MemberIndices index {patch.Index}."); + + var memberIndices = memt.MemberIndexCollection[patch.Index]; + memberIndices.Player = patch.Player; + memberIndices.Friend1 = patch.Friend1; + memberIndices.Friend2 = patch.Friend2; + memberIndices.FriendWorld = patch.FriendWorld; + + memt.MemberIndexCollection[patch.Index] = memberIndices; + } + + stream.Position = 0; + Kh2.SystemData.Memt.Write(stream, memt); + } + break; + + //New: Pref listpatches. More rigid as they're mostly offset-based. Can be updated to eventually support addition though. + case "fmab": + var fmabList = Kh2.SystemData.Fmab.Read(stream); + + var moddedFmab = deserializer.Deserialize(sourceText); + + foreach (var patch in moddedFmab.Entries) + { + if (patch.Key >= 0 && patch.Key < fmabList.Count) + { + fmabList[patch.Key] = patch.Value; + } + } + + stream.SetLength(0); + Kh2.SystemData.Fmab.Write(stream, fmabList); + break; + default: break; } } + } + private static void PatchSynth(Context context, List sources, Stream stream) + { + foreach (var source in sources) + { + string sourceText = File.ReadAllText(context.GetSourceModAssetPath(source.Name)); + switch (source.Type) + { + case "recipe": + var recipeList = Kh2.Mixdata.ReciLP.Read(stream); // Read existing Reci list + var moddedRecipes = deserializer.Deserialize>(sourceText); // Deserialize modded recipes + + foreach (var moddedRecipe in moddedRecipes) + { + var existingRecipe = recipeList.FirstOrDefault(x => x.Id == moddedRecipe.Id); + + if (existingRecipe != null) + { + // Update existing recipe in the list + recipeList[recipeList.IndexOf(existingRecipe)] = moddedRecipe; + // Update other properties as needed + } + else + { + // Add new recipe to the list + recipeList.Add(moddedRecipe); + } + } + + // Write the updated recipe list back to the stream + Kh2.Mixdata.ReciLP.Write(stream, recipeList); // Pass IEnumerable + break; + + case "condition": + var conditionList = Kh2.Mixdata.CondLP.Read(stream); + var moddedConditions = deserializer.Deserialize>(sourceText); + + foreach (var moddedCondition in moddedConditions) + { + var existingCondition = conditionList.FirstOrDefault(x => x.TextId == moddedCondition.TextId); + + if (existingCondition != null) + { + conditionList[conditionList.IndexOf(existingCondition)] = moddedCondition; + + } + else + { + conditionList.Add(moddedCondition); + } + } + + Kh2.Mixdata.CondLP.Write(stream, conditionList); // Pass IEnumerable + break; + + case "level": + var levelList = Kh2.Mixdata.LeveLP.Read(stream); + var moddedLevels = deserializer.Deserialize>(sourceText); + + foreach (var moddedLevel in moddedLevels) + { + var existingLevel = levelList.FirstOrDefault(x => x.Title == moddedLevel.Title); + + if (existingLevel != null) + { + levelList[levelList.IndexOf(existingLevel)] = moddedLevel; + } + else + { + levelList.Add(moddedLevel); + } + } + + Kh2.Mixdata.LeveLP.Write(stream, levelList); + break; + } + } } } } diff --git a/OpenKh.Tests/Patcher/PatcherTests.cs b/OpenKh.Tests/Patcher/PatcherTests.cs index 6f4f7ded4..904a368bd 100644 --- a/OpenKh.Tests/Patcher/PatcherTests.cs +++ b/OpenKh.Tests/Patcher/PatcherTests.cs @@ -1,2256 +1,3535 @@ -using OpenKh.Bbs; -using OpenKh.Command.Bdxio.Utils; -using OpenKh.Common; -using OpenKh.Imaging; -using OpenKh.Kh2; -using OpenKh.Kh2.Messages; -using OpenKh.Patcher; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; -using Xunit.Sdk; -using YamlDotNet.Serialization; - -namespace OpenKh.Tests.Patcher -{ - public class PatcherTests : IDisposable - { - private const string AssetsInputDir = "original_input"; - private const string ModInputDir = "mod_input"; - private const string ModOutputDir = "mod_output"; - - public PatcherTests() - { - Dispose(); - Directory.CreateDirectory(AssetsInputDir); - Directory.CreateDirectory(ModInputDir); - Directory.CreateDirectory(ModOutputDir); - } - - public void Dispose() - { - if (Directory.Exists(AssetsInputDir)) - Directory.Delete(AssetsInputDir, true); - if (Directory.Exists(ModInputDir)) - Directory.Delete(ModInputDir, true); - if (Directory.Exists(ModOutputDir)) - Directory.Delete(ModOutputDir, true); - } - - [Fact] - public void Kh2CopyBinariesTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "somedir/somefile.bin", - Method = "copy", - Source = new List - { - new AssetFile - { - Name = "somedir/somefile.bin" - } - } - } - } - }; - - CreateFile(ModInputDir, patch.Assets[0].Name).Dispose(); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void Kh2CreateBinArcIfSourceDoesntExistsTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "somedir/somefile.bar", - Method = "binarc", - Source = new List - { - new AssetFile - { - Name = "abcd", - Type = "list", - Method = "copy", - Source = new List - { - new AssetFile - { - Name = "somedir/somefile/abcd.bin" - } - } - } - } - } - } - }; - - CreateFile(ModInputDir, "somedir/somefile/abcd.bin").Using(x => - { - x.WriteByte(0); - x.WriteByte(1); - x.WriteByte(2); - x.WriteByte(3); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertBarFile("abcd", entry => - { - Assert.Equal(Bar.EntryType.List, entry.Type); - Assert.Equal(4, entry.Stream.Length); - Assert.Equal(0, entry.Stream.ReadByte()); - Assert.Equal(1, entry.Stream.ReadByte()); - Assert.Equal(2, entry.Stream.ReadByte()); - Assert.Equal(3, entry.Stream.ReadByte()); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void Kh2MergeWithOriginalBinArcTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "somedir/somefile.bar", - Method = "binarc", - Source = new List - { - new AssetFile - { - Name = "abcd", - Type = "list", - Method = "copy", - Source = new List - { - new AssetFile - { - Name = "somedir/somefile/abcd.bin" - } - } - } - } - } - } - }; - - CreateFile(ModInputDir, "somedir/somefile/abcd.bin").Using(x => - { - x.WriteByte(0); - x.WriteByte(1); - x.WriteByte(2); - x.WriteByte(3); - }); - - CreateFile(AssetsInputDir, "somedir/somefile.bar").Using(x => - { - Bar.Write(x, new Bar - { - new Bar.Entry - { - Name = "nice", - Type = Bar.EntryType.Model, - Stream = new MemoryStream(new byte[] { 4, 5, 6, 7 }) - } - }); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertBarFile("abcd", entry => - { - Assert.Equal(Bar.EntryType.List, entry.Type); - Assert.Equal(4, entry.Stream.Length); - Assert.Equal(0, entry.Stream.ReadByte()); - Assert.Equal(1, entry.Stream.ReadByte()); - Assert.Equal(2, entry.Stream.ReadByte()); - Assert.Equal(3, entry.Stream.ReadByte()); - }, ModOutputDir, patch.Assets[0].Name); - AssertBarFile("nice", entry => - { - Assert.Equal(Bar.EntryType.Model, entry.Type); - Assert.Equal(4, entry.Stream.Length); - Assert.Equal(4, entry.Stream.ReadByte()); - Assert.Equal(5, entry.Stream.ReadByte()); - Assert.Equal(6, entry.Stream.ReadByte()); - Assert.Equal(7, entry.Stream.ReadByte()); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void Kh2CreateImgdTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "somedir/somefile.bar", - Method = "binarc", - Source = new List - { - new AssetFile - { - Name = "test", - Method = "imgd", - Type = "imgd", - Source = new List - { - new AssetFile - { - Name = "sample.png", - IsSwizzled = false - } - } - } - } - } - } - }; - - File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertBarFile("test", entry => - { - Assert.True(Imgd.IsValid(entry.Stream)); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void Kh2MergeImzTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "out.imz", - Method = "imgz", - Source = new List - { - new AssetFile - { - Name = "test.imd", - Index = 1, - } - } - } - } - }; - - var tmpImd = Imgd.Create(new System.Drawing.Size(16, 16), PixelFormat.Indexed4, new byte[16 * 16 / 2], new byte[4], false); - var patchImd = Imgd.Create(new System.Drawing.Size(32, 16), PixelFormat.Indexed4, new byte[32 * 16 / 2], new byte[4], false); - CreateFile(AssetsInputDir, "out.imz").Using(x => - { - Imgz.Write(x, new Imgd[] - { - tmpImd, - tmpImd, - tmpImd, - }); - }); - CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "out.imz"); - File.OpenRead(Path.Combine(ModOutputDir, "out.imz")).Using(x => - { - var images = Imgz.Read(x).ToList(); - Assert.Equal(3, images.Count); - Assert.Equal(16, images[0].Size.Width); - Assert.Equal(32, images[1].Size.Width); - Assert.Equal(16, images[2].Size.Width); - }); - } - - [Fact] - public void Kh2MergeImzInsideBarTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "out.bar", - Method = "binarc", - Source = new List - { - new AssetFile - { - Name = "test", - Type = "imgz", - Method = "imgz", - Source = new List - { - new AssetFile - { - Name = "test.imd", - Index = 1 - } - }, - } - } - } - } - }; - - var tmpImd = Imgd.Create(new System.Drawing.Size(16, 16), PixelFormat.Indexed4, new byte[16 * 16 / 2], new byte[4], false); - var patchImd = Imgd.Create(new System.Drawing.Size(32, 16), PixelFormat.Indexed4, new byte[32 * 16 / 2], new byte[4], false); - CreateFile(AssetsInputDir, "out.bar").Using(x => - { - using var memoryStream = new MemoryStream(); - Imgz.Write(memoryStream, new Imgd[] - { - tmpImd, - tmpImd, - tmpImd, - }); - - Bar.Write(x, new Bar - { - new Bar.Entry - { - Name = "test", - Type = Bar.EntryType.Imgz, - Stream = memoryStream - } - }); - }); - CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "out.bar"); - AssertBarFile("test", x => - { - var images = Imgz.Read(x.Stream).ToList(); - Assert.Equal(3, images.Count); - Assert.Equal(16, images[0].Size.Width); - Assert.Equal(32, images[1].Size.Width); - Assert.Equal(16, images[2].Size.Width); - }, ModOutputDir, "out.bar"); - } - - [Fact] - public void MergeKh2MsgTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "msg/us/sys.msg", - Method = "kh2msg", - Source = new List - { - new AssetFile - { - Name = "sys.yml", - Language = "en", - } - } - }, - new AssetFile - { - Name = "msg/it/sys.msg", - Method = "kh2msg", - Source = new List - { - new AssetFile - { - Name = "sys.yml", - Language = "it", - } - } - }, - new AssetFile - { - Name = "msg/jp/sys.msg", - Method = "kh2msg", - Source = new List - { - new AssetFile - { - Name = "sys.yml", - Language = "jp", - } - } - } - } - }; - - Directory.CreateDirectory(Path.Combine(AssetsInputDir, "msg/us/")); - File.Create(Path.Combine(AssetsInputDir, "msg/us/sys.msg")).Using(stream => - { - Msg.Write(stream, new List - { - new Msg.Entry - { - Data = new byte[] { 1, 2, 3, 0 }, - Id = 123 - } - }); - }); - File.Create(Path.Combine(ModInputDir, "sys.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("- id: 456"); - writer.WriteLine(" en: English"); - writer.WriteLine(" it: Italiano"); - writer.WriteLine(" jp: テスト"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "msg/jp/sys.msg"); - File.OpenRead(Path.Combine(ModOutputDir, "msg/jp/sys.msg")).Using(stream => - { - var msg = Msg.Read(stream); - Assert.Single(msg); - Assert.Equal(456, msg[0].Id); - Assert.Equal("テスト", Encoders.JapaneseSystem.Decode(msg[0].Data).First().Text); - }); - - AssertFileExists(ModOutputDir, "msg/us/sys.msg"); - File.OpenRead(Path.Combine(ModOutputDir, "msg/us/sys.msg")).Using(stream => - { - var msg = Msg.Read(stream); - Assert.Equal(2, msg.Count); - Assert.Equal(123, msg[0].Id); - Assert.Equal(456, msg[1].Id); - Assert.Equal("English", Encoders.InternationalSystem.Decode(msg[1].Data).First().Text); - }); - - AssertFileExists(ModOutputDir, "msg/it/sys.msg"); - File.OpenRead(Path.Combine(ModOutputDir, "msg/it/sys.msg")).Using(stream => - { - var msg = Msg.Read(stream); - Assert.Single(msg); - Assert.Equal(456, msg[0].Id); - Assert.Equal("Italiano", Encoders.InternationalSystem.Decode(msg[0].Data).First().Text); - }); - } - - [Fact] - public void MergeKh2AreaDataScriptTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "map.script", - Method = "areadatascript", - Source = new List - { - new AssetFile - { - Name = "map.txt", - } - } - }, - } - }; - - File.Create(Path.Combine(AssetsInputDir, "map.script")).Using(stream => - { - var compiledProgram = Kh2.Ard.AreaDataScript.Compile("Program 1\nSpawn \"1111\""); - Kh2.Ard.AreaDataScript.Write(stream, compiledProgram); - }); - File.Create(Path.Combine(ModInputDir, "map.txt")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("Program 2"); - writer.WriteLine("Spawn \"2222\""); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "map.script"); - File.OpenRead(Path.Combine(ModOutputDir, "map.script")).Using(stream => - { - var scripts = Kh2.Ard.AreaDataScript.Read(stream); - var decompiled = Kh2.Ard.AreaDataScript.Decompile(scripts); - decompiled.Contains("Program 1"); - decompiled.Contains("Spawn \"1111\""); - decompiled.Contains("Program 2"); - decompiled.Contains("Spawn \"2222\""); - }); - } - - [Fact] - public void PatchKh2BdscriptTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "aaa", - Method = "bdscript", - Source = new List - { - new AssetFile - { - Name = "test.bdscript", - } - } - }, - } - }; - File.Create(Path.Combine(ModInputDir, "test.bdscript")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("---"); - writer.WriteLine("WorkSize: 64"); - writer.WriteLine("StackSize: 64"); - writer.WriteLine("TempSize: 64"); - writer.WriteLine("Triggers:"); - writer.WriteLine("- Key: 0"); - writer.WriteLine(" Addr: TR0"); - writer.WriteLine("Name: aaa"); - writer.WriteLine("---"); - writer.WriteLine(" section .text"); - writer.WriteLine("TR0:"); - writer.WriteLine(" ret"); - writer.WriteLine("DUMMY:"); - writer.WriteLine(" ret"); - writer.Flush(); - }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "aaa"); - - var bdxStream = new MemoryStream(File.ReadAllBytes(Path.Combine(ModOutputDir, "aaa"))); - var decoder = new BdxDecoder(bdxStream); - var script = BdxDecoder.TextFormatter.Format(decoder); - - var lines = script.Split("\r\n"); - - Assert.Equal("WorkSize: 64", lines[1]); - Assert.Equal("Name: aaa", lines[7]); - - } - - [Fact] - public void PatchKh2SpawnPointTest() - { - - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "map.script", - Method = "areadataspawn", - Source = new List - { - new AssetFile - { - Name = "map.yaml", - } - } - }, - } - }; - - File.Create(Path.Combine(AssetsInputDir, "map.script")).Using(stream => - { - var spawnPoint = new List(); - - Kh2.Ard.SpawnPoint.Write(stream, spawnPoint); - }); - File.Create(Path.Combine(ModInputDir, "map.yaml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("- Type: 2"); - writer.WriteLine(" Flag: 1"); - writer.Flush(); - }); - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "map.script"); - var content = File.ReadAllText(Path.Combine(ModOutputDir, "map.script")); - var scripts = new Deserializer().Deserialize> (content); - - Assert.Equal(2, scripts[0].Type); - Assert.Equal(1, scripts[0].Flag); - - } - - - public void ListPatchTrsrTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() { - Assets = new List() - { - new AssetFile() - { - Name = "03system.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "trsr", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "TrsrList.yml", - Type = "trsr" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => - { - var trsrEntry = new List() - { - new Kh2.SystemData.Trsr - { - Id = 1, - ItemId = 10 - } - }; - using var trsrStream = new MemoryStream(); - Kh2.SystemData.Trsr.Write(trsrStream, trsrEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "trsr", - Type = Bar.EntryType.List, - Stream = trsrStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "TrsrList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("1:"); - writer.WriteLine(" ItemId: 200"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "03system.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var trsrStream = Kh2.SystemData.Trsr.Read(binarc[0].Stream); - Assert.Equal(200, trsrStream[0].ItemId); - }); - - } - - [Fact] - public void ListPatchCmdTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() { - Assets = new List() - { - new AssetFile() - { - Name = "03system.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "cmd", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "CmdList.yml", - Type = "cmd" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => - { - var cmdEntry = new List() - { - new Kh2.SystemData.Cmd - { - Id = 1, - Execute = 3, - Argument = 3, - SubMenu = 1, - } - }; - using var cmdStream = new MemoryStream(); - Kh2.SystemData.Cmd.Write(cmdStream, cmdEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "cmd", - Type = Bar.EntryType.List, - Stream = cmdStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "CmdList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("- Id: 1"); - writer.WriteLine(" Execute: 3"); - writer.WriteLine(" Argument: 3"); - writer.WriteLine(" SubMenu: 1"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "03system.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var cmdStream = Kh2.SystemData.Cmd.Read(binarc[0].Stream); - Assert.Equal(1, cmdStream[0].Id); - Assert.Equal(3, cmdStream[0].Execute); - Assert.Equal(3, cmdStream[0].Argument); - Assert.Equal(1, cmdStream[0].SubMenu); - }); - - } - - [Fact] - public void ListPatchItemTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "03system.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "item", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "ItemList.yml", - Type = "item" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => - { - var itemEntry = new List() - { - new Kh2.SystemData.Item - { - Items = new List() - { - new Kh2.SystemData.Item.Entry() - { - Id = 1, - ShopBuy = 10 - } - }, - Stats = new List() - { - new Kh2.SystemData.Item.Stat() - { - Id = 10, - Ability = 15 - } - } - - } - }; - using var itemStream = new MemoryStream(); - itemEntry[0].Write(itemStream); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "item", - Type = Bar.EntryType.List, - Stream = itemStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "ItemList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("Items:"); - writer.WriteLine("- Id: 1"); - writer.WriteLine(" ShopBuy: 200"); - writer.WriteLine("Stats:"); - writer.WriteLine("- Id: 10"); - writer.WriteLine(" Ability: 150"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "03system.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var itemStream = Kh2.SystemData.Item.Read(binarc[0].Stream); - Assert.Equal(200, itemStream.Items[0].ShopBuy); - Assert.Equal(150, itemStream.Stats[0].Ability); - }); - - } - - [Fact] - public void ListPatchSkltTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "03system.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "sklt", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "SkltList.yml", - Type = "sklt" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => - { - var skltEntry = new List() - { - new Kh2.SystemData.Sklt - { - CharacterId = 1, - Bone1 = 178, - Bone2 = 86 - } - }; - - using var skltStream = new MemoryStream(); - Kh2.SystemData.Sklt.Write(skltStream, skltEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "sklt", - Type = Bar.EntryType.List, - Stream = skltStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "SkltList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("- CharacterId: 1"); - writer.WriteLine(" Bone1: 178"); - writer.WriteLine(" Bone2: 86"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "03system.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var skltStream = Kh2.SystemData.Sklt.Read(binarc[0].Stream); - Assert.Equal(1U, skltStream[0].CharacterId); - Assert.Equal(178, skltStream[0].Bone1); - Assert.Equal(86, skltStream[0].Bone2); - }); - } - - [Fact] - public void ListPatchFmlvTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "fmlv", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "FmlvList.yml", - Type = "fmlv" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var fmlvEntry = new List() - { - new Kh2.Battle.Fmlv - { - FormId = 1, - FormLevel = 1, - Exp = 100, - Ability = 200 - }, - new Kh2.Battle.Fmlv - { - FormId = 1, - FormLevel = 2, - Exp = 100, - Ability = 200 - }, - new Kh2.Battle.Fmlv - { - FormId = 2, - FormLevel = 1, - Exp = 100, - Ability = 200 - }, - }; - - using var fmlvStream = new MemoryStream(); - Kh2.Battle.Fmlv.Write(fmlvStream, fmlvEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "fmlv", - Type = Bar.EntryType.List, - Stream = fmlvStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "FmlvList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - var serializer = new Serializer(); - serializer.Serialize(writer, new Dictionary - { - ["Valor"] = new[] - { - new FmlvDTO - { - FormLevel = 1, - Experience = 5, - Ability = 127 - } - } - }); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var fmlv = Kh2.Battle.Fmlv.Read(binarc[0].Stream); - - Assert.Equal(3, fmlv.Count); - Assert.Equal(1, fmlv[0].FormId); - Assert.Equal(1, fmlv[0].FormLevel); - Assert.Equal(5, fmlv[0].Exp); - Assert.Equal(127, fmlv[0].Ability); - - Assert.Equal(1, fmlv[1].FormId); - Assert.Equal(2, fmlv[1].FormLevel); - - Assert.Equal(2, fmlv[2].FormId); - Assert.Equal(1, fmlv[2].FormLevel); - }); - } - - [Fact] - public void ListPatchBonsTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "bons", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "BonsList.yml", - Type = "bons" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var bonsEntry = new List() - { - new Kh2.Battle.Bons - { - CharacterId = 1, - RewardId = 15, - BonusItem1 = 10 - }, - new Kh2.Battle.Bons - { - CharacterId = 2, - RewardId = 15, - BonusItem1 = 5 - } - }; - using var bonsStream = new MemoryStream(); - Kh2.Battle.Bons.Write(bonsStream, bonsEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "bons", - Type = Bar.EntryType.List, - Stream = bonsStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "BonsList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("15:"); - writer.WriteLine(" Sora:"); - writer.WriteLine(" BonusItem1: 200"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var bonsStream = Kh2.Battle.Bons.Read(binarc[0].Stream); - Assert.Equal(200, bonsStream[0].BonusItem1); - Assert.Equal(5, bonsStream[1].BonusItem1); - }); - - } - - [Fact] - public void ListPatchLvupTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bin", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "lvup", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "LvupList.yml", - Type = "lvup" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bin")).Using(stream => - { - var lvupEntry = new Kh2.Battle.Lvup - { - Count = 13, - Unknown08 = new byte[0x38], - Characters = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - }, - new Kh2.Battle.Lvup.PlayableCharacter() - { - NumLevels = 1, - Levels = new List() - { - new Kh2.Battle.Lvup.PlayableCharacter.Level() - { - Exp = 50 - } - } - } - - } - }; - using var lvupStream = new MemoryStream(); - lvupEntry.Write(lvupStream); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "lvup", - Type = Bar.EntryType.List, - Stream = lvupStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "LvupList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("Sora:"); - writer.WriteLine(" 1:"); - writer.WriteLine(" Exp: 500"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bin"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bin")).Using(stream => - { - var binarc = Bar.Read(stream); - var lvupStream = Kh2.Battle.Lvup.Read(binarc[0].Stream); - Assert.Equal(500, lvupStream.Characters[0].Levels[0].Exp); - }); - - } - - void ListPatchAtkpTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "atkp", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "AtkpList.yml", - Type = "atkp" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var atkpEntry = new List() - { - new Kh2.Battle.Atkp - { - Id = 0, - SubId = 3, - Type = Kh2.Battle.Atkp.AttackType.PierceArmor, - CriticalAdjust = 0, - Power = 25, - Team = 0, - Element = 0, - EnemyReaction = 0, - EffectOnHit = 2, - KnockbackStrength1 = 32767, - KnockbackStrength2 = 0, - Unknown = 0, - Flags = Kh2.Battle.Atkp.AttackFlags.BGHit, - RefactSelf = Kh2.Battle.Atkp.Refact.Reflect, - RefactOther = Kh2.Battle.Atkp.Refact.Reflect, - ReflectedMotion = 0, - ReflectHitBack = 0, - ReflectAction = 0, - ReflectHitSound = 0, - ReflectRC = 0, - ReflectRange = 0, - ReflectAngle = 0, - DamageEffect = 0, - Switch = 1, - Interval = 1, - FloorCheck = 1, - DriveDrain = 1, - RevengeDamage = 1, - AttackTrReaction = Kh2.Battle.Atkp.TrReaction.Charge, - ComboGroup = 1, - RandomEffect = 1, - Kind = Kh2.Battle.Atkp.AttackKind.ComboFinisher, - HpDrain = 15 - } - }; - - using var atkpStream = new MemoryStream(); - Kh2.Battle.Atkp.Write(atkpStream, atkpEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "atkp", - Type = Bar.EntryType.List, - Stream = atkpStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "AtkpList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("- Id: 0"); - writer.WriteLine(" SubId: 3"); - writer.WriteLine(" Type: PierceArmor"); - writer.WriteLine(" CriticalAdjust: 0"); - writer.WriteLine(" Power: 25"); - writer.WriteLine(" Team: 0"); - writer.WriteLine(" Element: 0"); - writer.WriteLine(" EnemyReaction: 0"); - writer.WriteLine(" EffectOnHit: 2"); - writer.WriteLine(" KnockbackStrength1: 32767"); - writer.WriteLine(" KnockbackStrength2: 0"); - writer.WriteLine(" Unknown: 0"); - writer.WriteLine(" Flags: BGHit"); - writer.WriteLine(" RefactSelf: 0"); - writer.WriteLine(" RefactOther: 0"); - writer.WriteLine(" ReflectedMotion: 0"); - writer.WriteLine(" ReflectHitBack: 0"); - writer.WriteLine(" ReflectAction: 0"); - writer.WriteLine(" ReflectHitSound: 0"); - writer.WriteLine(" ReflectRC: 0"); - writer.WriteLine(" ReflectRange: 0"); - writer.WriteLine(" ReflectAngle: 0"); - writer.WriteLine(" DamageEffect: 0"); - writer.WriteLine(" Switch: 1"); - writer.WriteLine(" Interval: 1"); - writer.WriteLine(" FloorCheck: 1"); - writer.WriteLine(" DriveDrain: 1"); - writer.WriteLine(" RevengeDamage: 1"); - writer.WriteLine(" AttackTrReaction: 1"); - writer.WriteLine(" ComboGroup: 1"); - writer.WriteLine(" RandomEffect: 1"); - writer.WriteLine(" Kind: ComboFinisher"); - writer.WriteLine(" HpDrain: 15"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var atkpStream = Kh2.Battle.Atkp.Read(binarc[0].Stream); - Assert.Equal(0, atkpStream[0].Id); - Assert.Equal(3, atkpStream[0].SubId); - Assert.Equal(Kh2.Battle.Atkp.AttackType.PierceArmor, atkpStream[0].Type); - Assert.Equal(0, atkpStream[0].CriticalAdjust); - Assert.Equal(25, atkpStream[0].Power); - Assert.Equal(0, atkpStream[0].Team); - Assert.Equal(0, atkpStream[0].Element); - Assert.Equal(0, atkpStream[0].EnemyReaction); - Assert.Equal(2, atkpStream[0].EffectOnHit); - Assert.Equal(32767, atkpStream[0].KnockbackStrength1); - Assert.Equal(0, atkpStream[0].KnockbackStrength2); - Assert.Equal(0000, atkpStream[0].Unknown); - Assert.Equal(Kh2.Battle.Atkp.AttackFlags.BGHit, atkpStream[0].Flags); - Assert.Equal(Kh2.Battle.Atkp.Refact.Reflect, atkpStream[0].RefactSelf); - Assert.Equal(Kh2.Battle.Atkp.Refact.Reflect, atkpStream[0].RefactOther); - Assert.Equal(0, atkpStream[0].ReflectedMotion); - Assert.Equal(0, atkpStream[0].ReflectHitBack); - Assert.Equal(0, atkpStream[0].ReflectAction); - Assert.Equal(0, atkpStream[0].ReflectHitSound); - Assert.Equal(0, atkpStream[0].ReflectRC); - Assert.Equal(0, atkpStream[0].ReflectRange); - Assert.Equal(0, atkpStream[0].ReflectAngle); - Assert.Equal(0, atkpStream[0].DamageEffect); - Assert.Equal(1, atkpStream[0].Switch); - Assert.Equal(1, atkpStream[0].Interval); - Assert.Equal(1, atkpStream[0].FloorCheck); - Assert.Equal(1, atkpStream[0].DriveDrain); - Assert.Equal(1, atkpStream[0].RevengeDamage); - Assert.Equal(Kh2.Battle.Atkp.TrReaction.Charge, atkpStream[0].AttackTrReaction); - Assert.Equal(1, atkpStream[0].ComboGroup); - Assert.Equal(1, atkpStream[0].RandomEffect); - Assert.Equal(Kh2.Battle.Atkp.AttackKind.ComboFinisher, atkpStream[0].Kind); - Assert.Equal(15, atkpStream[0].HpDrain); - }); - } - - [Fact] - public void ListPatchObjEntryTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00objentry.bin", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "ObjList.yml", - Type = "objentry", - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00objentry.bin")).Using(stream => - { - var objEntry = new List() - { - new Kh2.Objentry - { - ObjectId = 1, - ModelName = "M_EX060", - AnimationName = "M_EX060.mset" - } - }; - Kh2.Objentry.Write(stream, objEntry); - }); - - File.Create(Path.Combine(ModInputDir, "ObjList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - writer.WriteLine("1:"); - writer.WriteLine(" ObjectId: 1"); - writer.WriteLine(" ModelName: M_EX100"); - writer.WriteLine(" AnimationName: M_EX100.mset"); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00objentry.bin"); - - File.OpenRead(Path.Combine(ModOutputDir, "00objentry.bin")).Using(stream => - { - var objStream = Kh2.Objentry.Read(stream); - Assert.Equal("M_EX100", objStream[0].ModelName); - Assert.Equal("M_EX100.mset", objStream[0].AnimationName); - }); - - } - - [Fact] - public void ListPatchPlrpTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "plrp", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "PlrpList.yml", - Type = "plrp" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var plrpEntry = new List() - { - new Kh2.Battle.Plrp - { - Id = 7, - Character = 1, - Ap = 2, - Items = new List(32), - Padding = new byte[52] - } - }; - - using var plrpStream = new MemoryStream(); - Kh2.Battle.Plrp.Write(plrpStream, plrpEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "plrp", - Type = Bar.EntryType.List, - Stream = plrpStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "PlrpList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - var serializer = new Serializer(); - var moddedPlrp = new List{ - new Kh2.Battle.Plrp - { - Id = 7, - Character = 1, - Ap = 200, - Items = new List(32), - Padding = new byte[52] - } - }; - writer.Write(serializer.Serialize(moddedPlrp)); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var plrp = Kh2.Battle.Plrp.Read(binarc[0].Stream); - - Assert.Equal(200, plrp[0].Ap); - }); - } - - [Fact] - public void ListPatchEnmpTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "enmp", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "EnmpList.yml", - Type = "enmp" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var enmpEntry = new List() - { - new Kh2.Battle.Enmp - { - Id = 7, - Level = 1, - Health = new short[32], - MaxDamage = 1, - MinDamage = 1, - PhysicalWeakness = 1, - FireWeakness = 1, - IceWeakness = 1, - ThunderWeakness = 1, - DarkWeakness = 1, - LightWeakness = 1, - GeneralWeakness = 1, - Experience = 1, - Prize = 1, - BonusLevel = 1 - } - }; - - using var enmpStream = new MemoryStream(); - Kh2.Battle.Enmp.Write(enmpStream, enmpEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "enmp", - Type = Bar.EntryType.List, - Stream = enmpStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "EnmpList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - var serializer = new Serializer(); - var moddedEnmp = new List{ - new Kh2.Battle.Enmp - { - Id = 7, - Level = 1, - Health = new short[32], - MaxDamage = 1, - MinDamage = 1, - PhysicalWeakness = 1, - FireWeakness = 1, - IceWeakness = 1, - ThunderWeakness = 1, - DarkWeakness = 1, - LightWeakness = 1, - GeneralWeakness = 1, - Experience = 1, - Prize = 1, - BonusLevel = 1 - } - }; - writer.Write(serializer.Serialize(moddedEnmp)); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var enmp = Kh2.Battle.Enmp.Read(binarc[0].Stream); - - Assert.Equal(1, enmp[0].Level); - }); - } - - [Fact] - public void ListPatchMagcTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "magc", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "MagcList.yml", - Type = "magc" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var magcEntry = new List() - { - new Kh2.Battle.Magc - { - Id = 7, - Level = 3, - World = 1, - FileName = "magic/FIRE_7.mag" - } - }; - - using var magcStream = new MemoryStream(); - Kh2.Battle.Magc.Write(magcStream, magcEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "magc", - Type = Bar.EntryType.List, - Stream = magcStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "MagcList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - var serializer = new Serializer(); - var moddedMagc = new List{ - new Kh2.Battle.Magc - { - Id = 7, - Level = 3, - World = 1, - FileName = "magic/FIRE_7.mag" - } - }; - writer.Write(serializer.Serialize(moddedMagc)); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var magc = Kh2.Battle.Magc.Read(binarc[0].Stream); - - Assert.Equal(3, magc[0].Level); - }); - } - - [Fact] - public void ListPatchPrztTest() - { - var patcher = new PatcherProcessor(); - var serializer = new Serializer(); - var patch = new Metadata() - { - Assets = new List() - { - new AssetFile() - { - Name = "00battle.bar", - Method = "binarc", - Source = new List() - { - new AssetFile() - { - Name = "przt", - Method = "listpatch", - Type = "List", - Source = new List() - { - new AssetFile() - { - Name = "PrztList.yml", - Type = "przt" - } - } - } - } - } - } - }; - - File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => - { - var prztEntry = new List() - { - new Kh2.Battle.Przt - { - Id = 1, - SmallHpOrbs = 0, - BigHpOrbs = 1 - } - }; - - using var prztStream = new MemoryStream(); - Kh2.Battle.Przt.Write(prztStream, prztEntry); - Bar.Write(stream, new Bar() { - new Bar.Entry() - { - Name = "przt", - Type = Bar.EntryType.List, - Stream = prztStream - } - }); - }); - - File.Create(Path.Combine(ModInputDir, "PrztList.yml")).Using(stream => - { - var writer = new StreamWriter(stream); - var serializer = new Serializer(); - var moddedPrzt = new List{ - new Kh2.Battle.Przt - { - Id = 1, - SmallHpOrbs = 0, - BigHpOrbs = 1 - } - }; - writer.Write(serializer.Serialize(moddedPrzt)); - writer.Flush(); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, "00battle.bar"); - - File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => - { - var binarc = Bar.Read(stream); - var przt = Kh2.Battle.Przt.Read(binarc[0].Stream); - - Assert.Equal(1, przt[0].BigHpOrbs); - }); - } - - [Fact] - public void BbsArcCreateArcTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List { - new AssetFile - { - Name = "somedir/somearc.arc", - Method = "bbsarc", - Source = new List { - new AssetFile - { - Name = "newfile", - Method = "copy", - Source = new List { - new AssetFile - { - Name = "somedir/somearc/newfile.bin" - } - } - } - } - } - } - }; - - CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => - { - x.Write(new byte[] { 4, 5, 6, 7 }); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertArcFile("newfile", entry => - { - Assert.Equal(4, entry.Data.Length); - Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void BbsArcAddToArcTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List { - new AssetFile - { - Name = "somedir/somearc.arc", - Method = "bbsarc", - Source = new List { - new AssetFile - { - Name = "newfile", - Method = "copy", - Source = new List { - new AssetFile - { - Name = "somedir/somearc/newfile.bin" - } - } - } - } - } - } - }; - - CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => - { - Arc.Write(new List - { - new Arc.Entry - { - Name = "abcd", - Data = new byte[] {0, 1, 2, 3 } - } - }, x); - }); - - CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => - { - x.Write(new byte[] { 4, 5, 6, 7 }); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertArcFile("abcd", entry => - { - Assert.Equal(4, entry.Data.Length); - Assert.Equal(new byte[] { 0, 1, 2, 3 }, entry.Data); - }, ModOutputDir, patch.Assets[0].Name); - AssertArcFile("newfile", entry => - { - Assert.Equal(4, entry.Data.Length); - Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void BbsArcReplaceInArcTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List { - new AssetFile - { - Name = "somedir/somearc.arc", - Method = "bbsarc", - Source = new List { - new AssetFile - { - Name = "abcd", - Method = "copy", - Source = new List { - new AssetFile - { - Name = "somedir/somearc/abcd.bin" - } - } - } - } - } - } - }; - - CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => - { - Arc.Write(new List - { - new Arc.Entry - { - Name = "abcd", - Data = new byte[] {0, 1, 2, 3} - } - }, x); - }); - - CreateFile(ModInputDir, "somedir/somearc/abcd.bin").Using(x => - { - x.Write(new byte[] { 4, 5, 6, 7 }); - }); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertArcFile("abcd", entry => - { - Assert.Equal(4, entry.Data.Length); - Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); - }, ModOutputDir, patch.Assets[0].Name); - } - - [Fact] - public void ProcessMultipleTest() - { - var patcher = new PatcherProcessor(); - var patch = new Metadata - { - Assets = new List - { - new AssetFile - { - Name = "somedir/somefile.bar", - Method = "binarc", - Multi = new List - { - new Multi { Name = "somedir/another.bar" } - }, - Source = new List - { - new AssetFile - { - Name = "test", - Method = "imgd", - Type = "imgd", - Source = new List - { - new AssetFile - { - Name = "sample.png", - IsSwizzled = false - } - } - } - } - } - } - }; - - File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); - - patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); - - AssertFileExists(ModOutputDir, patch.Assets[0].Name); - AssertBarFile("test", entry => - { - Assert.True(Imgd.IsValid(entry.Stream)); - }, ModOutputDir, patch.Assets[0].Name); - - AssertFileExists(ModOutputDir, patch.Assets[0].Multi[0].Name); - AssertBarFile("test", entry => - { - Assert.True(Imgd.IsValid(entry.Stream)); - }, ModOutputDir, patch.Assets[0].Multi[0].Name); - } - - - private static void AssertFileExists(params string[] paths) - { - var filePath = Path.Join(paths); - if (File.Exists(filePath) == false) - Assert.Fail($"File not found '{filePath}'"); - } - - private static void AssertBarFile(string name, Action assertion, params string[] paths) - { - var filePath = Path.Join(paths); - var entries = File.OpenRead(filePath).Using(x => - { - if (!Bar.IsValid(x)) - Assert.Fail($"Not a valid BinArc"); - return Bar.Read(x); - }); - - var entry = entries.SingleOrDefault(x => x.Name == name); - if (entry == null) - throw new XunitException($"Entry '{name}' not found"); - - assertion(entry); - } - - private static void AssertArcFile(string name, Action assertion, params string[] paths) - { - var filePath = Path.Join(paths); - var entries = File.OpenRead(filePath).Using(x => - { - if (!Arc.IsValid(x)) - Assert.Fail($"Not a valid Arc"); - return Arc.Read(x); - }); - - var entry = entries.SingleOrDefault(x => x.Name == name); - if (entry == null) - throw new XunitException($"Arc Entry '{name}' not found"); - - assertion(entry); - } - - private static Stream CreateFile(params string[] paths) - { - var filePath = Path.Join(paths); - var dirPath = Path.GetDirectoryName(filePath); - Directory.CreateDirectory(dirPath); - return File.Create(filePath); - } - } -} +using Antlr4.Runtime; +using OpenKh.Bbs; +using OpenKh.Command.Bdxio.Utils; +using OpenKh.Common; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Messages; +using OpenKh.Patcher; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Sdk; +using YamlDotNet.Serialization; + +namespace OpenKh.Tests.Patcher +{ + public class PatcherTests : IDisposable + { + private const string AssetsInputDir = "original_input"; + private const string ModInputDir = "mod_input"; + private const string ModOutputDir = "mod_output"; + + public PatcherTests() + { + Dispose(); + Directory.CreateDirectory(AssetsInputDir); + Directory.CreateDirectory(ModInputDir); + Directory.CreateDirectory(ModOutputDir); + } + + public void Dispose() + { + if (Directory.Exists(AssetsInputDir)) + Directory.Delete(AssetsInputDir, true); + if (Directory.Exists(ModInputDir)) + Directory.Delete(ModInputDir, true); + if (Directory.Exists(ModOutputDir)) + Directory.Delete(ModOutputDir, true); + } + + [Fact] + public void Kh2CopyBinariesTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bin", + Method = "copy", + Source = new List + { + new AssetFile + { + Name = "somedir/somefile.bin" + } + } + } + } + }; + + CreateFile(ModInputDir, patch.Assets[0].Name).Dispose(); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void Kh2CreateBinArcIfSourceDoesntExistsTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bar", + Method = "binarc", + Source = new List + { + new AssetFile + { + Name = "abcd", + Type = "list", + Method = "copy", + Source = new List + { + new AssetFile + { + Name = "somedir/somefile/abcd.bin" + } + } + } + } + } + } + }; + + CreateFile(ModInputDir, "somedir/somefile/abcd.bin").Using(x => + { + x.WriteByte(0); + x.WriteByte(1); + x.WriteByte(2); + x.WriteByte(3); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertBarFile("abcd", entry => + { + Assert.Equal(Bar.EntryType.List, entry.Type); + Assert.Equal(4, entry.Stream.Length); + Assert.Equal(0, entry.Stream.ReadByte()); + Assert.Equal(1, entry.Stream.ReadByte()); + Assert.Equal(2, entry.Stream.ReadByte()); + Assert.Equal(3, entry.Stream.ReadByte()); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void Kh2MergeWithOriginalBinArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bar", + Method = "binarc", + Source = new List + { + new AssetFile + { + Name = "abcd", + Type = "list", + Method = "copy", + Source = new List + { + new AssetFile + { + Name = "somedir/somefile/abcd.bin" + } + } + } + } + } + } + }; + + CreateFile(ModInputDir, "somedir/somefile/abcd.bin").Using(x => + { + x.WriteByte(0); + x.WriteByte(1); + x.WriteByte(2); + x.WriteByte(3); + }); + + CreateFile(AssetsInputDir, "somedir/somefile.bar").Using(x => + { + Bar.Write(x, new Bar + { + new Bar.Entry + { + Name = "nice", + Type = Bar.EntryType.Model, + Stream = new MemoryStream(new byte[] { 4, 5, 6, 7 }) + } + }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertBarFile("abcd", entry => + { + Assert.Equal(Bar.EntryType.List, entry.Type); + Assert.Equal(4, entry.Stream.Length); + Assert.Equal(0, entry.Stream.ReadByte()); + Assert.Equal(1, entry.Stream.ReadByte()); + Assert.Equal(2, entry.Stream.ReadByte()); + Assert.Equal(3, entry.Stream.ReadByte()); + }, ModOutputDir, patch.Assets[0].Name); + AssertBarFile("nice", entry => + { + Assert.Equal(Bar.EntryType.Model, entry.Type); + Assert.Equal(4, entry.Stream.Length); + Assert.Equal(4, entry.Stream.ReadByte()); + Assert.Equal(5, entry.Stream.ReadByte()); + Assert.Equal(6, entry.Stream.ReadByte()); + Assert.Equal(7, entry.Stream.ReadByte()); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void Kh2CreateImgdTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bar", + Method = "binarc", + Source = new List + { + new AssetFile + { + Name = "test", + Method = "imgd", + Type = "imgd", + Source = new List + { + new AssetFile + { + Name = "sample.png", + IsSwizzled = false + } + } + } + } + } + } + }; + + File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertBarFile("test", entry => + { + Assert.True(Imgd.IsValid(entry.Stream)); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void Kh2MergeImzTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "out.imz", + Method = "imgz", + Source = new List + { + new AssetFile + { + Name = "test.imd", + Index = 1, + } + } + } + } + }; + + var tmpImd = Imgd.Create(new System.Drawing.Size(16, 16), PixelFormat.Indexed4, new byte[16 * 16 / 2], new byte[4], false); + var patchImd = Imgd.Create(new System.Drawing.Size(32, 16), PixelFormat.Indexed4, new byte[32 * 16 / 2], new byte[4], false); + CreateFile(AssetsInputDir, "out.imz").Using(x => + { + Imgz.Write(x, new Imgd[] + { + tmpImd, + tmpImd, + tmpImd, + }); + }); + CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "out.imz"); + File.OpenRead(Path.Combine(ModOutputDir, "out.imz")).Using(x => + { + var images = Imgz.Read(x).ToList(); + Assert.Equal(3, images.Count); + Assert.Equal(16, images[0].Size.Width); + Assert.Equal(32, images[1].Size.Width); + Assert.Equal(16, images[2].Size.Width); + }); + } + + [Fact] + public void Kh2MergeImzInsideBarTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "out.bar", + Method = "binarc", + Source = new List + { + new AssetFile + { + Name = "test", + Type = "imgz", + Method = "imgz", + Source = new List + { + new AssetFile + { + Name = "test.imd", + Index = 1 + } + }, + } + } + } + } + }; + + var tmpImd = Imgd.Create(new System.Drawing.Size(16, 16), PixelFormat.Indexed4, new byte[16 * 16 / 2], new byte[4], false); + var patchImd = Imgd.Create(new System.Drawing.Size(32, 16), PixelFormat.Indexed4, new byte[32 * 16 / 2], new byte[4], false); + CreateFile(AssetsInputDir, "out.bar").Using(x => + { + using var memoryStream = new MemoryStream(); + Imgz.Write(memoryStream, new Imgd[] + { + tmpImd, + tmpImd, + tmpImd, + }); + + Bar.Write(x, new Bar + { + new Bar.Entry + { + Name = "test", + Type = Bar.EntryType.Imgz, + Stream = memoryStream + } + }); + }); + CreateFile(ModInputDir, "test.imd").Using(patchImd.Write); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "out.bar"); + AssertBarFile("test", x => + { + var images = Imgz.Read(x.Stream).ToList(); + Assert.Equal(3, images.Count); + Assert.Equal(16, images[0].Size.Width); + Assert.Equal(32, images[1].Size.Width); + Assert.Equal(16, images[2].Size.Width); + }, ModOutputDir, "out.bar"); + } + + [Fact] + public void MergeKh2MsgTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "msg/us/sys.msg", + Method = "kh2msg", + Source = new List + { + new AssetFile + { + Name = "sys.yml", + Language = "en", + } + } + }, + new AssetFile + { + Name = "msg/it/sys.msg", + Method = "kh2msg", + Source = new List + { + new AssetFile + { + Name = "sys.yml", + Language = "it", + } + } + }, + new AssetFile + { + Name = "msg/jp/sys.msg", + Method = "kh2msg", + Source = new List + { + new AssetFile + { + Name = "sys.yml", + Language = "jp", + } + } + } + } + }; + + Directory.CreateDirectory(Path.Combine(AssetsInputDir, "msg/us/")); + File.Create(Path.Combine(AssetsInputDir, "msg/us/sys.msg")).Using(stream => + { + Msg.Write(stream, new List + { + new Msg.Entry + { + Data = new byte[] { 1, 2, 3, 0 }, + Id = 123 + } + }); + }); + File.Create(Path.Combine(ModInputDir, "sys.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- id: 456"); + writer.WriteLine(" en: English"); + writer.WriteLine(" it: Italiano"); + writer.WriteLine(" jp: テスト"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "msg/jp/sys.msg"); + File.OpenRead(Path.Combine(ModOutputDir, "msg/jp/sys.msg")).Using(stream => + { + var msg = Msg.Read(stream); + Assert.Single(msg); + Assert.Equal(456, msg[0].Id); + Assert.Equal("テスト", Encoders.JapaneseSystem.Decode(msg[0].Data).First().Text); + }); + + AssertFileExists(ModOutputDir, "msg/us/sys.msg"); + File.OpenRead(Path.Combine(ModOutputDir, "msg/us/sys.msg")).Using(stream => + { + var msg = Msg.Read(stream); + Assert.Equal(2, msg.Count); + Assert.Equal(123, msg[0].Id); + Assert.Equal(456, msg[1].Id); + Assert.Equal("English", Encoders.InternationalSystem.Decode(msg[1].Data).First().Text); + }); + + AssertFileExists(ModOutputDir, "msg/it/sys.msg"); + File.OpenRead(Path.Combine(ModOutputDir, "msg/it/sys.msg")).Using(stream => + { + var msg = Msg.Read(stream); + Assert.Single(msg); + Assert.Equal(456, msg[0].Id); + Assert.Equal("Italiano", Encoders.InternationalSystem.Decode(msg[0].Data).First().Text); + }); + } + + [Fact] + public void MergeKh2AreaDataScriptTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "map.script", + Method = "areadatascript", + Source = new List + { + new AssetFile + { + Name = "map.txt", + } + } + }, + } + }; + + File.Create(Path.Combine(AssetsInputDir, "map.script")).Using(stream => + { + var compiledProgram = Kh2.Ard.AreaDataScript.Compile("Program 1\nSpawn \"1111\""); + Kh2.Ard.AreaDataScript.Write(stream, compiledProgram); + }); + File.Create(Path.Combine(ModInputDir, "map.txt")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("Program 2"); + writer.WriteLine("Spawn \"2222\""); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "map.script"); + File.OpenRead(Path.Combine(ModOutputDir, "map.script")).Using(stream => + { + var scripts = Kh2.Ard.AreaDataScript.Read(stream); + var decompiled = Kh2.Ard.AreaDataScript.Decompile(scripts); + decompiled.Contains("Program 1"); + decompiled.Contains("Spawn \"1111\""); + decompiled.Contains("Program 2"); + decompiled.Contains("Spawn \"2222\""); + }); + } + + [Fact] + public void PatchKh2BdscriptTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "aaa", + Method = "bdscript", + Source = new List + { + new AssetFile + { + Name = "test.bdscript", + } + } + }, + } + }; + File.Create(Path.Combine(ModInputDir, "test.bdscript")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("---"); + writer.WriteLine("WorkSize: 64"); + writer.WriteLine("StackSize: 64"); + writer.WriteLine("TempSize: 64"); + writer.WriteLine("Triggers:"); + writer.WriteLine("- Key: 0"); + writer.WriteLine(" Addr: TR0"); + writer.WriteLine("Name: aaa"); + writer.WriteLine("---"); + writer.WriteLine(" section .text"); + writer.WriteLine("TR0:"); + writer.WriteLine(" ret"); + writer.WriteLine("DUMMY:"); + writer.WriteLine(" ret"); + writer.Flush(); + }); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "aaa"); + + var bdxStream = new MemoryStream(File.ReadAllBytes(Path.Combine(ModOutputDir, "aaa"))); + var decoder = new BdxDecoder(bdxStream); + var script = BdxDecoder.TextFormatter.Format(decoder); + + var lines = script.Split("\r\n"); + + Assert.Equal("WorkSize: 64", lines[1]); + Assert.Equal("Name: aaa", lines[7]); + + } + + [Fact] + public void PatchKh2SpawnPointTest() + { + + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "map.script", + Method = "areadataspawn", + Source = new List + { + new AssetFile + { + Name = "map.yaml", + } + } + }, + } + }; + + File.Create(Path.Combine(AssetsInputDir, "map.script")).Using(stream => + { + var spawnPoint = new List(); + + Kh2.Ard.SpawnPoint.Write(stream, spawnPoint); + }); + File.Create(Path.Combine(ModInputDir, "map.yaml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Type: 2"); + writer.WriteLine(" Flag: 1"); + writer.Flush(); + }); + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "map.script"); + var content = File.ReadAllText(Path.Combine(ModOutputDir, "map.script")); + var scripts = new Deserializer().Deserialize> (content); + + Assert.Equal(2, scripts[0].Type); + Assert.Equal(1, scripts[0].Flag); + + } + + + public void ListPatchTrsrTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "trsr", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "TrsrList.yml", + Type = "trsr" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var trsrEntry = new List() + { + new Kh2.SystemData.Trsr + { + Id = 1, + ItemId = 10 + } + }; + using var trsrStream = new MemoryStream(); + Kh2.SystemData.Trsr.Write(trsrStream, trsrEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "trsr", + Type = Bar.EntryType.List, + Stream = trsrStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "TrsrList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("1:"); + writer.WriteLine(" ItemId: 200"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var trsrStream = Kh2.SystemData.Trsr.Read(binarc[0].Stream); + Assert.Equal(200, trsrStream[0].ItemId); + }); + + } + + [Fact] + public void ListPatchCmdTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "cmd", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "CmdList.yml", + Type = "cmd" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var cmdEntry = new List() + { + new Kh2.SystemData.Cmd + { + Id = 1, + Execute = 3, + Argument = 3, + SubMenu = 1, + } + }; + using var cmdStream = new MemoryStream(); + Kh2.SystemData.Cmd.Write(cmdStream, cmdEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "cmd", + Type = Bar.EntryType.List, + Stream = cmdStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "CmdList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Id: 1"); + writer.WriteLine(" Execute: 3"); + writer.WriteLine(" Argument: 3"); + writer.WriteLine(" SubMenu: 1"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var cmdStream = Kh2.SystemData.Cmd.Read(binarc[0].Stream); + Assert.Equal(1, cmdStream[0].Id); + Assert.Equal(3, cmdStream[0].Execute); + Assert.Equal(3, cmdStream[0].Argument); + Assert.Equal(1, cmdStream[0].SubMenu); + }); + + } + + [Fact] + public void ListPatchItemTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "item", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "ItemList.yml", + Type = "item" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var itemEntry = new List() + { + new Kh2.SystemData.Item + { + Items = new List() + { + new Kh2.SystemData.Item.Entry() + { + Id = 1, + ShopBuy = 10 + } + }, + Stats = new List() + { + new Kh2.SystemData.Item.Stat() + { + Id = 10, + Ability = 15 + } + } + + } + }; + using var itemStream = new MemoryStream(); + itemEntry[0].Write(itemStream); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "item", + Type = Bar.EntryType.List, + Stream = itemStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "ItemList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("Items:"); + writer.WriteLine("- Id: 1"); + writer.WriteLine(" ShopBuy: 200"); + writer.WriteLine("Stats:"); + writer.WriteLine("- Id: 10"); + writer.WriteLine(" Ability: 150"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var itemStream = Kh2.SystemData.Item.Read(binarc[0].Stream); + Assert.Equal(200, itemStream.Items[0].ShopBuy); + Assert.Equal(150, itemStream.Stats[0].Ability); + }); + + } + + [Fact] + public void ListPatchSkltTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "sklt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "SkltList.yml", + Type = "sklt" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var skltEntry = new List() + { + new Kh2.SystemData.Sklt + { + CharacterId = 1, + Bone1 = 178, + Bone2 = 86 + } + }; + + using var skltStream = new MemoryStream(); + Kh2.SystemData.Sklt.Write(skltStream, skltEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "sklt", + Type = Bar.EntryType.List, + Stream = skltStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "SkltList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- CharacterId: 1"); + writer.WriteLine(" Bone1: 178"); + writer.WriteLine(" Bone2: 86"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var skltStream = Kh2.SystemData.Sklt.Read(binarc[0].Stream); + Assert.Equal(1U, skltStream[0].CharacterId); + Assert.Equal(178, skltStream[0].Bone1); + Assert.Equal(86, skltStream[0].Bone2); + }); + } + + [Fact] //Fixed, needed to initialize the BGMSet & Reserved bytes. + public void ListPatchArifTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bin", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "arif", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "ArifList.yml", + Type = "arif" + } + } + } + } + } + } + }; + + using (var stream = File.Create(Path.Combine(AssetsInputDir, "03system.bin"))) + { + var arifEntry = new Kh2.SystemData.Arif + { + Flags = Kh2.SystemData.Arif.ArifFlags.IsKnownArea, + Reverb = 0, + SoundEffectBank1 = 13, + SoundEffectBank2 = 0, + Bgms = Enumerable.Range(0, 8).Select(_ => new Kh2.SystemData.BgmSet { BgmField = 0, BgmBattle = 0 }).ToArray(), + Voice = 0, + NavigationMapItem = 0, + Command = 0, + Reserved = new byte[11] + }; + + using (var arifStream = new MemoryStream()) + { + Kh2.SystemData.Arif.Write(arifStream, new List> { new List { arifEntry } }); + Bar.Write(stream, new Bar + { + new Bar.Entry() + { + Name = "arif", + Type = Bar.EntryType.List, + Stream = arifStream + } + }); + } + } + + File.Create(Path.Combine(ModInputDir, "ArifList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("EndOfSea:"); + writer.WriteLine(" 1:"); + writer.WriteLine(" SoundEffectBank1: 13"); + writer.WriteLine(" Voice: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bin")).Using(stream => + { + var binarc = Bar.Read(stream); + var arifList = Kh2.SystemData.Arif.Read(binarc[0].Stream); + var arifEntry = arifList[0][0]; + Assert.Equal(13, arifEntry.SoundEffectBank1); + Assert.Equal(0, arifEntry.Voice); + }); + } + + [Fact] + public void ListPatchMemtTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "memt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "MemtList.yml", + Type = "memt" + } + } + } + } + } + } + }; + + // Create the 03system.bar file with initial data + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var memtEntries = new List() + { + new Kh2.SystemData.Memt.EntryFinalMix + { + WorldId = 1, + CheckStoryFlag = 1, + CheckStoryFlagNegation = 0, + Unk06 = 0, + Unk08 = 0, + Unk0A = 0, + Unk0C = 0, + Unk0E = 0, + Members = new short[18] + } + }; + + var memberIndices = new Kh2.SystemData.Memt.MemberIndices[7]; // Ensure there are 7 MemberIndices + for (int i = 0; i < memberIndices.Length; i++) + { + memberIndices[i] = new Kh2.SystemData.Memt.MemberIndices + { + Player = 0, + Friend1 = 0, + Friend2 = 0, + FriendWorld = 0 + }; + } + + using var memtStream = new MemoryStream(); + var memt = new Kh2.SystemData.Memt(); + memt.Entries.AddRange(memtEntries.Cast()); + + Kh2.SystemData.Memt.Write(memtStream, memt); + memtStream.Seek(0, SeekOrigin.Begin); + + Bar.Write(stream, new Bar() + { + new Bar.Entry() + { + Name = "memt", + Type = Bar.EntryType.List, + Stream = memtStream + } + }); + }); + + // Create the MemtList.yml patch file + File.Create(Path.Combine(ModInputDir, "MemtList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("MemtEntries:"); + writer.WriteLine(" - Index: 0"); + writer.WriteLine(" WorldId: 2"); + writer.WriteLine(" CheckStoryFlag: 3"); + writer.WriteLine(" CheckStoryFlagNegation: 4"); + writer.WriteLine(" Unk06: 5"); + writer.WriteLine(" Unk08: 6"); + writer.WriteLine(" Unk0A: 7"); + writer.WriteLine(" Unk0C: 8"); + writer.WriteLine(" Unk0E: 9"); + writer.WriteLine(" Members: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]"); + writer.WriteLine("MemberIndices:"); + writer.WriteLine(" - Index: 0"); + writer.WriteLine(" Player: 0"); + writer.WriteLine(" Friend1: 0"); + writer.WriteLine(" Friend2: 0"); + writer.WriteLine(" FriendWorld: 0"); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + // Verify the patched data + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var memtStream = binarc[0].Stream; + memtStream.Seek(0, SeekOrigin.Begin); + + var memt = Kh2.SystemData.Memt.Read(memtStream); + + var memtEntry = memt.Entries.Cast().First(); + Assert.Equal((short)2, memtEntry.WorldId); + Assert.Equal((short)3, memtEntry.CheckStoryFlag); + Assert.Equal((short)4, memtEntry.CheckStoryFlagNegation); + Assert.Equal((short)5, memtEntry.Unk06); + Assert.Equal((short)6, memtEntry.Unk08); + Assert.Equal((short)7, memtEntry.Unk0A); + Assert.Equal((short)8, memtEntry.Unk0C); + Assert.Equal((short)9, memtEntry.Unk0E); + Assert.Equal(new short[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 }, memtEntry.Members); + + // Check MemberIndices deserialization + var memberIndices = memt.MemberIndexCollection.First(); + Assert.Equal((byte)0, memberIndices.Player); + Assert.Equal((byte)0, memberIndices.Friend1); + Assert.Equal((byte)0, memberIndices.Friend2); + Assert.Equal((byte)0, memberIndices.FriendWorld); + }); + } + + + + [Fact] + public void ListPatchFmabTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "03system.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "pref", + Method = "binarc", + Type = "Binary", + Source = new List() + { + new AssetFile() + { + Name = "fmab", + Method = "listpatch", + Type = "list", + Source = new List() + { + new AssetFile() + { + Name = "FmabList.yml", + Type = "fmab" + } + } + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "03system.bar")).Using(stream => + { + var FmabEntry = new List() + { + new Kh2.SystemData.Fmab + { + HighJumpHeight = 178, + AirDodgeHeight = 86 + } + }; + + using var fmabStream = new MemoryStream(); + Kh2.SystemData.Fmab.Write(fmabStream, FmabEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "fmab", + Type = Bar.EntryType.List, + Stream = fmabStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "FmabList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- HighJumpHeight: 178"); + writer.WriteLine(" AirDodgeHeight: 86"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "03system.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "03system.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var fmabStream = Kh2.SystemData.Fmab.Read(binarc[0].Stream); + Assert.Equal(178, fmabStream[0].HighJumpHeight); + Assert.Equal(86, fmabStream[0].AirDodgeHeight); + }); + } + + [Fact] + public void ListPatchFmlvTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "fmlv", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "FmlvList.yml", + Type = "fmlv" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var fmlvEntry = new List() + { + new Kh2.Battle.Fmlv + { + FormId = 1, + FormLevel = 1, + Exp = 100, + Ability = 200 + }, + new Kh2.Battle.Fmlv + { + FormId = 1, + FormLevel = 2, + Exp = 100, + Ability = 200 + }, + new Kh2.Battle.Fmlv + { + FormId = 2, + FormLevel = 1, + Exp = 100, + Ability = 200 + }, + }; + + using var fmlvStream = new MemoryStream(); + Kh2.Battle.Fmlv.Write(fmlvStream, fmlvEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "fmlv", + Type = Bar.EntryType.List, + Stream = fmlvStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "FmlvList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + serializer.Serialize(writer, new Dictionary + { + ["Valor"] = new[] + { + new FmlvDTO + { + FormLevel = 1, + Experience = 5, + Ability = 127 + } + } + }); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var fmlv = Kh2.Battle.Fmlv.Read(binarc[0].Stream); + + Assert.Equal(3, fmlv.Count); + Assert.Equal(1, fmlv[0].FormId); + Assert.Equal(1, fmlv[0].FormLevel); + Assert.Equal(5, fmlv[0].Exp); + Assert.Equal(127, fmlv[0].Ability); + + Assert.Equal(1, fmlv[1].FormId); + Assert.Equal(2, fmlv[1].FormLevel); + + Assert.Equal(2, fmlv[2].FormId); + Assert.Equal(1, fmlv[2].FormLevel); + }); + } + + [Fact] + public void ListPatchBonsTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "bons", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "BonsList.yml", + Type = "bons" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var bonsEntry = new List() + { + new Kh2.Battle.Bons + { + CharacterId = 1, + RewardId = 15, + BonusItem1 = 10 + }, + new Kh2.Battle.Bons + { + CharacterId = 2, + RewardId = 15, + BonusItem1 = 5 + } + }; + using var bonsStream = new MemoryStream(); + Kh2.Battle.Bons.Write(bonsStream, bonsEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "bons", + Type = Bar.EntryType.List, + Stream = bonsStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "BonsList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("15:"); + writer.WriteLine(" Sora:"); + writer.WriteLine(" BonusItem1: 200"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var bonsStream = Kh2.Battle.Bons.Read(binarc[0].Stream); + Assert.Equal(200, bonsStream[0].BonusItem1); + Assert.Equal(5, bonsStream[1].BonusItem1); + }); + + } + + [Fact] + public void ListPatchLvupTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bin", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "lvup", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LvupList.yml", + Type = "lvup" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bin")).Using(stream => + { + var lvupEntry = new Kh2.Battle.Lvup + { + Count = 13, + Unknown08 = new byte[0x38], + Characters = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + }, + new Kh2.Battle.Lvup.PlayableCharacter() + { + NumLevels = 1, + Levels = new List() + { + new Kh2.Battle.Lvup.PlayableCharacter.Level() + { + Exp = 50 + } + } + } + + } + }; + using var lvupStream = new MemoryStream(); + lvupEntry.Write(lvupStream); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "lvup", + Type = Bar.EntryType.List, + Stream = lvupStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "LvupList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("Sora:"); + writer.WriteLine(" 1:"); + writer.WriteLine(" Exp: 500"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bin")).Using(stream => + { + var binarc = Bar.Read(stream); + var lvupStream = Kh2.Battle.Lvup.Read(binarc[0].Stream); + Assert.Equal(500, lvupStream.Characters[0].Levels[0].Exp); + }); + + } + + [Fact] + void ListPatchAtkpTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "atkp", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "AtkpList.yml", + Type = "atkp" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var atkpEntry = new List() + { + new Kh2.Battle.Atkp + { + Id = 0, + SubId = 3, + Type = Kh2.Battle.Atkp.AttackType.PierceArmor, + CriticalAdjust = 0, + Power = 25, + Team = 0, + Element = 0, + EnemyReaction = 0, + EffectOnHit = 2, + KnockbackStrength1 = 32767, + KnockbackStrength2 = 0, + Unknown = 0, + Flags = Kh2.Battle.Atkp.AttackFlags.BGHit, + RefactSelf = Kh2.Battle.Atkp.Refact.Reflect, + RefactOther = Kh2.Battle.Atkp.Refact.Reflect, + ReflectedMotion = 0, + ReflectHitBack = 0, + ReflectAction = 0, + ReflectHitSound = 0, + ReflectRC = 0, + ReflectRange = 0, + ReflectAngle = 0, + DamageEffect = 0, + Switch = 1, + Interval = 1, + FloorCheck = 1, + DriveDrain = 1, + RevengeDamage = 1, + AttackTrReaction = Kh2.Battle.Atkp.TrReaction.Charge, + ComboGroup = 1, + RandomEffect = 1, + Kind = Kh2.Battle.Atkp.AttackKind.ComboFinisher, + HpDrain = 15 + } + }; + + using var atkpStream = new MemoryStream(); + Kh2.Battle.Atkp.Write(atkpStream, atkpEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "atkp", + Type = Bar.EntryType.List, + Stream = atkpStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "AtkpList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Id: 0"); + writer.WriteLine(" SubId: 3"); + writer.WriteLine(" Type: PierceArmor"); + writer.WriteLine(" CriticalAdjust: 0"); + writer.WriteLine(" Power: 25"); + writer.WriteLine(" Team: 0"); + writer.WriteLine(" Element: 0"); + writer.WriteLine(" EnemyReaction: 0"); + writer.WriteLine(" EffectOnHit: 2"); + writer.WriteLine(" KnockbackStrength1: 32767"); + writer.WriteLine(" KnockbackStrength2: 0"); + writer.WriteLine(" Unknown: 0"); + writer.WriteLine(" Flags: BGHit"); + writer.WriteLine(" RefactSelf: 0"); + writer.WriteLine(" RefactOther: 0"); + writer.WriteLine(" ReflectedMotion: 0"); + writer.WriteLine(" ReflectHitBack: 0"); + writer.WriteLine(" ReflectAction: 0"); + writer.WriteLine(" ReflectHitSound: 0"); + writer.WriteLine(" ReflectRC: 0"); + writer.WriteLine(" ReflectRange: 0"); + writer.WriteLine(" ReflectAngle: 0"); + writer.WriteLine(" DamageEffect: 0"); + writer.WriteLine(" Switch: 1"); + writer.WriteLine(" Interval: 1"); + writer.WriteLine(" FloorCheck: 1"); + writer.WriteLine(" DriveDrain: 1"); + writer.WriteLine(" RevengeDamage: 1"); + writer.WriteLine(" AttackTrReaction: 1"); + writer.WriteLine(" ComboGroup: 1"); + writer.WriteLine(" RandomEffect: 1"); + writer.WriteLine(" Kind: ComboFinisher"); + writer.WriteLine(" HpDrain: 15"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var atkpStream = Kh2.Battle.Atkp.Read(binarc[0].Stream); + Assert.Equal(0, atkpStream[0].Id); + Assert.Equal(3, atkpStream[0].SubId); + Assert.Equal(Kh2.Battle.Atkp.AttackType.PierceArmor, atkpStream[0].Type); + Assert.Equal(0, atkpStream[0].CriticalAdjust); + Assert.Equal(25, atkpStream[0].Power); + Assert.Equal(0, atkpStream[0].Team); + Assert.Equal(0, atkpStream[0].Element); + Assert.Equal(0, atkpStream[0].EnemyReaction); + Assert.Equal(2, atkpStream[0].EffectOnHit); + Assert.Equal(32767, atkpStream[0].KnockbackStrength1); + Assert.Equal(0, atkpStream[0].KnockbackStrength2); + Assert.Equal(0000, atkpStream[0].Unknown); + Assert.Equal(Kh2.Battle.Atkp.AttackFlags.BGHit, atkpStream[0].Flags); + Assert.Equal(Kh2.Battle.Atkp.Refact.Reflect, atkpStream[0].RefactSelf); + Assert.Equal(Kh2.Battle.Atkp.Refact.Reflect, atkpStream[0].RefactOther); + Assert.Equal(0, atkpStream[0].ReflectedMotion); + Assert.Equal(0, atkpStream[0].ReflectHitBack); + Assert.Equal(0, atkpStream[0].ReflectAction); + Assert.Equal(0, atkpStream[0].ReflectHitSound); + Assert.Equal(0, atkpStream[0].ReflectRC); + Assert.Equal(0, atkpStream[0].ReflectRange); + Assert.Equal(0, atkpStream[0].ReflectAngle); + Assert.Equal(0, atkpStream[0].DamageEffect); + Assert.Equal(1, atkpStream[0].Switch); + Assert.Equal(1, atkpStream[0].Interval); + Assert.Equal(1, atkpStream[0].FloorCheck); + Assert.Equal(1, atkpStream[0].DriveDrain); + Assert.Equal(1, atkpStream[0].RevengeDamage); + Assert.Equal(Kh2.Battle.Atkp.TrReaction.Charge, atkpStream[0].AttackTrReaction); + Assert.Equal(1, atkpStream[0].ComboGroup); + Assert.Equal(1, atkpStream[0].RandomEffect); + Assert.Equal(Kh2.Battle.Atkp.AttackKind.ComboFinisher, atkpStream[0].Kind); + Assert.Equal(15, atkpStream[0].HpDrain); + }); + } + + [Fact] + public void ListPatchPrztTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "przt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "PrztList.yml", + Type = "przt" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var prztEntry = new List() + { + new Kh2.Battle.Przt + { + Id = 1, + SmallHpOrbs = 0, + BigHpOrbs = 1 + } + }; + + using var prztStream = new MemoryStream(); + Kh2.Battle.Przt.Write(prztStream, prztEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "przt", + Type = Bar.EntryType.List, + Stream = prztStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "PrztList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedPrzt = new List{ + new Kh2.Battle.Przt + { + Id = 1, + SmallHpOrbs = 0, + BigHpOrbs = 1 + } + }; + writer.Write(serializer.Serialize(moddedPrzt)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var przt = Kh2.Battle.Przt.Read(binarc[0].Stream); + + Assert.Equal(1, przt[0].BigHpOrbs); + }); + } + + + [Fact] + public void ListPatchObjEntryTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00objentry.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "ObjList.yml", + Type = "objentry", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00objentry.bin")).Using(stream => + { + var objEntry = new List() + { + new Kh2.Objentry + { + ObjectId = 1, + ModelName = "M_EX060", + AnimationName = "M_EX060.mset" + } + }; + Kh2.Objentry.Write(stream, objEntry); + }); + + File.Create(Path.Combine(ModInputDir, "ObjList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("1:"); + writer.WriteLine(" ObjectId: 1"); + writer.WriteLine(" ModelName: M_EX100"); + writer.WriteLine(" AnimationName: M_EX100.mset"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00objentry.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "00objentry.bin")).Using(stream => + { + var objStream = Kh2.Objentry.Read(stream); + Assert.Equal("M_EX100", objStream[0].ModelName); + Assert.Equal("M_EX100.mset", objStream[0].AnimationName); + }); + + } + + [Fact] + public void ListPatchPlrpTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "plrp", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "PlrpList.yml", + Type = "plrp" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var plrpEntry = new List() + { + new Kh2.Battle.Plrp + { + Id = 7, + Character = 1, + Ap = 2, + Items = new List(32), + Padding = new byte[52] + } + }; + + using var plrpStream = new MemoryStream(); + Kh2.Battle.Plrp.Write(plrpStream, plrpEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "plrp", + Type = Bar.EntryType.List, + Stream = plrpStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "PlrpList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedPlrp = new List{ + new Kh2.Battle.Plrp + { + Id = 7, + Character = 1, + Ap = 200, + Items = new List(32), + Padding = new byte[52] + } + }; + writer.Write(serializer.Serialize(moddedPlrp)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var plrp = Kh2.Battle.Plrp.Read(binarc[0].Stream); + + Assert.Equal(200, plrp[0].Ap); + }); + } + + [Fact] + public void ListPatchEnmpTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "enmp", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "EnmpList.yml", + Type = "enmp" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var enmpEntry = new List() + { + new Kh2.Battle.Enmp + { + Id = 7, + Level = 1, + Health = new short[32], + MaxDamage = 1, + MinDamage = 1, + PhysicalWeakness = 1, + FireWeakness = 1, + IceWeakness = 1, + ThunderWeakness = 1, + DarkWeakness = 1, + LightWeakness = 1, + GeneralWeakness = 1, + Experience = 1, + Prize = 1, + BonusLevel = 1 + } + }; + + using var enmpStream = new MemoryStream(); + Kh2.Battle.Enmp.Write(enmpStream, enmpEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "enmp", + Type = Bar.EntryType.List, + Stream = enmpStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "EnmpList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedEnmp = new List{ + new Kh2.Battle.Enmp + { + Id = 7, + Level = 1, + Health = new short[32], + MaxDamage = 1, + MinDamage = 1, + PhysicalWeakness = 1, + FireWeakness = 1, + IceWeakness = 1, + ThunderWeakness = 1, + DarkWeakness = 1, + LightWeakness = 1, + GeneralWeakness = 1, + Experience = 1, + Prize = 1, + BonusLevel = 1 + } + }; + writer.Write(serializer.Serialize(moddedEnmp)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var enmp = Kh2.Battle.Enmp.Read(binarc[0].Stream); + + Assert.Equal(1, enmp[0].Level); + }); + } + + [Fact] + public void ListPatchMagcTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "magc", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "MagcList.yml", + Type = "magc" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var magcEntry = new List() + { + new Kh2.Battle.Magc + { + Id = 7, + Level = 3, + World = 1, + FileName = "magic/FIRE_7.mag" + } + }; + + using var magcStream = new MemoryStream(); + Kh2.Battle.Magc.Write(magcStream, magcEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "magc", + Type = Bar.EntryType.List, + Stream = magcStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "MagcList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedMagc = new List{ + new Kh2.Battle.Magc + { + Id = 7, + Level = 3, + World = 1, + FileName = "magic/FIRE_7.mag" + } + }; + writer.Write(serializer.Serialize(moddedMagc)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var magc = Kh2.Battle.Magc.Read(binarc[0].Stream); + + Assert.Equal(3, magc[0].Level); + }); + } + + [Fact] + public void ListPatchLimtTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "Limt", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LimtList.yml", + Type = "limt" + } + } + } + } + } + } + }; + + // Create the initial 00battle.bar file with Limt entry + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var limtEntry = new List() + { + new Kh2.Battle.Limt + { + Id = 0, + Character = Kh2.Battle.Limt.Characters.Auron, + Summon = Kh2.Battle.Limt.Characters.Sora, + Group = 0, + FileName = "auron.bar", + SpawnId = 0, + Command = 82, + Limit = 204, + World = 0, + Padding = new byte[18] + } + }; + + using var limtStream = new MemoryStream(); + Kh2.Battle.Limt.Write(limtStream, limtEntry); + limtStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "Limt", + Type = Bar.EntryType.List, + Stream = limtStream + } + }); + }); + + // Create the LimtList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "LimtList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedLimt = new List + { + new Kh2.Battle.Limt + { + Id = 0, + Character = Kh2.Battle.Limt.Characters.Auron, + Summon = Kh2.Battle.Limt.Characters.Sora, + Group = 0, + FileName = "auron.bar", + SpawnId = 0, + Command = 82, + Limit = 204, + World = 0, + Padding = new byte[18] + } + }; + writer.Write(serializer.Serialize(moddedLimt)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "00battle.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var limt = Kh2.Battle.Limt.Read(binarc[0].Stream); + + Assert.Equal(0, limt[0].Id); + Assert.Equal(Kh2.Battle.Limt.Characters.Auron, limt[0].Character); + Assert.Equal(Kh2.Battle.Limt.Characters.Sora, limt[0].Summon); + Assert.Equal(0, limt[0].Group); + Assert.Equal("auron.bar", limt[0].FileName.Trim()); + Assert.Equal(0u, limt[0].SpawnId); + Assert.Equal(82, limt[0].Command); + Assert.Equal(204, limt[0].Limit); + Assert.Equal(0, limt[0].World); + Assert.Equal(new byte[18], limt[0].Padding); + }); + } + + + [Fact] + public void ListPatchBtlvTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "btlv", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "BtlvList.yml", + Type = "btlv" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var btlvEntry = new List() + { + new Kh2.Battle.Btlv + { + Id = 0, + ProgressFlag = 3, + WorldZZ = 1, + Padding = new byte[5], + } + }; + + using var btlvStream = new MemoryStream(); + Kh2.Battle.Btlv.Write(btlvStream, btlvEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "btlv", + Type = Bar.EntryType.List, + Stream = btlvStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "BtlvList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var serializer = new Serializer(); + var moddedBtlv = new List{ + new Kh2.Battle.Btlv + { + Id = 0, + ProgressFlag = 3, + WorldZZ = 1, + Padding = new byte[5], + + } + }; + writer.Write(serializer.Serialize(moddedBtlv)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var btlv = Kh2.Battle.Btlv.Read(binarc[0].Stream); + + Assert.Equal(3, btlv[0].ProgressFlag); + }); + } + + [Fact] + public void ListPatchVtblTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "00battle.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "vtbl", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "VtblList.yml", + Type = "vtbl" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "00battle.bar")).Using(stream => + { + var vtblEntry = new List() + { + new Kh2.Battle.Vtbl + { + Id = 0, + CharacterId = 1, + Priority = 1, + Reserved = 0, + Voices = new List + { + new Kh2.Battle.Vtbl.Voice { VsbIndex = 0, Weight = 0 } + } + } + }; + + using var vtblStream = new MemoryStream(); + Kh2.Battle.Vtbl.Write(vtblStream, vtblEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "vtbl", + Type = Bar.EntryType.List, + Stream = vtblStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "VtblList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + var moddedVtbl = new List{ + new Kh2.Battle.Vtbl + { + Id = 0, + CharacterId = 1, + Priority = 1, + Reserved = 0, + Voices = new List + { + new Kh2.Battle.Vtbl.Voice { VsbIndex = 0, Weight = 0 } + } + } + }; + writer.Write(serializer.Serialize(moddedVtbl)); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "00battle.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "00battle.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var vtbl = Kh2.Battle.Vtbl.Read(binarc[0].Stream); + + Assert.Equal(1, vtbl[0].Priority); + }); + } + + + + [Fact] //Libretto test. + public void ListPatchLibrettoTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "libretto-ca.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "ca", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LibrettoList.yml", + Type = "libretto" + } + } + } + } + } + } + }; + + using (var stream = File.Create(Path.Combine(AssetsInputDir, "libretto-ca.bar"))) + { + var librettoEntry = new Kh2.Libretto + { + MagicCode = 0x03, // "LIBR" in ASCII + Count = 1, + Definitions = new List + { + new Kh2.Libretto.TalkMessageDefinition + { + TalkMessageId = 1, + Unknown = 0, + ContentPointer = 8 + 8 // MagicCode (4 bytes) + Count (4 bytes) + Definitions (8 bytes each) + } + }, + Contents = new List> + { + new List + { + new Kh2.Libretto.TalkMessageContent { Unknown1 = 0x02000200, TextId = 2500 } + } + } + }; + + using (var librettoStream = new MemoryStream()) + { + Kh2.Libretto.Write(librettoStream, librettoEntry); + Bar.Write(stream, new Bar + { + new Bar.Entry() + { + Name = "libretto", + Type = Bar.EntryType.List, + Stream = librettoStream + } + }); + } + } + + File.Create(Path.Combine(ModInputDir, "LibrettoList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- TalkMessageId: 1"); + writer.WriteLine(" Unknown: 0"); + writer.WriteLine(" Contents:"); + writer.WriteLine(" - Unknown1: 0x02000200"); + writer.WriteLine(" TextId: 2500"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "libretto-ca.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "libretto-ca.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var librettoList = Kh2.Libretto.Read(binarc[0].Stream); + var librettoEntry = librettoList.Contents[0][0]; + Assert.Equal(0x02000200u, librettoEntry.Unknown1); + Assert.Equal(2500u, librettoEntry.TextId); + }); + } + + [Fact] + public void ListPatchLocalsetTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "07localset.bin", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "loca", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "LocalsetList.yml", + Type = "localset" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "07localset.bin")).Using(stream => + { + var localEntry = new List() + { + new Kh2.Localset + { + ProgramId = 1300, + MapNumber = 6, + } + }; + + using var localStream = new MemoryStream(); + Kh2.Localset.Write(localStream, localEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "loca", + Type = Bar.EntryType.List, + Stream = localStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "LocalsetList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- ProgramId: 1300"); + writer.WriteLine(" MapNumber: 6"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "07localset.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "07localset.bin")).Using(stream => + { + var binarc = Bar.Read(stream); + var localStream = Kh2.Localset.Read(binarc[0].Stream); + Assert.Equal(1300u, localStream[0].ProgramId); + Assert.Equal(6u, localStream[0].MapNumber); + }); + } + + [Fact] + public void ListPatchJigsawTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "15jigsaw.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "JigsawList.yml", + Type = "jigsaw", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "15jigsaw.bin")).Using(stream => + { + var jigsawEntry = new List() + { + new Kh2.Jigsaw + { + Picture = Kh2.Jigsaw.PictureName.Duality, + Part = 2, + Text = 1500 + } + }; + Kh2.Jigsaw.Write(stream, jigsawEntry); + }); + + File.Create(Path.Combine(ModInputDir, "JigsawList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Picture: Duality"); + writer.WriteLine(" Part: 2"); + writer.WriteLine(" Text: 1500"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "15jigsaw.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "15jigsaw.bin")).Using(stream => + { + var jigsawStream = Kh2.Jigsaw.Read(stream); + Assert.Equal(2, jigsawStream[0].Part); + }); + + } + + [Fact] + public void ListPatchPlacesTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "place.bin", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "PlaceList.yml", + Type = "place", + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "place.bin")).Using(stream => + { + var placeEntry = new List() + { + new Kh2.Places + { + MessageId = 100, + Padding = 0, + } + }; + Kh2.Places.Write(stream, placeEntry); + }); + + File.Create(Path.Combine(ModInputDir, "PlaceList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- MessageId: 100"); + writer.WriteLine(" Padding: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "place.bin"); + + File.OpenRead(Path.Combine(ModOutputDir, "place.bin")).Using(stream => + { + var placesStream = Kh2.Places.Read(stream); + Assert.Equal(100, placesStream[0].MessageId); + }); + + } + + [Fact] + public void ListPatchSoundInfoTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "12soundinfo.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "zz", + Method = "listpatch", + Type = "List", + Source = new List() + { + new AssetFile() + { + Name = "SoundInfoList.yml", + Type = "soundinfo" + } + } + } + } + } + } + }; + File.Create(Path.Combine(AssetsInputDir, "12soundinfo.bar")).Using(stream => + { + var soundinfoEntry = new List() + { + new Kh2.Soundinfo + { + Reverb = -1, + Rate = 1, + } + }; + Kh2.Soundinfo.Write(stream, soundinfoEntry); + }); + + File.Create(Path.Combine(ModInputDir, "SoundInfoList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Reverb: -1"); + writer.WriteLine(" Rate: 1"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "12soundinfo.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "12soundinfo.bar")).Using(stream => + { + var soundinfoStream = Kh2.Soundinfo.Read(stream); + Assert.Equal(1, soundinfoStream[0].Rate); + }); + + } + + [Fact] + public void ListPatchMixdataReciTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "reci", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "ReciList.yml", + Type = "recipe" + } + } + } + } + } + } + }; + + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var recipeEntry = new List() + { + new Kh2.Mixdata.ReciLP + { + Id = 1, + Unlock = 0, + Rank = 0, + } + }; + using var recipeStream = new MemoryStream(); + Kh2.Mixdata.ReciLP.Write(recipeStream, recipeEntry); + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "reci", + Type = Bar.EntryType.List, + Stream = recipeStream + } + }); + }); + + File.Create(Path.Combine(ModInputDir, "ReciList.yml")).Using(stream => + { + var writer = new StreamWriter(stream); + writer.WriteLine("- Id: 1"); + writer.WriteLine(" Rank: 0"); + writer.Flush(); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, "mixdata.bar"); + + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var recipeStream = Kh2.Mixdata.ReciLP.Read(binarc[0].Stream); + Assert.Equal(1, recipeStream[0].Id); + Assert.Equal(0, recipeStream[0].Rank); + }); + + } + + [Fact] + public void ListPatchMixdataLeveLPTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "leve", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "LeveList.yml", + Type = "level" + } + } + } + } + } + } + }; + + // Create the initial mixdata.bar file with LeveLP entry + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var leveEntry = new List() + { + new Kh2.Mixdata.LeveLP + { + Title = 1, + Stat = 2, + Enable = 1, + Padding = 0, + Exp = 100 + } + }; + + using var leveStream = new MemoryStream(); + Kh2.Mixdata.LeveLP.Write(leveStream, leveEntry); + leveStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "leve", + Type = Bar.EntryType.List, + Stream = leveStream + } + }); + }); + + // Create the LeveList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "LeveList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedLeve = new List + { + new Kh2.Mixdata.LeveLP + { + Title = 1, + Stat = 2, + Enable = 1, + Padding = 0, + Exp = 100 + } + }; + writer.Write(serializer.Serialize(moddedLeve)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "mixdata.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var leveStream = Kh2.Mixdata.LeveLP.Read(binarc[0].Stream); + + Assert.Equal(1, leveStream[0].Title); + Assert.Equal(2, leveStream[0].Stat); + Assert.Equal(1, leveStream[0].Enable); + Assert.Equal(0, leveStream[0].Padding); + Assert.Equal(100, leveStream[0].Exp); + }); + } + + [Fact] + public void ListPatchMixdataCondLPTest() + { + var patcher = new PatcherProcessor(); + var serializer = new Serializer(); + var patch = new Metadata() + { + Assets = new List() + { + new AssetFile() + { + Name = "mixdata.bar", + Method = "binarc", + Source = new List() + { + new AssetFile() + { + Name = "cond", + Method = "synthpatch", + Type = "Synthesis", + Source = new List() + { + new AssetFile() + { + Name = "CondList.yml", + Type = "condition" + } + } + } + } + } + } + }; + + // Create the initial mixdata.bar file with CondLP entry + File.Create(Path.Combine(AssetsInputDir, "mixdata.bar")).Using(stream => + { + var condEntry = new List() + { + new Kh2.Mixdata.CondLP + { + TextId = 1, + Reward = 100, + Type = Kh2.Mixdata.CondLP.RewardType.Item, + MaterialType = 0, + MaterialRank = 1, + ItemCollect = Kh2.Mixdata.CondLP.CollectionType.Stack, + Count = 10, + ShopUnlock = 5 + } + }; + + using var condStream = new MemoryStream(); + Kh2.Mixdata.CondLP.Write(condStream, condEntry); + condStream.Position = 0; // Ensure stream position is reset before writing to Bar + Bar.Write(stream, new Bar() { + new Bar.Entry() + { + Name = "cond", + Type = Bar.EntryType.List, + Stream = condStream + } + }); + }); + + // Create the CondList.yml file using the serializer + File.Create(Path.Combine(ModInputDir, "CondList.yml")).Using(stream => + { + using var writer = new StreamWriter(stream); + var moddedCond = new List + { + new Kh2.Mixdata.CondLP + { + TextId = 1, + Reward = 100, + Type = Kh2.Mixdata.CondLP.RewardType.Item, + MaterialType = 0, + MaterialRank = 1, + ItemCollect = Kh2.Mixdata.CondLP.CollectionType.Stack, + Count = 10, + ShopUnlock = 5 + } + }; + writer.Write(serializer.Serialize(moddedCond)); + writer.Flush(); + }); + + // Apply the patch + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + // Verify the output file exists + AssertFileExists(ModOutputDir, "mixdata.bar"); + + // Read and validate the output file + File.OpenRead(Path.Combine(ModOutputDir, "mixdata.bar")).Using(stream => + { + var binarc = Bar.Read(stream); + var condStream = Kh2.Mixdata.CondLP.Read(binarc[0].Stream); + + Assert.Equal(1, condStream[0].TextId); + Assert.Equal(100, condStream[0].Reward); + Assert.Equal(Kh2.Mixdata.CondLP.RewardType.Item, condStream[0].Type); + Assert.Equal(0, condStream[0].MaterialType); + Assert.Equal(1, condStream[0].MaterialRank); + Assert.Equal(Kh2.Mixdata.CondLP.CollectionType.Stack, condStream[0].ItemCollect); + Assert.Equal(10, condStream[0].Count); + Assert.Equal(5, condStream[0].ShopUnlock); + }); + } + + + [Fact] + public void BbsArcCreateArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "newfile", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/newfile.bin" + } + } + } + } + } + } + }; + + CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("newfile", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void BbsArcAddToArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "newfile", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/newfile.bin" + } + } + } + } + } + } + }; + + CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => + { + Arc.Write(new List + { + new Arc.Entry + { + Name = "abcd", + Data = new byte[] {0, 1, 2, 3 } + } + }, x); + }); + + CreateFile(ModInputDir, "somedir/somearc/newfile.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("abcd", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 0, 1, 2, 3 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + AssertArcFile("newfile", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void BbsArcReplaceInArcTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List { + new AssetFile + { + Name = "somedir/somearc.arc", + Method = "bbsarc", + Source = new List { + new AssetFile + { + Name = "abcd", + Method = "copy", + Source = new List { + new AssetFile + { + Name = "somedir/somearc/abcd.bin" + } + } + } + } + } + } + }; + + CreateFile(AssetsInputDir, "somedir/somearc.arc").Using(x => + { + Arc.Write(new List + { + new Arc.Entry + { + Name = "abcd", + Data = new byte[] {0, 1, 2, 3} + } + }, x); + }); + + CreateFile(ModInputDir, "somedir/somearc/abcd.bin").Using(x => + { + x.Write(new byte[] { 4, 5, 6, 7 }); + }); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertArcFile("abcd", entry => + { + Assert.Equal(4, entry.Data.Length); + Assert.Equal(new byte[] { 4, 5, 6, 7 }, entry.Data); + }, ModOutputDir, patch.Assets[0].Name); + } + + [Fact] + public void ProcessMultipleTest() + { + var patcher = new PatcherProcessor(); + var patch = new Metadata + { + Assets = new List + { + new AssetFile + { + Name = "somedir/somefile.bar", + Method = "binarc", + Multi = new List + { + new Multi { Name = "somedir/another.bar" } + }, + Source = new List + { + new AssetFile + { + Name = "test", + Method = "imgd", + Type = "imgd", + Source = new List + { + new AssetFile + { + Name = "sample.png", + IsSwizzled = false + } + } + } + } + } + } + }; + + File.Copy("Imaging/res/png/32.png", Path.Combine(ModInputDir, "sample.png")); + + patcher.Patch(AssetsInputDir, ModOutputDir, patch, ModInputDir); + + AssertFileExists(ModOutputDir, patch.Assets[0].Name); + AssertBarFile("test", entry => + { + Assert.True(Imgd.IsValid(entry.Stream)); + }, ModOutputDir, patch.Assets[0].Name); + + AssertFileExists(ModOutputDir, patch.Assets[0].Multi[0].Name); + AssertBarFile("test", entry => + { + Assert.True(Imgd.IsValid(entry.Stream)); + }, ModOutputDir, patch.Assets[0].Multi[0].Name); + } + + + private static void AssertFileExists(params string[] paths) + { + var filePath = Path.Join(paths); + if (File.Exists(filePath) == false) + Assert.Fail($"File not found '{filePath}'"); + } + + private static void AssertBarFile(string name, Action assertion, params string[] paths) + { + var filePath = Path.Join(paths); + var entries = File.OpenRead(filePath).Using(x => + { + if (!Bar.IsValid(x)) + Assert.Fail($"Not a valid BinArc"); + return Bar.Read(x); + }); + + var entry = entries.SingleOrDefault(x => x.Name == name); + if (entry == null) + throw new XunitException($"Entry '{name}' not found"); + + assertion(entry); + } + + private static void AssertArcFile(string name, Action assertion, params string[] paths) + { + var filePath = Path.Join(paths); + var entries = File.OpenRead(filePath).Using(x => + { + if (!Arc.IsValid(x)) + Assert.Fail($"Not a valid Arc"); + return Arc.Read(x); + }); + + var entry = entries.SingleOrDefault(x => x.Name == name); + if (entry == null) + throw new XunitException($"Arc Entry '{name}' not found"); + + assertion(entry); + } + + private static Stream CreateFile(params string[] paths) + { + var filePath = Path.Join(paths); + var dirPath = Path.GetDirectoryName(filePath); + Directory.CreateDirectory(dirPath); + return File.Create(filePath); + } + } +} diff --git a/docs/tool/GUI.ModsManager/creatingMods.md b/docs/tool/GUI.ModsManager/creatingMods.md index 68e2d5f32..1261f95a5 100644 --- a/docs/tool/GUI.ModsManager/creatingMods.md +++ b/docs/tool/GUI.ModsManager/creatingMods.md @@ -19,6 +19,8 @@ This document will focus on teaching you how to create mods using the OpenKH Mod * [cmd](#cmd-source-example) * [item](#item-source-example) * [sklt](#sklt-source-example) + * [arif](#arif-source-example) + * [memt](#memt-source-example) * [enmp](#enmp-source-example) * [fmlv](#fmlv-source-example) * [lvup](#lvup-source-example) @@ -26,7 +28,15 @@ This document will focus on teaching you how to create mods using the OpenKH Mod * [atkp](#atkp-source-example) * [przt](#przt-source-example) * [magc](#magc-source-example) + * [limt](#limt-source-example) + * [vtbl](#vtbl-source-example) + * [btlv](#btlv-source-example) * [objentry](#objentry-source-example) + * [libretto](#libretto-source-example) + * [localset](#localset-source-example) + * [soundinfo](#soundinfo-source-example) + * [place](#place-source-example) + * [jigsaw](#jigsaw-source-example) * [bbsarc](#bbsarc-bbs) * [Example of a Fully Complete `mod.yml` File](#an-example-of-a-fully-complete-modyml-can-be-seen-below-and-the-full-source-of-the-mod-can-be-seen-here) * [Generating a Simple `mod.yml` for New Mod Authors](#generating-a-simple-modyml-for-new-mod-authors) @@ -272,6 +282,8 @@ Asset Example * `cmd` * `item` * `sklt` + * 'arif' + * 'memt' * `enmp` * `fmlv` * `lvup` @@ -279,7 +291,15 @@ Asset Example * `atkp` * `przt` * `magc` + * 'limt' + * 'vtbl' + * 'btlv' * `objentry` + * 'libretto' + * 'localset' + * 'soundinfo' + * 'place' + * 'jigsaw' Asset Example ``` @@ -294,12 +314,12 @@ Asset Example type: fmlv ``` -### `trsr` Source Example +`trsr` Source Example ``` 2: ItemId: 347 ``` -### `cmd` Source Example +`cmd` Source Example ``` - Id: 1 Execute: 3 @@ -326,7 +346,7 @@ Asset Example Group: 2 Reserve: 0 ``` -### `item` Source Example +`item` Source Example ``` Stats: - Ability: 412 @@ -360,14 +380,74 @@ Items: Icon1: 9 Icon2: 0 ``` -### `sklt` Source Example +`sklt` Source Example ``` - CharacterId: 1 Bone1: 178 Bone2: 86 ``` - -### `enmp` Source Example +`arif` Source Example +``` +End of Sea: #End of Sea. Names are taken from worlds.md + 2: + Flags: IsKnownArea, IndoorArea, Monochrome #Other acceptable flags are NoShadow and HasGlow. + Reverb: 10 + SoundEffectBank1: 20 + SoundEffectBank2: 30 + Bgms: + - BgmField: 2000 + BgmBattle: 2000 + - BgmField: 600 + BgmBattle: 600 + - BgmField: 1200 + BgmBattle: 1200 + - BgmField: 1200 + BgmBattle: 1200 + - BgmField: 1000 + BgmBattle: 1000 + - BgmField: 1000 + BgmBattle: 1000 + - BgmField: 1500 + BgmBattle: 1500 + - BgmField: 1500 + BgmBattle: 1500 + Voice: 40 + NavigationMapItem: 50 + Command: 60 + Reserved: [] +``` +`memt` Source Example +``` +MemtEntries: + - Index: 0 #Specify a memt index to edit. Use a new index to create a new MemtEntry. + WorldId: 2 + CheckStoryFlag: 3 + CheckStoryFlagNegation: 4 + Unk06: 5 + Unk08: 6 + Unk0A: 7 + Unk0C: 8 + Unk0E: 9 + Members: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] + - Index: 37 + WorldId: 2 + CheckStoryFlag: 3 + CheckStoryFlagNegation: 4 + Unk06: 5 + Unk08: 6 + Unk0A: 7 + Unk0C: 8 + Unk0E: 9 + Members: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] + +MemberIndices: + - Index: 0 + Player: 15 + Friend1: 20 + Friend2: 32 + FriendWorld: 42 +``` +`enmp` Source Example ``` - Id: 0 Level: 1 @@ -393,7 +473,7 @@ Items: BonusLevel: 1 ``` -### `fmlv` Source Example +`fmlv` Source Example ``` Final: - Ability: 578 @@ -403,7 +483,7 @@ Final: GrowthAbilityLevel: 1 ``` -### `lvup` Source Example +`lvup` Source Example ``` Sora: 2: @@ -420,7 +500,7 @@ Sora: SwordAbility: 577 ``` -### `bons` Source Example +`bons` Source Example ``` 2: Sora: @@ -438,7 +518,7 @@ Sora: Unknown0c: 0 ``` -### `atkp` Source Example +`atkp` Source Example ``` - Id: 0 #Hitbox 0 SubId: 3 @@ -475,7 +555,7 @@ Sora: HpDrain: 15 ``` -### `przt` Source Example +`przt` Source Example ``` - Id: 1 SmallHpOrbs: 0 @@ -495,7 +575,7 @@ Sora: Item3Percentage: 0 ``` -### `magc` Source Example +`magc` Source Example ``` - Id: 0 Level: 3 @@ -513,8 +593,75 @@ Sora: VoiceFinisher: 11 VoiceSelf: -1 ``` - -### `objentry` Source Example +`limt` Source Example +``` +- Id: 1 + Character: Sora + Summon: None + Group: 0 + FileName: TESTLIMIT + SpawnId: 0 + Command: 100 + Limit: 0 + World: 00 + Padding: [] +- Id: 30 + Character: Donald + Summon: Goofy + Group: 0 + FileName: TESTLIMIT + SpawnId: 0 + Command: 100 + Limit: 0 + World: 00 + Padding: [] +``` + +`vtbl` Source Example +``` +- Id: 26 + CharacterId: 1 + Priority: 01 + Voices: + - VsbIndex: 5 + Weight: 100 + - VsbIndex: -1 + Weight: 0 + - VsbIndex: -1 + Weight: 0 + - VsbIndex: -1 + Weight: 0 + - VsbIndex: 6 + Weight: 5 + Reserved: 0 +``` + +`btlv` Source Example +``` +- Id: 0 + ProgressFlag: 0x1099 + WorldZZ: 1 + WorldOfDarkness: 1 + TwilightTown: 1 + DestinyIslands: 1 + HollowBastion: 1 + BeastCastle: 1 + OlympusColiseum: 1 + Agrabah: 1 + LandOfDragons: 1 + HundredAcreWoods: 1 + PrideLands: 1 + Atlantica: 1 + DisneyCastle: 1 + TimelessRiver: 1 + HalloweenTown: 1 + PortRoyal: 1 + SpaceParanoids: 1 + TheWorldThatNeverWas: 1 + Padding: [] +``` + +`objentry` Source Example ``` 4: ObjectId: 4 @@ -540,6 +687,117 @@ Sora: SpawnObject4: 0 ``` +`libretto` Source Example +``` +- TalkMessageId: 752 #Id to update. + Unknown: 3 #Unknown to update. + Contents: #Contents to update. Will insert additional Contents as necessary. When no additional are detected, terminates w/ 0. + - Unknown1: 0x00010001 + TextId: 0x3DEB + - Unknown1: 0x00010001 + TextId: 0x183C +``` +`localset` Source Example +``` +- ProgramId: 999 + MapNumber: 25 +``` +`soundinfo` Source Example +``` +- Index: 0 #Specify an index to modify; otherwise if the index doesn't exist it will be created. + Reverb: -1 + Rate: 1 + EnvironmentWAV: 99 + EnvironmentSEB: 99 + EnvironmentNUMBER: 99 + FootstepWAV: 99 + FootstepSORA: 99 + FootstepDONALD: 99 + FootstepGOOFY: 99 + FootstepWORLDFRIEND: 99 + FootstepOTHER: 99 +``` +`place` Source Example +``` +- Index: 0 #Index should match the ID of the room in the world; i.e, Index 0 = al00 if you were modifying Agrabah. + MessageId: 1234 + Padding: 0 +``` +`jigsaw` Source Example +``` +- Picture: 2 + Part: 4 + Text: 1500 + World: 2 + Room: 1 + JigsawIdWorld: 99 + Unk07: 0 + Unk08: 0 +``` + +* `synthpatch` (KH2) - Modifies Mixdata.bar, a file used for various properties related to synthesis in KH2. + + * `recipe` + * `level` + * `condition` + + +Asset Example + +``` +- name: menu/us/mixdata.bar + method: binarc + source: + - name: reci + method: synthpatch + type: Synthesis + source: + - name: ReciList.yml + type: recipe +``` + +`recipe` Source Example +``` +- Id: 1 + Unlock: 0 + Rank: 5 + Item: 100 + UpgradedItem: 101 + Ingredient1: 200 + Ingredient1Amount: 1 + Ingredient2: 201 + Ingredient2Amount: 2 + Ingredient3: 202 + Ingredient3Amount: 3 + Ingredient4: 203 + Ingredient4Amount: 4 + Ingredient5: 204 + Ingredient5Amount: 5 + Ingredient6: 205 + Ingredient6Amount: 6 +``` + +`level` Source Example +``` +- Title: 48338 #TextID to use for Moogle Level "Title", pulls from Sys.Bar. + Stat: 48740 + Enable: -1 + Padding: 0 + Exp: 0 +``` + +`condition` Source Example +``` +- TextId: 151 + RewardId: 0 + Type: 5 + MaterialType: 100 + MaterialRank: 101 + ItemCollect: 200 + Count: 1 + ShopUnlock: 201 +``` + ### `bbsarc` (BBS) Allows you to add/patch files inside a bbs `.arc` container without having to `copy` the entire arc file into your mod. You can use any method to patch those files, although at time of writing the only one that works for BBS files (other than `bbsarc`) is `copy`. @@ -667,4 +925,4 @@ It is recommended to apply the following tags to the repository, in order to mak * `kh2` * `bbs` * `ddd` - * `recom` \ No newline at end of file + * `recom`