From efce00663aa883eb5baea35af85f95d86242a02b Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Mon, 14 Mar 2022 12:27:21 -0700 Subject: [PATCH 1/9] Move PubRecordProperty to EOLib.IO.Pub --- EOLib.IO/{ => Pub}/PubRecordProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename EOLib.IO/{ => Pub}/PubRecordProperty.cs (99%) diff --git a/EOLib.IO/PubRecordProperty.cs b/EOLib.IO/Pub/PubRecordProperty.cs similarity index 99% rename from EOLib.IO/PubRecordProperty.cs rename to EOLib.IO/Pub/PubRecordProperty.cs index 76f63a802..50ae2ca04 100644 --- a/EOLib.IO/PubRecordProperty.cs +++ b/EOLib.IO/Pub/PubRecordProperty.cs @@ -1,4 +1,4 @@ -namespace EOLib.IO +namespace EOLib.IO.Pub { /// /// Enum representing the different properties that exist within the pub records From 75e7f41844902a3a43e2641fa073f24d0e1e91d1 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 13:36:22 -0700 Subject: [PATCH 2/9] Make pub files/records immutable. Separate serialization of pub files/records into separate serializer objects. Use generic approach to data storage for pub records. EOLib.IO(.Test) builds and tests pass. --- EOLib.IO.Test/EIFRecordExtensionsTest.cs | 131 ++------ EOLib.IO.Test/Pub/BasePubFileTest.cs | 102 ------ EOLib.IO.Test/Pub/DummyFile.cs | 31 -- EOLib.IO.Test/Pub/DummyRecord.cs | 35 -- EOLib.IO.Test/Pub/ECFFileTest.cs | 110 ++---- EOLib.IO.Test/Pub/ECFRecordTest.cs | 185 ----------- EOLib.IO.Test/Pub/EIFFileTest.cs | 112 +++---- EOLib.IO.Test/Pub/EIFRecordTest.cs | 313 ------------------ EOLib.IO.Test/Pub/ENFFileTest.cs | 110 ++---- EOLib.IO.Test/Pub/ENFRecordTest.cs | 202 ----------- EOLib.IO.Test/Pub/ESFFileTest.cs | 110 ++---- EOLib.IO.Test/Pub/ESFRecordTest.cs | 215 ------------ EOLib.IO/Caster.cs | 37 +++ EOLib.IO/Extensions/EIFFileExtensions.cs | 8 +- EOLib.IO/Pub/BasePubFile.cs | 105 +++--- EOLib.IO/Pub/ECFFile.cs | 42 +-- EOLib.IO/Pub/ECFRecord.cs | 88 ++--- EOLib.IO/Pub/EIFFile.cs | 42 +-- EOLib.IO/Pub/EIFRecord.cs | 282 ++++------------ EOLib.IO/Pub/ENFFile.cs | 42 +-- EOLib.IO/Pub/ENFRecord.cs | 149 ++------- EOLib.IO/Pub/ESFFile.cs | 50 +-- EOLib.IO/Pub/ESFRecord.cs | 198 +++-------- EOLib.IO/Pub/IPubFile.cs | 15 +- EOLib.IO/Pub/IPubRecord.cs | 34 +- EOLib.IO/Pub/PubRecord.cs | 99 ++++++ EOLib.IO/Pub/PubRecordProperty.cs | 145 ++++++-- EOLib.IO/Pub/RecordData.cs | 46 +++ EOLib.IO/Pub/RecordDataAttribute.cs | 18 + EOLib.IO/Services/ClassFileLoadService.cs | 13 +- EOLib.IO/Services/IPubFileSaveService.cs | 3 +- EOLib.IO/Services/IPubLoadService.cs | 2 +- EOLib.IO/Services/ItemFileLoadService.cs | 13 +- EOLib.IO/Services/NPCFileLoadService.cs | 13 +- EOLib.IO/Services/PubFileSaveService.cs | 12 +- .../Serializers/IPubFileDeserializer.cs | 17 + .../Serializers/IPubRecordSerializer.cs | 12 + .../Services/Serializers/PubFileSerializer.cs | 99 ++++++ .../Serializers/PubRecordSerializer.cs | 78 +++++ EOLib.IO/Services/SpellFileLoadService.cs | 13 +- 40 files changed, 1034 insertions(+), 2297 deletions(-) delete mode 100644 EOLib.IO.Test/Pub/BasePubFileTest.cs delete mode 100644 EOLib.IO.Test/Pub/DummyFile.cs delete mode 100644 EOLib.IO.Test/Pub/DummyRecord.cs delete mode 100644 EOLib.IO.Test/Pub/ECFRecordTest.cs delete mode 100644 EOLib.IO.Test/Pub/EIFRecordTest.cs delete mode 100644 EOLib.IO.Test/Pub/ENFRecordTest.cs delete mode 100644 EOLib.IO.Test/Pub/ESFRecordTest.cs create mode 100644 EOLib.IO/Caster.cs create mode 100644 EOLib.IO/Pub/PubRecord.cs create mode 100644 EOLib.IO/Pub/RecordData.cs create mode 100644 EOLib.IO/Pub/RecordDataAttribute.cs create mode 100644 EOLib.IO/Services/Serializers/IPubFileDeserializer.cs create mode 100644 EOLib.IO/Services/Serializers/IPubRecordSerializer.cs create mode 100644 EOLib.IO/Services/Serializers/PubFileSerializer.cs create mode 100644 EOLib.IO/Services/Serializers/PubRecordSerializer.cs diff --git a/EOLib.IO.Test/EIFRecordExtensionsTest.cs b/EOLib.IO.Test/EIFRecordExtensionsTest.cs index f8990f935..bb14d36a8 100644 --- a/EOLib.IO.Test/EIFRecordExtensionsTest.cs +++ b/EOLib.IO.Test/EIFRecordExtensionsTest.cs @@ -8,100 +8,41 @@ namespace EOLib.IO.Test [TestFixture, ExcludeFromCodeCoverage] public class EIFRecordExtensionsTest { - [Test] - public void GetEquipLocation_Accessory_ReturnsAccessory() - { - Assert.AreEqual(EquipLocation.Accessory, new EIFRecord {Type = ItemType.Accessory}.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Armlet_ReturnsArmlet1() - { - Assert.AreEqual(EquipLocation.Armlet1, new EIFRecord { Type = ItemType.Armlet }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Armor_ReturnsArmor() - { - Assert.AreEqual(EquipLocation.Armor, new EIFRecord { Type = ItemType.Armor }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Belt_ReturnsBelt() - { - Assert.AreEqual(EquipLocation.Belt, new EIFRecord { Type = ItemType.Belt }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Boots_ReturnsBoots() - { - Assert.AreEqual(EquipLocation.Boots, new EIFRecord { Type = ItemType.Boots }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Bracer_ReturnsBracer() - { - Assert.AreEqual(EquipLocation.Bracer1, new EIFRecord { Type = ItemType.Bracer }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Gloves_ReturnsGloves() - { - Assert.AreEqual(EquipLocation.Gloves, new EIFRecord { Type = ItemType.Gloves }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Hat_ReturnsHat() - { - Assert.AreEqual(EquipLocation.Hat, new EIFRecord { Type = ItemType.Hat }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Necklace_ReturnsNecklace() - { - Assert.AreEqual(EquipLocation.Necklace, new EIFRecord { Type = ItemType.Necklace }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Ring_ReturnsRing1() - { - Assert.AreEqual(EquipLocation.Ring1, new EIFRecord { Type = ItemType.Ring }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Shield_ReturnsShield() - { - Assert.AreEqual(EquipLocation.Shield, new EIFRecord { Type = ItemType.Shield }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Weapon_ReturnsWeapon() - { - Assert.AreEqual(EquipLocation.Weapon, new EIFRecord { Type = ItemType.Weapon }.GetEquipLocation()); - } - - [Test] - public void GetEquipLocation_Unsupported_ReturnsPaperdollMax() - { - var unsupported = new[] - { - ItemType.Beer, - ItemType.CureCurse, - ItemType.EXPReward, - ItemType.EffectPotion, - ItemType.HairDye, - ItemType.Heal, - ItemType.Key, - ItemType.Money, - ItemType.SkillReward, - ItemType.StatReward, - ItemType.Static, - ItemType.Teleport, - ItemType.UnknownType1 - }; - - foreach (var type in unsupported) - Assert.AreEqual(EquipLocation.PAPERDOLL_MAX, new EIFRecord {Type = type}.GetEquipLocation()); - } + [TestCase(EquipLocation.Accessory, ItemType.Accessory)] + [TestCase(EquipLocation.Armlet1, ItemType.Armlet)] + [TestCase(EquipLocation.Armor, ItemType.Armor)] + [TestCase(EquipLocation.Belt, ItemType.Belt)] + [TestCase(EquipLocation.Boots, ItemType.Boots)] + [TestCase(EquipLocation.Bracer1, ItemType.Bracer)] + [TestCase(EquipLocation.Gloves, ItemType.Gloves)] + [TestCase(EquipLocation.Hat, ItemType.Hat)] + [TestCase(EquipLocation.Necklace, ItemType.Necklace)] + [TestCase(EquipLocation.Ring1, ItemType.Ring)] + [TestCase(EquipLocation.Shield, ItemType.Shield)] + [TestCase(EquipLocation.Weapon, ItemType.Weapon)] + public void GetEquipLocation_Matches_ItemType(EquipLocation equipLocation, ItemType itemType) + { + Assert.That(WithItemType(itemType).GetEquipLocation(), Is.EqualTo(equipLocation)); + } + + [TestCase(ItemType.Beer)] + [TestCase(ItemType.CureCurse)] + [TestCase(ItemType.EXPReward)] + [TestCase(ItemType.EffectPotion)] + [TestCase(ItemType.HairDye)] + [TestCase(ItemType.Heal)] + [TestCase(ItemType.Key)] + [TestCase(ItemType.Money)] + [TestCase(ItemType.SkillReward)] + [TestCase(ItemType.StatReward)] + [TestCase(ItemType.Static)] + [TestCase(ItemType.Teleport)] + [TestCase(ItemType.UnknownType1)] + public void GetEquipLocation_Unsupported_ReturnsPaperdollMax(ItemType type) + { + Assert.That(WithItemType(type).GetEquipLocation(), Is.EqualTo(EquipLocation.PAPERDOLL_MAX)); + } + + private static EIFRecord WithItemType(ItemType type) => (EIFRecord)new EIFRecord().WithProperty(PubRecordProperty.ItemType, (int)type); } } diff --git a/EOLib.IO.Test/Pub/BasePubFileTest.cs b/EOLib.IO.Test/Pub/BasePubFileTest.cs deleted file mode 100644 index 4b2647ece..000000000 --- a/EOLib.IO.Test/Pub/BasePubFileTest.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; - -namespace EOLib.IO.Test.Pub -{ - [TestFixture, ExcludeFromCodeCoverage] - public class BasePubFileTest - { - //This covers the BasePubFile abstract class. - private BasePubFile _baseFile; - - [SetUp] - public void SetUp() - { - _baseFile = new DummyFile(); - } - - [Test] - public void PubFile_HasExpectedChecksumAndLength() - { - Assert.AreEqual(0, _baseFile.CheckSum); - Assert.AreEqual(0, _baseFile.Length); - } - - [Test] - public void PubFile_WithOneItemRecord_HasExpectedLength() - { - var bytes = MakeDummyFile(new DummyRecord { ID = 1, Name = "TestItem" }); - - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - Assert.AreEqual(1, _baseFile.Length); - } - - [Test] - public void PubFile_Indexing_ReturnsNullWhenLessThan1() - { - var bytes = MakeDummyFile(new DummyRecord { ID = 1, Name = "TestItem" }, - new DummyRecord { ID = 2, Name = "Test2" }, - new DummyRecord { ID = 3, Name = "Test3" }, - new DummyRecord { ID = 4, Name = "Test4" }); - - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - Assert.AreEqual(4, _baseFile.Length); - Assert.IsNull(_baseFile[0]); - } - - [Test] - public void PubFile_Indexing_ReturnsNullWhenGreaterThanCount() - { - var bytes = MakeDummyFile(new DummyRecord { ID = 1, Name = "TestItem" }, - new DummyRecord { ID = 2, Name = "Test2" }); - - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - Assert.AreEqual(2, _baseFile.Length); - Assert.IsNull(_baseFile[3]); - } - - [Test] - public void PubFile_Indexing_ReturnsExpectedItemWhenRequestedByID() - { - var records = new[] - { - new DummyRecord {ID = 1, Name = "TestItem"}, - new DummyRecord {ID = 2, Name = "Test2"}, - new DummyRecord {ID = 3, Name = "Test3"}, - new DummyRecord {ID = 4, Name = "Test4"}, - new DummyRecord {ID = 5, Name = "Test5"}, - new DummyRecord {ID = 6, Name = "Test6"}, - new DummyRecord {ID = 7, Name = "Test7"}, - new DummyRecord {ID = 8, Name = "Test8"} - }; - - var bytes = MakeDummyFile(records); - - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - Assert.AreEqual(records.Length, _baseFile.Length); - - for (int i = 0; i < records.Length; ++i) - Assert.AreEqual(records[i].Name, _baseFile[records[i].ID].Name, "Failed at index {0}", i); - } - - private byte[] MakeDummyFile(params DummyRecord[] records) - { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - - bytes.Add((byte)records.Length); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); - } - } -} diff --git a/EOLib.IO.Test/Pub/DummyFile.cs b/EOLib.IO.Test/Pub/DummyFile.cs deleted file mode 100644 index e6705093b..000000000 --- a/EOLib.IO.Test/Pub/DummyFile.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; - -namespace EOLib.IO.Test.Pub -{ - [ExcludeFromCodeCoverage] - internal class DummyFile : BasePubFile - { - public override string FileType => " "; - - public override void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService) - { - using (var ms = new MemoryStream(bytes)) - { - var num = ms.ReadByte(); - - for (int i = 0; i < num; ++i) - { - var nameLen = ms.ReadByte(); - var rawName = new byte[nameLen]; - ms.Read(rawName, 0, nameLen); - - _data.Add(new DummyRecord {ID = i + 1, Name = Encoding.ASCII.GetString(rawName)}); - } - } - } - } -} diff --git a/EOLib.IO.Test/Pub/DummyRecord.cs b/EOLib.IO.Test/Pub/DummyRecord.cs deleted file mode 100644 index f33c33c8b..000000000 --- a/EOLib.IO.Test/Pub/DummyRecord.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; - -namespace EOLib.IO.Test.Pub -{ - [ExcludeFromCodeCoverage] - internal class DummyRecord : IPubRecord - { - public int RecordSize => 0; - - public int ID { get; set; } - - public string Name { get; set; } - - public TValue Get(PubRecordProperty type) - { - return default(TValue); - } - - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService) - { - var bytes = new byte[Name.Length + 1]; - bytes[0] = (byte) Name.Length; - Array.Copy(Encoding.ASCII.GetBytes(Name), 0, bytes, 1, Name.Length); - return bytes; - } - - public void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService) - { - } - } -} diff --git a/EOLib.IO.Test/Pub/ECFFileTest.cs b/EOLib.IO.Test/Pub/ECFFileTest.cs index 5d84b757f..e13e75ee7 100644 --- a/EOLib.IO.Test/Pub/ECFFileTest.cs +++ b/EOLib.IO.Test/Pub/ECFFileTest.cs @@ -1,100 +1,70 @@ -using System.Collections.Generic; +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; namespace EOLib.IO.Test.Pub { [TestFixture, ExcludeFromCodeCoverage] public class ECFFileTest { - private IPubFile _classFile; - - [SetUp] - public void SetUp() - { - _classFile = new ECFFile(); - } - [Test] public void HasCorrectFileType() { - Assert.AreEqual("ECF", _classFile.FileType); + Assert.That(new ECFFile().FileType, Is.EqualTo("ECF")); } [Test] public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeECFFile(55565554, - new ECFRecord { ID = 1, Name = "TestFixture" }, - new ECFRecord { ID = 2, Name = "Test2" }, - new ECFRecord { ID = 3, Name = "Test3" }, - new ECFRecord { ID = 4, Name = "Test4" }, - new ECFRecord { ID = 5, Name = "Test5" }, - new ECFRecord { ID = 6, Name = "Test6" }, - new ECFRecord { ID = 7, Name = "Test7" }, - new ECFRecord { ID = 8, Name = "Test8" }, - new ECFRecord { ID = 9, Name = "eof" }); + new ECFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new ECFRecord().WithID(2).WithNames(new List { "Test2" }), + new ECFRecord().WithID(3).WithNames(new List { "Test3" }), + new ECFRecord().WithID(4).WithNames(new List { "Test4" }), + new ECFRecord().WithID(5).WithNames(new List { "Test5" }), + new ECFRecord().WithID(6).WithNames(new List { "Test6" }), + new ECFRecord().WithID(7).WithNames(new List { "Test7" }), + new ECFRecord().WithID(8).WithNames(new List { "Test8" }), + new ECFRecord().WithID(9).WithNames(new List { "eof" })); - _classFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ECFFile()); - var actualBytes = _classFile.SerializeToByteArray(new NumberEncoderService(), rewriteChecksum: false); + var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); CollectionAssert.AreEqual(expectedBytes, actualBytes); } - [Test] - public void HeaderFormat_IsCorrect() - { - var nes = new NumberEncoderService(); - - var actualBytes = _classFile.SerializeToByteArray(nes, rewriteChecksum: false); - - CollectionAssert.AreEqual(Encoding.ASCII.GetBytes(_classFile.FileType), actualBytes.Take(3).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_classFile.CheckSum, 4), actualBytes.Skip(3).Take(4).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_classFile.Length, 2), actualBytes.Skip(7).Take(2).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(1, 1), actualBytes.Skip(9).Take(1).ToArray()); - } - - [Test] - public void LengthMismatch_ThrowsIOException() - { - var bytes = MakeECFFileWithWrongLength(12345678, 5, - new ECFRecord { ID = 1, Name = "Class1" }, - new ECFRecord { ID = 2, Name = "Class2" }, - new ECFRecord { ID = 3, Name = "Class3" }); - - Assert.Throws(() => _classFile.DeserializeFromByteArray(bytes, new NumberEncoderService())); - } - [Test] public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new ECFRecord {ID = 1, Name = "TestFixture"}, - new ECFRecord {ID = 2, Name = "Test2"}, - new ECFRecord {ID = 3, Name = "Test3"}, - new ECFRecord {ID = 4, Name = "Test4"}, - new ECFRecord {ID = 5, Name = "Test5"}, - new ECFRecord {ID = 6, Name = "Test6"}, - new ECFRecord {ID = 7, Name = "Test7"}, - new ECFRecord {ID = 8, Name = "Test8"}, - new ECFRecord {ID = 9, Name = "eof"} + new ECFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new ECFRecord().WithID(2).WithNames(new List { "Test2" }), + new ECFRecord().WithID(3).WithNames(new List { "Test3" }), + new ECFRecord().WithID(4).WithNames(new List { "Test4" }), + new ECFRecord().WithID(5).WithNames(new List { "Test5" }), + new ECFRecord().WithID(6).WithNames(new List { "Test6" }), + new ECFRecord().WithID(7).WithNames(new List { "Test7" }), + new ECFRecord().WithID(8).WithNames(new List { "Test8" }), + new ECFRecord().WithID(9).WithNames(new List { "eof" }) }; var bytes = MakeECFFile(55565554, records); - _classFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new ECFFile()); CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _classFile.Data.Select(x => new { x.ID, x.Name }).ToList()); + file.Select(x => new { x.ID, x.Name }).ToList()); } - private byte[] MakeECFFile(int checksum, params ECFRecord[] records) + private byte[] MakeECFFile(int checksum, params IPubRecord[] records) { var numberEncoderService = new NumberEncoderService(); @@ -103,25 +73,17 @@ private byte[] MakeECFFile(int checksum, params ECFRecord[] records) bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); + + var recordSerializer = new PubRecordSerializer(numberEncoderService); foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); + bytes.AddRange(recordSerializer.SerializeToByteArray(record)); return bytes.ToArray(); } - private byte[] MakeECFFileWithWrongLength(int checksum, int length, params ECFRecord[] records) + private static IPubFileSerializer CreateFileSerializer() { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ECF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); } } } diff --git a/EOLib.IO.Test/Pub/ECFRecordTest.cs b/EOLib.IO.Test/Pub/ECFRecordTest.cs deleted file mode 100644 index 3cf23216a..000000000 --- a/EOLib.IO.Test/Pub/ECFRecordTest.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; - -namespace EOLib.IO.Test.Pub -{ - [TestFixture, ExcludeFromCodeCoverage] - public class ECFRecordTest - { - [Test] - public void ECFRecord_GetGlobalPropertyID_GetsRecordID() - { - const int expected = 44; - var rec = new ECFRecord { ID = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalID); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ECFRecord_GetGlobalPropertyName_GetsRecordName() - { - const string expected = "some name"; - var rec = new ECFRecord { Name = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalName); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ECFRecord_GetClassPropertiesComprehensive_NoException() - { - var classProperties = Enum.GetNames(typeof(PubRecordProperty)) - .Where(x => x.StartsWith("Class")) - .Select(x => (PubRecordProperty)Enum.Parse(typeof(PubRecordProperty), x)) - .ToArray(); - - Assert.AreNotEqual(0, classProperties.Length); - - var record = new ECFRecord(); - - foreach (var property in classProperties) - { - var dummy = record.Get(property); - Assert.IsNotNull(dummy); - } - } - - [Test] - public void ECFRecord_GetItemProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ItemSubType; - - var record = new ECFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ECFRecord_GetSpellProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.SpellAccuracy; - - var record = new ECFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ECFRecord_GetNPCProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.NPCAccuracy; - - var record = new ECFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ECFRecord_InvalidPropertyReturnType_ThrowsInvalidCastException() - { - var rec = new ECFRecord { Name = "" }; - - Assert.Throws(() => rec.Get(PubRecordProperty.GlobalName)); - } - - [Test] - public void ECFRecord_SerializeToByteArray_WritesExpectedFormat() - { - var numberEncoderService = new NumberEncoderService(); - var record = CreateRecordWithSomeGoodTestData(); - - var actualBytes = record.SerializeToByteArray(numberEncoderService); - - var expectedBytes = GetExpectedBytes(record, numberEncoderService); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void ECFRecord_DeserializeFromByteArray_HasCorrectData() - { - var numberEncoderService = new NumberEncoderService(); - var sourceRecord = CreateRecordWithSomeGoodTestData(); - var sourceRecordBytes = GetExpectedBytesWithoutName(sourceRecord, numberEncoderService); - - var record = new ECFRecord { ID = sourceRecord.ID, Name = sourceRecord.Name }; - record.DeserializeFromByteArray(sourceRecordBytes, numberEncoderService); - - var properties = record.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - - Assert.IsTrue(properties.Length > 0); - - foreach (var property in properties) - { - var expectedValue = property.GetValue(sourceRecord); - var actualValue = property.GetValue(record); - - Assert.AreEqual(expectedValue, actualValue, "Property: {0}", property.Name); - } - } - - [Test] - public void ECFRecord_DeserializeFromByteArray_InvalidArrayLength_ThrowsException() - { - var record = new ECFRecord(); - - Assert.Throws(() => record.DeserializeFromByteArray(new byte[] { 1, 2, 3 }, new NumberEncoderService())); - } - - private static ECFRecord CreateRecordWithSomeGoodTestData() - { - return new ECFRecord - { - ID = 1, - Name = "TestName", - - Base = 33, - Type = 99, - - Str = 10, - Int = 20, - Wis = 30, - Agi = 200, - Con = 190, - Cha = 180 - }; - } - - private static byte[] GetExpectedBytes(ECFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Name.Length, 1)); - ret.AddRange(Encoding.ASCII.GetBytes(rec.Name)); - ret.AddRange(GetExpectedBytesWithoutName(rec, nes)); - - return ret.ToArray(); - } - - private static byte[] GetExpectedBytesWithoutName(ECFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Base, 1)); - ret.AddRange(nes.EncodeNumber(rec.Type, 1)); - ret.AddRange(nes.EncodeNumber(rec.Str, 2)); - ret.AddRange(nes.EncodeNumber(rec.Int, 2)); - ret.AddRange(nes.EncodeNumber(rec.Wis, 2)); - ret.AddRange(nes.EncodeNumber(rec.Agi, 2)); - ret.AddRange(nes.EncodeNumber(rec.Con, 2)); - ret.AddRange(nes.EncodeNumber(rec.Cha, 2)); - - return ret.ToArray(); - } - } -} diff --git a/EOLib.IO.Test/Pub/EIFFileTest.cs b/EOLib.IO.Test/Pub/EIFFileTest.cs index 03dcd31dd..68375846f 100644 --- a/EOLib.IO.Test/Pub/EIFFileTest.cs +++ b/EOLib.IO.Test/Pub/EIFFileTest.cs @@ -1,48 +1,41 @@ -using System.Collections.Generic; +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; namespace EOLib.IO.Test.Pub { [TestFixture, ExcludeFromCodeCoverage] public class EIFFileTest { - private IPubFile _itemFile; - - [SetUp] - public void SetUp() - { - _itemFile = new EIFFile(); - } - [Test] public void HasCorrectFileType() { - Assert.AreEqual("EIF", _itemFile.FileType); + Assert.That(new EIFFile().FileType, Is.EqualTo("EIF")); } [Test] public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeEIFFile(55565554, - new EIFRecord {ID = 1, Name = "TestItem"}, - new EIFRecord {ID = 2, Name = "Test2"}, - new EIFRecord {ID = 3, Name = "Test3"}, - new EIFRecord {ID = 4, Name = "Test4"}, - new EIFRecord {ID = 5, Name = "Test5"}, - new EIFRecord {ID = 6, Name = "Test6"}, - new EIFRecord {ID = 7, Name = "Test7"}, - new EIFRecord {ID = 8, Name = "Test8"}, - new EIFRecord {ID = 9, Name = "eof"}); + new EIFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new EIFRecord().WithID(2).WithNames(new List { "Test2" }), + new EIFRecord().WithID(3).WithNames(new List { "Test3" }), + new EIFRecord().WithID(4).WithNames(new List { "Test4" }), + new EIFRecord().WithID(5).WithNames(new List { "Test5" }), + new EIFRecord().WithID(6).WithNames(new List { "Test6" }), + new EIFRecord().WithID(7).WithNames(new List { "Test7" }), + new EIFRecord().WithID(8).WithNames(new List { "Test8" }), + new EIFRecord().WithID(9).WithNames(new List { "eof" })); - _itemFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(expectedBytes, () => new EIFFile()); - var actualBytes = _itemFile.SerializeToByteArray(new NumberEncoderService(), rewriteChecksum: false); + var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); CollectionAssert.AreEqual(expectedBytes, actualBytes); } @@ -52,49 +45,26 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new EIFRecord {ID = 1, Name = "TestItem"}, - new EIFRecord {ID = 2, Name = "Test2"}, - new EIFRecord {ID = 3, Name = "Test3"}, - new EIFRecord {ID = 4, Name = "Test4"}, - new EIFRecord {ID = 5, Name = "Test5"}, - new EIFRecord {ID = 6, Name = "Test6"}, - new EIFRecord {ID = 7, Name = "Test7"}, - new EIFRecord {ID = 8, Name = "Test8"}, - new EIFRecord {ID = 9, Name = "eof"} + new EIFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new EIFRecord().WithID(2).WithNames(new List { "Test2" }), + new EIFRecord().WithID(3).WithNames(new List { "Test3" }), + new EIFRecord().WithID(4).WithNames(new List { "Test4" }), + new EIFRecord().WithID(5).WithNames(new List { "Test5" }), + new EIFRecord().WithID(6).WithNames(new List { "Test6" }), + new EIFRecord().WithID(7).WithNames(new List { "Test7" }), + new EIFRecord().WithID(8).WithNames(new List { "Test8" }), + new EIFRecord().WithID(9).WithNames(new List { "eof" }) }; var bytes = MakeEIFFile(55565554, records); - _itemFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new EIFFile()); - CollectionAssert.AreEqual(records.Select(x => new {x.ID, x.Name}).ToList(), - _itemFile.Data.Select(x => new {x.ID, x.Name}).ToList()); + CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), + file.Select(x => new { x.ID, x.Name }).ToList()); } - [Test] - public void HeaderFormat_IsCorrect() - { - var nes = new NumberEncoderService(); - - var actualBytes = _itemFile.SerializeToByteArray(nes, rewriteChecksum: false); - - CollectionAssert.AreEqual(Encoding.ASCII.GetBytes(_itemFile.FileType), actualBytes.Take(3).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_itemFile.CheckSum, 4), actualBytes.Skip(3).Take(4).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_itemFile.Length, 2), actualBytes.Skip(7).Take(2).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(1, 1), actualBytes.Skip(9).Take(1).ToArray()); - } - - [Test] - public void LengthMismatch_ThrowsIOException() - { - var bytes = MakeEIFFileWithWrongLength(12345678, 5, - new EIFRecord {ID = 1, Name = "Item1"}, - new EIFRecord {ID = 2, Name = "Item2"}, - new EIFRecord {ID = 3, Name = "Item3"}); - - Assert.Throws(() => _itemFile.DeserializeFromByteArray(bytes, new NumberEncoderService())); - } - - private byte[] MakeEIFFile(int checksum, params EIFRecord[] records) + private byte[] MakeEIFFile(int checksum, params IPubRecord[] records) { var numberEncoderService = new NumberEncoderService(); @@ -103,25 +73,17 @@ private byte[] MakeEIFFile(int checksum, params EIFRecord[] records) bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); + + var recordSerializer = new PubRecordSerializer(numberEncoderService); foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); + bytes.AddRange(recordSerializer.SerializeToByteArray(record)); return bytes.ToArray(); } - private byte[] MakeEIFFileWithWrongLength(int checksum, int length, params EIFRecord[] records) + private static IPubFileSerializer CreateFileSerializer() { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("EIF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); } } } diff --git a/EOLib.IO.Test/Pub/EIFRecordTest.cs b/EOLib.IO.Test/Pub/EIFRecordTest.cs deleted file mode 100644 index 3c51d803b..000000000 --- a/EOLib.IO.Test/Pub/EIFRecordTest.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; - -namespace EOLib.IO.Test.Pub -{ - [TestFixture, ExcludeFromCodeCoverage] - public class EIFRecordTest - { - [Test] - public void EIFRecord_GetGlobalPropertyID_GetsRecordID() - { - const int expected = 44; - var rec = new EIFRecord {ID = expected}; - - var actual = rec.Get(PubRecordProperty.GlobalID); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void EIFRecord_GetGlobalPropertyName_GetsRecordName() - { - const string expected = "some name"; - var rec = new EIFRecord { Name = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalName); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void EIFRecord_GetItemPropertiesComprehensive_NoException() - { - var itemProperties = Enum.GetNames(typeof (PubRecordProperty)) - .Where(x => x.StartsWith("Item")) - .Select(x => (PubRecordProperty) Enum.Parse(typeof (PubRecordProperty), x)) - .ToArray(); - - Assert.AreNotEqual(0, itemProperties.Length); - - var record = new EIFRecord(); - - foreach (var property in itemProperties) - { - var dummy = record.Get(property); - Assert.IsNotNull(dummy); - } - } - - [Test] - public void EIFRecord_GetNPCProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.NPCAccuracy; - - var record = new EIFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void EIFRecord_GetSpellProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.SpellAccuracy; - - var record = new EIFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void EIFRecord_GetClassProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ClassAgi; - - var record = new EIFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void EIFRecord_InvalidPropertyReturnType_ThrowsInvalidCastException() - { - var rec = new EIFRecord {Name = ""}; - - Assert.Throws(() => rec.Get(PubRecordProperty.GlobalName)); - } - - [Test] - public void EIFRecord_SerializeToByteArray_WritesExpectedFormat() - { - var numberEncoderService = new NumberEncoderService(); - var record = CreateRecordWithSomeGoodTestData(); - - var actualBytes = record.SerializeToByteArray(numberEncoderService); - - var expectedBytes = GetExpectedBytes(record, numberEncoderService); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void EIFRecord_DeserializeFromByteArray_HasCorrectData() - { - var numberEncoderService = new NumberEncoderService(); - var sourceRecord = CreateRecordWithSomeGoodTestData(); - var sourceRecordBytes = GetExpectedBytesWithoutName(sourceRecord, numberEncoderService); - - var record = new EIFRecord { ID = sourceRecord.ID, Name = sourceRecord.Name }; - record.DeserializeFromByteArray(sourceRecordBytes, numberEncoderService); - - var properties = record.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - - Assert.IsTrue(properties.Length > 0); - - foreach (var property in properties) - { - var expectedValue = property.GetValue(sourceRecord); - var actualValue = property.GetValue(record); - - Assert.AreEqual(expectedValue, actualValue, "Property: {0}", property.Name); - } - } - - [Test] - public void EIFRecord_DeserializeFromByteArray_InvalidArrayLength_ThrowsException() - { - var record = new EIFRecord(); - - Assert.Throws(() => record.DeserializeFromByteArray(new byte[] { 1, 2, 3 }, new NumberEncoderService())); - } - - [Test] - public void EIFRecord_GunOverride_ConvertsItemSubTypeToRanged() - { - var record = new EIFRecord {ID = 365, Name = "Gun", SubType = ItemSubType.Arrows}; - record.DeserializeFromByteArray(Enumerable.Repeat((byte)128, EIFRecord.DATA_SIZE).ToArray(), new NumberEncoderService()); - - Assert.AreEqual(ItemSubType.Ranged, record.SubType); - } - - [Test] - public void EIFRecord_SharedValueSet1_HaveSameValue() - { - var properties = new[] { "ScrollMap", "DollGraphic", "ExpReward", "HairColor", "Effect", "Key" }; - var record = new EIFRecord(); - - var value = new Random().Next(100); - foreach (var prop in properties) - { - record.GetType().GetProperty(prop, BindingFlags.Instance | BindingFlags.Public) - .SetValue(record, value); - - Assert.AreEqual(value, record.ScrollMap); - Assert.AreEqual(value, record.DollGraphic); - Assert.AreEqual(value, record.ExpReward); - Assert.AreEqual(value, record.HairColor); - Assert.AreEqual(value, record.Effect); - Assert.AreEqual(value, record.Key); - - value++; - } - } - - [Test] - public void EIFRecord_SharedValueSet2_HaveSameValue() - { - var properties = new[] { "Gender", "ScrollX" }; - var record = new EIFRecord(); - - var value = new Random().Next(100); - foreach (var prop in properties) - { - record.GetType().GetProperty(prop, BindingFlags.Instance | BindingFlags.Public) - .SetValue(record, (byte)value); - - Assert.AreEqual(value, record.Gender); - Assert.AreEqual(value, record.ScrollX); - - value++; - } - } - - [Test] - public void EIFRecord_SharedValueSet3_HaveSameValue() - { - var properties = new[] { "ScrollY", "DualWieldDollGraphic" }; - var record = new EIFRecord(); - - var value = new Random().Next(100); - foreach (var prop in properties) - { - record.GetType().GetProperty(prop, BindingFlags.Instance | BindingFlags.Public) - .SetValue(record, (byte)value); - - Assert.AreEqual(value, record.ScrollY); - Assert.AreEqual(value, record.DualWieldDollGraphic); - - value++; - } - } - - private static EIFRecord CreateRecordWithSomeGoodTestData() - { - return new EIFRecord - { - ID = 1, - Name = "TestName", - Graphic = 123, - Type = ItemType.Bracer, - SubType = ItemSubType.Ranged, - Special = ItemSpecial.Unique, - HP = 456, - TP = 654, - MinDam = 33, - MaxDam = 66, - Accuracy = 100, - Evade = 200, - Armor = 300, - Str = 40, - Int = 50, - Wis = 60, - Agi = 70, - Con = 80, - Cha = 90, - Light = 3, - Dark = 6, - Earth = 9, - Air = 12, - Water = 15, - Fire = 18, - ScrollMap = 33, - Gender = 44, - ScrollY = 55, - LevelReq = 66, - ClassReq = 77, - StrReq = 88, - IntReq = 99, - WisReq = 30, - AgiReq = 20, - ConReq = 10, - ChaReq = 5, - Weight = 200, - Size = ItemSize.Size2x3 - }; - } - - private static byte[] GetExpectedBytes(EIFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Name.Length, 1)); - ret.AddRange(Encoding.ASCII.GetBytes(rec.Name)); - ret.AddRange(GetExpectedBytesWithoutName(rec, nes)); - - return ret.ToArray(); - } - - private static byte[] GetExpectedBytesWithoutName(EIFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Graphic, 2)); - ret.AddRange(nes.EncodeNumber((byte)rec.Type, 1)); - ret.AddRange(nes.EncodeNumber((byte)rec.SubType, 1)); - ret.AddRange(nes.EncodeNumber((byte)rec.Special, 1)); - ret.AddRange(nes.EncodeNumber(rec.HP, 2)); - ret.AddRange(nes.EncodeNumber(rec.TP, 2)); - ret.AddRange(nes.EncodeNumber(rec.MinDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.MaxDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.Accuracy, 2)); - ret.AddRange(nes.EncodeNumber(rec.Evade, 2)); - ret.AddRange(nes.EncodeNumber(rec.Armor, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte19, 1)); - ret.AddRange(nes.EncodeNumber(rec.Str, 1)); - ret.AddRange(nes.EncodeNumber(rec.Int, 1)); - ret.AddRange(nes.EncodeNumber(rec.Wis, 1)); - ret.AddRange(nes.EncodeNumber(rec.Agi, 1)); - ret.AddRange(nes.EncodeNumber(rec.Con, 1)); - ret.AddRange(nes.EncodeNumber(rec.Cha, 1)); - ret.AddRange(nes.EncodeNumber(rec.Light, 1)); - ret.AddRange(nes.EncodeNumber(rec.Dark, 1)); - ret.AddRange(nes.EncodeNumber(rec.Earth, 1)); - ret.AddRange(nes.EncodeNumber(rec.Air, 1)); - ret.AddRange(nes.EncodeNumber(rec.Water, 1)); - ret.AddRange(nes.EncodeNumber(rec.Fire, 1)); - ret.AddRange(nes.EncodeNumber(rec.ScrollMap, 3)); - ret.AddRange(nes.EncodeNumber(rec.ScrollX, 1)); - ret.AddRange(nes.EncodeNumber(rec.ScrollY, 1)); - ret.AddRange(nes.EncodeNumber(rec.LevelReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.ClassReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.StrReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.IntReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.WisReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.AgiReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.ConReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.ChaReq, 2)); - ret.AddRange(nes.EncodeNumber(rec.Element, 1)); - ret.AddRange(nes.EncodeNumber(rec.ElementPower, 1)); - ret.AddRange(nes.EncodeNumber(rec.Weight, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte56, 1)); - ret.AddRange(nes.EncodeNumber((byte)rec.Size, 1)); - - return ret.ToArray(); - } - } -} diff --git a/EOLib.IO.Test/Pub/ENFFileTest.cs b/EOLib.IO.Test/Pub/ENFFileTest.cs index c24c829e0..aba624965 100644 --- a/EOLib.IO.Test/Pub/ENFFileTest.cs +++ b/EOLib.IO.Test/Pub/ENFFileTest.cs @@ -1,100 +1,70 @@ -using System.Collections.Generic; +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; namespace EOLib.IO.Test.Pub { [TestFixture, ExcludeFromCodeCoverage] public class ENFFileTest { - private IPubFile _npcFile; - - [SetUp] - public void SetUp() - { - _npcFile = new ENFFile(); - } - [Test] public void HasCorrectFileType() { - Assert.AreEqual("ENF", _npcFile.FileType); + Assert.That(new ENFFile().FileType, Is.EqualTo("ENF")); } [Test] public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeENFFile(55565554, - new ENFRecord { ID = 1, Name = "TestNPC" }, - new ENFRecord { ID = 2, Name = "Test2" }, - new ENFRecord { ID = 3, Name = "Test3" }, - new ENFRecord { ID = 4, Name = "Test4" }, - new ENFRecord { ID = 5, Name = "Test5" }, - new ENFRecord { ID = 6, Name = "Test6" }, - new ENFRecord { ID = 7, Name = "Test7" }, - new ENFRecord { ID = 8, Name = "Test8" }, - new ENFRecord { ID = 9, Name = "eof" }); + new ENFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new ENFRecord().WithID(2).WithNames(new List { "Test2" }), + new ENFRecord().WithID(3).WithNames(new List { "Test3" }), + new ENFRecord().WithID(4).WithNames(new List { "Test4" }), + new ENFRecord().WithID(5).WithNames(new List { "Test5" }), + new ENFRecord().WithID(6).WithNames(new List { "Test6" }), + new ENFRecord().WithID(7).WithNames(new List { "Test7" }), + new ENFRecord().WithID(8).WithNames(new List { "Test8" }), + new ENFRecord().WithID(9).WithNames(new List { "eof" })); - _npcFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ENFFile()); - var actualBytes = _npcFile.SerializeToByteArray(new NumberEncoderService(), rewriteChecksum: false); + var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); CollectionAssert.AreEqual(expectedBytes, actualBytes); } - [Test] - public void HeaderFormat_IsCorrect() - { - var nes = new NumberEncoderService(); - - var actualBytes = _npcFile.SerializeToByteArray(nes, rewriteChecksum: false); - - CollectionAssert.AreEqual(Encoding.ASCII.GetBytes(_npcFile.FileType), actualBytes.Take(3).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_npcFile.CheckSum, 4), actualBytes.Skip(3).Take(4).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_npcFile.Length, 2), actualBytes.Skip(7).Take(2).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(1, 1), actualBytes.Skip(9).Take(1).ToArray()); - } - - [Test] - public void LengthMismatch_ThrowsIOException() - { - var bytes = MakeENFFileWithWrongLength(12345678, 5, - new ENFRecord { ID = 1, Name = "NPC1" }, - new ENFRecord { ID = 2, Name = "NPC2" }, - new ENFRecord { ID = 3, Name = "NPC3" }); - - Assert.Throws(() => _npcFile.DeserializeFromByteArray(bytes, new NumberEncoderService())); - } - [Test] public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new ENFRecord {ID = 1, Name = "Test"}, - new ENFRecord {ID = 2, Name = "Test2"}, - new ENFRecord {ID = 3, Name = "Test3"}, - new ENFRecord {ID = 4, Name = "Test4"}, - new ENFRecord {ID = 5, Name = "Test5"}, - new ENFRecord {ID = 6, Name = "Test6"}, - new ENFRecord {ID = 7, Name = "Test7"}, - new ENFRecord {ID = 8, Name = "Test8"}, - new ENFRecord {ID = 9, Name = "eof"} + new ENFRecord().WithID(1).WithNames(new List { "TestFixture" }), + new ENFRecord().WithID(2).WithNames(new List { "Test2" }), + new ENFRecord().WithID(3).WithNames(new List { "Test3" }), + new ENFRecord().WithID(4).WithNames(new List { "Test4" }), + new ENFRecord().WithID(5).WithNames(new List { "Test5" }), + new ENFRecord().WithID(6).WithNames(new List { "Test6" }), + new ENFRecord().WithID(7).WithNames(new List { "Test7" }), + new ENFRecord().WithID(8).WithNames(new List { "Test8" }), + new ENFRecord().WithID(9).WithNames(new List { "eof" }) }; var bytes = MakeENFFile(55565554, records); - _npcFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new ENFFile()); CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _npcFile.Data.Select(x => new { x.ID, x.Name }).ToList()); + file.Select(x => new { x.ID, x.Name }).ToList()); } - private byte[] MakeENFFile(int checksum, params ENFRecord[] records) + private byte[] MakeENFFile(int checksum, params IPubRecord[] records) { var numberEncoderService = new NumberEncoderService(); @@ -103,25 +73,17 @@ private byte[] MakeENFFile(int checksum, params ENFRecord[] records) bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); + + var recordSerializer = new PubRecordSerializer(numberEncoderService); foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); + bytes.AddRange(recordSerializer.SerializeToByteArray(record)); return bytes.ToArray(); } - private byte[] MakeENFFileWithWrongLength(int checksum, int length, params ENFRecord[] records) + private static IPubFileSerializer CreateFileSerializer() { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ENF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); } } } diff --git a/EOLib.IO.Test/Pub/ENFRecordTest.cs b/EOLib.IO.Test/Pub/ENFRecordTest.cs deleted file mode 100644 index 29fdb80f7..000000000 --- a/EOLib.IO.Test/Pub/ENFRecordTest.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; - -namespace EOLib.IO.Test.Pub -{ - [TestFixture, ExcludeFromCodeCoverage] - public class ENFRecordTest - { - [Test] - public void ENFRecord_GetGlobalPropertyID_GetsRecordID() - { - const int expected = 44; - var rec = new ENFRecord { ID = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalID); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ENFRecord_GetGlobalPropertyName_GetsRecordName() - { - const string expected = "some name"; - var rec = new ENFRecord { Name = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalName); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ENFRecord_GetNPCPropertiesComprehensive_NoException() - { - var npcProperties = Enum.GetNames(typeof(PubRecordProperty)) - .Where(x => x.StartsWith("NPC")) - .Select(x => (PubRecordProperty)Enum.Parse(typeof(PubRecordProperty), x)) - .ToArray(); - - Assert.AreNotEqual(0, npcProperties.Length); - - var record = new ENFRecord(); - - foreach (var property in npcProperties) - { - var dummy = record.Get(property); - Assert.IsNotNull(dummy); - } - } - - [Test] - public void ENFRecord_GetItemProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ItemSubType; - - var record = new ENFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ENFRecord_GetSpellProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.SpellAccuracy; - - var record = new ENFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ENFRecord_GetClassProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ClassAgi; - - var record = new ENFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ENFRecord_InvalidPropertyReturnType_ThrowsInvalidCastException() - { - var rec = new ENFRecord { Name = "" }; - - Assert.Throws(() => rec.Get(PubRecordProperty.GlobalName)); - } - - [Test] - public void ENFRecord_SerializeToByteArray_WritesExpectedFormat() - { - var numberEncoderService = new NumberEncoderService(); - var record = CreateRecordWithSomeGoodTestData(); - - var actualBytes = record.SerializeToByteArray(numberEncoderService); - - var expectedBytes = GetExpectedBytes(record, numberEncoderService); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void ENFRecord_DeserializeFromByteArray_HasCorrectData() - { - var numberEncoderService = new NumberEncoderService(); - var sourceRecord = CreateRecordWithSomeGoodTestData(); - var sourceRecordBytes = GetExpectedBytesWithoutName(sourceRecord, numberEncoderService); - - var record = new ENFRecord { ID = sourceRecord.ID, Name = sourceRecord.Name }; - record.DeserializeFromByteArray(sourceRecordBytes, numberEncoderService); - - var properties = record.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - - Assert.IsTrue(properties.Length > 0); - - foreach (var property in properties) - { - var expectedValue = property.GetValue(sourceRecord); - var actualValue = property.GetValue(record); - - Assert.AreEqual(expectedValue, actualValue, "Property: {0}", property.Name); - } - } - - [Test] - public void ENFRecord_DeserializeFromByteArray_InvalidArrayLength_ThrowsException() - { - var record = new ENFRecord(); - - Assert.Throws(() => record.DeserializeFromByteArray(new byte[] { 1, 2, 3 }, new NumberEncoderService())); - } - - private static ENFRecord CreateRecordWithSomeGoodTestData() - { - return new ENFRecord - { - ID = 1, - Name = "TestName", - Graphic = 123, - Boss = 321, - Child = 4321, - Type = NPCType.Barber, - - VendorID = 1234, - - HP = 123456, - Exp = 44332, - MinDam = 16543, - MaxDam = 16544, - - Accuracy = 31313, - Evade = 13131, - Armor = 222 - }; - } - - private static byte[] GetExpectedBytes(ENFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Name.Length, 1)); - ret.AddRange(Encoding.ASCII.GetBytes(rec.Name)); - ret.AddRange(GetExpectedBytesWithoutName(rec, nes)); - - return ret.ToArray(); - } - - private static byte[] GetExpectedBytesWithoutName(ENFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Graphic, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte2, 1)); - ret.AddRange(nes.EncodeNumber(rec.Boss, 2)); - ret.AddRange(nes.EncodeNumber(rec.Child, 2)); - ret.AddRange(nes.EncodeNumber((short)rec.Type, 2)); - ret.AddRange(nes.EncodeNumber(rec.VendorID, 2)); - ret.AddRange(nes.EncodeNumber(rec.HP, 3)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort14, 2)); - ret.AddRange(nes.EncodeNumber(rec.MinDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.MaxDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.Accuracy, 2)); - ret.AddRange(nes.EncodeNumber(rec.Evade, 2)); - ret.AddRange(nes.EncodeNumber(rec.Armor, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte26, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort27, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort29, 2)); - ret.AddRange(nes.EncodeNumber(rec.ElementWeak, 2)); - ret.AddRange(nes.EncodeNumber(rec.ElementWeakPower, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte35, 1)); - ret.AddRange(nes.EncodeNumber(rec.Exp, 3)); - - return ret.ToArray(); - } - } -} diff --git a/EOLib.IO.Test/Pub/ESFFileTest.cs b/EOLib.IO.Test/Pub/ESFFileTest.cs index cb958441c..7af8d4fe9 100644 --- a/EOLib.IO.Test/Pub/ESFFileTest.cs +++ b/EOLib.IO.Test/Pub/ESFFileTest.cs @@ -1,100 +1,70 @@ -using System.Collections.Generic; +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; namespace EOLib.IO.Test.Pub { [TestFixture, ExcludeFromCodeCoverage] public class ESFFileTest { - private IPubFile _spellFile; - - [SetUp] - public void SetUp() - { - _spellFile = new ESFFile(); - } - [Test] public void HasCorrectFileType() { - Assert.AreEqual("ESF", _spellFile.FileType); + Assert.That(new ESFFile().FileType, Is.EqualTo("ESF")); } [Test] public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeESFFile(55565554, - new ESFRecord { ID = 1, Name = "TestSpell", Shout = "TestShout" }, - new ESFRecord { ID = 2, Name = "Test2", Shout = "TestShout2" }, - new ESFRecord { ID = 3, Name = "Test3", Shout = "TestShout3" }, - new ESFRecord { ID = 4, Name = "Test4", Shout = "TestShout4" }, - new ESFRecord { ID = 5, Name = "Test5", Shout = "TestShout5" }, - new ESFRecord { ID = 6, Name = "Test6", Shout = "TestShout6" }, - new ESFRecord { ID = 7, Name = "Test7", Shout = "TestShout7" }, - new ESFRecord { ID = 8, Name = "Test8", Shout = "TestShout8" }, - new ESFRecord { ID = 9, Name = "eof", Shout = "-" }); + new ESFRecord().WithID(1).WithNames(new List { "TestFixture", "Shout" }), + new ESFRecord().WithID(2).WithNames(new List { "Test2", "Shout" }), + new ESFRecord().WithID(3).WithNames(new List { "Test3", "Shout" }), + new ESFRecord().WithID(4).WithNames(new List { "Test4", "Shout" }), + new ESFRecord().WithID(5).WithNames(new List { "Test5", "Shout" }), + new ESFRecord().WithID(6).WithNames(new List { "Test6", "Shout" }), + new ESFRecord().WithID(7).WithNames(new List { "Test7", "Shout" }), + new ESFRecord().WithID(8).WithNames(new List { "Test8", "Shout" }), + new ESFRecord().WithID(9).WithNames(new List { "eof", "eof" })); - _spellFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ESFFile()); - var actualBytes = _spellFile.SerializeToByteArray(new NumberEncoderService(), rewriteChecksum: false); + var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); CollectionAssert.AreEqual(expectedBytes, actualBytes); } - [Test] - public void HeaderFormat_IsCorrect() - { - var nes = new NumberEncoderService(); - - var actualBytes = _spellFile.SerializeToByteArray(nes, rewriteChecksum: false); - - CollectionAssert.AreEqual(Encoding.ASCII.GetBytes(_spellFile.FileType), actualBytes.Take(3).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_spellFile.CheckSum, 4), actualBytes.Skip(3).Take(4).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(_spellFile.Length, 2), actualBytes.Skip(7).Take(2).ToArray()); - CollectionAssert.AreEqual(nes.EncodeNumber(1, 1), actualBytes.Skip(9).Take(1).ToArray()); - } - - [Test] - public void LengthMismatch_ThrowsIOException() - { - var bytes = MakeESFFileWithWrongLength(12345678, 5, - new ESFRecord { ID = 1, Name = "Spell1", Shout = "Spell1" }, - new ESFRecord { ID = 2, Name = "Spell2", Shout = "Spell2" }, - new ESFRecord { ID = 3, Name = "Spell3", Shout = "Spell3" }); - - Assert.Throws(() => _spellFile.DeserializeFromByteArray(bytes, new NumberEncoderService())); - } - [Test] public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new ESFRecord {ID = 1, Name = "Test", Shout = "Test"}, - new ESFRecord {ID = 2, Name = "Test2", Shout = "Test2"}, - new ESFRecord {ID = 3, Name = "Test3", Shout = "Test3"}, - new ESFRecord {ID = 4, Name = "Test4", Shout = "Test4"}, - new ESFRecord {ID = 5, Name = "Test5", Shout = "Test5"}, - new ESFRecord {ID = 6, Name = "Test6", Shout = "Test6"}, - new ESFRecord {ID = 7, Name = "Test7", Shout = "Test7"}, - new ESFRecord {ID = 8, Name = "Test8", Shout = "Test8"}, - new ESFRecord {ID = 9, Name = "eof", Shout = ""} + new ESFRecord().WithID(1).WithNames(new List { "TestFixture", "Shout" }), + new ESFRecord().WithID(2).WithNames(new List { "Test2", "Shout" }), + new ESFRecord().WithID(3).WithNames(new List { "Test3", "Shout" }), + new ESFRecord().WithID(4).WithNames(new List { "Test4", "Shout" }), + new ESFRecord().WithID(5).WithNames(new List { "Test5", "Shout" }), + new ESFRecord().WithID(6).WithNames(new List { "Test6", "Shout" }), + new ESFRecord().WithID(7).WithNames(new List { "Test7", "Shout" }), + new ESFRecord().WithID(8).WithNames(new List { "Test8", "Shout" }), + new ESFRecord().WithID(9).WithNames(new List { "eof", "eof" }) }; var bytes = MakeESFFile(55565554, records); - _spellFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + var serializer = CreateFileSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new ESFFile()); CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _spellFile.Data.Select(x => new { x.ID, x.Name }).ToList()); + file.Select(x => new { x.ID, x.Name }).ToList()); } - private byte[] MakeESFFile(int checksum, params ESFRecord[] records) + private byte[] MakeESFFile(int checksum, params IPubRecord[] records) { var numberEncoderService = new NumberEncoderService(); @@ -103,25 +73,17 @@ private byte[] MakeESFFile(int checksum, params ESFRecord[] records) bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); + + var recordSerializer = new PubRecordSerializer(numberEncoderService); foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); + bytes.AddRange(recordSerializer.SerializeToByteArray(record)); return bytes.ToArray(); } - private byte[] MakeESFFileWithWrongLength(int checksum, int length, params ESFRecord[] records) + private static IPubFileSerializer CreateFileSerializer() { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ESF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); } } } diff --git a/EOLib.IO.Test/Pub/ESFRecordTest.cs b/EOLib.IO.Test/Pub/ESFRecordTest.cs deleted file mode 100644 index 31206d294..000000000 --- a/EOLib.IO.Test/Pub/ESFRecordTest.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Pub; -using EOLib.IO.Services; -using NUnit.Framework; - -namespace EOLib.IO.Test.Pub -{ - [TestFixture, ExcludeFromCodeCoverage] - public class ESFRecordTest - { - [Test] - public void ESFRecord_GetGlobalPropertyID_GetsRecordID() - { - const int expected = 44; - var rec = new ESFRecord { ID = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalID); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ESFRecord_GetGlobalPropertyName_GetsRecordName() - { - const string expected = "some name"; - var rec = new ESFRecord { Name = expected }; - - var actual = rec.Get(PubRecordProperty.GlobalName); - - Assert.AreEqual(expected, actual); - } - - [Test] - public void ESFRecord_GetSpellPropertiesComprehensive_NoException() - { - var spellProperties = Enum.GetNames(typeof(PubRecordProperty)) - .Where(x => x.StartsWith("Spell")) - .Select(x => (PubRecordProperty)Enum.Parse(typeof(PubRecordProperty), x)) - .ToArray(); - - Assert.AreNotEqual(0, spellProperties.Length); - - var record = new ESFRecord {Shout = ""}; - - foreach (var property in spellProperties) - { - var dummy = record.Get(property); - Assert.IsNotNull(dummy); - } - } - - [Test] - public void ESFRecord_GetItemProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ItemSubType; - - var record = new ESFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ESFRecord_GetNPCProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.NPCAccuracy; - - var record = new ESFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ESFRecord_GetClassProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ClassAgi; - - var record = new ESFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); - } - - [Test] - public void ESFRecord_InvalidPropertyReturnType_ThrowsInvalidCastException() - { - var rec = new ESFRecord { Name = "" }; - - Assert.Throws(() => rec.Get(PubRecordProperty.GlobalName)); - } - - [Test] - public void ESFRecord_SerializeToByteArray_WritesExpectedFormat() - { - var numberEncoderService = new NumberEncoderService(); - var record = CreateRecordWithSomeGoodTestData(); - - var actualBytes = record.SerializeToByteArray(numberEncoderService); - - var expectedBytes = GetExpectedBytes(record, numberEncoderService); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void ESFRecord_DeserializeFromByteArray_HasCorrectData() - { - var numberEncoderService = new NumberEncoderService(); - var sourceRecord = CreateRecordWithSomeGoodTestData(); - var sourceRecordBytes = GetExpectedBytesWithoutNames(sourceRecord, numberEncoderService); - - var record = new ESFRecord { ID = sourceRecord.ID, Name = sourceRecord.Name, Shout = sourceRecord.Shout }; - record.DeserializeFromByteArray(sourceRecordBytes, numberEncoderService); - - var properties = record.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - - Assert.IsTrue(properties.Length > 0); - - foreach (var property in properties) - { - var expectedValue = property.GetValue(sourceRecord); - var actualValue = property.GetValue(record); - - Assert.AreEqual(expectedValue, actualValue, "Property: {0}", property.Name); - } - } - - [Test] - public void ESFRecord_DeserializeFromByteArray_InvalidArrayLength_ThrowsException() - { - var record = new ESFRecord(); - - Assert.Throws(() => record.DeserializeFromByteArray(new byte[] { 1, 2, 3 }, new NumberEncoderService())); - } - - private static ESFRecord CreateRecordWithSomeGoodTestData() - { - return new ESFRecord - { - ID = 1, - Name = "TestName", - Shout = "TestShout", - Icon = 321, - Graphic = 123, - TP = 400, - SP = 900, - - CastTime = 12, - - Type = SpellType.Bard, - TargetRestrict = SpellTargetRestrict.Opponent, - Target = SpellTarget.Unknown1, - - MinDam = 3212, - MaxDam = 16543, - Accuracy = 222, - HP = 777 - }; - } - - private static byte[] GetExpectedBytes(ESFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Name.Length, 1)); - ret.AddRange(nes.EncodeNumber(rec.Shout.Length, 1)); - ret.AddRange(Encoding.ASCII.GetBytes(rec.Name)); - ret.AddRange(Encoding.ASCII.GetBytes(rec.Shout)); - ret.AddRange(GetExpectedBytesWithoutNames(rec, nes)); - - return ret.ToArray(); - } - - private static byte[] GetExpectedBytesWithoutNames(ESFRecord rec, INumberEncoderService nes) - { - var ret = new List(); - - ret.AddRange(nes.EncodeNumber(rec.Icon, 2)); - ret.AddRange(nes.EncodeNumber(rec.Graphic, 2)); - ret.AddRange(nes.EncodeNumber(rec.TP, 2)); - ret.AddRange(nes.EncodeNumber(rec.SP, 2)); - ret.AddRange(nes.EncodeNumber(rec.CastTime, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte9, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte10, 1)); - ret.AddRange(nes.EncodeNumber((int)rec.Type, 3)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte14, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort15, 2)); - ret.AddRange(nes.EncodeNumber((byte)rec.TargetRestrict, 1)); - ret.AddRange(nes.EncodeNumber((byte)rec.Target, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte19, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte20, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort21, 2)); - ret.AddRange(nes.EncodeNumber(rec.MinDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.MaxDam, 2)); - ret.AddRange(nes.EncodeNumber(rec.Accuracy, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort29, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort31, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte33, 1)); - ret.AddRange(nes.EncodeNumber(rec.HP, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort36, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkByte38, 1)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort39, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort41, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort43, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort45, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort47, 2)); - ret.AddRange(nes.EncodeNumber(rec.UnkShort49, 2)); - - return ret.ToArray(); - } - } -} diff --git a/EOLib.IO/Caster.cs b/EOLib.IO/Caster.cs new file mode 100644 index 000000000..a05496011 --- /dev/null +++ b/EOLib.IO/Caster.cs @@ -0,0 +1,37 @@ +// This implementation was yoinked directly from: https://stackoverflow.com/a/23391746/2562283 + +using System; +using System.Linq.Expressions; + +namespace EOLib.IO +{ + /// + /// Class to cast to type + /// + /// Target type + public static class CastTo + { + /// + /// Casts to . + /// This does not cause boxing for value types. + /// Useful in generic methods. + /// + /// Source type to cast from. Usually a generic type. + public static T From(S s) + { + return Cache.caster(s); + } + + private static class Cache + { + public static readonly Func caster = Get(); + + private static Func Get() + { + var p = Expression.Parameter(typeof(S)); + var c = Expression.ConvertChecked(p, typeof(T)); + return Expression.Lambda>(c, p).Compile(); + } + } + } +} diff --git a/EOLib.IO/Extensions/EIFFileExtensions.cs b/EOLib.IO/Extensions/EIFFileExtensions.cs index 21bba5cf9..ce6221bbd 100644 --- a/EOLib.IO/Extensions/EIFFileExtensions.cs +++ b/EOLib.IO/Extensions/EIFFileExtensions.cs @@ -7,10 +7,10 @@ public static class EIFFileExtensions { public static bool IsShieldOnBack(this IPubFile itemFile, short graphic) { - if (itemFile == null || itemFile.Data == null) + if (itemFile == null) return false; - var shieldInfo = itemFile.Data.FirstOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == graphic); + var shieldInfo = itemFile.FirstOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == graphic); return shieldInfo != null && (shieldInfo.Name == "Bag" || @@ -20,10 +20,10 @@ public static bool IsShieldOnBack(this IPubFile itemFile, short graph public static bool IsRangedWeapon(this IPubFile itemFile, short graphic) { - if (itemFile == null || itemFile.Data == null) + if (itemFile == null) return false; - var weaponInfo = itemFile.Data.FirstOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == graphic); + var weaponInfo = itemFile.FirstOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == graphic); return weaponInfo != null && (weaponInfo.Name == "Gun" || weaponInfo.SubType == ItemSubType.Ranged); } diff --git a/EOLib.IO/Pub/BasePubFile.cs b/EOLib.IO/Pub/BasePubFile.cs index 118aa75ef..8d594dc88 100644 --- a/EOLib.IO/Pub/BasePubFile.cs +++ b/EOLib.IO/Pub/BasePubFile.cs @@ -1,73 +1,82 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Text; -using EOLib.IO.Services; namespace EOLib.IO.Pub { - public abstract class BasePubFile : IPubFile - where T : class, IPubRecord, new() + public abstract class BasePubFile : IPubFile + where TRecord : class, IPubRecord, new() { - protected readonly List _data; + private readonly List _data; public abstract string FileType { get; } - public int CheckSum { get; set; } + public int CheckSum { get; private set; } public int Length => _data.Count; - public T this[int id] + // pub files use 1-based indexing + public TRecord this[int id] => _data[id - 1]; + + protected BasePubFile() + { + _data = new List(); + } + + protected BasePubFile(int checksum, List data) { - get - { - if (id < 1 || id > _data.Count) - return null; + CheckSum = checksum; + _data = data; + } - return _data[id - 1]; - } + public IPubFile WithCheckSum(int checksum) + { + var copy = MakeCopy(); + copy.CheckSum = checksum; + return copy; } - public IReadOnlyList Data => _data; + public IPubFile WithAddedRecord(TRecord record) + { + var copy = MakeCopy(); + copy._data.Add(record); + return copy; + } - protected BasePubFile() + public IPubFile WithUpdatedRecord(TRecord record) { - _data = new List(); + if (_data.Count <= record.ID) + throw new ArgumentException($"Record {record.ID} ({record.Name}) is not part of the pub file"); + + var copy = MakeCopy(); + copy._data[record.ID] = record; + return copy; + } + + public IPubFile WithRemovedRecord(TRecord record) + { + var copy = MakeCopy(); + if (copy._data.Remove(record)) + AdjustIDs(copy._data); + return copy; + } + + public IEnumerator GetEnumerator() + { + return _data.GetEnumerator(); } - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService, bool rewriteChecksum = true) + IEnumerator IEnumerable.GetEnumerator() { - byte[] fileBytes; - - using (var mem = new MemoryStream()) //write to memory so we can get a CRC for the new RID value - { - mem.Write(Encoding.ASCII.GetBytes(FileType), 0, 3); - mem.Write(numberEncoderService.EncodeNumber(0, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(0, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Length, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber(1, 1)[0]); - - foreach (var dataRecord in _data) - { - var toWrite = dataRecord.SerializeToByteArray(numberEncoderService); - mem.Write(toWrite, 0, toWrite.Length); - } - - fileBytes = mem.ToArray(); - } - - var checksumBytes = numberEncoderService.EncodeNumber(CheckSum, 4); - if (rewriteChecksum) - { - var checksum = CRC32.Check(fileBytes); - checksumBytes = numberEncoderService.EncodeNumber((int)checksum, 4); - } - - Array.Copy(checksumBytes, 0, fileBytes, 3, 4); - return fileBytes; + return _data.GetEnumerator(); } - public abstract void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService); + protected abstract BasePubFile MakeCopy(); + + private static void AdjustIDs(List data) + { + for (int i = 0; i < data.Count; i++) + data[i] = (TRecord)data[i].WithID(i); + } } } diff --git a/EOLib.IO/Pub/ECFFile.cs b/EOLib.IO/Pub/ECFFile.cs index 7fca2ae8d..05c29f38e 100644 --- a/EOLib.IO/Pub/ECFFile.cs +++ b/EOLib.IO/Pub/ECFFile.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { @@ -8,42 +6,18 @@ public class ECFFile : BasePubFile { public override string FileType => "ECF"; - public override void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService) + public ECFFile() { - using (var mem = new MemoryStream(bytes)) - ReadFromStream(mem, numberEncoderService); } - private void ReadFromStream(Stream mem, INumberEncoderService numberEncoderService) + public ECFFile(int checksum, List data) + : base(checksum, data) { - mem.Seek(3, SeekOrigin.Begin); - - var checksum = new byte[4]; - mem.Read(checksum, 0, 4); - CheckSum = numberEncoderService.DecodeNumber(checksum); - - var lenBytes = new byte[2]; - mem.Read(lenBytes, 0, 2); - var recordsInFile = (short)numberEncoderService.DecodeNumber(lenBytes); - - mem.Seek(1, SeekOrigin.Current); - - var rawData = new byte[ECFRecord.DATA_SIZE]; - for (int i = 1; i <= recordsInFile && mem.Position < mem.Length; ++i) - { - var nameLength = numberEncoderService.DecodeNumber((byte)mem.ReadByte()); - var rawName = new byte[nameLength]; - mem.Read(rawName, 0, nameLength); - mem.Read(rawData, 0, ECFRecord.DATA_SIZE); - - var record = new ECFRecord { ID = i, Name = Encoding.ASCII.GetString(rawName) }; - record.DeserializeFromByteArray(rawData, numberEncoderService); - - _data.Add(record); - } + } - if (recordsInFile != Length) - throw new IOException("Mismatch between expected length and actual length!"); + protected override BasePubFile MakeCopy() + { + return new ECFFile(CheckSum, new List(this)); } } } diff --git a/EOLib.IO/Pub/ECFRecord.cs b/EOLib.IO/Pub/ECFRecord.cs index f348f64af..84019807b 100644 --- a/EOLib.IO/Pub/ECFRecord.cs +++ b/EOLib.IO/Pub/ECFRecord.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -7,81 +8,36 @@ namespace EOLib.IO.Pub { - public class ECFRecord : IPubRecord + public class ECFRecord : PubRecord { - public const int DATA_SIZE = 14; - - public int RecordSize => DATA_SIZE; - - public int ID { get; set; } - - public string Name { get; set; } - - public byte Base { get; set; } - public byte Type { get; set; } - - public short Str { get; set; } - public short Int { get; set; } - public short Wis { get; set; } - public short Agi { get; set; } - public short Con { get; set; } - public short Cha { get; set; } - - public TValue Get(PubRecordProperty type) + public byte Base => Get(PubRecordProperty.ClassBase); + public byte Type => Get(PubRecordProperty.ClassType); + + public short Str => Get(PubRecordProperty.ClassStr); + public short Int => Get(PubRecordProperty.ClassInt); + public short Wis => Get(PubRecordProperty.ClassWis); + public short Agi => Get(PubRecordProperty.ClassAgi); + public short Con => Get(PubRecordProperty.ClassCon); + public short Cha => Get(PubRecordProperty.ClassCha); + + public ECFRecord() + : this(0, string.Empty) { - var name = Enum.GetName(type.GetType(), type) ?? ""; - if (!name.StartsWith("Global") && !name.StartsWith("Class")) - throw new ArgumentOutOfRangeException(nameof(type), "Unsupported property requested for ECFRecord"); - - if (name.StartsWith("Global")) - name = name.Substring(6); - else if (name.StartsWith("Class")) - name = name.Substring(5); - - var propertyInfo = GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); - var boxedValue = propertyInfo.GetValue(this); - - return (TValue)boxedValue; } - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService) + public ECFRecord(int id, string name) + : base(id, name, PubRecordProperty.Class) { - var ret = Enumerable.Repeat(254, DATA_SIZE + 1 + Name.Length).ToArray(); - - using (var mem = new MemoryStream(ret)) - { - mem.WriteByte(numberEncoderService.EncodeNumber(Name.Length, 1)[0]); - var name = Encoding.ASCII.GetBytes(Name); - mem.Write(name, 0, name.Length); - - mem.WriteByte(numberEncoderService.EncodeNumber(Base, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Type, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(Str, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Int, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Wis, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Agi, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Con, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Cha, 2), 0, 2); - } - - return ret; } - public void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService) + private ECFRecord(int id, List names, Dictionary propertyBag) + : base(id, names, propertyBag) { - if (recordBytes.Length != DATA_SIZE) - throw new ArgumentOutOfRangeException(nameof(recordBytes), "Data is not properly sized for correct deserialization"); - - Base = (byte)numberEncoderService.DecodeNumber(recordBytes[0]); - Type = (byte)numberEncoderService.DecodeNumber(recordBytes[1]); + } - Str = (short)numberEncoderService.DecodeNumber(recordBytes[2], recordBytes[3]); - Int = (short)numberEncoderService.DecodeNumber(recordBytes[4], recordBytes[5]); - Wis = (short)numberEncoderService.DecodeNumber(recordBytes[6], recordBytes[7]); - Agi = (short)numberEncoderService.DecodeNumber(recordBytes[8], recordBytes[9]); - Con = (short)numberEncoderService.DecodeNumber(recordBytes[10], recordBytes[11]); - Cha = (short)numberEncoderService.DecodeNumber(recordBytes[12], recordBytes[13]); + protected override PubRecord MakeCopy(List names, Dictionary propertyBag) + { + return new ECFRecord(ID, new List(names), new Dictionary(propertyBag)); } } } diff --git a/EOLib.IO/Pub/EIFFile.cs b/EOLib.IO/Pub/EIFFile.cs index 602c6a274..5a00f9730 100644 --- a/EOLib.IO/Pub/EIFFile.cs +++ b/EOLib.IO/Pub/EIFFile.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { @@ -8,42 +6,18 @@ public class EIFFile : BasePubFile { public override string FileType => "EIF"; - public override void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService) + public EIFFile() { - using (var mem = new MemoryStream(bytes)) - ReadFromStream(mem, numberEncoderService); } - private void ReadFromStream(Stream mem, INumberEncoderService numberEncoderService) + public EIFFile(int checksum, List data) + : base (checksum, data) { - mem.Seek(3, SeekOrigin.Begin); - - var checksum = new byte[4]; - mem.Read(checksum, 0, 4); - CheckSum = numberEncoderService.DecodeNumber(checksum); - - var lenBytes = new byte[2]; - mem.Read(lenBytes, 0, 2); - var recordsInFile = (short)numberEncoderService.DecodeNumber(lenBytes); - - mem.Seek(1, SeekOrigin.Current); - - var rawData = new byte[EIFRecord.DATA_SIZE]; - for (int i = 1; i <= recordsInFile && mem.Position < mem.Length; ++i) - { - var nameLength = numberEncoderService.DecodeNumber((byte)mem.ReadByte()); - var rawName = new byte[nameLength]; - mem.Read(rawName, 0, nameLength); - mem.Read(rawData, 0, EIFRecord.DATA_SIZE); - - var record = new EIFRecord {ID = i, Name = Encoding.ASCII.GetString(rawName)}; - record.DeserializeFromByteArray(rawData, numberEncoderService); - - _data.Add(record); - } + } - if (recordsInFile != Length) - throw new IOException("Mismatch between expected length and actual length!"); + protected override BasePubFile MakeCopy() + { + return new EIFFile(CheckSum, new List(this)); } } } diff --git a/EOLib.IO/Pub/EIFRecord.cs b/EOLib.IO/Pub/EIFRecord.cs index dab8a562f..8c3ea5efc 100644 --- a/EOLib.IO/Pub/EIFRecord.cs +++ b/EOLib.IO/Pub/EIFRecord.cs @@ -1,224 +1,88 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { - public class EIFRecord : IPubRecord + public class EIFRecord : PubRecord { - public const int DATA_SIZE = 58; - - public int RecordSize => DATA_SIZE; - - public int ID { get; set; } - - public string Name { get; set; } - - public short Graphic { get; set; } - public ItemType Type { get; set; } - public ItemSubType SubType { get; set; } - - public ItemSpecial Special { get; set; } - public short HP { get; set; } - public short TP { get; set; } - public short MinDam { get; set; } - public short MaxDam { get; set; } - public short Accuracy { get; set; } - public short Evade { get; set; } - public short Armor { get; set; } - - public byte UnkByte19 { get; set; } - - public byte Str { get; set; } - public byte Int { get; set; } - public byte Wis { get; set; } - public byte Agi { get; set; } - public byte Con { get; set; } - public byte Cha { get; set; } - - public byte Light { get; set; } - public byte Dark { get; set; } - public byte Earth { get; set; } - public byte Air { get; set; } - public byte Water { get; set; } - public byte Fire { get; set; } - - private int _itemSpecificParam1; - private byte _itemSpecificParam2; - private byte _itemSpecificParam3; - - public int ScrollMap { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - public int DollGraphic { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - public int ExpReward { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - public int HairColor { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - public int Effect { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - public int Key { get { return _itemSpecificParam1; } set { _itemSpecificParam1 = value; } } - - public byte Gender { get { return _itemSpecificParam2; } set { _itemSpecificParam2 = value; } } - public byte ScrollX { get { return _itemSpecificParam2; } set { _itemSpecificParam2 = value; } } - - public byte ScrollY { get { return _itemSpecificParam3; } set { _itemSpecificParam3 = value; } } - public byte DualWieldDollGraphic { get { return _itemSpecificParam3; } set { _itemSpecificParam3 = value; } } - - public short LevelReq { get; set; } - public short ClassReq { get; set; } - public short StrReq { get; set; } - public short IntReq { get; set; } - public short WisReq { get; set; } - public short AgiReq { get; set; } - public short ConReq { get; set; } - public short ChaReq { get; set; } - - public byte Element { get; set; } - public byte ElementPower { get; set; } - - public byte Weight { get; set; } - - public byte UnkByte56 { get; set; } - - public ItemSize Size { get; set; } - - public TValue Get(PubRecordProperty type) + public short Graphic => Get(PubRecordProperty.ItemGraphic); + + public ItemType Type => Get(PubRecordProperty.ItemType); + public ItemSubType SubType => Get(PubRecordProperty.ItemSubType); + + public ItemSpecial Special => Get(PubRecordProperty.ItemSpecial); + public short HP => Get(PubRecordProperty.ItemHP); + public short TP => Get(PubRecordProperty.ItemTP); + public short MinDam => Get(PubRecordProperty.ItemMinDam); + public short MaxDam => Get(PubRecordProperty.ItemMaxDam); + public short Accuracy => Get(PubRecordProperty.ItemAccuracy); + public short Evade => Get(PubRecordProperty.ItemEvade); + public short Armor => Get(PubRecordProperty.ItemArmor); + + public byte UnkByte19 => Get(PubRecordProperty.ItemUnkByte19); + + public byte Str => Get(PubRecordProperty.ItemStr); + public byte Int => Get(PubRecordProperty.ItemInt); + public byte Wis => Get(PubRecordProperty.ItemWis); + public byte Agi => Get(PubRecordProperty.ItemAgi); + public byte Con => Get(PubRecordProperty.ItemCon); + public byte Cha => Get(PubRecordProperty.ItemCha); + + public byte Light => Get(PubRecordProperty.ItemLight); + public byte Dark => Get(PubRecordProperty.ItemDark); + public byte Earth => Get(PubRecordProperty.ItemEarth); + public byte Air => Get(PubRecordProperty.ItemAir); + public byte Water => Get(PubRecordProperty.ItemWater); + public byte Fire => Get(PubRecordProperty.ItemFire); + + public int ScrollMap => Get(PubRecordProperty.ItemScrollMap); + public int DollGraphic => Get(PubRecordProperty.ItemDollGraphic); + public int ExpReward => Get(PubRecordProperty.ItemExpReward); + public int HairColor => Get(PubRecordProperty.ItemHairColor); + public int Effect => Get(PubRecordProperty.ItemEffect); + public int Key => Get(PubRecordProperty.ItemKey); + + public byte Gender => Get(PubRecordProperty.ItemGender); + public byte ScrollX => Get(PubRecordProperty.ItemScrollX); + + public byte ScrollY => Get(PubRecordProperty.ItemScrollY); + public byte DualWieldDollGraphic => Get(PubRecordProperty.ItemDualWieldDollGraphic); + + public short LevelReq => Get(PubRecordProperty.ItemLevelReq); + public short ClassReq => Get(PubRecordProperty.ItemClassReq); + public short StrReq => Get(PubRecordProperty.ItemStrReq); + public short IntReq => Get(PubRecordProperty.ItemIntReq); + public short WisReq => Get(PubRecordProperty.ItemWisReq); + public short AgiReq => Get(PubRecordProperty.ItemAgiReq); + public short ConReq => Get(PubRecordProperty.ItemConReq); + public short ChaReq => Get(PubRecordProperty.ItemChaReq); + + public byte Element => Get(PubRecordProperty.ItemElement); + public byte ElementPower => Get(PubRecordProperty.ItemElementPower); + + public byte Weight => Get(PubRecordProperty.ItemWeight); + + public byte UnkByte56 => Get(PubRecordProperty.ItemUnkByte56); + + public ItemSize Size => Get(PubRecordProperty.ItemSize); + + public EIFRecord() + : this(0, string.Empty) { - var name = Enum.GetName(type.GetType(), type) ?? ""; - if (!name.StartsWith("Global") && !name.StartsWith("Item")) - throw new ArgumentOutOfRangeException(nameof(type), "Unsupported property requested for EIFRecord"); - - if (name.StartsWith("Global")) - name = name.Substring(6); - else if (name.StartsWith("Item")) - name = name.Substring(4); - - var propertyInfo = GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); - var boxedValue = propertyInfo.GetValue(this); + } - return (TValue)boxedValue; + public EIFRecord(int id, string name) + : base (id, name, PubRecordProperty.Item) + { } - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService) + private EIFRecord(int id, List names, Dictionary propertyBag) + : base(id, names, propertyBag) { - var ret = Enumerable.Repeat(254, DATA_SIZE + 1 + Name.Length).ToArray(); - - using (var mem = new MemoryStream(ret)) - { - mem.WriteByte(numberEncoderService.EncodeNumber(Name.Length, 1)[0]); - var name = Encoding.ASCII.GetBytes(Name); - mem.Write(name, 0, name.Length); - - mem.Write(numberEncoderService.EncodeNumber(Graphic, 2), 0, 2); - mem.WriteByte(numberEncoderService.EncodeNumber((byte)Type, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber((byte)SubType, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber((byte)Special, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(HP, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(TP, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(MinDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(MaxDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Accuracy, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Evade, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Armor, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte19, 1)[0]); - - mem.WriteByte(numberEncoderService.EncodeNumber(Str, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Int, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Wis, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Agi, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Con, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Cha, 1)[0]); - - mem.WriteByte(numberEncoderService.EncodeNumber(Light, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Dark, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Earth, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Air, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Water, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Fire, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(ScrollMap, 3), 0, 3); - mem.WriteByte(numberEncoderService.EncodeNumber(ScrollX, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(ScrollY, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(LevelReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(ClassReq, 2), 0, 2); - - mem.Write(numberEncoderService.EncodeNumber(StrReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(IntReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(WisReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(AgiReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(ConReq, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(ChaReq, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber(Element, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(ElementPower, 1)[0]); - - mem.WriteByte(numberEncoderService.EncodeNumber(Weight, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte56, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber((byte)Size, 1)[0]); - } - - return ret; } - public void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService) + protected override PubRecord MakeCopy(List names, Dictionary propertyBag) { - if (recordBytes.Length != DATA_SIZE) - throw new ArgumentOutOfRangeException(nameof(recordBytes), "Data is not properly sized for correct deserialization"); - - Graphic = (short)numberEncoderService.DecodeNumber(recordBytes[0], recordBytes[1]); - Type = (ItemType)numberEncoderService.DecodeNumber(recordBytes[2]); - SubType = (ItemSubType)numberEncoderService.DecodeNumber(recordBytes[3]); - - Special = (ItemSpecial)numberEncoderService.DecodeNumber(recordBytes[4]); - HP = (short)numberEncoderService.DecodeNumber(recordBytes[5], recordBytes[6]); - TP = (short)numberEncoderService.DecodeNumber(recordBytes[7], recordBytes[8]); - MinDam = (short)numberEncoderService.DecodeNumber(recordBytes[9], recordBytes[10]); - MaxDam = (short)numberEncoderService.DecodeNumber(recordBytes[11], recordBytes[12]); - Accuracy = (short)numberEncoderService.DecodeNumber(recordBytes[13], recordBytes[14]); - Evade = (short)numberEncoderService.DecodeNumber(recordBytes[15], recordBytes[16]); - Armor = (short)numberEncoderService.DecodeNumber(recordBytes[17], recordBytes[18]); - - UnkByte19 = (byte)numberEncoderService.DecodeNumber(recordBytes[19]); - - Str = (byte)numberEncoderService.DecodeNumber(recordBytes[20]); - Int = (byte)numberEncoderService.DecodeNumber(recordBytes[21]); - Wis = (byte)numberEncoderService.DecodeNumber(recordBytes[22]); - Agi = (byte)numberEncoderService.DecodeNumber(recordBytes[23]); - Con = (byte)numberEncoderService.DecodeNumber(recordBytes[24]); - Cha = (byte)numberEncoderService.DecodeNumber(recordBytes[25]); - - Light = (byte)numberEncoderService.DecodeNumber(recordBytes[26]); - Dark = (byte)numberEncoderService.DecodeNumber(recordBytes[27]); - Earth = (byte)numberEncoderService.DecodeNumber(recordBytes[28]); - Air = (byte)numberEncoderService.DecodeNumber(recordBytes[29]); - Water = (byte)numberEncoderService.DecodeNumber(recordBytes[30]); - Fire = (byte)numberEncoderService.DecodeNumber(recordBytes[31]); - - ScrollMap = numberEncoderService.DecodeNumber(recordBytes[32], recordBytes[33], recordBytes[34]); - ScrollX = (byte)numberEncoderService.DecodeNumber(recordBytes[35]); - ScrollY = (byte)numberEncoderService.DecodeNumber(recordBytes[36]); - - LevelReq = (short)numberEncoderService.DecodeNumber(recordBytes[37], recordBytes[38]); - ClassReq = (short)numberEncoderService.DecodeNumber(recordBytes[39], recordBytes[40]); - - StrReq = (short)numberEncoderService.DecodeNumber(recordBytes[41], recordBytes[42]); - IntReq = (short)numberEncoderService.DecodeNumber(recordBytes[43], recordBytes[44]); - WisReq = (short)numberEncoderService.DecodeNumber(recordBytes[45], recordBytes[46]); - AgiReq = (short)numberEncoderService.DecodeNumber(recordBytes[47], recordBytes[48]); - ConReq = (short)numberEncoderService.DecodeNumber(recordBytes[49], recordBytes[50]); - ChaReq = (short)numberEncoderService.DecodeNumber(recordBytes[51], recordBytes[52]); - - Element = (byte)numberEncoderService.DecodeNumber(recordBytes[53]); - ElementPower = (byte)numberEncoderService.DecodeNumber(recordBytes[54]); - - Weight = (byte)numberEncoderService.DecodeNumber(recordBytes[55]); - UnkByte56 = (byte)numberEncoderService.DecodeNumber(recordBytes[56]); - Size = (ItemSize)numberEncoderService.DecodeNumber(recordBytes[57]); - - if (ID == 365 && Name == "Gun") - SubType = ItemSubType.Ranged; + return new EIFRecord(ID, new List(names), new Dictionary(propertyBag)); } } } \ No newline at end of file diff --git a/EOLib.IO/Pub/ENFFile.cs b/EOLib.IO/Pub/ENFFile.cs index 6d15fda4d..714a6a6b1 100644 --- a/EOLib.IO/Pub/ENFFile.cs +++ b/EOLib.IO/Pub/ENFFile.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { @@ -8,42 +6,18 @@ public class ENFFile : BasePubFile { public override string FileType => "ENF"; - public override void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService) + public ENFFile() { - using (var mem = new MemoryStream(bytes)) - ReadFromStream(mem, numberEncoderService); } - private void ReadFromStream(Stream mem, INumberEncoderService numberEncoderService) + public ENFFile(int checksum, List data) + : base(checksum, data) { - mem.Seek(3, SeekOrigin.Begin); - - var checksum = new byte[4]; - mem.Read(checksum, 0, 4); - CheckSum = numberEncoderService.DecodeNumber(checksum); - - var lenBytes = new byte[2]; - mem.Read(lenBytes, 0, 2); - var recordsInFile = (short)numberEncoderService.DecodeNumber(lenBytes); - - mem.Seek(1, SeekOrigin.Current); - - var rawData = new byte[ENFRecord.DATA_SIZE]; - for (int i = 1; i <= recordsInFile && mem.Position < mem.Length; ++i) - { - var nameLength = numberEncoderService.DecodeNumber((byte)mem.ReadByte()); - var rawName = new byte[nameLength]; - mem.Read(rawName, 0, nameLength); - mem.Read(rawData, 0, ENFRecord.DATA_SIZE); - - var record = new ENFRecord { ID = i, Name = Encoding.ASCII.GetString(rawName) }; - record.DeserializeFromByteArray(rawData, numberEncoderService); - - _data.Add(record); - } + } - if (recordsInFile != Length) - throw new IOException("Mismatch between expected length and actual length!"); + protected override BasePubFile MakeCopy() + { + return new ENFFile(CheckSum, new List(this)); } } } diff --git a/EOLib.IO/Pub/ENFRecord.cs b/EOLib.IO/Pub/ENFRecord.cs index 0ccab1957..59297d599 100644 --- a/EOLib.IO/Pub/ENFRecord.cs +++ b/EOLib.IO/Pub/ENFRecord.cs @@ -1,144 +1,59 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { - public class ENFRecord : IPubRecord + public class ENFRecord : PubRecord { - public const int DATA_SIZE = 39; + public int Graphic => Get(PubRecordProperty.NPCGraphic); - public int RecordSize => DATA_SIZE; + public byte UnkByte2 => Get(PubRecordProperty.NPCUnkByte2); - public int ID { get; set; } + public short Boss => Get(PubRecordProperty.NPCBoss); + public short Child => Get(PubRecordProperty.NPCChild); + public NPCType Type => Get(PubRecordProperty.NPCType); - public string Name { get; set; } + public short UnkShort14 => Get(PubRecordProperty.NPCUnkShort14); - public int Graphic { get; set; } + public short VendorID => Get(PubRecordProperty.NPCVendorID); - public byte UnkByte2 { get; set; } + public int HP => Get(PubRecordProperty.NPCHP); - public short Boss { get; set; } - public short Child { get; set; } - public NPCType Type { get; set; } + public short MinDam => Get(PubRecordProperty.NPCMinDam); + public short MaxDam => Get(PubRecordProperty.NPCMaxDam); - public short UnkShort14 { get; set; } + public short Accuracy => Get(PubRecordProperty.NPCAccuracy); + public short Evade => Get(PubRecordProperty.NPCEvade); + public short Armor => Get(PubRecordProperty.NPCArmor); - public short VendorID { get; set; } + public byte UnkByte26 => Get(PubRecordProperty.NPCUnkByte26); + public short UnkShort27 => Get(PubRecordProperty.NPCUnkShort27); + public short UnkShort29 => Get(PubRecordProperty.NPCUnkShort29); - public int HP { get; set; } + public short ElementWeak => Get(PubRecordProperty.NPCElementWeak); + public short ElementWeakPower => Get(PubRecordProperty.NPCElementWeakPower); - public short MinDam { get; set; } - public short MaxDam { get; set; } + public byte UnkByte35 => Get(PubRecordProperty.NPCUnkByte35); - public short Accuracy { get; set; } - public short Evade { get; set; } - public short Armor { get; set; } + public int Exp => Get(PubRecordProperty.NPCExp); - public byte UnkByte26 { get; set; } - public short UnkShort27 { get; set; } - public short UnkShort29 { get; set; } - - public short ElementWeak { get; set; } - public short ElementWeakPower { get; set; } - - public byte UnkByte35 { get; set; } - - public int Exp { get; set; } - - public TValue Get(PubRecordProperty type) + public ENFRecord() + : this(0, string.Empty) { - var name = Enum.GetName(type.GetType(), type) ?? ""; - if (!name.StartsWith("Global") && !name.StartsWith("NPC")) - throw new ArgumentOutOfRangeException(nameof(type), "Unsupported property requested for ENFRecord"); - - if (name.StartsWith("Global")) - name = name.Substring(6); - else if (name.StartsWith("NPC")) - name = name.Substring(3); - - var propertyInfo = GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); - var boxedValue = propertyInfo.GetValue(this); - - return (TValue)boxedValue; } - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService) + public ENFRecord(int id, string name) + : base(id, name, PubRecordProperty.NPC) { - var ret = Enumerable.Repeat(254, DATA_SIZE + 1 + Name.Length).ToArray(); - - using (var mem = new MemoryStream(ret)) - { - mem.WriteByte(numberEncoderService.EncodeNumber(Name.Length, 1)[0]); - var name = Encoding.ASCII.GetBytes(Name); - mem.Write(name, 0, name.Length); - - mem.Write(numberEncoderService.EncodeNumber(Graphic, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte2, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(Boss, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Child, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber((short)Type, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(VendorID, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(HP, 3), 0, 3); - - mem.Write(numberEncoderService.EncodeNumber(UnkShort14, 2), 0, 2); - - mem.Write(numberEncoderService.EncodeNumber(MinDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(MaxDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Accuracy, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Evade, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Armor, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte26, 1)[0]); - mem.Write(numberEncoderService.EncodeNumber(UnkShort27, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort29, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(ElementWeak, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(ElementWeakPower, 2), 0, 2); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte35, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(Exp, 3), 0, 3); - } - - return ret; } - public void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService) + private ENFRecord(int id, List names, Dictionary propertyBag) + : base(id, names, propertyBag) { - if (recordBytes.Length != DATA_SIZE) - throw new ArgumentOutOfRangeException(nameof(recordBytes), "Data is not properly sized for correct deserialization"); - - Graphic = numberEncoderService.DecodeNumber(recordBytes[0], recordBytes[1]); - - UnkByte2 = (byte)numberEncoderService.DecodeNumber(recordBytes[2]); - - Boss = (short)numberEncoderService.DecodeNumber(recordBytes[3], recordBytes[4]); - Child = (short)numberEncoderService.DecodeNumber(recordBytes[5], recordBytes[6]); - Type = (NPCType)numberEncoderService.DecodeNumber(recordBytes[7], recordBytes[8]); - VendorID = (short)numberEncoderService.DecodeNumber(recordBytes[9], recordBytes[10]); - HP = numberEncoderService.DecodeNumber(recordBytes[11], recordBytes[12], recordBytes[13]); - - UnkShort14 = (short)numberEncoderService.DecodeNumber(recordBytes[14], recordBytes[15]); - - MinDam = (short)numberEncoderService.DecodeNumber(recordBytes[16], recordBytes[17]); - MaxDam = (short)numberEncoderService.DecodeNumber(recordBytes[18], recordBytes[19]); - - Accuracy = (short)numberEncoderService.DecodeNumber(recordBytes[20], recordBytes[21]); - Evade = (short)numberEncoderService.DecodeNumber(recordBytes[22], recordBytes[23]); - Armor = (short)numberEncoderService.DecodeNumber(recordBytes[24], recordBytes[25]); - - UnkByte26 = (byte)numberEncoderService.DecodeNumber(recordBytes[26]); - UnkShort27 = (short)numberEncoderService.DecodeNumber(recordBytes[27], recordBytes[28]); - UnkShort29 = (short)numberEncoderService.DecodeNumber(recordBytes[29], recordBytes[30]); - ElementWeak = (short)numberEncoderService.DecodeNumber(recordBytes[31], recordBytes[32]); - ElementWeakPower = (short)numberEncoderService.DecodeNumber(recordBytes[33], recordBytes[34]); - UnkByte35 = (byte)numberEncoderService.DecodeNumber(recordBytes[35]); + } - Exp = numberEncoderService.DecodeNumber(recordBytes[36], recordBytes[37], recordBytes[38]); + protected override PubRecord MakeCopy(List names, Dictionary propertyBag) + { + return new ENFRecord(ID, new List(names), new Dictionary(propertyBag)); } } } \ No newline at end of file diff --git a/EOLib.IO/Pub/ESFFile.cs b/EOLib.IO/Pub/ESFFile.cs index 5eeacc372..c0f00aea3 100644 --- a/EOLib.IO/Pub/ESFFile.cs +++ b/EOLib.IO/Pub/ESFFile.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { @@ -8,50 +6,18 @@ public class ESFFile : BasePubFile { public override string FileType => "ESF"; - public override void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService) + public ESFFile() { - using (var mem = new MemoryStream(bytes)) - ReadFromStream(mem, numberEncoderService); } - private void ReadFromStream(Stream mem, INumberEncoderService numberEncoderService) + public ESFFile(int checksum, List data) + : base(checksum, data) { - mem.Seek(3, SeekOrigin.Begin); - - var checksum = new byte[4]; - mem.Read(checksum, 0, 4); - CheckSum = numberEncoderService.DecodeNumber(checksum); - - var lenBytes = new byte[2]; - mem.Read(lenBytes, 0, 2); - var recordsInFile = (short)numberEncoderService.DecodeNumber(lenBytes); - - mem.Seek(1, SeekOrigin.Current); - - var rawData = new byte[ESFRecord.DATA_SIZE]; - for (int i = 1; i <= recordsInFile && mem.Position < mem.Length; ++i) - { - var nameLength = numberEncoderService.DecodeNumber((byte)mem.ReadByte()); - var shoutLength = numberEncoderService.DecodeNumber((byte)mem.ReadByte()); - var rawName = new byte[nameLength]; - var rawShout = new byte[shoutLength]; - mem.Read(rawName, 0, nameLength); - mem.Read(rawShout, 0, shoutLength); - mem.Read(rawData, 0, ESFRecord.DATA_SIZE); - - var record = new ESFRecord - { - ID = i, - Name = Encoding.ASCII.GetString(rawName), - Shout = Encoding.ASCII.GetString(rawShout) - }; - record.DeserializeFromByteArray(rawData, numberEncoderService); - - _data.Add(record); - } + } - if (recordsInFile != Length) - throw new IOException("Mismatch between expected length and actual length!"); + protected override BasePubFile MakeCopy() + { + return new ESFFile(CheckSum, new List(this)); } } } diff --git a/EOLib.IO/Pub/ESFRecord.cs b/EOLib.IO/Pub/ESFRecord.cs index 9cbca28f9..84231a477 100644 --- a/EOLib.IO/Pub/ESFRecord.cs +++ b/EOLib.IO/Pub/ESFRecord.cs @@ -1,183 +1,73 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { - public class ESFRecord : IPubRecord + public class ESFRecord : PubRecord { - public const int DATA_SIZE = 51; + public override int NumberOfNames => 2; - public int RecordSize => DATA_SIZE; + public string Shout => Names[1]; - public int ID { get; set; } + public short Icon => Get(PubRecordProperty.SpellIcon); + public short Graphic => Get(PubRecordProperty.SpellGraphic); - public string Name { get; set; } - public string Shout { get; set; } + public short TP => Get(PubRecordProperty.SpellTP); + public short SP => Get(PubRecordProperty.SpellSP); - public short Icon { get; set; } - public short Graphic { get; set; } + public byte CastTime => Get(PubRecordProperty.SpellCastTime); - public short TP { get; set; } - public short SP { get; set; } + public byte UnkByte9 => Get(PubRecordProperty.SpellUnkByte9); + public byte UnkByte10 => Get(PubRecordProperty.SpellUnkByte10); - public byte CastTime { get; set; } + public SpellType Type => Get(PubRecordProperty.SpellType); - public byte UnkByte9 { get; set; } - public byte UnkByte10 { get; set; } + public byte UnkByte14 => Get(PubRecordProperty.SpellUnkByte14); + public short UnkShort15 => Get(PubRecordProperty.SpellUnkShort15); - public SpellType Type { get; set; } + public SpellTargetRestrict TargetRestrict => Get(PubRecordProperty.SpellTargetRestrict); + public SpellTarget Target => Get(PubRecordProperty.SpellTarget); - public byte UnkByte14 { get; set; } - public short UnkShort15 { get; set; } + public byte UnkByte19 => Get(PubRecordProperty.SpellUnkByte19); + public byte UnkByte20 => Get(PubRecordProperty.SpellUnkByte20); + public short UnkShort21 => Get(PubRecordProperty.SpellUnkShort21); - public SpellTargetRestrict TargetRestrict { get; set; } - public SpellTarget Target { get; set; } + public short MinDam => Get(PubRecordProperty.SpellMinDam); + public short MaxDam => Get(PubRecordProperty.SpellMaxDam); + public short Accuracy => Get(PubRecordProperty.SpellAccuracy); - public byte UnkByte19 { get; set; } - public byte UnkByte20 { get; set; } - public short UnkShort21 { get; set; } + public short UnkShort29 => Get(PubRecordProperty.SpellUnkShort29); + public short UnkShort31 => Get(PubRecordProperty.SpellUnkShort31); + public byte UnkByte33 => Get(PubRecordProperty.SpellUnkByte33); - public short MinDam { get; set; } - public short MaxDam { get; set; } - public short Accuracy { get; set; } + public short HP => Get(PubRecordProperty.SpellHP); - public short UnkShort29 { get; set; } - public short UnkShort31 { get; set; } - public byte UnkByte33 { get; set; } + public short UnkShort36 => Get(PubRecordProperty.SpellUnkShort36); + public byte UnkByte38 => Get(PubRecordProperty.SpellUnkByte38); + public short UnkShort39 => Get(PubRecordProperty.SpellUnkShort39); + public short UnkShort41 => Get(PubRecordProperty.SpellUnkShort41); + public short UnkShort43 => Get(PubRecordProperty.SpellUnkShort43); + public short UnkShort45 => Get(PubRecordProperty.SpellUnkShort45); + public short UnkShort47 => Get(PubRecordProperty.SpellUnkShort47); + public short UnkShort49 => Get(PubRecordProperty.SpellUnkShort49); - public short HP { get; set; } - - public short UnkShort36 { get; set; } - public byte UnkByte38 { get; set; } - public short UnkShort39 { get; set; } - public short UnkShort41 { get; set; } - public short UnkShort43 { get; set; } - public short UnkShort45 { get; set; } - public short UnkShort47 { get; set; } - public short UnkShort49 { get; set; } - - public TValue Get(PubRecordProperty type) + public ESFRecord() + : this(0, string.Empty) { - var name = Enum.GetName(type.GetType(), type) ?? ""; - if (!name.StartsWith("Global") && !name.StartsWith("Spell")) - throw new ArgumentOutOfRangeException(nameof(type), "Unsupported property requested for ESFRecord"); - - if (name.StartsWith("Global")) - name = name.Substring(6); - else if (name.StartsWith("Spell")) - name = name.Substring(5); - - var propertyInfo = GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); - var boxedValue = propertyInfo.GetValue(this); - - return (TValue)boxedValue; } - public byte[] SerializeToByteArray(INumberEncoderService numberEncoderService) + public ESFRecord(int id, string name) + : base(id, name, PubRecordProperty.Spell) { - var ret = Enumerable.Repeat(254, DATA_SIZE + 2 + Name.Length + Shout.Length).ToArray(); - - using (var mem = new MemoryStream(ret)) - { - mem.WriteByte(numberEncoderService.EncodeNumber(Name.Length, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(Shout.Length, 1)[0]); - var name = Encoding.ASCII.GetBytes(Name); - var shout = Encoding.ASCII.GetBytes(Shout); //shout, shout, let it all out! - mem.Write(name, 0, name.Length); - mem.Write(shout, 0, shout.Length); - - mem.Write(numberEncoderService.EncodeNumber(Icon, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Graphic, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(TP, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(SP, 2), 0, 2); - mem.WriteByte(numberEncoderService.EncodeNumber(CastTime, 1)[0]); - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte9, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte10, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber((int)Type, 3), 0, 3); //This is documented as a 3 byte int. - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte14, 1)[0]); - mem.Write(numberEncoderService.EncodeNumber(UnkShort15, 2), 0, 2); - - mem.WriteByte(numberEncoderService.EncodeNumber((byte)TargetRestrict, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber((byte)Target, 1)[0]); - - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte19, 1)[0]); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte20, 1)[0]); - mem.Write(numberEncoderService.EncodeNumber(UnkShort21, 2), 0, 2); - - mem.Write(numberEncoderService.EncodeNumber(MinDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(MaxDam, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(Accuracy, 2), 0, 2); - - mem.Write(numberEncoderService.EncodeNumber(UnkShort29, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort31, 2), 0, 2); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte33, 1)[0]); - - mem.Write(numberEncoderService.EncodeNumber(HP, 2), 0, 2); - - mem.Write(numberEncoderService.EncodeNumber(UnkShort36, 2), 0, 2); - mem.WriteByte(numberEncoderService.EncodeNumber(UnkByte38, 1)[0]); - mem.Write(numberEncoderService.EncodeNumber(UnkShort39, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort41, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort43, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort45, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort47, 2), 0, 2); - mem.Write(numberEncoderService.EncodeNumber(UnkShort49, 2), 0, 2); - } - - return ret; } - public void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService) + private ESFRecord(int id, List names, Dictionary propertyBag) + : base(id, names, propertyBag) { - if (recordBytes.Length != DATA_SIZE) - throw new ArgumentOutOfRangeException(nameof(recordBytes), "Data is not properly sized for correct deserialization"); - - Icon = (short)numberEncoderService.DecodeNumber(recordBytes[0], recordBytes[1]); - Graphic = (short)numberEncoderService.DecodeNumber(recordBytes[2], recordBytes[3]); - TP = (short)numberEncoderService.DecodeNumber(recordBytes[4], recordBytes[5]); - SP = (short)numberEncoderService.DecodeNumber(recordBytes[6], recordBytes[7]); - CastTime = (byte)numberEncoderService.DecodeNumber(recordBytes[8]); - - UnkByte9 = (byte)numberEncoderService.DecodeNumber(recordBytes[9]); - UnkByte10 = (byte)numberEncoderService.DecodeNumber(recordBytes[10]); - - Type = (SpellType)numberEncoderService.DecodeNumber(recordBytes[11], recordBytes[12], recordBytes[13]); - - UnkByte14 = (byte)numberEncoderService.DecodeNumber(recordBytes[14]); - UnkShort15 = (short)numberEncoderService.DecodeNumber(recordBytes[15], recordBytes[16]); - - TargetRestrict = (SpellTargetRestrict)numberEncoderService.DecodeNumber(recordBytes[17]); - Target = (SpellTarget)numberEncoderService.DecodeNumber(recordBytes[18]); - - UnkByte19 = (byte)numberEncoderService.DecodeNumber(recordBytes[19]); - UnkByte20 = (byte)numberEncoderService.DecodeNumber(recordBytes[20]); - UnkShort21 = (short)numberEncoderService.DecodeNumber(recordBytes[21], recordBytes[22]); - - MinDam = (short)numberEncoderService.DecodeNumber(recordBytes[23], recordBytes[24]); - MaxDam = (short)numberEncoderService.DecodeNumber(recordBytes[25], recordBytes[26]); - Accuracy = (short)numberEncoderService.DecodeNumber(recordBytes[27], recordBytes[28]); - - UnkShort29 = (short)numberEncoderService.DecodeNumber(recordBytes[29], recordBytes[30]); - UnkShort31 = (short)numberEncoderService.DecodeNumber(recordBytes[31], recordBytes[32]); - UnkByte33 = (byte)numberEncoderService.DecodeNumber(recordBytes[33]); - - HP = (short)numberEncoderService.DecodeNumber(recordBytes[34], recordBytes[35]); + } - UnkShort36 = (short)numberEncoderService.DecodeNumber(recordBytes[36], recordBytes[37]); - UnkByte38 = (byte)numberEncoderService.DecodeNumber(recordBytes[38]); - UnkShort39 = (short)numberEncoderService.DecodeNumber(recordBytes[39], recordBytes[40]); - UnkShort41 = (short)numberEncoderService.DecodeNumber(recordBytes[41], recordBytes[42]); - UnkShort43 = (short)numberEncoderService.DecodeNumber(recordBytes[43], recordBytes[44]); - UnkShort45 = (short)numberEncoderService.DecodeNumber(recordBytes[45], recordBytes[46]); - UnkShort47 = (short)numberEncoderService.DecodeNumber(recordBytes[47], recordBytes[48]); - UnkShort49 = (short)numberEncoderService.DecodeNumber(recordBytes[49], recordBytes[50]); + protected override PubRecord MakeCopy(List names, Dictionary propertyBag) + { + return new ESFRecord(ID, new List(names), new Dictionary(propertyBag)); } } } \ No newline at end of file diff --git a/EOLib.IO/Pub/IPubFile.cs b/EOLib.IO/Pub/IPubFile.cs index 23d4604e8..b5c99d28c 100644 --- a/EOLib.IO/Pub/IPubFile.cs +++ b/EOLib.IO/Pub/IPubFile.cs @@ -1,26 +1,27 @@ using System.Collections.Generic; -using EOLib.IO.Services; namespace EOLib.IO.Pub { - public interface IPubFile : IPubFile + public interface IPubFile : IPubFile, IEnumerable where TRecord : class, IPubRecord, new() { TRecord this[int id] { get; } - IReadOnlyList Data { get; } + IPubFile WithAddedRecord(TRecord record); + + IPubFile WithUpdatedRecord(TRecord record); + + IPubFile WithRemovedRecord(TRecord record); } public interface IPubFile { string FileType { get; } - int CheckSum { get; set; } + int CheckSum { get; } int Length { get; } - byte[] SerializeToByteArray(INumberEncoderService numberEncoderService, bool rewriteChecksum = true); - - void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService); + IPubFile WithCheckSum(int checkSum); } } diff --git a/EOLib.IO/Pub/IPubRecord.cs b/EOLib.IO/Pub/IPubRecord.cs index f8d100cee..dde8f975b 100644 --- a/EOLib.IO/Pub/IPubRecord.cs +++ b/EOLib.IO/Pub/IPubRecord.cs @@ -1,19 +1,39 @@ -using EOLib.IO.Services; +using System.Collections.Generic; namespace EOLib.IO.Pub { public interface IPubRecord { - int RecordSize { get; } + int ID { get; } - int ID { get; set; } + /// + /// The first name in the list of names for the record + /// + string Name { get; } - string Name { get; set; } + /// + /// Collection of all names in the record + /// + IReadOnlyList Names { get; } - TValue Get(PubRecordProperty type); + /// + /// Expected number of names per record in a data file (ESF files have 'name' and 'shout' variable strings) + /// + int NumberOfNames { get; } - byte[] SerializeToByteArray(INumberEncoderService numberEncoderService); + /// + /// Constant size of a data record + /// + int DataSize { get; } - void DeserializeFromByteArray(byte[] recordBytes, INumberEncoderService numberEncoderService); + IReadOnlyDictionary Bag { get; } + + T Get(PubRecordProperty property); + + IPubRecord WithID(int id); + + IPubRecord WithNames(IReadOnlyList name); + + IPubRecord WithProperty(PubRecordProperty type, int value); } } \ No newline at end of file diff --git a/EOLib.IO/Pub/PubRecord.cs b/EOLib.IO/Pub/PubRecord.cs new file mode 100644 index 000000000..397808481 --- /dev/null +++ b/EOLib.IO/Pub/PubRecord.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace EOLib.IO.Pub +{ + public class PubRecord : IPubRecord + { + private readonly Lazy _dataSize; + + private readonly List _names; + private readonly Dictionary _propertyBag; + + public int ID { get; private set; } + + public string Name => _names.FirstOrDefault() ?? string.Empty; + + public IReadOnlyList Names => _names; + + public virtual int NumberOfNames => 1; + + public int DataSize => _dataSize.Value; + + public IReadOnlyDictionary Bag => _propertyBag; + + public PubRecord() + : this(0, new List { string.Empty }, new Dictionary()) + { + } + + public PubRecord(int id, string name, PubRecordProperty flag) + : this(id, new List { name }, flag) + { + } + + public PubRecord(int id, List names, PubRecordProperty flag) + : this(id, names, GetPropertiesWithFlag(flag)) + { + } + + public PubRecord(int id, List names, Dictionary propertyBag) + { + ID = id; + _names = names; + _propertyBag = propertyBag; + + _dataSize = new Lazy(() => _propertyBag.Values + .GroupBy(x => x.Offset) + .Select(x => x.First().Length) + .Aggregate((a, b) => a + b)); + } + + public IPubRecord WithID(int id) + { + var copy = MakeCopy(_names, _propertyBag); + copy.ID = id; + return copy; + } + + public IPubRecord WithNames(IReadOnlyList names) + { + var copy = MakeCopy(_names, _propertyBag); + copy._names.Clear(); + copy._names.AddRange(names); + return copy; + } + + public IPubRecord WithProperty(PubRecordProperty type, int value) + { + var copy = MakeCopy(_names, _propertyBag); + var existing = copy._propertyBag[type]; + copy._propertyBag[type] = new RecordData(existing.Offset, existing.Length, value); + return copy; + } + + public T Get(PubRecordProperty property) => CastTo.From(Bag[property].Value); + + protected virtual PubRecord MakeCopy(List names, Dictionary propertyBag) + { + return new PubRecord(ID, new List(names), new Dictionary(propertyBag)); + } + + private static Dictionary GetPropertiesWithFlag(PubRecordProperty flag) + { + var enumType = typeof(PubRecordProperty); + var enumValues = ((PubRecordProperty[])enumType.GetEnumValues()) + .Where(x => x.HasFlag(flag)) + .ToList(); + + return enumValues + .Select(x => new { Key = x, Value = enumType.GetMember(x.ToString()) }) + .Select(x => new { x.Key, Value = x.Value.FirstOrDefault(m => m.DeclaringType == enumType) }) + .Select(x => new { x.Key, Value = x.Value.GetCustomAttributes(false).SingleOrDefault() }) + .Where(x => x.Value != null) + .ToDictionary(k => k.Key, v => new RecordData(v.Value.Offset, v.Value.Length, 0)); + } + } +} diff --git a/EOLib.IO/Pub/PubRecordProperty.cs b/EOLib.IO/Pub/PubRecordProperty.cs index 50ae2ca04..47dfaaaa8 100644 --- a/EOLib.IO/Pub/PubRecordProperty.cs +++ b/EOLib.IO/Pub/PubRecordProperty.cs @@ -1,175 +1,266 @@ +using System; + namespace EOLib.IO.Pub { /// /// Enum representing the different properties that exist within the pub records /// - /// - /// IPubRecord::Get uses reflection to resolve the correct property. Property value resolution is based on the naming of the values in this enum. - /// These values should always match the names of the actual properties in the implementations of the IPubRecord files (plus the type prefix). - /// To be safe, just don't change the names here unless you know what you're doing. It's a crappy design, but it checks out. - /// - public enum PubRecordProperty + [Flags] + public enum PubRecordProperty : uint { - //Applicable to all records - GlobalID, - GlobalName, - - #region Item Specific + Item = 0x100, + [RecordData(0, 2)] ItemGraphic, + [RecordData(2, 1)] ItemType, + [RecordData(3, 1)] ItemSubType, + [RecordData(4, 1)] ItemSpecial, + [RecordData(5, 2)] ItemHP, + [RecordData(7, 2)] ItemTP, + [RecordData(9, 2)] ItemMinDam, + [RecordData(11, 2)] ItemMaxDam, + [RecordData(13, 2)] ItemAccuracy, + [RecordData(15, 2)] ItemEvade, + [RecordData(17, 2)] ItemArmor, + [RecordData(19, 1)] ItemUnkByte19, + [RecordData(20, 1)] ItemStr, + [RecordData(21, 1)] ItemInt, + [RecordData(22, 1)] ItemWis, + [RecordData(23, 1)] ItemAgi, + [RecordData(24, 1)] ItemCon, + [RecordData(25, 1)] ItemCha, + [RecordData(26, 1)] ItemLight, + [RecordData(27, 1)] ItemDark, + [RecordData(28, 1)] ItemEarth, + [RecordData(29, 1)] ItemAir, + [RecordData(30, 1)] ItemWater, + [RecordData(31, 1)] ItemFire, + [RecordData(32, 3)] ItemScrollMap, + [RecordData(32, 3)] ItemDollGraphic, + [RecordData(32, 3)] ItemExpReward, + [RecordData(32, 3)] ItemHairColor, + [RecordData(32, 3)] ItemEffect, + [RecordData(32, 3)] ItemKey, + [RecordData(35, 1)] ItemGender, + [RecordData(35, 1)] ItemScrollX, + [RecordData(36, 1)] ItemScrollY, + [RecordData(36, 1)] ItemDualWieldDollGraphic, + [RecordData(37, 2)] ItemLevelReq, + [RecordData(39, 2)] ItemClassReq, + [RecordData(41, 2)] ItemStrReq, + [RecordData(43, 2)] ItemIntReq, + [RecordData(45, 2)] ItemWisReq, + [RecordData(47, 2)] ItemAgiReq, + [RecordData(49, 2)] ItemConReq, + [RecordData(51, 2)] ItemChaReq, + [RecordData(53, 1)] ItemElement, + [RecordData(54, 1)] ItemElementPower, + [RecordData(55, 1)] ItemWeight, + [RecordData(56, 1)] ItemUnkByte56, + [RecordData(57, 1)] ItemSize, - #endregion - - #region NPC Specific + NPC = 0x200, + [RecordData(0, 2)] NPCGraphic, + [RecordData(2, 1)] NPCUnkByte2, + [RecordData(3, 2)] NPCBoss, + [RecordData(5, 2)] NPCChild, + [RecordData(7, 2)] NPCType, - - NPCUnkShort14, - + [RecordData(9, 2)] NPCVendorID, + [RecordData(11, 3)] NPCHP, - NPCExp, + + [RecordData(14, 2)] + NPCUnkShort14, + + [RecordData(16, 2)] NPCMinDam, + [RecordData(18, 2)] NPCMaxDam, + [RecordData(20, 2)] NPCAccuracy, + [RecordData(22, 2)] NPCEvade, + [RecordData(24, 2)] NPCArmor, + [RecordData(26, 1)] NPCUnkByte26, + [RecordData(27, 2)] NPCUnkShort27, + [RecordData(29, 2)] NPCUnkShort29, + + [RecordData(31, 2)] NPCElementWeak, + [RecordData(33, 2)] NPCElementWeakPower, + [RecordData(35, 1)] NPCUnkByte35, - #endregion - - #region Spell Specific + [RecordData(36, 3)] + NPCExp, - SpellShout, + Spell = 0x400, + [RecordData(0, 2)] SpellIcon, + [RecordData(2, 2)] SpellGraphic, + [RecordData(4, 2)] SpellTP, + [RecordData(6, 2)] SpellSP, + [RecordData(8, 1)] SpellCastTime, + [RecordData(9, 1)] SpellUnkByte9, + [RecordData(10, 1)] SpellUnkByte10, + [RecordData(11, 3)] SpellType, + [RecordData(14, 1)] SpellUnkByte14, + [RecordData(15, 2)] SpellUnkShort15, + [RecordData(17, 1)] SpellTargetRestrict, + [RecordData(18, 1)] SpellTarget, + [RecordData(19, 1)] SpellUnkByte19, + [RecordData(20, 1)] SpellUnkByte20, + [RecordData(21, 2)] SpellUnkShort21, + [RecordData(23, 2)] SpellMinDam, + [RecordData(25, 2)] SpellMaxDam, + [RecordData(27, 2)] SpellAccuracy, + [RecordData(29, 2)] SpellUnkShort29, + [RecordData(31, 2)] SpellUnkShort31, + [RecordData(33, 1)] SpellUnkByte33, + [RecordData(34, 2)] SpellHP, + [RecordData(36, 2)] SpellUnkShort36, + [RecordData(38, 1)] SpellUnkByte38, + [RecordData(39, 2)] SpellUnkShort39, + [RecordData(41, 2)] SpellUnkShort41, + [RecordData(43, 2)] SpellUnkShort43, + [RecordData(45, 2)] SpellUnkShort45, + [RecordData(47, 2)] SpellUnkShort47, + [RecordData(49, 2)] SpellUnkShort49, - #endregion - - #region Class Specific + Class = 0x800, + [RecordData(0, 1)] ClassBase, + [RecordData(1, 1)] ClassType, + [RecordData(2, 2)] ClassStr, + [RecordData(4, 2)] ClassInt, + [RecordData(6, 2)] ClassWis, + [RecordData(8, 2)] ClassAgi, + [RecordData(10, 2)] ClassCon, + [RecordData(12, 2)] ClassCha - - #endregion } } diff --git a/EOLib.IO/Pub/RecordData.cs b/EOLib.IO/Pub/RecordData.cs new file mode 100644 index 000000000..e07b4f5c4 --- /dev/null +++ b/EOLib.IO/Pub/RecordData.cs @@ -0,0 +1,46 @@ +namespace EOLib.IO.Pub +{ + public struct RecordData + { + public static RecordData Default { get; } = new RecordData(0, 0, 0); + + public int Offset { get; } + + public int Length { get; } + + public int Value { get; } + + public RecordData(int offset, int length, int value) + : this() + { + Offset = offset; + Length = length; + Value = value; + } + + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != typeof(RecordData)) + return false; + + RecordData other = (RecordData)obj; + + return Offset == other.Offset && + Length == other.Length && + Value == other.Value; + } + + public override int GetHashCode() + { + var hash = 397 ^ Offset.GetHashCode(); + hash = (hash * 397) ^ Length.GetHashCode(); + hash = (hash * 397) ^ Value.GetHashCode(); + return hash; + } + + public override string ToString() + { + return $"{Offset} {Length} {Value}"; + } + } +} diff --git a/EOLib.IO/Pub/RecordDataAttribute.cs b/EOLib.IO/Pub/RecordDataAttribute.cs new file mode 100644 index 000000000..664604e95 --- /dev/null +++ b/EOLib.IO/Pub/RecordDataAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace EOLib.IO.Pub +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class RecordDataAttribute : Attribute + { + public int Offset { get; } + + public int Length { get; } + + public RecordDataAttribute(int offset, int length) + { + Offset = offset; + Length = length; + } + } +} diff --git a/EOLib.IO/Services/ClassFileLoadService.cs b/EOLib.IO/Services/ClassFileLoadService.cs index 62e77a17f..952d7489f 100644 --- a/EOLib.IO/Services/ClassFileLoadService.cs +++ b/EOLib.IO/Services/ClassFileLoadService.cs @@ -1,17 +1,18 @@ using System.IO; using AutomaticTypeMapper; using EOLib.IO.Pub; +using EOLib.IO.Services.Serializers; namespace EOLib.IO.Services { [MappedType(BaseType = typeof(IPubLoadService))] public class ClassFileLoadService : IPubLoadService { - private readonly INumberEncoderService _numberEncoderService; + private readonly IPubFileDeserializer _pubFileDeserializer; - public ClassFileLoadService(INumberEncoderService numberEncoderService) + public ClassFileLoadService(IPubFileDeserializer pubFileDeserializer) { - _numberEncoderService = numberEncoderService; + _pubFileDeserializer = pubFileDeserializer; } public IPubFile LoadPubFromDefaultFile() @@ -22,11 +23,7 @@ public IPubFile LoadPubFromDefaultFile() public IPubFile LoadPubFromExplicitFile(string fileName) { var fileBytes = File.ReadAllBytes(fileName); - - var pub = new ECFFile(); - pub.DeserializeFromByteArray(fileBytes, _numberEncoderService); - - return pub; + return _pubFileDeserializer.DeserializeFromByteArray(fileBytes, () => new ECFFile()); } } } diff --git a/EOLib.IO/Services/IPubFileSaveService.cs b/EOLib.IO/Services/IPubFileSaveService.cs index 60afe4897..f0add58e8 100644 --- a/EOLib.IO/Services/IPubFileSaveService.cs +++ b/EOLib.IO/Services/IPubFileSaveService.cs @@ -4,6 +4,7 @@ namespace EOLib.IO.Services { public interface IPubFileSaveService { - void SaveFile(string path, IPubFile pubFile, bool rewriteChecksum = true); + void SaveFile(string path, IPubFile pubFile, bool rewriteChecksum = true) + where TRecord : class, IPubRecord, new(); } } diff --git a/EOLib.IO/Services/IPubLoadService.cs b/EOLib.IO/Services/IPubLoadService.cs index 9839aaf59..774e8b55d 100644 --- a/EOLib.IO/Services/IPubLoadService.cs +++ b/EOLib.IO/Services/IPubLoadService.cs @@ -2,7 +2,7 @@ namespace EOLib.IO.Services { - public interface IPubLoadService + public interface IPubLoadService where T : class, IPubRecord, new() { IPubFile LoadPubFromDefaultFile(); diff --git a/EOLib.IO/Services/ItemFileLoadService.cs b/EOLib.IO/Services/ItemFileLoadService.cs index d6f8653a3..fb21c5385 100644 --- a/EOLib.IO/Services/ItemFileLoadService.cs +++ b/EOLib.IO/Services/ItemFileLoadService.cs @@ -1,17 +1,18 @@ using System.IO; using AutomaticTypeMapper; using EOLib.IO.Pub; +using EOLib.IO.Services.Serializers; namespace EOLib.IO.Services { [MappedType(BaseType = typeof(IPubLoadService))] public class ItemFileLoadService : IPubLoadService { - private readonly INumberEncoderService _numberEncoderService; + private readonly IPubFileDeserializer _pubFileDeserializer; - public ItemFileLoadService(INumberEncoderService numberEncoderService) + public ItemFileLoadService(IPubFileDeserializer pubFileDeserializer) { - _numberEncoderService = numberEncoderService; + _pubFileDeserializer = pubFileDeserializer; } public IPubFile LoadPubFromDefaultFile() @@ -22,11 +23,7 @@ public IPubFile LoadPubFromDefaultFile() public IPubFile LoadPubFromExplicitFile(string fileName) { var fileBytes = File.ReadAllBytes(fileName); - - var pub = new EIFFile(); - pub.DeserializeFromByteArray(fileBytes, _numberEncoderService); - - return pub; + return _pubFileDeserializer.DeserializeFromByteArray(fileBytes, () => new EIFFile()); } } } diff --git a/EOLib.IO/Services/NPCFileLoadService.cs b/EOLib.IO/Services/NPCFileLoadService.cs index deda1aff1..eafdd1303 100644 --- a/EOLib.IO/Services/NPCFileLoadService.cs +++ b/EOLib.IO/Services/NPCFileLoadService.cs @@ -1,17 +1,18 @@ using System.IO; using AutomaticTypeMapper; using EOLib.IO.Pub; +using EOLib.IO.Services.Serializers; namespace EOLib.IO.Services { [MappedType(BaseType = typeof(IPubLoadService))] public class NPCFileLoadService : IPubLoadService { - private readonly INumberEncoderService _numberEncoderService; + private readonly IPubFileDeserializer _pubFileDeserializer; - public NPCFileLoadService(INumberEncoderService numberEncoderService) + public NPCFileLoadService(IPubFileDeserializer pubFileDeserializer) { - _numberEncoderService = numberEncoderService; + _pubFileDeserializer = pubFileDeserializer; } public IPubFile LoadPubFromDefaultFile() @@ -22,11 +23,7 @@ public IPubFile LoadPubFromDefaultFile() public IPubFile LoadPubFromExplicitFile(string fileName) { var fileBytes = File.ReadAllBytes(fileName); - - var pub = new ENFFile(); - pub.DeserializeFromByteArray(fileBytes, _numberEncoderService); - - return pub; + return _pubFileDeserializer.DeserializeFromByteArray(fileBytes, () => new ENFFile()); } } } diff --git a/EOLib.IO/Services/PubFileSaveService.cs b/EOLib.IO/Services/PubFileSaveService.cs index 54590d762..8c5094c37 100644 --- a/EOLib.IO/Services/PubFileSaveService.cs +++ b/EOLib.IO/Services/PubFileSaveService.cs @@ -1,26 +1,28 @@ using System.IO; using AutomaticTypeMapper; using EOLib.IO.Pub; +using EOLib.IO.Services.Serializers; namespace EOLib.IO.Services { [MappedType(BaseType = typeof(IPubFileSaveService))] public class PubFileSaveService : IPubFileSaveService { - private readonly INumberEncoderService _numberEncoderService; + private readonly IPubFileSerializer _pubFileSerializer; - public PubFileSaveService(INumberEncoderService numberEncoderService) + public PubFileSaveService(IPubFileSerializer pubFileSerializer) { - _numberEncoderService = numberEncoderService; + _pubFileSerializer = pubFileSerializer; } - public void SaveFile(string path, IPubFile pubFile, bool rewriteChecksum = true) + public void SaveFile(string path, IPubFile pubFile, bool rewriteChecksum = true) + where TRecord : class, IPubRecord, new() { var directoryName = Path.GetDirectoryName(path) ?? ""; if (!Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); - var pubFileBytes = pubFile.SerializeToByteArray(_numberEncoderService, rewriteChecksum); + var pubFileBytes = _pubFileSerializer.SerializeToByteArray(pubFile, rewriteChecksum); File.WriteAllBytes(path, pubFileBytes); } } diff --git a/EOLib.IO/Services/Serializers/IPubFileDeserializer.cs b/EOLib.IO/Services/Serializers/IPubFileDeserializer.cs new file mode 100644 index 000000000..bfff0b677 --- /dev/null +++ b/EOLib.IO/Services/Serializers/IPubFileDeserializer.cs @@ -0,0 +1,17 @@ +using EOLib.IO.Pub; +using System; + +namespace EOLib.IO.Services.Serializers +{ + public interface IPubFileDeserializer + { + IPubFile DeserializeFromByteArray(byte[] data, Func> fileFactory) + where TRecord : class, IPubRecord, new(); + } + + public interface IPubFileSerializer : IPubFileDeserializer + { + byte[] SerializeToByteArray(IPubFile file, bool rewriteChecksum = true) + where TRecord : class, IPubRecord, new(); + } +} diff --git a/EOLib.IO/Services/Serializers/IPubRecordSerializer.cs b/EOLib.IO/Services/Serializers/IPubRecordSerializer.cs new file mode 100644 index 000000000..5643e64c9 --- /dev/null +++ b/EOLib.IO/Services/Serializers/IPubRecordSerializer.cs @@ -0,0 +1,12 @@ +using EOLib.IO.Pub; +using System; + +namespace EOLib.IO.Services.Serializers +{ + public interface IPubRecordSerializer + { + IPubRecord DeserializeFromByteArray(byte[] data, Func recordFactory); + + byte[] SerializeToByteArray(IPubRecord record); + } +} diff --git a/EOLib.IO/Services/Serializers/PubFileSerializer.cs b/EOLib.IO/Services/Serializers/PubFileSerializer.cs new file mode 100644 index 000000000..9890097a2 --- /dev/null +++ b/EOLib.IO/Services/Serializers/PubFileSerializer.cs @@ -0,0 +1,99 @@ +using AutomaticTypeMapper; +using EOLib.IO.Pub; +using System; +using System.IO; +using System.Text; + +namespace EOLib.IO.Services.Serializers +{ + [AutoMappedType] + public class PubFileSerializer : IPubFileSerializer + { + private readonly INumberEncoderService _numberEncoderService; + private readonly IPubRecordSerializer _pubRecordSerializer; + + public PubFileSerializer(INumberEncoderService numberEncoderService, + IPubRecordSerializer pubRecordSerializer) + { + _numberEncoderService = numberEncoderService; + _pubRecordSerializer = pubRecordSerializer; + } + + public IPubFile DeserializeFromByteArray(byte[] data, Func> fileFactory) + where TRecord : class, IPubRecord, new() + { + using (var mem = new MemoryStream(data)) + { + mem.Seek(3, SeekOrigin.Begin); + + var checksumBytes = new byte[4]; + mem.Read(checksumBytes, 0, 4); + var checksum = _numberEncoderService.DecodeNumber(checksumBytes); + + var file = (IPubFile)fileFactory().WithCheckSum(checksum); + + var lenBytes = new byte[2]; + mem.Read(lenBytes, 0, 2); + var recordsInFile = (short)_numberEncoderService.DecodeNumber(lenBytes); + + mem.Seek(1, SeekOrigin.Current); + + var dummyRecord = new TRecord(); + var dataSize = dummyRecord.DataSize; + var numberOfNames = dummyRecord.NumberOfNames; + for (int i = 1; i <= recordsInFile && mem.Position < mem.Length; ++i) + { + int nameLength = 0; + for (int nameNdx = 0; nameNdx < numberOfNames; nameNdx++) + nameLength += _numberEncoderService.DecodeNumber((byte)mem.ReadByte()); + mem.Seek(-numberOfNames, SeekOrigin.Current); + + var rawData = new byte[nameLength + numberOfNames + dataSize]; + mem.Read(rawData, 0, rawData.Length); + + var record = _pubRecordSerializer.DeserializeFromByteArray(rawData, () => new TRecord().WithID(i)); + file = file.WithAddedRecord((TRecord)record); + } + + if (recordsInFile != file.Length) + throw new IOException("Mismatch between expected length and actual length!"); + + return file; + } + } + + public byte[] SerializeToByteArray(IPubFile file, bool rewriteChecksum = true) + where TRecord : class, IPubRecord, new() + { + byte[] fileBytes; + + using (var mem = new MemoryStream()) //write to memory so we can get a CRC for the new RID value + { + mem.Write(Encoding.ASCII.GetBytes(file.FileType), 0, 3); + mem.Write(_numberEncoderService.EncodeNumber(0, 2), 0, 2); + mem.Write(_numberEncoderService.EncodeNumber(0, 2), 0, 2); + mem.Write(_numberEncoderService.EncodeNumber(file.Length, 2), 0, 2); + + mem.WriteByte(_numberEncoderService.EncodeNumber(1, 1)[0]); + + foreach (var dataRecord in file) + { + var toWrite = _pubRecordSerializer.SerializeToByteArray(dataRecord); + mem.Write(toWrite, 0, toWrite.Length); + } + + fileBytes = mem.ToArray(); + } + + var checksumBytes = _numberEncoderService.EncodeNumber(file.CheckSum, 4); + if (rewriteChecksum) + { + var checksum = CRC32.Check(fileBytes); + checksumBytes = _numberEncoderService.EncodeNumber((int)checksum, 4); + } + + Array.Copy(checksumBytes, 0, fileBytes, 3, 4); + return fileBytes; + } + } +} diff --git a/EOLib.IO/Services/Serializers/PubRecordSerializer.cs b/EOLib.IO/Services/Serializers/PubRecordSerializer.cs new file mode 100644 index 000000000..c631fd068 --- /dev/null +++ b/EOLib.IO/Services/Serializers/PubRecordSerializer.cs @@ -0,0 +1,78 @@ +using AutomaticTypeMapper; +using EOLib.IO.Pub; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EOLib.IO.Services.Serializers +{ + [AutoMappedType] + public class PubRecordSerializer : IPubRecordSerializer + { + private readonly INumberEncoderService _numberEncoderService; + + public PubRecordSerializer(INumberEncoderService numberEncoderService) + { + _numberEncoderService = numberEncoderService; + } + + public IPubRecord DeserializeFromByteArray(byte[] data, Func recordFactory) + { + var record = recordFactory(); + + var nameLengths = new List(); + for (int i = 0; i < record.NumberOfNames; i++) + nameLengths.Add((byte)_numberEncoderService.DecodeNumber(data[i])); + + int offset = nameLengths.Count; + var names = new List(nameLengths.Count); + foreach (var nameLength in nameLengths) + { + names.Add(Encoding.ASCII.GetString(data, offset, nameLength)); + offset += names.Last().Length; + } + + record = record.WithNames(names); + foreach (var propertyKvp in record.Bag) + { + var property = propertyKvp.Value; + var propertyRawBytes = data.Skip(offset + property.Offset).Take(property.Length).ToArray(); + record = record.WithProperty(propertyKvp.Key, _numberEncoderService.DecodeNumber(propertyRawBytes)); + } + + return record; + } + + public byte[] SerializeToByteArray(IPubRecord record) + { + var retList = new List(); + + foreach (var name in record.Names) + { + var nameLength = _numberEncoderService.EncodeNumber(name.Length, 1)[0]; + retList.Add(nameLength); + } + + foreach (var name in record.Names) + { + var nameBytes = Encoding.ASCII.GetBytes(name); + retList.AddRange(nameBytes); + } + + var distinctOffsets = record.Bag + .GroupBy(x => x.Value.Offset) + .Select(x => new KeyValuePair(x.First().Key, x.First().Value)); + + foreach (var propertyKvp in distinctOffsets) + { + var property = propertyKvp.Value; + var propertyRawBytes = _numberEncoderService.EncodeNumber(property.Value, property.Length); + Array.Resize(ref propertyRawBytes, property.Length); + retList.AddRange(propertyRawBytes); + } + + return retList.ToArray(); + } + } +} diff --git a/EOLib.IO/Services/SpellFileLoadService.cs b/EOLib.IO/Services/SpellFileLoadService.cs index ed071c69e..387f17317 100644 --- a/EOLib.IO/Services/SpellFileLoadService.cs +++ b/EOLib.IO/Services/SpellFileLoadService.cs @@ -1,17 +1,18 @@ using System.IO; using AutomaticTypeMapper; using EOLib.IO.Pub; +using EOLib.IO.Services.Serializers; namespace EOLib.IO.Services { [MappedType(BaseType = typeof(IPubLoadService))] public class SpellFileLoadService : IPubLoadService { - private readonly INumberEncoderService _numberEncoderService; + private readonly IPubFileDeserializer _pubFileDeserializer; - public SpellFileLoadService(INumberEncoderService numberEncoderService) + public SpellFileLoadService(IPubFileDeserializer pubFileDeserializer) { - _numberEncoderService = numberEncoderService; + _pubFileDeserializer = pubFileDeserializer; } public IPubFile LoadPubFromDefaultFile() @@ -22,11 +23,7 @@ public IPubFile LoadPubFromDefaultFile() public IPubFile LoadPubFromExplicitFile(string fileName) { var fileBytes = File.ReadAllBytes(fileName); - - var pub = new ESFFile(); - pub.DeserializeFromByteArray(fileBytes, _numberEncoderService); - - return pub; + return _pubFileDeserializer.DeserializeFromByteArray(fileBytes, () => new ESFFile()); } } } From e682dcf40c52f1740467752802cacbd27a35dec5 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 16:11:36 -0700 Subject: [PATCH 3/9] Improve test coverage for new pub file stuff --- EOLib.IO.Test/Pub/ECFRecordTest.cs | 34 +++ EOLib.IO.Test/Pub/EIFRecordTest.cs | 34 +++ EOLib.IO.Test/Pub/ENFRecordTest.cs | 34 +++ EOLib.IO.Test/Pub/ESFRecordTest.cs | 34 +++ .../Serializers/PubFileSerializerTest.cs | 205 ++++++++++++++++++ .../Services/Serializers/PubFileSerializer.cs | 2 +- 6 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 EOLib.IO.Test/Pub/ECFRecordTest.cs create mode 100644 EOLib.IO.Test/Pub/EIFRecordTest.cs create mode 100644 EOLib.IO.Test/Pub/ENFRecordTest.cs create mode 100644 EOLib.IO.Test/Pub/ESFRecordTest.cs create mode 100644 EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs diff --git a/EOLib.IO.Test/Pub/ECFRecordTest.cs b/EOLib.IO.Test/Pub/ECFRecordTest.cs new file mode 100644 index 000000000..beb111628 --- /dev/null +++ b/EOLib.IO.Test/Pub/ECFRecordTest.cs @@ -0,0 +1,34 @@ +using EOLib.IO.Pub; +using NUnit.Framework; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace EOLib.IO.Test.Pub +{ + [TestFixture, ExcludeFromCodeCoverage] + public class ECFRecordTest + { + [Test] + public void ECFRecord_HasAllExpectedProperties() + { + var record = new ECFRecord(); + + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Class)) + .Except(new[] { PubRecordProperty.Class }); + + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); + + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); + } + + [Test] + public void ECFRecord_HasExpectedDataSize() + { + const int ExpectedDataSize = 14; + Assert.That(new ECFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); + } + } +} diff --git a/EOLib.IO.Test/Pub/EIFRecordTest.cs b/EOLib.IO.Test/Pub/EIFRecordTest.cs new file mode 100644 index 000000000..45710d4cd --- /dev/null +++ b/EOLib.IO.Test/Pub/EIFRecordTest.cs @@ -0,0 +1,34 @@ +using EOLib.IO.Pub; +using NUnit.Framework; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace EOLib.IO.Test.Pub +{ + [TestFixture, ExcludeFromCodeCoverage] + public class EIFRecordTest + { + [Test] + public void EIFRecord_HasAllExpectedProperties() + { + var record = new EIFRecord(); + + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Item)) + .Except(new[] { PubRecordProperty.Item }); + + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); + + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); + } + + [Test] + public void EIFRecord_HasExpectedDataSize() + { + const int ExpectedDataSize = 58; + Assert.That(new EIFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); + } + } +} diff --git a/EOLib.IO.Test/Pub/ENFRecordTest.cs b/EOLib.IO.Test/Pub/ENFRecordTest.cs new file mode 100644 index 000000000..c15ba99c1 --- /dev/null +++ b/EOLib.IO.Test/Pub/ENFRecordTest.cs @@ -0,0 +1,34 @@ +using EOLib.IO.Pub; +using NUnit.Framework; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace EOLib.IO.Test.Pub +{ + [TestFixture, ExcludeFromCodeCoverage] + public class ENFRecordTest + { + [Test] + public void ENFRecord_HasAllExpectedProperties() + { + var record = new ENFRecord(); + + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.NPC)) + .Except(new[] { PubRecordProperty.NPC }); + + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); + + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); + } + + [Test] + public void ENFRecord_HasExpectedDataSize() + { + const int ExpectedDataSize = 39; + Assert.That(new ENFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); + } + } +} diff --git a/EOLib.IO.Test/Pub/ESFRecordTest.cs b/EOLib.IO.Test/Pub/ESFRecordTest.cs new file mode 100644 index 000000000..aa2d1dc50 --- /dev/null +++ b/EOLib.IO.Test/Pub/ESFRecordTest.cs @@ -0,0 +1,34 @@ +using EOLib.IO.Pub; +using NUnit.Framework; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace EOLib.IO.Test.Pub +{ + [TestFixture, ExcludeFromCodeCoverage] + public class ESFRecordTest + { + [Test] + public void ESFRecord_HasAllExpectedProperties() + { + var record = new ESFRecord(); + + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Spell)) + .Except(new[] { PubRecordProperty.Spell }); + + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); + + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); + } + + [Test] + public void ESFRecord_HasExpectedDataSize() + { + const int ExpectedDataSize = 51; + Assert.That(new ESFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); + } + } +} diff --git a/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs new file mode 100644 index 000000000..19aa04df0 --- /dev/null +++ b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs @@ -0,0 +1,205 @@ +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; + +namespace EOLib.IO.Test.Services.Serializers +{ + [TestFixture, ExcludeFromCodeCoverage] + public class PubFileSerializerTest + { + [Test] + public void EIFFile_DeserializeFromByteArray_WrongLength_Throws() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new EIFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new EIFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new EIFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new EIFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytesLong = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength - 1, records); + + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new EIFFile()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new EIFFile()), Throws.InstanceOf()); + } + + [Test] + public void ENFFile_DeserializeFromByteArray_WrongLength_Throws() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ENFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new ENFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new ENFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new ENFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytesLong = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength - 1, records); + + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ENFFile()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ENFFile()), Throws.InstanceOf()); + } + + [Test] + public void ESFFile_DeserializeFromByteArray_WrongLength_Throws() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ESFRecord().WithID(1).WithNames(new List { "Rec_1", "1_ceR" }), + new ESFRecord().WithID(2).WithNames(new List { "Rec_2", "2_ceR" }), + new ESFRecord().WithID(3).WithNames(new List { "Rec_3", "3_ceR" }), + new ESFRecord().WithID(4).WithNames(new List { "Rec_4", "4_ceR" }), + }; + + var pubBytesLong = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength - 1, records); + + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ESFFile()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ESFFile()), Throws.InstanceOf()); + } + + [Test] + public void ECFFile_DeserializeFromByteArray_WrongLength_Throws() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ECFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new ECFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new ECFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new ECFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytesLong = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength - 1, records); + + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ECFFile()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ECFFile()), Throws.InstanceOf()); + } + + [Test] + public void EIFFile_DeserializeFromByteArray_HasExpectedHeader() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new EIFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new EIFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new EIFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new EIFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytes = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength, records); + var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new EIFFile()); + + Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); + Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + } + + [Test] + public void ENFFile_DeserializeFromByteArray_HasExpectedHeader() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ENFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new ENFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new ENFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new ENFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytes = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength, records); + var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ENFFile()); + + Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); + Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + } + + [Test] + public void ESFFile_DeserializeFromByteArray_HasExpectedHeader() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ESFRecord().WithID(1).WithNames(new List { "Rec_1", "1_ceR" }), + new ESFRecord().WithID(2).WithNames(new List { "Rec_2", "2_ceR" }), + new ESFRecord().WithID(3).WithNames(new List { "Rec_3", "3_ceR" }), + new ESFRecord().WithID(4).WithNames(new List { "Rec_4", "4_ceR" }), + }; + + var pubBytes = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength, records); + var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ESFFile()); + + Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); + Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + } + + [Test] + public void ECFFile_DeserializeFromByteArray_HasExpectedHeader() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + new ECFRecord().WithID(1).WithNames(new List { "Rec_1" }), + new ECFRecord().WithID(2).WithNames(new List { "Rec_2" }), + new ECFRecord().WithID(3).WithNames(new List { "Rec_3" }), + new ECFRecord().WithID(4).WithNames(new List { "Rec_4" }), + }; + + var pubBytes = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength, records); + var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ECFFile()); + + Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); + Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + } + + private byte[] MakePubFileBytes(string type, int checksum, int length, params IPubRecord[] records) + { + var numberEncoderService = new NumberEncoderService(); + var recordSerializer = new PubRecordSerializer(numberEncoderService); + + var bytes = new List(); + bytes.AddRange(Encoding.ASCII.GetBytes(type)); + bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); + bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); + bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); + foreach (var record in records) + bytes.AddRange(recordSerializer.SerializeToByteArray(record)); + + return bytes.ToArray(); + } + + private static IPubFileSerializer CreateSerializer() + { + return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); + } + } +} diff --git a/EOLib.IO/Services/Serializers/PubFileSerializer.cs b/EOLib.IO/Services/Serializers/PubFileSerializer.cs index 9890097a2..44424185f 100644 --- a/EOLib.IO/Services/Serializers/PubFileSerializer.cs +++ b/EOLib.IO/Services/Serializers/PubFileSerializer.cs @@ -55,7 +55,7 @@ public IPubFile DeserializeFromByteArray(byte[] data, Func Date: Wed, 16 Mar 2022 16:30:54 -0700 Subject: [PATCH 4/9] Fix errors in EOLib/EndlessClient due to refactor --- EOLib/Domain/Character/CharacterActions.cs | 2 +- EOLib/Net/FileTransfer/FileRequestActions.cs | 8 ++--- EOLib/Net/FileTransfer/FileRequestService.cs | 35 ++++++++++--------- EOLib/PacketHandlers/Items/UseItemHandler.cs | 2 +- EndlessClient/Dialogs/SkillmasterDialog.cs | 18 +++++----- .../HUD/Panels/Old/OldOnlineListPanel.cs | 2 +- EndlessClient/Old/OldCharacter.cs | 18 +++++----- EndlessClient/Old/PacketAPICallbackManager.cs | 2 +- .../CharacterPropertyRendererBuilder.cs | 4 +-- .../Rendering/OldCharacterRenderer.cs | 6 ++-- .../Sprites/CharacterSpriteCalculator.cs | 7 ++-- EndlessClient/Test/CharacterStateTest.cs | 4 +-- 12 files changed, 55 insertions(+), 53 deletions(-) diff --git a/EOLib/Domain/Character/CharacterActions.cs b/EOLib/Domain/Character/CharacterActions.cs index 4083ce690..e0a0b9095 100644 --- a/EOLib/Domain/Character/CharacterActions.cs +++ b/EOLib/Domain/Character/CharacterActions.cs @@ -100,7 +100,7 @@ public void PrepareCastSpell(int spellId) public void CastSpell(int spellId, ISpellTargetable target) { - var data = _spellFileProvider.ESFFile.Data.Single(x => x.ID == spellId); + var data = _spellFileProvider.ESFFile.Single(x => x.ID == spellId); var action = data.Target == IO.SpellTarget.Self ? PacketAction.TargetSelf diff --git a/EOLib/Net/FileTransfer/FileRequestActions.cs b/EOLib/Net/FileTransfer/FileRequestActions.cs index e14a565f5..9b7f08261 100644 --- a/EOLib/Net/FileTransfer/FileRequestActions.cs +++ b/EOLib/Net/FileTransfer/FileRequestActions.cs @@ -72,28 +72,28 @@ public async Task GetMapForWarp(short mapID) public async Task GetItemFileFromServer() { - var itemFile = await _fileRequestService.RequestFile(InitFileType.Item, _playerInfoProvider.PlayerID); + var itemFile = await _fileRequestService.RequestFile(InitFileType.Item, _playerInfoProvider.PlayerID); _pubFileSaveService.SaveFile(PubFileNameConstants.PathToEIFFile, itemFile, rewriteChecksum: false); _pubFileRepository.EIFFile = (EIFFile)itemFile; } public async Task GetNPCFileFromServer() { - var npcFile = await _fileRequestService.RequestFile(InitFileType.Npc, _playerInfoProvider.PlayerID); + var npcFile = await _fileRequestService.RequestFile(InitFileType.Npc, _playerInfoProvider.PlayerID); _pubFileSaveService.SaveFile(PubFileNameConstants.PathToENFFile, npcFile, rewriteChecksum: false); _pubFileRepository.ENFFile = (ENFFile)npcFile; } public async Task GetSpellFileFromServer() { - var spellFile = await _fileRequestService.RequestFile(InitFileType.Spell, _playerInfoProvider.PlayerID); + var spellFile = await _fileRequestService.RequestFile(InitFileType.Spell, _playerInfoProvider.PlayerID); _pubFileSaveService.SaveFile(PubFileNameConstants.PathToESFFile, spellFile, rewriteChecksum: false); _pubFileRepository.ESFFile = (ESFFile)spellFile; } public async Task GetClassFileFromServer() { - var classFile = await _fileRequestService.RequestFile(InitFileType.Class, _playerInfoProvider.PlayerID); + var classFile = await _fileRequestService.RequestFile(InitFileType.Class, _playerInfoProvider.PlayerID); _pubFileSaveService.SaveFile(PubFileNameConstants.PathToECFFile, classFile, rewriteChecksum: false); _pubFileRepository.ECFFile = (ECFFile)classFile; } diff --git a/EOLib/Net/FileTransfer/FileRequestService.cs b/EOLib/Net/FileTransfer/FileRequestService.cs index 26f7cc130..df08d5306 100644 --- a/EOLib/Net/FileTransfer/FileRequestService.cs +++ b/EOLib/Net/FileTransfer/FileRequestService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using AutomaticTypeMapper; using EOLib.Domain.Protocol; @@ -14,16 +15,16 @@ namespace EOLib.Net.FileTransfer public class FileRequestService : IFileRequestService { private readonly IPacketSendService _packetSendService; - private readonly INumberEncoderService _numberEncoderService; private readonly IMapDeserializer _mapFileSerializer; + private readonly IPubFileDeserializer _pubFileDeserializer; public FileRequestService(IPacketSendService packetSendService, - INumberEncoderService numberEncoderService, - IMapDeserializer mapFileSerializer) + IMapDeserializer mapFileSerializer, + IPubFileDeserializer pubFileDeserializer) { _packetSendService = packetSendService; - _numberEncoderService = numberEncoderService; _mapFileSerializer = mapFileSerializer; + _pubFileDeserializer = pubFileDeserializer; } public async Task RequestMapFile(short mapID, short playerID) @@ -43,7 +44,8 @@ public async Task RequestMapFileForWarp(short mapID) return await GetMapFile(request, mapID, true); } - public async Task RequestFile(InitFileType fileType, short playerID) + public async Task> RequestFile(InitFileType fileType, short playerID) + where TRecord : class, IPubRecord, new() { var request = new PacketBuilder(PacketFamily.Welcome, PacketAction.Agree) .AddChar((byte)fileType) @@ -61,21 +63,21 @@ public async Task RequestFile(InitFileType fileType, short playerID) if (extraByte != 1) throw new MalformedPacketException("Missing extra single byte in file transfer packet", response); - IPubFile retFile; + Func> factory; switch (responseFileType) { - case InitReply.ItemFile: retFile = new EIFFile(); break; - case InitReply.NpcFile: retFile = new ENFFile(); break; - case InitReply.SpellFile: retFile = new ESFFile(); break; - case InitReply.ClassFile: retFile = new ECFFile(); break; + case InitReply.ItemFile: factory = () => (IPubFile)new EIFFile(); break; + case InitReply.NpcFile: factory = () => (IPubFile)new ENFFile(); break; + case InitReply.SpellFile: factory = () => (IPubFile)new ESFFile(); break; + case InitReply.ClassFile: factory = () => (IPubFile)new ECFFile(); break; default: throw new EmptyPacketReceivedException(); } - var responseBytes = response.ReadBytes(response.Length - response.ReadPosition) - .ToArray(); - retFile.DeserializeFromByteArray(responseBytes, _numberEncoderService); + var responseBytes = response + .ReadBytes(response.Length - response.ReadPosition) + .ToArray(); - return retFile; + return _pubFileDeserializer.DeserializeFromByteArray(responseBytes, factory); } private async Task GetMapFile(IPacket request, int mapID, bool isWarp) @@ -109,6 +111,7 @@ public interface IFileRequestService Task RequestMapFileForWarp(short mapID); - Task RequestFile(InitFileType fileType, short playerID); + Task> RequestFile(InitFileType fileType, short playerID) + where TRecord : class, IPubRecord, new(); } } \ No newline at end of file diff --git a/EOLib/PacketHandlers/Items/UseItemHandler.cs b/EOLib/PacketHandlers/Items/UseItemHandler.cs index f6bde45b3..c894b21f9 100644 --- a/EOLib/PacketHandlers/Items/UseItemHandler.cs +++ b/EOLib/PacketHandlers/Items/UseItemHandler.cs @@ -107,7 +107,7 @@ public override bool HandlePacket(IPacket packet) var cureCurseEvade = packet.ReadShort(); var cureCurseArmor = packet.ReadShort(); - var cursedItems = _itemFileProvider.EIFFile.Data.Where(x => x.Special == ItemSpecial.Cursed).ToList(); + var cursedItems = _itemFileProvider.EIFFile.Where(x => x.Special == ItemSpecial.Cursed).ToList(); if (cursedItems.Any(x => x.Graphic == renderProps.BootsGraphic && x.Type == ItemType.Boots)) renderProps = renderProps.WithBootsGraphic(0); if (cursedItems.Any(x => x.Graphic == renderProps.ArmorGraphic && x.Type == ItemType.Armor)) diff --git a/EndlessClient/Dialogs/SkillmasterDialog.cs b/EndlessClient/Dialogs/SkillmasterDialog.cs index 4f8e6d4c7..eda53fd9e 100644 --- a/EndlessClient/Dialogs/SkillmasterDialog.cs +++ b/EndlessClient/Dialogs/SkillmasterDialog.cs @@ -169,7 +169,7 @@ private void _setState(SkillState newState) continue; int localI = i; - var spellData = OldWorld.Instance.ESF.Data[m_skills[localI].ID]; + var spellData = OldWorld.Instance.ESF[m_skills[localI].ID]; ListDialogItem nextListItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Large, index++) { @@ -200,7 +200,7 @@ private void _setState(SkillState newState) if (args.Result == XNADialogResult.Cancel) return; bool found = OldWorld.Instance.MainPlayer.ActiveCharacter.Spells.Any( - _spell => OldWorld.Instance.ESF.Data[_spell.ID].Name.ToLower() == input.ResponseText.ToLower()); + _spell => OldWorld.Instance.ESF[_spell.ID].Name.ToLower() == input.ResponseText.ToLower()); if (!found) { @@ -211,7 +211,7 @@ private void _setState(SkillState newState) if (!m_api.ForgetSpell( OldWorld.Instance.MainPlayer.ActiveCharacter.Spells.Find( - _spell => OldWorld.Instance.ESF.Data[_spell.ID].Name.ToLower() == input.ResponseText.ToLower()).ID)) + _spell => OldWorld.Instance.ESF[_spell.ID].Name.ToLower() == input.ResponseText.ToLower()).ID)) { Close(); ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); @@ -254,11 +254,11 @@ private void _learn(Skill skill) if (skill.ClassReq > 0 && c.Class != skill.ClassReq) { - EOMessageBox.Show(DialogResourceID.SKILL_LEARN_WRONG_CLASS, " " + OldWorld.Instance.ECF.Data[skill.ClassReq].Name + "!", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + EOMessageBox.Show(DialogResourceID.SKILL_LEARN_WRONG_CLASS, " " + OldWorld.Instance.ECF[skill.ClassReq].Name + "!", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); return; } - EOMessageBox.Show(DialogResourceID.SKILL_LEARN_CONFIRMATION, " " + OldWorld.Instance.ESF.Data[skill.ID].Name + "?", EODialogButtons.OkCancel, EOMessageBoxStyle.SmallDialogSmallHeader, + EOMessageBox.Show(DialogResourceID.SKILL_LEARN_CONFIRMATION, " " + OldWorld.Instance.ESF[skill.ID].Name + "?", EODialogButtons.OkCancel, EOMessageBoxStyle.SmallDialogSmallHeader, (o, e) => { if (e.Result != XNADialogResult.OK) @@ -294,12 +294,12 @@ private void _showRequirements(Skill skill) List drawStrings = new List(15) { - OldWorld.Instance.ESF.Data[skill.ID].Name + (skill.ClassReq > 0 ? " [" + OldWorld.Instance.ECF.Data[skill.ClassReq].Name + "]" : ""), + OldWorld.Instance.ESF[skill.ID].Name + (skill.ClassReq > 0 ? " [" + OldWorld.Instance.ECF[skill.ClassReq].Name + "]" : ""), " " }; if (skill.SkillReq.Any(x => x != 0)) { - drawStrings.AddRange(from req in skill.SkillReq where req != 0 select OldWorld.GetString(EOResourceID.SKILLMASTER_WORD_SKILL) + ": " + OldWorld.Instance.ESF.Data[req].Name); + drawStrings.AddRange(from req in skill.SkillReq where req != 0 select OldWorld.GetString(EOResourceID.SKILLMASTER_WORD_SKILL) + ": " + OldWorld.Instance.ESF[req].Name); drawStrings.Add(" "); } @@ -329,7 +329,7 @@ private void _showRequirements(Skill skill) private void _showRequirementsLabel(Skill skill) { - string full = $"{OldWorld.Instance.ESF.Data[skill.ID].Name} {skill.LevelReq} LVL, "; + string full = $"{OldWorld.Instance.ESF[skill.ID].Name} {skill.LevelReq} LVL, "; if (skill.StrReq > 0) full += $"{skill.StrReq} STR, "; if (skill.IntReq > 0) @@ -345,7 +345,7 @@ private void _showRequirementsLabel(Skill skill) if (skill.GoldReq > 0) full += $"{skill.GoldReq} Gold"; if (skill.ClassReq > 0) - full += $", {OldWorld.Instance.ECF.Data[skill.ClassReq].Name}"; + full += $", {OldWorld.Instance.ECF[skill.ClassReq].Name}"; ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, full); } diff --git a/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs b/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs index 5e1dfb46e..3ca0ba03a 100644 --- a/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs +++ b/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs @@ -24,7 +24,7 @@ public class ClientOnlineEntry : OnlineEntry public ClientOnlineEntry(string name, string title, string guild, int @class, PaperdollIconType icon) : base(name, title, guild, @class, icon) { - var record = OldWorld.Instance.ECF[@class] ?? new ECFRecord {Name = ""}; + var record = OldWorld.Instance.ECF[@class] ?? new ECFRecord().WithNames(new[] { string.Empty }); ClassString = record.ID == 0 ? "-" : record.Name; } diff --git a/EndlessClient/Old/OldCharacter.cs b/EndlessClient/Old/OldCharacter.cs index 8bf8a44bb..7e23f0388 100644 --- a/EndlessClient/Old/OldCharacter.cs +++ b/EndlessClient/Old/OldCharacter.cs @@ -509,11 +509,11 @@ public void UpdateInventoryItem(short id, int characterAmount, byte characterWei public void SetDisplayItemsFromRenderData(CharRenderData newRenderData) { - EquipItem(ItemType.Boots, (short)(OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Boots && x.DollGraphic == newRenderData.boots) ?? new EIFRecord()).ID, newRenderData.boots, true); - EquipItem(ItemType.Armor, (short)(OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Armor && x.DollGraphic == newRenderData.armor) ?? new EIFRecord()).ID, newRenderData.armor, true); - EquipItem(ItemType.Hat, (short)(OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Hat && x.DollGraphic == newRenderData.hat) ?? new EIFRecord()).ID, newRenderData.hat, true); - EquipItem(ItemType.Shield, (short)(OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == newRenderData.shield) ?? new EIFRecord()).ID, newRenderData.shield, true); - EquipItem(ItemType.Weapon, (short)(OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == newRenderData.weapon) ?? new EIFRecord()).ID, newRenderData.weapon, true); + EquipItem(ItemType.Boots, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Boots && x.DollGraphic == newRenderData.boots) ?? new EIFRecord()).ID, newRenderData.boots, true); + EquipItem(ItemType.Armor, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Armor && x.DollGraphic == newRenderData.armor) ?? new EIFRecord()).ID, newRenderData.armor, true); + EquipItem(ItemType.Hat, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Hat && x.DollGraphic == newRenderData.hat) ?? new EIFRecord()).ID, newRenderData.hat, true); + EquipItem(ItemType.Shield, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == newRenderData.shield) ?? new EIFRecord()).ID, newRenderData.shield, true); + EquipItem(ItemType.Weapon, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == newRenderData.weapon) ?? new EIFRecord()).ID, newRenderData.weapon, true); } public void UpdateStatsAfterEquip(PaperdollEquipData data) @@ -543,16 +543,16 @@ public ChestKey CanOpenChest(ChestSpawnMapEntity chest) switch (permission) //note - it would be nice to be able to send the Item IDs of the keys in the welcome packet or something { case ChestKey.Normal: - rec = OldWorld.Instance.EIF.Data.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "normal key"); + rec = OldWorld.Instance.EIF.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "normal key"); break; case ChestKey.Crystal: - rec = OldWorld.Instance.EIF.Data.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "crystal key"); + rec = OldWorld.Instance.EIF.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "crystal key"); break; case ChestKey.Silver: - rec = OldWorld.Instance.EIF.Data.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "silver key"); + rec = OldWorld.Instance.EIF.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "silver key"); break; case ChestKey.Wraith: - rec = OldWorld.Instance.EIF.Data.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "wraith key"); + rec = OldWorld.Instance.EIF.Single(_rec => _rec.Name != null && _rec.Name.ToLower() == "wraith key"); break; default: return permission; diff --git a/EndlessClient/Old/PacketAPICallbackManager.cs b/EndlessClient/Old/PacketAPICallbackManager.cs index 902e5850b..20630c278 100644 --- a/EndlessClient/Old/PacketAPICallbackManager.cs +++ b/EndlessClient/Old/PacketAPICallbackManager.cs @@ -437,7 +437,7 @@ private void _statskillLearnError(SkillMasterReply reply, short id) { //not sure if this will ever actually be sent because client validates data before trying to learn a skill case SkillMasterReply.ErrorWrongClass: - EOMessageBox.Show(DialogResourceID.SKILL_LEARN_WRONG_CLASS, " " + OldWorld.Instance.ECF.Data[id].Name + "!", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + EOMessageBox.Show(DialogResourceID.SKILL_LEARN_WRONG_CLASS, " " + OldWorld.Instance.ECF[id].Name + "!", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); break; case SkillMasterReply.ErrorRemoveItems: EOMessageBox.Show(DialogResourceID.SKILL_RESET_CHARACTER_CLEAR_PAPERDOLL, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); diff --git a/EndlessClient/Rendering/CharacterProperties/CharacterPropertyRendererBuilder.cs b/EndlessClient/Rendering/CharacterProperties/CharacterPropertyRendererBuilder.cs index 00795a33b..10e9ff9e4 100644 --- a/EndlessClient/Rendering/CharacterProperties/CharacterPropertyRendererBuilder.cs +++ b/EndlessClient/Rendering/CharacterProperties/CharacterPropertyRendererBuilder.cs @@ -72,7 +72,7 @@ private bool IsShieldBehindCharacter(ICharacterRenderProperties renderProperties private bool IsWeaponBehindCharacter(ICharacterRenderProperties renderProperties) { - var weaponInfo = EIFFile.Data.FirstOrDefault( + var weaponInfo = EIFFile.FirstOrDefault( x => x.Type == ItemType.Weapon && x.DollGraphic == renderProperties.WeaponGraphic); @@ -85,7 +85,7 @@ private bool IsWeaponBehindCharacter(ICharacterRenderProperties renderProperties private HatMaskType GetHatMaskType(ICharacterRenderProperties renderProperties) { - var hatInfo = EIFFile.Data.FirstOrDefault( + var hatInfo = EIFFile.FirstOrDefault( x => x.Type == ItemType.Hat && x.DollGraphic == renderProperties.HatGraphic); diff --git a/EndlessClient/Rendering/OldCharacterRenderer.cs b/EndlessClient/Rendering/OldCharacterRenderer.cs index cbe38f2e3..d62b7e58c 100644 --- a/EndlessClient/Rendering/OldCharacterRenderer.cs +++ b/EndlessClient/Rendering/OldCharacterRenderer.cs @@ -284,7 +284,7 @@ private void _updateDisplayDataSprites() { if (OldWorld.Instance.EIF != null) { - shieldInfo = OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == Data.shield); + shieldInfo = OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == Data.shield); if(shieldInfo != null) shield = spriteSheet.GetShield(shieldInfo.Name == "Bag" || shieldInfo.SubType == ItemSubType.Arrows || shieldInfo.SubType == ItemSubType.Wings); } @@ -299,7 +299,7 @@ private void _updateDisplayDataSprites() { if (OldWorld.Instance.EIF != null) { - weaponInfo = OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == Data.weapon); + weaponInfo = OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == Data.weapon); if(weaponInfo != null) weapon = spriteSheet.GetWeapon(weaponInfo.SubType == ItemSubType.Ranged); } @@ -321,7 +321,7 @@ private void _updateDisplayDataSprites() lock (hatHairLock) hat = spriteSheet.GetHat(); if (OldWorld.Instance.EIF != null) - hatInfo = OldWorld.Instance.EIF.Data.SingleOrDefault(x => x.Type == ItemType.Hat && x.DollGraphic == Data.hat); + hatInfo = OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Hat && x.DollGraphic == Data.hat); } else { diff --git a/EndlessClient/Rendering/Sprites/CharacterSpriteCalculator.cs b/EndlessClient/Rendering/Sprites/CharacterSpriteCalculator.cs index 73bf55e84..c5f1670ff 100644 --- a/EndlessClient/Rendering/Sprites/CharacterSpriteCalculator.cs +++ b/EndlessClient/Rendering/Sprites/CharacterSpriteCalculator.cs @@ -498,12 +498,11 @@ private int GetOffsetBasedOnState(WeaponSpriteType type) private bool BowIsEquipped(ICharacterRenderProperties characterRenderProperties) { - if (EIFFile == null || EIFFile.Data == null) + if (EIFFile == null) return false; - var itemData = EIFFile.Data; - var weaponInfo = itemData.FirstOrDefault(x => x.Type == ItemType.Weapon && - x.DollGraphic == characterRenderProperties.WeaponGraphic); + var weaponInfo = EIFFile.FirstOrDefault(x => x.Type == ItemType.Weapon && + x.DollGraphic == characterRenderProperties.WeaponGraphic); return weaponInfo != null && weaponInfo.SubType == ItemSubType.Ranged; } diff --git a/EndlessClient/Test/CharacterStateTest.cs b/EndlessClient/Test/CharacterStateTest.cs index e1d9ea979..7e4b757a5 100644 --- a/EndlessClient/Test/CharacterStateTest.cs +++ b/EndlessClient/Test/CharacterStateTest.cs @@ -171,7 +171,7 @@ public override void Update(GameTime gameTime) if (!_isBowEquipped) { _lastGraphic = _baseProperties.WeaponGraphic; - var firstBowWeapon = EIFFile.Data.First(x => x.Type == ItemType.Weapon && x.SubType == ItemSubType.Ranged); + var firstBowWeapon = EIFFile.First(x => x.Type == ItemType.Weapon && x.SubType == ItemSubType.Ranged); _baseProperties = _baseProperties.WithWeaponGraphic((short)firstBowWeapon.DollGraphic, isRanged: true); } else @@ -273,7 +273,7 @@ private bool KeyPressed(Keys key) private short GetNextItemGraphicMatching(ItemType type, short currentGraphic) { var increment = ShiftPressed ? -1 : 1; - var matchingItems = EIFFile.Data.Where(x => x.Type == type).OrderBy(x => x.ID).ToList(); + var matchingItems = EIFFile.Where(x => x.Type == type).OrderBy(x => x.ID).ToList(); _itemIndices[type] = (_itemIndices[type] + increment) % matchingItems.Count; if (_itemIndices[type] + increment < 0) From 0b668f7b05d0a7efc26675b7eb9a4d318b5b1aa2 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 20:18:21 -0700 Subject: [PATCH 5/9] Fix build in remaining projects --- BatchMap/Program.cs | 4 +- BatchPub/frmMain.cs | 14 +++---- .../BuiltInIdentifierConfigurator.cs | 8 ++-- EOBot/Program.cs | 2 +- EOBot/TrainerBot.cs | 14 +++---- .../FileTransfer/FileRequestServiceTest.cs | 38 ++++++++++--------- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/BatchMap/Program.cs b/BatchMap/Program.cs index cb7bb69c7..56c3ee086 100644 --- a/BatchMap/Program.cs +++ b/BatchMap/Program.cs @@ -194,7 +194,7 @@ private static void ProcessFiles(string src, string dst, bool singleFile) { var npcSpawn = mapFile.NPCSpawns[ndx]; var npcRec = _pubProvider.ENFFile[npcSpawn.ID]; - if (npcSpawn.ID > _pubProvider.ENFFile.Data.Count || npcRec == null) + if (npcSpawn.ID > _pubProvider.ENFFile.Length || npcRec == null) { Console.WriteLine("[MAP {0}] NPC Spawn {1}x{2} uses non-existent NPC #{3}. Removing.", mapID, npcSpawn.X, npcSpawn.Y, npcSpawn.ID); mapFile = mapFile.RemoveNPCSpawn(npcSpawn); @@ -241,7 +241,7 @@ private static void ProcessFiles(string src, string dst, bool singleFile) { var chestSpawn = mapFile.Chests[ndx]; var rec = _pubProvider.EIFFile[chestSpawn.ItemID]; - if (chestSpawn.ItemID > _pubProvider.EIFFile.Data.Count || rec == null) + if (chestSpawn.ItemID > _pubProvider.EIFFile.Length || rec == null) { Console.WriteLine("[MAP {0}] Chest Spawn {1}x{2} uses non-existent Item #{3}. Removing.", mapID, chestSpawn.X, chestSpawn.Y, chestSpawn.ItemID); mapFile = mapFile.RemoveChestSpawn(chestSpawn); diff --git a/BatchPub/frmMain.cs b/BatchPub/frmMain.cs index 26442d570..fa85a8f3e 100644 --- a/BatchPub/frmMain.cs +++ b/BatchPub/frmMain.cs @@ -7,6 +7,7 @@ using EOLib.IO; using EOLib.IO.Pub; using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; namespace BatchPub { @@ -96,7 +97,7 @@ private void btnProcess_Click(object sender, EventArgs e) } rtfOutput.Text += "Processing change: set " + pi.Name + "(" + pi.PropertyType.ToString() + ")=" + newValue.ToString() + " for all items..."; - foreach (var rec in eif.Data) + foreach (var rec in eif) { System.Reflection.PropertyInfo prop = rec.GetType().GetProperty(pi.Name); prop.SetValue(rec, Convert.ChangeType(newValue, pi.PropertyType)); @@ -169,7 +170,7 @@ private void btnProcess_Click(object sender, EventArgs e) } } - List filtered = eif.Data.Where(record => + List filtered = eif.Where(record => { EIFRecord rec = (EIFRecord)record; if (rec == null || rec.ID == 0) return false; @@ -255,13 +256,12 @@ private void btnBrowse_Click(object sender, EventArgs e) private void btnLoad_Click(object sender, EventArgs e) { - eif = new EIFFile(); - + var deserializer = new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); _fname = ""; try { var fileBytes = File.ReadAllBytes(_fname = string.IsNullOrEmpty(txtFileName.Text) ? PubFileNameConstants.PathToEIFFile : txtFileName.Text); - eif.DeserializeFromByteArray(fileBytes, new NumberEncoderService()); + eif = deserializer.DeserializeFromByteArray(fileBytes, () => new EIFFile()); lblFileName.Text = "Loaded file: " + _fname; grpStepTwo.Enabled = true; btnReset.Enabled = true; @@ -417,8 +417,8 @@ private void btnSave_Click(object sender, EventArgs e) try { - eif.CheckSum++;//todo: recalculate checksum - var bytes = eif.SerializeToByteArray(new NumberEncoderService()); + var serializer = new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); + var bytes = serializer.SerializeToByteArray(eif); } catch(Exception ex) { diff --git a/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs b/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs index 7245fb9d2..65be892a5 100644 --- a/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs +++ b/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs @@ -210,7 +210,7 @@ private Task LoginToCharacterAsync(string charName) () => new ArrayVariable( inventoryProvider.ItemInventory.Select(x => { - var itemName = pubProvider.EIFFile.Data.Single(d => d.ID == x.ItemID).Name; + var itemName = pubProvider.EIFFile.Single(d => d.ID == x.ItemID).Name; var retObj = new ObjectVariable(); retObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(itemName)); @@ -222,7 +222,7 @@ private Task LoginToCharacterAsync(string charName) () => new ArrayVariable( inventoryProvider.SpellInventory.Select(x => { - var spellName = pubProvider.ESFFile.Data.Single(d => d.ID == x.ID).Name; + var spellName = pubProvider.ESFFile.Single(d => d.ID == x.ID).Name; var retObj = new ObjectVariable(); retObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(spellName)); @@ -274,7 +274,7 @@ private IVariable GetMapStateNPC(INPC npc) var npcFile = DependencyMaster.TypeRegistry[_botIndex].Resolve().ENFFile; var npcObj = new ObjectVariable(); - npcObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(npcFile.Data.Single(x => x.ID == npc.ID).Name)); + npcObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(npcFile.Single(x => x.ID == npc.ID).Name)); npcObj.SymbolTable["x"] = Readonly(new IntVariable(npc.X)); npcObj.SymbolTable["y"] = Readonly(new IntVariable(npc.Y)); npcObj.SymbolTable["id"] = Readonly(new IntVariable(npc.ID)); @@ -287,7 +287,7 @@ private IVariable GetMapStateItem(IItem item) var itemFile = DependencyMaster.TypeRegistry[_botIndex].Resolve().EIFFile; var itemObj = new ObjectVariable(); - itemObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(itemFile.Data.Single(x => x.ID == item.ItemID).Name)); + itemObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(itemFile.Single(x => x.ID == item.ItemID).Name)); itemObj.SymbolTable["x"] = Readonly(new IntVariable(item.X)); itemObj.SymbolTable["y"] = Readonly(new IntVariable(item.Y)); itemObj.SymbolTable["id"] = Readonly(new IntVariable(item.ItemID)); diff --git a/EOBot/Program.cs b/EOBot/Program.cs index d7c96f022..5b3a6cff4 100644 --- a/EOBot/Program.cs +++ b/EOBot/Program.cs @@ -39,7 +39,7 @@ public void NPCTakeDamage(short npcIndex, int fromPlayerId, int damageToNpc, sho return; var npc = _currentMapStateRepository.NPCs.SingleOrDefault(x => x.Index == npcIndex); - var npcName = _enfFileProvider.ENFFile.Data.SingleOrDefault(x => npc != null && npc.ID == x.ID)?.Name; + var npcName = _enfFileProvider.ENFFile.SingleOrDefault(x => npc != null && npc.ID == x.ID)?.Name; var color = npcPctHealth < 25 ? ConsoleColor.Red diff --git a/EOBot/TrainerBot.cs b/EOBot/TrainerBot.cs index fd3e83e28..460542174 100644 --- a/EOBot/TrainerBot.cs +++ b/EOBot/TrainerBot.cs @@ -182,11 +182,11 @@ protected override async Task DoWorkAsync(CancellationToken ct) } healItems = charInventoryRepo.ItemInventory - .Where(x => _itemData.Data.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal)) + .Where(x => _itemData.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal)) .ToList(); healSpells = charInventoryRepo.SpellInventory - .Where(x => _spellData.Data.Any(y => y.ID == x.ID && y.Type == SpellType.Heal)) + .Where(x => _spellData.Any(y => y.ID == x.ID && y.Type == SpellType.Heal)) .ToList(); } else @@ -237,7 +237,7 @@ protected override async Task DoWorkAsync(CancellationToken ct) private async Task Attack(IMapCellState cellState) { - ConsoleHelper.WriteMessage(ConsoleHelper.Type.Attack, $"{cellState.NPC.Value.Index,7} - {_npcData.Data.Single(x => x.ID == cellState.NPC.Value.ID).Name}"); + ConsoleHelper.WriteMessage(ConsoleHelper.Type.Attack, $"{cellState.NPC.Value.Index,7} - {_npcData.Single(x => x.ID == cellState.NPC.Value.ID).Name}"); await TrySend(_characterActions.Attack); await Task.Delay(TimeSpan.FromMilliseconds(ATTACK_BACKOFF_MS)); } @@ -298,7 +298,7 @@ private async Task PickUpItem(IItem item) { await TrySend(() => { - var itemName = _itemData.Data.Single(x => x.ID == item.ItemID).Name; + var itemName = _itemData.Single(x => x.ID == item.ItemID).Name; var pickupResult = _mapActions.PickUpItem(item); if (pickupResult == ItemPickupResult.Ok) ConsoleHelper.WriteMessage(ConsoleHelper.Type.TakeItem, $"{item.Amount,7} - {itemName}"); @@ -310,14 +310,14 @@ private async Task PickUpItem(IItem item) private async Task JunkItem(IItem item) { - ConsoleHelper.WriteMessage(ConsoleHelper.Type.JunkItem, $"{item.Amount,7} - {_itemData.Data.Single(x => x.ID == item.ItemID).Name}"); + ConsoleHelper.WriteMessage(ConsoleHelper.Type.JunkItem, $"{item.Amount,7} - {_itemData.Single(x => x.ID == item.ItemID).Name}"); await TrySend(() => _mapActions.JunkItem(item)); await Task.Delay(TimeSpan.FromMilliseconds(ATTACK_BACKOFF_MS)); } private async Task CastHealSpell(IEnumerable healSpells) { - var spellToUse = _spellData.Data + var spellToUse = _spellData .Where(x => healSpells.Any(y => y.ID == x.ID) && x.Target != SpellTarget.Group) .OrderByDescending(x => x.HP) .First(); @@ -334,7 +334,7 @@ private async Task CastHealSpell(IEnumerable healSpells) private async Task UseHealItem(IEnumerable healItems) { - var itemToUse = _itemData.Data + var itemToUse = _itemData .Where(x => healItems.Any(y => y.ItemID == x.ID)) .OrderBy(x => x.HP) .First(); diff --git a/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs b/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs index 9289f042c..6909be957 100644 --- a/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs +++ b/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs @@ -12,6 +12,7 @@ using EOLib.Test.TestHelpers; using NUnit.Framework; using Moq; +using System.Collections.Generic; namespace EOLib.Test.Net.FileTransfer { @@ -23,6 +24,7 @@ public class FileRequestServiceTest private IPacketSendService _packetSendService; private INumberEncoderService _numberEncoderService; private IMapDeserializer _mapFileSerializer; + private IPubFileDeserializer _pubFileDeserializer; [SetUp] public void SetUp() @@ -30,10 +32,11 @@ public void SetUp() _packetSendService = Mock.Of(); _numberEncoderService = new NumberEncoderService(); _mapFileSerializer = Mock.Of>(); + _pubFileDeserializer = Mock.Of(); _fileRequestService = new FileRequestService(_packetSendService, - _numberEncoderService, - _mapFileSerializer); + _mapFileSerializer, + _pubFileDeserializer); } #region RequestFile Tests @@ -42,7 +45,7 @@ public void SetUp() public void RequestFile_ResponsePacketHasInvalidHeader_ThrowsEmptyPacketReceivedException() { Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Account, PacketAction.Accept); - Assert.ThrowsAsync(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1)); + Assert.ThrowsAsync(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1)); } [Test] @@ -50,7 +53,7 @@ public void RequestFile_ResponsePacketInvalidExtraByte_ThrowsMalformedPacketExce { Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Init, PacketAction.Init, (byte)InitReply.ItemFile, 33); - Assert.ThrowsAsync(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1)); + Assert.ThrowsAsync(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1)); } [Test] @@ -64,7 +67,7 @@ public void RequestFile_SendsPacket_BasedOnSpecifiedType() Mock.Get(_packetSendService).Setup(x => x.SendEncodedPacketAndWaitAsync(It.IsAny())) .Callback((IPacket packet) => packetIsCorrect = IsCorrectFileRequestPacket(packet, localType)); - _fileRequestService.RequestFile(type, 1); + _fileRequestService.RequestFile(type, 1); Assert.IsTrue(packetIsCorrect, "Incorrect packet for {0}", type); } @@ -81,10 +84,10 @@ public void RequestFile_CorrectResponse_ExecutesWithoutFault() AggregateException aggEx = null; switch (type) { - case InitFileType.Item: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; - case InitFileType.Npc: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; - case InitFileType.Spell: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; - case InitFileType.Class: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; + case InitFileType.Item: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; + case InitFileType.Npc: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; + case InitFileType.Spell: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; + case InitFileType.Class: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break; } if (aggEx != null) @@ -159,6 +162,7 @@ private static byte[] CreateFilePacket(InitFileType type) IPacketBuilder packetBuilder = new PacketBuilder(); var nes = new NumberEncoderService(); + var rs = new PubRecordSerializer(nes); switch (type) { @@ -168,8 +172,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("EIF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(new EIFRecord { ID = 1, Name = "Test1" }.SerializeToByteArray(nes)) - .AddBytes(new EIFRecord { ID = 2, Name = "eof" }.SerializeToByteArray(nes)); + .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(1).WithNames(new List { "Test1" }))) + .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(2).WithNames(new List { "eof" }))); break; case InitFileType.Npc: packetBuilder = packetBuilder @@ -177,8 +181,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("ENF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(new ENFRecord { ID = 1, Name = "Test1" }.SerializeToByteArray(nes)) - .AddBytes(new ENFRecord { ID = 2, Name = "eof" }.SerializeToByteArray(nes)); + .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(1).WithNames(new List { "Test1" }))) + .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(2).WithNames(new List { "eof" }))); break; case InitFileType.Spell: packetBuilder = packetBuilder @@ -186,8 +190,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("ESF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(new ESFRecord { ID = 1, Name = "Test1", Shout = "" }.SerializeToByteArray(nes)) - .AddBytes(new ESFRecord { ID = 2, Name = "eof", Shout = "" }.SerializeToByteArray(nes)); + .AddBytes(rs.SerializeToByteArray(new ESFRecord().WithID(1).WithNames(new List { "Test1", "" }))) + .AddBytes(rs.SerializeToByteArray(new ESFRecord().WithID(2).WithNames(new List { "eof", "" }))); break; case InitFileType.Class: packetBuilder = packetBuilder @@ -195,8 +199,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("ECF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(new ECFRecord { ID = 1, Name = "Test1" }.SerializeToByteArray(nes)) - .AddBytes(new ECFRecord { ID = 2, Name = "eof" }.SerializeToByteArray(nes)); + .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(1).WithNames(new List { "Test1" }))) + .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(2).WithNames(new List { "eof" }))); break; } From c5d0a197520ecb3fff3a6e150d4a5b3c65264985 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 20:37:22 -0700 Subject: [PATCH 6/9] Add WithName method Check that number of names passed to WithNames or ctor matches expected number of names --- EOLib.IO.Test/Pub/ECFFileTest.cs | 36 +++++++------- EOLib.IO.Test/Pub/EIFFileTest.cs | 36 +++++++------- EOLib.IO.Test/Pub/ENFFileTest.cs | 36 +++++++------- .../Serializers/PubFileSerializerTest.cs | 48 +++++++++---------- EOLib.IO/Pub/ESFRecord.cs | 6 +-- EOLib.IO/Pub/IPubRecord.cs | 2 + EOLib.IO/Pub/PubRecord.cs | 18 +++++++ .../FileTransfer/FileRequestServiceTest.cs | 12 ++--- EOLib/Net/FileTransfer/FileRequestService.cs | 9 ++-- .../HUD/Panels/Old/OldOnlineListPanel.cs | 2 +- 10 files changed, 112 insertions(+), 93 deletions(-) diff --git a/EOLib.IO.Test/Pub/ECFFileTest.cs b/EOLib.IO.Test/Pub/ECFFileTest.cs index e13e75ee7..d346713a1 100644 --- a/EOLib.IO.Test/Pub/ECFFileTest.cs +++ b/EOLib.IO.Test/Pub/ECFFileTest.cs @@ -22,15 +22,15 @@ public void HasCorrectFileType() public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeECFFile(55565554, - new ECFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new ECFRecord().WithID(2).WithNames(new List { "Test2" }), - new ECFRecord().WithID(3).WithNames(new List { "Test3" }), - new ECFRecord().WithID(4).WithNames(new List { "Test4" }), - new ECFRecord().WithID(5).WithNames(new List { "Test5" }), - new ECFRecord().WithID(6).WithNames(new List { "Test6" }), - new ECFRecord().WithID(7).WithNames(new List { "Test7" }), - new ECFRecord().WithID(8).WithNames(new List { "Test8" }), - new ECFRecord().WithID(9).WithNames(new List { "eof" })); + new ECFRecord().WithID(1).WithName("TestFixture"), + new ECFRecord().WithID(2).WithName("Test2"), + new ECFRecord().WithID(3).WithName("Test3"), + new ECFRecord().WithID(4).WithName("Test4"), + new ECFRecord().WithID(5).WithName("Test5"), + new ECFRecord().WithID(6).WithName("Test6"), + new ECFRecord().WithID(7).WithName("Test7"), + new ECFRecord().WithID(8).WithName("Test8"), + new ECFRecord().WithID(9).WithName("eof")); var serializer = CreateFileSerializer(); var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ECFFile()); @@ -45,15 +45,15 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new ECFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new ECFRecord().WithID(2).WithNames(new List { "Test2" }), - new ECFRecord().WithID(3).WithNames(new List { "Test3" }), - new ECFRecord().WithID(4).WithNames(new List { "Test4" }), - new ECFRecord().WithID(5).WithNames(new List { "Test5" }), - new ECFRecord().WithID(6).WithNames(new List { "Test6" }), - new ECFRecord().WithID(7).WithNames(new List { "Test7" }), - new ECFRecord().WithID(8).WithNames(new List { "Test8" }), - new ECFRecord().WithID(9).WithNames(new List { "eof" }) + new ECFRecord().WithID(1).WithName("TestFixture"), + new ECFRecord().WithID(2).WithName("Test2"), + new ECFRecord().WithID(3).WithName("Test3"), + new ECFRecord().WithID(4).WithName("Test4"), + new ECFRecord().WithID(5).WithName("Test5"), + new ECFRecord().WithID(6).WithName("Test6"), + new ECFRecord().WithID(7).WithName("Test7"), + new ECFRecord().WithID(8).WithName("Test8"), + new ECFRecord().WithID(9).WithName("eof") }; var bytes = MakeECFFile(55565554, records); diff --git a/EOLib.IO.Test/Pub/EIFFileTest.cs b/EOLib.IO.Test/Pub/EIFFileTest.cs index 68375846f..48dd2be77 100644 --- a/EOLib.IO.Test/Pub/EIFFileTest.cs +++ b/EOLib.IO.Test/Pub/EIFFileTest.cs @@ -22,15 +22,15 @@ public void HasCorrectFileType() public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeEIFFile(55565554, - new EIFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new EIFRecord().WithID(2).WithNames(new List { "Test2" }), - new EIFRecord().WithID(3).WithNames(new List { "Test3" }), - new EIFRecord().WithID(4).WithNames(new List { "Test4" }), - new EIFRecord().WithID(5).WithNames(new List { "Test5" }), - new EIFRecord().WithID(6).WithNames(new List { "Test6" }), - new EIFRecord().WithID(7).WithNames(new List { "Test7" }), - new EIFRecord().WithID(8).WithNames(new List { "Test8" }), - new EIFRecord().WithID(9).WithNames(new List { "eof" })); + new EIFRecord().WithID(1).WithName("TestFixture"), + new EIFRecord().WithID(2).WithName("Test2"), + new EIFRecord().WithID(3).WithName("Test3"), + new EIFRecord().WithID(4).WithName("Test4"), + new EIFRecord().WithID(5).WithName("Test5"), + new EIFRecord().WithID(6).WithName("Test6"), + new EIFRecord().WithID(7).WithName("Test7"), + new EIFRecord().WithID(8).WithName("Test8"), + new EIFRecord().WithID(9).WithName("eof")); var serializer = CreateFileSerializer(); var file = serializer.DeserializeFromByteArray(expectedBytes, () => new EIFFile()); @@ -45,15 +45,15 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new EIFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new EIFRecord().WithID(2).WithNames(new List { "Test2" }), - new EIFRecord().WithID(3).WithNames(new List { "Test3" }), - new EIFRecord().WithID(4).WithNames(new List { "Test4" }), - new EIFRecord().WithID(5).WithNames(new List { "Test5" }), - new EIFRecord().WithID(6).WithNames(new List { "Test6" }), - new EIFRecord().WithID(7).WithNames(new List { "Test7" }), - new EIFRecord().WithID(8).WithNames(new List { "Test8" }), - new EIFRecord().WithID(9).WithNames(new List { "eof" }) + new EIFRecord().WithID(1).WithName("TestFixture"), + new EIFRecord().WithID(2).WithName("Test2"), + new EIFRecord().WithID(3).WithName("Test3"), + new EIFRecord().WithID(4).WithName("Test4"), + new EIFRecord().WithID(5).WithName("Test5"), + new EIFRecord().WithID(6).WithName("Test6"), + new EIFRecord().WithID(7).WithName("Test7"), + new EIFRecord().WithID(8).WithName("Test8"), + new EIFRecord().WithID(9).WithName("eof") }; var bytes = MakeEIFFile(55565554, records); diff --git a/EOLib.IO.Test/Pub/ENFFileTest.cs b/EOLib.IO.Test/Pub/ENFFileTest.cs index aba624965..0458a0066 100644 --- a/EOLib.IO.Test/Pub/ENFFileTest.cs +++ b/EOLib.IO.Test/Pub/ENFFileTest.cs @@ -22,15 +22,15 @@ public void HasCorrectFileType() public void SerializeToByteArray_ReturnsExpectedBytes() { var expectedBytes = MakeENFFile(55565554, - new ENFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new ENFRecord().WithID(2).WithNames(new List { "Test2" }), - new ENFRecord().WithID(3).WithNames(new List { "Test3" }), - new ENFRecord().WithID(4).WithNames(new List { "Test4" }), - new ENFRecord().WithID(5).WithNames(new List { "Test5" }), - new ENFRecord().WithID(6).WithNames(new List { "Test6" }), - new ENFRecord().WithID(7).WithNames(new List { "Test7" }), - new ENFRecord().WithID(8).WithNames(new List { "Test8" }), - new ENFRecord().WithID(9).WithNames(new List { "eof" })); + new ENFRecord().WithID(1).WithName("TestFixture"), + new ENFRecord().WithID(2).WithName("Test2"), + new ENFRecord().WithID(3).WithName("Test3"), + new ENFRecord().WithID(4).WithName("Test4"), + new ENFRecord().WithID(5).WithName("Test5"), + new ENFRecord().WithID(6).WithName("Test6"), + new ENFRecord().WithID(7).WithName("Test7"), + new ENFRecord().WithID(8).WithName("Test8"), + new ENFRecord().WithID(9).WithName("eof")); var serializer = CreateFileSerializer(); var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ENFFile()); @@ -45,15 +45,15 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames() { var records = new[] { - new ENFRecord().WithID(1).WithNames(new List { "TestFixture" }), - new ENFRecord().WithID(2).WithNames(new List { "Test2" }), - new ENFRecord().WithID(3).WithNames(new List { "Test3" }), - new ENFRecord().WithID(4).WithNames(new List { "Test4" }), - new ENFRecord().WithID(5).WithNames(new List { "Test5" }), - new ENFRecord().WithID(6).WithNames(new List { "Test6" }), - new ENFRecord().WithID(7).WithNames(new List { "Test7" }), - new ENFRecord().WithID(8).WithNames(new List { "Test8" }), - new ENFRecord().WithID(9).WithNames(new List { "eof" }) + new ENFRecord().WithID(1).WithName("TestFixture"), + new ENFRecord().WithID(2).WithName("Test2"), + new ENFRecord().WithID(3).WithName("Test3"), + new ENFRecord().WithID(4).WithName("Test4"), + new ENFRecord().WithID(5).WithName("Test5"), + new ENFRecord().WithID(6).WithName("Test6"), + new ENFRecord().WithID(7).WithName("Test7"), + new ENFRecord().WithID(8).WithName("Test8"), + new ENFRecord().WithID(9).WithName("eof") }; var bytes = MakeENFFile(55565554, records); diff --git a/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs index 19aa04df0..fd24c062d 100644 --- a/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs +++ b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs @@ -21,10 +21,10 @@ public void EIFFile_DeserializeFromByteArray_WrongLength_Throws() var records = new[] { - new EIFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new EIFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new EIFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new EIFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new EIFRecord().WithID(1).WithName("Rec_1"), + new EIFRecord().WithID(2).WithName("Rec_2"), + new EIFRecord().WithID(3).WithName("Rec_3"), + new EIFRecord().WithID(4).WithName("Rec_4"), }; var pubBytesLong = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength + 1, records); @@ -42,10 +42,10 @@ public void ENFFile_DeserializeFromByteArray_WrongLength_Throws() var records = new[] { - new ENFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new ENFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new ENFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new ENFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new ENFRecord().WithID(1).WithName("Rec_1"), + new ENFRecord().WithID(2).WithName("Rec_2"), + new ENFRecord().WithID(3).WithName("Rec_3"), + new ENFRecord().WithID(4).WithName("Rec_4"), }; var pubBytesLong = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength + 1, records); @@ -84,10 +84,10 @@ public void ECFFile_DeserializeFromByteArray_WrongLength_Throws() var records = new[] { - new ECFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new ECFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new ECFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new ECFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new ECFRecord().WithID(1).WithName("Rec_1"), + new ECFRecord().WithID(2).WithName("Rec_2"), + new ECFRecord().WithID(3).WithName("Rec_3"), + new ECFRecord().WithID(4).WithName("Rec_4"), }; var pubBytesLong = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength + 1, records); @@ -105,10 +105,10 @@ public void EIFFile_DeserializeFromByteArray_HasExpectedHeader() var records = new[] { - new EIFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new EIFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new EIFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new EIFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new EIFRecord().WithID(1).WithName("Rec_1"), + new EIFRecord().WithID(2).WithName("Rec_2"), + new EIFRecord().WithID(3).WithName("Rec_3"), + new EIFRecord().WithID(4).WithName("Rec_4"), }; var pubBytes = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength, records); @@ -126,10 +126,10 @@ public void ENFFile_DeserializeFromByteArray_HasExpectedHeader() var records = new[] { - new ENFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new ENFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new ENFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new ENFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new ENFRecord().WithID(1).WithName("Rec_1"), + new ENFRecord().WithID(2).WithName("Rec_2"), + new ENFRecord().WithID(3).WithName("Rec_3"), + new ENFRecord().WithID(4).WithName("Rec_4"), }; var pubBytes = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength, records); @@ -168,10 +168,10 @@ public void ECFFile_DeserializeFromByteArray_HasExpectedHeader() var records = new[] { - new ECFRecord().WithID(1).WithNames(new List { "Rec_1" }), - new ECFRecord().WithID(2).WithNames(new List { "Rec_2" }), - new ECFRecord().WithID(3).WithNames(new List { "Rec_3" }), - new ECFRecord().WithID(4).WithNames(new List { "Rec_4" }), + new ECFRecord().WithID(1).WithName("Rec_1"), + new ECFRecord().WithID(2).WithName("Rec_2"), + new ECFRecord().WithID(3).WithName("Rec_3"), + new ECFRecord().WithID(4).WithName("Rec_4"), }; var pubBytes = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength, records); diff --git a/EOLib.IO/Pub/ESFRecord.cs b/EOLib.IO/Pub/ESFRecord.cs index 84231a477..d2a379756 100644 --- a/EOLib.IO/Pub/ESFRecord.cs +++ b/EOLib.IO/Pub/ESFRecord.cs @@ -51,12 +51,12 @@ public class ESFRecord : PubRecord public short UnkShort49 => Get(PubRecordProperty.SpellUnkShort49); public ESFRecord() - : this(0, string.Empty) + : this(0, string.Empty, string.Empty) { } - public ESFRecord(int id, string name) - : base(id, name, PubRecordProperty.Spell) + public ESFRecord(int id, string name, string shout) + : base(id, new List { name, shout }, PubRecordProperty.Spell) { } diff --git a/EOLib.IO/Pub/IPubRecord.cs b/EOLib.IO/Pub/IPubRecord.cs index dde8f975b..923f6f9fa 100644 --- a/EOLib.IO/Pub/IPubRecord.cs +++ b/EOLib.IO/Pub/IPubRecord.cs @@ -32,6 +32,8 @@ public interface IPubRecord IPubRecord WithID(int id); + IPubRecord WithName(string name); + IPubRecord WithNames(IReadOnlyList name); IPubRecord WithProperty(PubRecordProperty type, int value); diff --git a/EOLib.IO/Pub/PubRecord.cs b/EOLib.IO/Pub/PubRecord.cs index 397808481..029fad02e 100644 --- a/EOLib.IO/Pub/PubRecord.cs +++ b/EOLib.IO/Pub/PubRecord.cs @@ -41,6 +41,9 @@ public PubRecord(int id, List names, PubRecordProperty flag) public PubRecord(int id, List names, Dictionary propertyBag) { + if (names.Count != NumberOfNames) + throw new ArgumentException($"Expected {NumberOfNames} names, but got {names.Count} instead", nameof(names)); + ID = id; _names = names; _propertyBag = propertyBag; @@ -58,8 +61,23 @@ public IPubRecord WithID(int id) return copy; } + public IPubRecord WithName(string name) + { + var copy = MakeCopy(_names, _propertyBag); + + if (!copy._names.Any()) + copy._names.Add(name); + else + copy._names[0] = name; + + return copy; + } + public IPubRecord WithNames(IReadOnlyList names) { + if (names.Count != NumberOfNames) + throw new ArgumentException($"Expected {NumberOfNames} names, but got {names.Count} instead", nameof(names)); + var copy = MakeCopy(_names, _propertyBag); copy._names.Clear(); copy._names.AddRange(names); diff --git a/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs b/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs index 6909be957..8c3fa7fc6 100644 --- a/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs +++ b/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs @@ -172,8 +172,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("EIF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(1).WithNames(new List { "Test1" }))) - .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(2).WithNames(new List { "eof" }))); + .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(1).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(2).WithName("eof"))); break; case InitFileType.Npc: packetBuilder = packetBuilder @@ -181,8 +181,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("ENF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(1).WithNames(new List { "Test1" }))) - .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(2).WithNames(new List { "eof" }))); + .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(1).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(2).WithName("eof"))); break; case InitFileType.Spell: packetBuilder = packetBuilder @@ -199,8 +199,8 @@ private static byte[] CreateFilePacket(InitFileType type) .AddString("ECF").AddInt(1) //RID .AddShort(2) //Len .AddByte(1) //filler byte - .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(1).WithNames(new List { "Test1" }))) - .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(2).WithNames(new List { "eof" }))); + .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(1).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(2).WithName("eof"))); break; } diff --git a/EOLib/Net/FileTransfer/FileRequestService.cs b/EOLib/Net/FileTransfer/FileRequestService.cs index df08d5306..573aae5a8 100644 --- a/EOLib/Net/FileTransfer/FileRequestService.cs +++ b/EOLib/Net/FileTransfer/FileRequestService.cs @@ -1,13 +1,12 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AutomaticTypeMapper; +using AutomaticTypeMapper; using EOLib.Domain.Protocol; using EOLib.IO.Map; using EOLib.IO.Pub; -using EOLib.IO.Services; using EOLib.IO.Services.Serializers; using EOLib.Net.Communication; +using System; +using System.Linq; +using System.Threading.Tasks; namespace EOLib.Net.FileTransfer { diff --git a/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs b/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs index 3ca0ba03a..afba48bfb 100644 --- a/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs +++ b/EndlessClient/HUD/Panels/Old/OldOnlineListPanel.cs @@ -24,7 +24,7 @@ public class ClientOnlineEntry : OnlineEntry public ClientOnlineEntry(string name, string title, string guild, int @class, PaperdollIconType icon) : base(name, title, guild, @class, icon) { - var record = OldWorld.Instance.ECF[@class] ?? new ECFRecord().WithNames(new[] { string.Empty }); + var record = OldWorld.Instance.ECF[@class] ?? new ECFRecord().WithName(string.Empty); ClassString = record.ID == 0 ? "-" : record.Name; } From 96b22f7f89fd5c1e0849db39a02831279081b29d Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 22:26:34 -0700 Subject: [PATCH 7/9] Add test coverage for BasePubFile methods. Improve constraints on pub modifications and add record insert operation --- EOLib.IO.Test/Pub/BasePubFileTest.cs | 175 +++++++++++++++++++++++++++ EOLib.IO/Pub/BasePubFile.cs | 44 +++++-- EOLib.IO/Pub/IPubFile.cs | 41 +++++++ EOLib.IO/Pub/PubRecord.cs | 29 +++++ 4 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 EOLib.IO.Test/Pub/BasePubFileTest.cs diff --git a/EOLib.IO.Test/Pub/BasePubFileTest.cs b/EOLib.IO.Test/Pub/BasePubFileTest.cs new file mode 100644 index 000000000..c1effa031 --- /dev/null +++ b/EOLib.IO.Test/Pub/BasePubFileTest.cs @@ -0,0 +1,175 @@ +using EOLib.IO.Pub; +using NUnit.Framework; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace EOLib.IO.Test.Pub +{ + [TestFixture] + public class BasePubFileTest_EIFImpl : BasePubFileTest { } + + [TestFixture] + public class BasePubFileTest_ENFImpl : BasePubFileTest { } + + [TestFixture] + public class BasePubFileTest_ESFImpl : BasePubFileTest { } + + [TestFixture] + public class BasePubFileTest_ECFImpl : BasePubFileTest { } + + // These tests are run from the implementations + [ExcludeFromCodeCoverage] + public abstract class BasePubFileTest + where T : IPubFile, new() + where U : class, IPubRecord, new() + { + [Test] + public void WithAddedRecord_AddsRecord() + { + var file = new T(); + var record = (U)new U().WithID(1).WithName("My record"); + + var updatedFile = file.WithAddedRecord(record); + + Assert.That(file, Is.Empty); + Assert.That(updatedFile, Has.Length.EqualTo(1)); + Assert.That(updatedFile, Has.Exactly(1).Items.EqualTo(record)); + } + + [Test] + public void WithAddedRecord_DuplicateID_ThrowsArgumentException() + { + var file = new T(); + var record = (U)new U().WithID(1).WithName("My record"); + + var updatedFile = file.WithAddedRecord(record); + + Assert.That(() => updatedFile.WithAddedRecord(record), Throws.ArgumentException); + } + + [Test] + public void WithAddedRecord_IDOutOfBounds_ThrowsArgumentException() + { + var file = new T(); + var record = (U)new U().WithID(400); + + Assert.That(() => file.WithAddedRecord(record), Throws.ArgumentException); + } + + [Test] + public void WithInsertedRecord_InsertsRecordAtPosition_SpecifiedByID() + { + IPubFile file = new T(); + var record = (U)new U().WithID(1); + + file = file.WithAddedRecord(record); + + var updatedRecord = (U)record.WithName("updated"); + var updatedFile = file.WithInsertedRecord(updatedRecord); + + Assert.That(file, Has.Length.EqualTo(1)); + Assert.That(updatedFile, Has.Length.EqualTo(2)); + Assert.That(updatedFile[1].Name, Is.EqualTo("updated")); + } + + [Test] + public void WithInsertedRecord_UpdatesExistingRecordIDs() + { + IPubFile file = new T(); + var record = (U)new U().WithID(1); + + file = file.WithAddedRecord(record); + + var updatedRecord = (U)record.WithName("updated"); + var updatedFile = file.WithInsertedRecord(updatedRecord) + .WithInsertedRecord((U)updatedRecord.WithName("updated 2")) + .WithInsertedRecord((U)updatedRecord.WithName("updated 3")); + + Assert.That(file, Has.Length.EqualTo(1)); + Assert.That(updatedFile, Has.Length.EqualTo(4)); + Assert.That(updatedFile[1], Has.Property("Name").EqualTo("updated 3").And.Property("ID").EqualTo(1)); + Assert.That(updatedFile[2], Has.Property("Name").EqualTo("updated 2").And.Property("ID").EqualTo(2)); + Assert.That(updatedFile[3], Has.Property("Name").EqualTo("updated").And.Property("ID").EqualTo(3)); + Assert.That(updatedFile[4], Has.Property("Name").EqualTo(string.Empty).And.Property("ID").EqualTo(4)); + } + + [Test] + public void WithInsertedRecord_IDOutOfRange_ThrowsArgumentException() + { + IPubFile file = new T(); + var record = (U)new U().WithID(2); + + Assert.That(() => file.WithInsertedRecord(record), Throws.ArgumentException); + } + + [Test] + public void WithUpdatedRecord_UpdatesRecordProperties_ByRecordID() + { + IPubFile file = new T(); + var record = (U)new U().WithID(1); + file = file.WithAddedRecord(record); + + var updatedRecord = (U)record.WithName("Some name"); + var updatedFile = file.WithUpdatedRecord(updatedRecord); + + Assert.That(updatedFile[1].Name, Is.EqualTo("Some name")); + } + + [Test] + public void WithUpdatedRecord_IDOutOfRange_ThrowsArgumentException() + { + IPubFile file = new T(); + var record = (U)new U().WithID(1); + file = file.WithAddedRecord(record); + + Assert.That(() => file.WithUpdatedRecord((U)record.WithID(2)), Throws.ArgumentException); + } + + [Test] + public void WithRemovedRecord_RemovesRecord() + { + var record = (U)new U().WithID(1).WithName("My record"); + + var file = new T().WithAddedRecord(record); + var updatedFile = file.WithRemovedRecord(record); + + Assert.That(updatedFile, Is.Empty); + Assert.That(file, Has.Length.EqualTo(1)); + } + + [Test] + public void WithRemovedRecord_RemovesRecord_UpdatesIDs() + { + var record = (U)new U().WithID(1).WithName("My record"); + var record2 = (U)new U().WithID(2).WithName("My record 2"); + var record3 = (U)new U().WithID(3).WithName("My record 3"); + var record4 = (U)new U().WithID(4).WithName("My record 4"); + + var file = new T().WithAddedRecord(record) + .WithAddedRecord(record2) + .WithAddedRecord(record3) + .WithAddedRecord(record4); + var updatedFile = file.WithRemovedRecord(record); + + Assert.That(file, Has.Length.EqualTo(4)); + Assert.That(updatedFile, Has.Length.EqualTo(3)); + + Assert.That(updatedFile[1].ID, Is.EqualTo(1)); + + Assert.That(updatedFile, Has.Exactly(1).Items.EqualTo(record2.WithID(1))); + Assert.That(updatedFile, Has.Exactly(1).Items.EqualTo(record3.WithID(2))); + Assert.That(updatedFile, Has.Exactly(1).Items.EqualTo(record4.WithID(3))); + Assert.That(updatedFile, Has.None.With.Property("ID").EqualTo(4)); + } + + [Test] + public void WithRemovedRecord_IDOutOfrange_ThrowsArgumentException() + { + IPubFile file = new T(); + var record = (U)new U().WithID(1); + file = file.WithAddedRecord(record); + + Assert.That(() => file.WithRemovedRecord((U)record.WithID(2)), Throws.ArgumentException); + } + } +} diff --git a/EOLib.IO/Pub/BasePubFile.cs b/EOLib.IO/Pub/BasePubFile.cs index 8d594dc88..09e6dd1a0 100644 --- a/EOLib.IO/Pub/BasePubFile.cs +++ b/EOLib.IO/Pub/BasePubFile.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace EOLib.IO.Pub { @@ -9,13 +10,16 @@ public abstract class BasePubFile : IPubFile { private readonly List _data; + /// public abstract string FileType { get; } + /// public int CheckSum { get; private set; } + /// public int Length => _data.Count; - // pub files use 1-based indexing + /// public TRecord this[int id] => _data[id - 1]; protected BasePubFile() @@ -29,6 +33,7 @@ protected BasePubFile(int checksum, List data) _data = data; } + /// public IPubFile WithCheckSum(int checksum) { var copy = MakeCopy(); @@ -36,28 +41,53 @@ public IPubFile WithCheckSum(int checksum) return copy; } + /// public IPubFile WithAddedRecord(TRecord record) { + if (_data.Any(x => x.ID == record.ID)) + throw new ArgumentException($"A record with ID {record.ID} already exists in this pub file", nameof(record)); + + if (record.ID != _data.Count + 1) + throw new ArgumentException($"Record ID {record.ID} is beyond the bounds of the collection", nameof(record)); + var copy = MakeCopy(); copy._data.Add(record); + copy._data.Sort((a, b) => a.ID - b.ID); return copy; } + /// + public IPubFile WithInsertedRecord(TRecord record) + { + if (_data.Count < record.ID) + throw new ArgumentException($"Record {record.ID} ({record.Name}) is not part of the pub file", nameof(record)); + + var copy = MakeCopy(); + copy._data.Insert(record.ID - 1, record); + AdjustIDs(copy._data); + return copy; + } + + /// public IPubFile WithUpdatedRecord(TRecord record) { - if (_data.Count <= record.ID) - throw new ArgumentException($"Record {record.ID} ({record.Name}) is not part of the pub file"); + if (_data.Count < record.ID) + throw new ArgumentException($"Record {record.ID} ({record.Name}) is not part of the pub file", nameof(record)); var copy = MakeCopy(); - copy._data[record.ID] = record; + copy._data[record.ID - 1] = record; return copy; } + /// public IPubFile WithRemovedRecord(TRecord record) { + if (_data.Count < record.ID) + throw new ArgumentException($"Record {record.ID} ({record.Name}) is not part of the pub file", nameof(record)); + var copy = MakeCopy(); - if (copy._data.Remove(record)) - AdjustIDs(copy._data); + copy._data.Remove(record); + AdjustIDs(copy._data); return copy; } @@ -76,7 +106,7 @@ IEnumerator IEnumerable.GetEnumerator() private static void AdjustIDs(List data) { for (int i = 0; i < data.Count; i++) - data[i] = (TRecord)data[i].WithID(i); + data[i] = (TRecord)data[i].WithID(i + 1); } } } diff --git a/EOLib.IO/Pub/IPubFile.cs b/EOLib.IO/Pub/IPubFile.cs index b5c99d28c..f304a84c1 100644 --- a/EOLib.IO/Pub/IPubFile.cs +++ b/EOLib.IO/Pub/IPubFile.cs @@ -5,23 +5,64 @@ namespace EOLib.IO.Pub public interface IPubFile : IPubFile, IEnumerable where TRecord : class, IPubRecord, new() { + /// + /// Get the record at the corresponding record ID (1-based: the first record is at ID 1). + /// + /// The ID of the record to find. + /// The record. TRecord this[int id] { get; } + /// + /// Create a copy of this pub file with a newly added record. + /// + /// The record to add. + /// The updated pub file. IPubFile WithAddedRecord(TRecord record); + /// + /// Create a copy of this pub file with a record inserted at the ID specified by record.ID. Updates all record IDs in the file to match their corresponding index (1-based). + /// + /// The record to insert. + /// The updated pub file. + IPubFile WithInsertedRecord(TRecord record); + + /// + /// Create a copy of this pub file with a record updated at the ID specified by record.ID. + /// + /// The record to insert. + /// The updated pub file. IPubFile WithUpdatedRecord(TRecord record); + /// + /// Create a copy of this pub file with a record removed at the ID specified by record.ID. Updates all record IDs in the file to match their corresponding index (1-based). + /// + /// The record to insert. + /// The updated pub file. IPubFile WithRemovedRecord(TRecord record); } public interface IPubFile { + /// + /// The type of the file, usually a 3-character string e.g. EIF/ENF/ESF/ECF + /// string FileType { get; } + /// + /// The file checksum. This is really two shorts encoded next to each other but is represented as an int. + /// int CheckSum { get; } + /// + /// The length of the file (number of records) + /// int Length { get; } + /// + /// Create a copy of this pub file with the specified checksum value. + /// + /// The new checksum. + /// The updated pub file. IPubFile WithCheckSum(int checkSum); } } diff --git a/EOLib.IO/Pub/PubRecord.cs b/EOLib.IO/Pub/PubRecord.cs index 029fad02e..8961851f8 100644 --- a/EOLib.IO/Pub/PubRecord.cs +++ b/EOLib.IO/Pub/PubRecord.cs @@ -56,6 +56,9 @@ public PubRecord(int id, List names, Dictionary(PubRecordProperty property) => CastTo.From(Bag[property].Value); + public override bool Equals(object obj) + { + var pr = obj as PubRecord; + if (pr == null) + return false; + + return ID == pr.ID && + DataSize == pr.DataSize && + Names.Intersect(pr.Names).Count() == Names.Count && + Bag.Intersect(pr.Bag).Count() == Bag.Count; + } + + public override int GetHashCode() + { + var hash = 397 ^ ID.GetHashCode(); + hash = (hash * 397) ^ DataSize.GetHashCode(); + hash = (hash * 397) ^ Names.Select(x => x.GetHashCode()).Aggregate(hash, (a, b) => (a * 397) ^ b); + hash = (hash * 397) ^ Bag.Values.Select(x => x.GetHashCode()).Aggregate(hash, (a, b) => (a * 397) ^ b); + return hash; + } + + public override string ToString() + { + return $"{GetType().Name}: {ID} {Name} (size={DataSize})"; + } + protected virtual PubRecord MakeCopy(List names, Dictionary propertyBag) { return new PubRecord(ID, new List(names), new Dictionary(propertyBag)); From a3bc199bc8cec60000826d8364ad217e46ac226a Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 22:44:53 -0700 Subject: [PATCH 8/9] Make existing pub file tests more generic --- EOLib.IO.Test/Pub/ECFFileTest.cs | 73 ------- EOLib.IO.Test/Pub/EIFFileTest.cs | 73 ------- EOLib.IO.Test/Pub/ENFFileTest.cs | 73 ------- EOLib.IO.Test/Pub/ESFFileTest.cs | 73 ------- .../Serializers/PubFileSerializerTest.cs | 197 ++++++------------ 5 files changed, 66 insertions(+), 423 deletions(-) diff --git a/EOLib.IO.Test/Pub/ECFFileTest.cs b/EOLib.IO.Test/Pub/ECFFileTest.cs index d346713a1..88d0622bc 100644 --- a/EOLib.IO.Test/Pub/ECFFileTest.cs +++ b/EOLib.IO.Test/Pub/ECFFileTest.cs @@ -1,11 +1,6 @@ using EOLib.IO.Pub; -using EOLib.IO.Services; -using EOLib.IO.Services.Serializers; using NUnit.Framework; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; namespace EOLib.IO.Test.Pub { @@ -17,73 +12,5 @@ public void HasCorrectFileType() { Assert.That(new ECFFile().FileType, Is.EqualTo("ECF")); } - - [Test] - public void SerializeToByteArray_ReturnsExpectedBytes() - { - var expectedBytes = MakeECFFile(55565554, - new ECFRecord().WithID(1).WithName("TestFixture"), - new ECFRecord().WithID(2).WithName("Test2"), - new ECFRecord().WithID(3).WithName("Test3"), - new ECFRecord().WithID(4).WithName("Test4"), - new ECFRecord().WithID(5).WithName("Test5"), - new ECFRecord().WithID(6).WithName("Test6"), - new ECFRecord().WithID(7).WithName("Test7"), - new ECFRecord().WithID(8).WithName("Test8"), - new ECFRecord().WithID(9).WithName("eof")); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ECFFile()); - - var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void DeserializeFromByteArray_HasExpectedIDAndNames() - { - var records = new[] - { - new ECFRecord().WithID(1).WithName("TestFixture"), - new ECFRecord().WithID(2).WithName("Test2"), - new ECFRecord().WithID(3).WithName("Test3"), - new ECFRecord().WithID(4).WithName("Test4"), - new ECFRecord().WithID(5).WithName("Test5"), - new ECFRecord().WithID(6).WithName("Test6"), - new ECFRecord().WithID(7).WithName("Test7"), - new ECFRecord().WithID(8).WithName("Test8"), - new ECFRecord().WithID(9).WithName("eof") - }; - var bytes = MakeECFFile(55565554, records); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(bytes, () => new ECFFile()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - file.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeECFFile(int checksum, params IPubRecord[] records) - { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ECF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - - var recordSerializer = new PubRecordSerializer(numberEncoderService); - foreach (var record in records) - bytes.AddRange(recordSerializer.SerializeToByteArray(record)); - - return bytes.ToArray(); - } - - private static IPubFileSerializer CreateFileSerializer() - { - return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); - } } } diff --git a/EOLib.IO.Test/Pub/EIFFileTest.cs b/EOLib.IO.Test/Pub/EIFFileTest.cs index 48dd2be77..00c1209da 100644 --- a/EOLib.IO.Test/Pub/EIFFileTest.cs +++ b/EOLib.IO.Test/Pub/EIFFileTest.cs @@ -1,11 +1,6 @@ using EOLib.IO.Pub; -using EOLib.IO.Services; -using EOLib.IO.Services.Serializers; using NUnit.Framework; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; namespace EOLib.IO.Test.Pub { @@ -17,73 +12,5 @@ public void HasCorrectFileType() { Assert.That(new EIFFile().FileType, Is.EqualTo("EIF")); } - - [Test] - public void SerializeToByteArray_ReturnsExpectedBytes() - { - var expectedBytes = MakeEIFFile(55565554, - new EIFRecord().WithID(1).WithName("TestFixture"), - new EIFRecord().WithID(2).WithName("Test2"), - new EIFRecord().WithID(3).WithName("Test3"), - new EIFRecord().WithID(4).WithName("Test4"), - new EIFRecord().WithID(5).WithName("Test5"), - new EIFRecord().WithID(6).WithName("Test6"), - new EIFRecord().WithID(7).WithName("Test7"), - new EIFRecord().WithID(8).WithName("Test8"), - new EIFRecord().WithID(9).WithName("eof")); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(expectedBytes, () => new EIFFile()); - - var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void DeserializeFromByteArray_HasExpectedIDAndNames() - { - var records = new[] - { - new EIFRecord().WithID(1).WithName("TestFixture"), - new EIFRecord().WithID(2).WithName("Test2"), - new EIFRecord().WithID(3).WithName("Test3"), - new EIFRecord().WithID(4).WithName("Test4"), - new EIFRecord().WithID(5).WithName("Test5"), - new EIFRecord().WithID(6).WithName("Test6"), - new EIFRecord().WithID(7).WithName("Test7"), - new EIFRecord().WithID(8).WithName("Test8"), - new EIFRecord().WithID(9).WithName("eof") - }; - var bytes = MakeEIFFile(55565554, records); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(bytes, () => new EIFFile()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - file.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeEIFFile(int checksum, params IPubRecord[] records) - { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("EIF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - - var recordSerializer = new PubRecordSerializer(numberEncoderService); - foreach (var record in records) - bytes.AddRange(recordSerializer.SerializeToByteArray(record)); - - return bytes.ToArray(); - } - - private static IPubFileSerializer CreateFileSerializer() - { - return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); - } } } diff --git a/EOLib.IO.Test/Pub/ENFFileTest.cs b/EOLib.IO.Test/Pub/ENFFileTest.cs index 0458a0066..a9ba82954 100644 --- a/EOLib.IO.Test/Pub/ENFFileTest.cs +++ b/EOLib.IO.Test/Pub/ENFFileTest.cs @@ -1,11 +1,6 @@ using EOLib.IO.Pub; -using EOLib.IO.Services; -using EOLib.IO.Services.Serializers; using NUnit.Framework; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; namespace EOLib.IO.Test.Pub { @@ -17,73 +12,5 @@ public void HasCorrectFileType() { Assert.That(new ENFFile().FileType, Is.EqualTo("ENF")); } - - [Test] - public void SerializeToByteArray_ReturnsExpectedBytes() - { - var expectedBytes = MakeENFFile(55565554, - new ENFRecord().WithID(1).WithName("TestFixture"), - new ENFRecord().WithID(2).WithName("Test2"), - new ENFRecord().WithID(3).WithName("Test3"), - new ENFRecord().WithID(4).WithName("Test4"), - new ENFRecord().WithID(5).WithName("Test5"), - new ENFRecord().WithID(6).WithName("Test6"), - new ENFRecord().WithID(7).WithName("Test7"), - new ENFRecord().WithID(8).WithName("Test8"), - new ENFRecord().WithID(9).WithName("eof")); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ENFFile()); - - var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void DeserializeFromByteArray_HasExpectedIDAndNames() - { - var records = new[] - { - new ENFRecord().WithID(1).WithName("TestFixture"), - new ENFRecord().WithID(2).WithName("Test2"), - new ENFRecord().WithID(3).WithName("Test3"), - new ENFRecord().WithID(4).WithName("Test4"), - new ENFRecord().WithID(5).WithName("Test5"), - new ENFRecord().WithID(6).WithName("Test6"), - new ENFRecord().WithID(7).WithName("Test7"), - new ENFRecord().WithID(8).WithName("Test8"), - new ENFRecord().WithID(9).WithName("eof") - }; - var bytes = MakeENFFile(55565554, records); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(bytes, () => new ENFFile()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - file.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeENFFile(int checksum, params IPubRecord[] records) - { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ENF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - - var recordSerializer = new PubRecordSerializer(numberEncoderService); - foreach (var record in records) - bytes.AddRange(recordSerializer.SerializeToByteArray(record)); - - return bytes.ToArray(); - } - - private static IPubFileSerializer CreateFileSerializer() - { - return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); - } } } diff --git a/EOLib.IO.Test/Pub/ESFFileTest.cs b/EOLib.IO.Test/Pub/ESFFileTest.cs index 7af8d4fe9..85715868f 100644 --- a/EOLib.IO.Test/Pub/ESFFileTest.cs +++ b/EOLib.IO.Test/Pub/ESFFileTest.cs @@ -1,11 +1,6 @@ using EOLib.IO.Pub; -using EOLib.IO.Services; -using EOLib.IO.Services.Serializers; using NUnit.Framework; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; namespace EOLib.IO.Test.Pub { @@ -17,73 +12,5 @@ public void HasCorrectFileType() { Assert.That(new ESFFile().FileType, Is.EqualTo("ESF")); } - - [Test] - public void SerializeToByteArray_ReturnsExpectedBytes() - { - var expectedBytes = MakeESFFile(55565554, - new ESFRecord().WithID(1).WithNames(new List { "TestFixture", "Shout" }), - new ESFRecord().WithID(2).WithNames(new List { "Test2", "Shout" }), - new ESFRecord().WithID(3).WithNames(new List { "Test3", "Shout" }), - new ESFRecord().WithID(4).WithNames(new List { "Test4", "Shout" }), - new ESFRecord().WithID(5).WithNames(new List { "Test5", "Shout" }), - new ESFRecord().WithID(6).WithNames(new List { "Test6", "Shout" }), - new ESFRecord().WithID(7).WithNames(new List { "Test7", "Shout" }), - new ESFRecord().WithID(8).WithNames(new List { "Test8", "Shout" }), - new ESFRecord().WithID(9).WithNames(new List { "eof", "eof" })); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(expectedBytes, () => new ESFFile()); - - var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - public void DeserializeFromByteArray_HasExpectedIDAndNames() - { - var records = new[] - { - new ESFRecord().WithID(1).WithNames(new List { "TestFixture", "Shout" }), - new ESFRecord().WithID(2).WithNames(new List { "Test2", "Shout" }), - new ESFRecord().WithID(3).WithNames(new List { "Test3", "Shout" }), - new ESFRecord().WithID(4).WithNames(new List { "Test4", "Shout" }), - new ESFRecord().WithID(5).WithNames(new List { "Test5", "Shout" }), - new ESFRecord().WithID(6).WithNames(new List { "Test6", "Shout" }), - new ESFRecord().WithID(7).WithNames(new List { "Test7", "Shout" }), - new ESFRecord().WithID(8).WithNames(new List { "Test8", "Shout" }), - new ESFRecord().WithID(9).WithNames(new List { "eof", "eof" }) - }; - var bytes = MakeESFFile(55565554, records); - - var serializer = CreateFileSerializer(); - var file = serializer.DeserializeFromByteArray(bytes, () => new ESFFile()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - file.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeESFFile(int checksum, params IPubRecord[] records) - { - var numberEncoderService = new NumberEncoderService(); - - var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes("ESF")); - bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); - bytes.AddRange(numberEncoderService.EncodeNumber(records.Length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - - var recordSerializer = new PubRecordSerializer(numberEncoderService); - foreach (var record in records) - bytes.AddRange(recordSerializer.SerializeToByteArray(record)); - - return bytes.ToArray(); - } - - private static IPubFileSerializer CreateFileSerializer() - { - return new PubFileSerializer(new NumberEncoderService(), new PubRecordSerializer(new NumberEncoderService())); - } } } diff --git a/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs index fd24c062d..4295c51c9 100644 --- a/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs +++ b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs @@ -2,192 +2,127 @@ using EOLib.IO.Services; using EOLib.IO.Services.Serializers; using NUnit.Framework; -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Text; namespace EOLib.IO.Test.Services.Serializers { - [TestFixture, ExcludeFromCodeCoverage] - public class PubFileSerializerTest - { - [Test] - public void EIFFile_DeserializeFromByteArray_WrongLength_Throws() - { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; - - var records = new[] - { - new EIFRecord().WithID(1).WithName("Rec_1"), - new EIFRecord().WithID(2).WithName("Rec_2"), - new EIFRecord().WithID(3).WithName("Rec_3"), - new EIFRecord().WithID(4).WithName("Rec_4"), - }; - - var pubBytesLong = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength + 1, records); - var pubBytesShort = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength - 1, records); - - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new EIFFile()), Throws.InstanceOf()); - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new EIFFile()), Throws.InstanceOf()); - } - - [Test] - public void ENFFile_DeserializeFromByteArray_WrongLength_Throws() - { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; - - var records = new[] - { - new ENFRecord().WithID(1).WithName("Rec_1"), - new ENFRecord().WithID(2).WithName("Rec_2"), - new ENFRecord().WithID(3).WithName("Rec_3"), - new ENFRecord().WithID(4).WithName("Rec_4"), - }; - - var pubBytesLong = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength + 1, records); - var pubBytesShort = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength - 1, records); - - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ENFFile()), Throws.InstanceOf()); - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ENFFile()), Throws.InstanceOf()); - } + [TestFixture] + public class PubFileSerializerTest_EIFImpl : PubFileSerializerTest { } - [Test] - public void ESFFile_DeserializeFromByteArray_WrongLength_Throws() - { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; + [TestFixture] + public class PubFileSerializerTest_ENFImpl : PubFileSerializerTest { } - var records = new[] - { - new ESFRecord().WithID(1).WithNames(new List { "Rec_1", "1_ceR" }), - new ESFRecord().WithID(2).WithNames(new List { "Rec_2", "2_ceR" }), - new ESFRecord().WithID(3).WithNames(new List { "Rec_3", "3_ceR" }), - new ESFRecord().WithID(4).WithNames(new List { "Rec_4", "4_ceR" }), - }; + [TestFixture] + public class PubFileSerializerTest_ESFImpl : PubFileSerializerTest { } - var pubBytesLong = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength + 1, records); - var pubBytesShort = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength - 1, records); - - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ESFFile()), Throws.InstanceOf()); - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ESFFile()), Throws.InstanceOf()); - } + [TestFixture] + public class PubFileSerializerTest_ECFImpl : PubFileSerializerTest { } + [ExcludeFromCodeCoverage] + public abstract class PubFileSerializerTest + where T : IPubFile, new() + where U : class, IPubRecord, new() + { [Test] - public void ECFFile_DeserializeFromByteArray_WrongLength_Throws() + public void DeserializeFromByteArray_WrongLength_Throws() { const int ExpectedChecksum = 1234567890; const int ExpectedLength = 4; var records = new[] { - new ECFRecord().WithID(1).WithName("Rec_1"), - new ECFRecord().WithID(2).WithName("Rec_2"), - new ECFRecord().WithID(3).WithName("Rec_3"), - new ECFRecord().WithID(4).WithName("Rec_4"), + new U().WithID(1).WithName("Rec_1"), + new U().WithID(2).WithName("Rec_2"), + new U().WithID(3).WithName("Rec_3"), + new U().WithID(4).WithName("Rec_4"), }; - var pubBytesLong = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength + 1, records); - var pubBytesShort = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength - 1, records); + var pubBytesLong = MakePubFileBytes(ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes(ExpectedChecksum, ExpectedLength - 1, records); - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new ECFFile()), Throws.InstanceOf()); - Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new ECFFile()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new T()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new T()), Throws.InstanceOf()); } [Test] - public void EIFFile_DeserializeFromByteArray_HasExpectedHeader() + public void DeserializeFromByteArray_HasExpectedHeader() { const int ExpectedChecksum = 1234567890; const int ExpectedLength = 4; var records = new[] { - new EIFRecord().WithID(1).WithName("Rec_1"), - new EIFRecord().WithID(2).WithName("Rec_2"), - new EIFRecord().WithID(3).WithName("Rec_3"), - new EIFRecord().WithID(4).WithName("Rec_4"), + new U().WithID(1).WithName("Rec_1"), + new U().WithID(2).WithName("Rec_2"), + new U().WithID(3).WithName("Rec_3"), + new U().WithID(4).WithName("Rec_4"), }; - var pubBytes = MakePubFileBytes("EIF", ExpectedChecksum, ExpectedLength, records); - var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new EIFFile()); + var pubBytes = MakePubFileBytes(ExpectedChecksum, ExpectedLength, records); + var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new T()); Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); Assert.That(file.Length, Is.EqualTo(ExpectedLength)); } [Test] - public void ENFFile_DeserializeFromByteArray_HasExpectedHeader() + public void SerializeToByteArray_ReturnsExpectedBytes() { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; - - var records = new[] - { - new ENFRecord().WithID(1).WithName("Rec_1"), - new ENFRecord().WithID(2).WithName("Rec_2"), - new ENFRecord().WithID(3).WithName("Rec_3"), - new ENFRecord().WithID(4).WithName("Rec_4"), - }; - - var pubBytes = MakePubFileBytes("ENF", ExpectedChecksum, ExpectedLength, records); - var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ENFFile()); - - Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); - Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + var expectedBytes = MakePubFileBytes(55565554, + 9, + new U().WithID(1).WithName("TestFixture"), + new U().WithID(2).WithName("Test2"), + new U().WithID(3).WithName("Test3"), + new U().WithID(4).WithName("Test4"), + new U().WithID(5).WithName("Test5"), + new U().WithID(6).WithName("Test6"), + new U().WithID(7).WithName("Test7"), + new U().WithID(8).WithName("Test8"), + new U().WithID(9).WithName("eof")); + + var serializer = CreateSerializer(); + var file = serializer.DeserializeFromByteArray(expectedBytes, () => new T()); + + var actualBytes = serializer.SerializeToByteArray(file, rewriteChecksum: false); + + CollectionAssert.AreEqual(expectedBytes, actualBytes); } [Test] - public void ESFFile_DeserializeFromByteArray_HasExpectedHeader() + public void DeserializeFromByteArray_HasExpectedIDAndNames() { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; - var records = new[] { - new ESFRecord().WithID(1).WithNames(new List { "Rec_1", "1_ceR" }), - new ESFRecord().WithID(2).WithNames(new List { "Rec_2", "2_ceR" }), - new ESFRecord().WithID(3).WithNames(new List { "Rec_3", "3_ceR" }), - new ESFRecord().WithID(4).WithNames(new List { "Rec_4", "4_ceR" }), + new U().WithID(1).WithName("TestFixture"), + new U().WithID(2).WithName("Test2"), + new U().WithID(3).WithName("Test3"), + new U().WithID(4).WithName("Test4"), + new U().WithID(5).WithName("Test5"), + new U().WithID(6).WithName("Test6"), + new U().WithID(7).WithName("Test7"), + new U().WithID(8).WithName("Test8"), + new U().WithID(9).WithName("eof") }; + var bytes = MakePubFileBytes(55565554, 9, records); - var pubBytes = MakePubFileBytes("ESF", ExpectedChecksum, ExpectedLength, records); - var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ESFFile()); + var serializer = CreateSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new T()); - Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); - Assert.That(file.Length, Is.EqualTo(ExpectedLength)); - } - - [Test] - public void ECFFile_DeserializeFromByteArray_HasExpectedHeader() - { - const int ExpectedChecksum = 1234567890; - const int ExpectedLength = 4; - - var records = new[] - { - new ECFRecord().WithID(1).WithName("Rec_1"), - new ECFRecord().WithID(2).WithName("Rec_2"), - new ECFRecord().WithID(3).WithName("Rec_3"), - new ECFRecord().WithID(4).WithName("Rec_4"), - }; - - var pubBytes = MakePubFileBytes("ECF", ExpectedChecksum, ExpectedLength, records); - var file = CreateSerializer().DeserializeFromByteArray(pubBytes, () => new ECFFile()); - - Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum)); - Assert.That(file.Length, Is.EqualTo(ExpectedLength)); + CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), + file.Select(x => new { x.ID, x.Name }).ToList()); } - private byte[] MakePubFileBytes(string type, int checksum, int length, params IPubRecord[] records) + private byte[] MakePubFileBytes(int checksum, int length, params IPubRecord[] records) { var numberEncoderService = new NumberEncoderService(); var recordSerializer = new PubRecordSerializer(numberEncoderService); var bytes = new List(); - bytes.AddRange(Encoding.ASCII.GetBytes(type)); + bytes.AddRange(Encoding.ASCII.GetBytes(new T().FileType)); bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4)); bytes.AddRange(numberEncoderService.EncodeNumber(length, 2)); bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); From 771735f08c83784cbd77ef702b2025ad2777f028 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 16 Mar 2022 23:12:07 -0700 Subject: [PATCH 9/9] Add coverage for testing serialization/deserialization of pub record implementations Fix bug where properties with same offset would not be updated when changed Fix bug in Equals comparison with multiple identical names (e.g. for spell records name/shout) --- .../Serializers/PubRecordSerializerTest.cs | 62 +++++++++++++++++++ EOLib.IO/Pub/PubRecord.cs | 24 +++++-- 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 EOLib.IO.Test/Services/Serializers/PubRecordSerializerTest.cs diff --git a/EOLib.IO.Test/Services/Serializers/PubRecordSerializerTest.cs b/EOLib.IO.Test/Services/Serializers/PubRecordSerializerTest.cs new file mode 100644 index 000000000..9e61634ec --- /dev/null +++ b/EOLib.IO.Test/Services/Serializers/PubRecordSerializerTest.cs @@ -0,0 +1,62 @@ +using EOLib.IO.Pub; +using EOLib.IO.Services; +using EOLib.IO.Services.Serializers; +using NUnit.Framework; +using System.Collections.Generic; + +namespace EOLib.IO.Test.Services.Serializers +{ + [TestFixture] + public class PubRecordSerializerTest_EIFRecordImpl : PubRecordSerializerTest { } + + [TestFixture] + public class PubRecordSerializerTest_ENFRecordImpl : PubRecordSerializerTest { } + + [TestFixture] + public class PubRecordSerializerTest_ESFRecordImpl : PubRecordSerializerTest { } + + [TestFixture] + public class PubRecordSerializerTest_ECFRecordImpl : PubRecordSerializerTest { } + + public abstract class PubRecordSerializerTest + where T : class, IPubRecord, new() + { + [Test] + public void SerializeAndDeserialize_HasExpectedProperties() + { + var record = new T(); + + var names = new List(); + for (int nameNdx = 0; nameNdx < record.NumberOfNames; nameNdx++) + names.Add($"name {nameNdx + 1}"); + record = (T)record.WithNames(names); + + int i = 1; + var expectedValues = new List<(PubRecordProperty Key, int Value)>(record.Bag.Count); + var offsets = new HashSet(); + foreach (var property in record.Bag.Keys) + { + // don't overwrite values for duplicate offsets (unions in records) + if (offsets.Contains(record.Bag[property].Offset)) + continue; + + offsets.Add(record.Bag[property].Offset); + + record = (T)record.WithProperty(property, i); + expectedValues.Add((property, i++)); + } + + var serializer = Create(); + var bytes = serializer.SerializeToByteArray(record); + + var loadedRecord = serializer.DeserializeFromByteArray(bytes, () => new T()); + + Assert.That(record, Is.EqualTo(loadedRecord)); + } + + private static IPubRecordSerializer Create() + { + return new PubRecordSerializer(new NumberEncoderService()); + } + } +} diff --git a/EOLib.IO/Pub/PubRecord.cs b/EOLib.IO/Pub/PubRecord.cs index 8961851f8..a23f88d53 100644 --- a/EOLib.IO/Pub/PubRecord.cs +++ b/EOLib.IO/Pub/PubRecord.cs @@ -91,7 +91,14 @@ public IPubRecord WithProperty(PubRecordProperty type, int value) { var copy = MakeCopy(_names, _propertyBag); var existing = copy._propertyBag[type]; - copy._propertyBag[type] = new RecordData(existing.Offset, existing.Length, value); + + var allPropertiesWithOffset = copy._propertyBag + .Where(x => x.Value.Offset == existing.Offset && x.Value.Length == existing.Length) + .Select(x => x.Key); + + foreach (var propertyKey in allPropertiesWithOffset) + copy._propertyBag[propertyKey] = new RecordData(existing.Offset, existing.Length, value); + return copy; } @@ -103,10 +110,17 @@ public override bool Equals(object obj) if (pr == null) return false; - return ID == pr.ID && - DataSize == pr.DataSize && - Names.Intersect(pr.Names).Count() == Names.Count && - Bag.Intersect(pr.Bag).Count() == Bag.Count; + if (ID == pr.ID && + DataSize == pr.DataSize) + { + var namesMatch = true; + for (int i = 0; i < NumberOfNames; i++) + namesMatch &= Names[i] == pr.Names[i]; + + return namesMatch && Bag.Intersect(pr.Bag).Count() == Bag.Count; + } + + return false; } public override int GetHashCode()