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.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 index 4b2647ece..c1effa031 100644 --- a/EOLib.IO.Test/Pub/BasePubFileTest.cs +++ b/EOLib.IO.Test/Pub/BasePubFileTest.cs @@ -1,102 +1,175 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using EOLib.IO.Pub; -using EOLib.IO.Services; +using EOLib.IO.Pub; using NUnit.Framework; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace EOLib.IO.Test.Pub { - [TestFixture, ExcludeFromCodeCoverage] - public class BasePubFileTest + [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() { - //This covers the BasePubFile abstract class. - private BasePubFile _baseFile; + [Test] + public void WithAddedRecord_AddsRecord() + { + var file = new T(); + var record = (U)new U().WithID(1).WithName("My record"); - [SetUp] - public void SetUp() + 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() { - _baseFile = new DummyFile(); + 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 PubFile_HasExpectedChecksumAndLength() + public void WithAddedRecord_IDOutOfBounds_ThrowsArgumentException() { - Assert.AreEqual(0, _baseFile.CheckSum); - Assert.AreEqual(0, _baseFile.Length); + var file = new T(); + var record = (U)new U().WithID(400); + + Assert.That(() => file.WithAddedRecord(record), Throws.ArgumentException); } [Test] - public void PubFile_WithOneItemRecord_HasExpectedLength() + public void WithInsertedRecord_InsertsRecordAtPosition_SpecifiedByID() { - var bytes = MakeDummyFile(new DummyRecord { ID = 1, Name = "TestItem" }); + IPubFile file = new T(); + var record = (U)new U().WithID(1); - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + file = file.WithAddedRecord(record); - Assert.AreEqual(1, _baseFile.Length); + 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 PubFile_Indexing_ReturnsNullWhenLessThan1() + public void WithInsertedRecord_UpdatesExistingRecordIDs() { - 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" }); + 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)); + } - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + [Test] + public void WithInsertedRecord_IDOutOfRange_ThrowsArgumentException() + { + IPubFile file = new T(); + var record = (U)new U().WithID(2); - Assert.AreEqual(4, _baseFile.Length); - Assert.IsNull(_baseFile[0]); + Assert.That(() => file.WithInsertedRecord(record), Throws.ArgumentException); } [Test] - public void PubFile_Indexing_ReturnsNullWhenGreaterThanCount() + public void WithUpdatedRecord_UpdatesRecordProperties_ByRecordID() { - var bytes = MakeDummyFile(new DummyRecord { ID = 1, Name = "TestItem" }, - new DummyRecord { ID = 2, Name = "Test2" }); + IPubFile file = new T(); + var record = (U)new U().WithID(1); + file = file.WithAddedRecord(record); - _baseFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); + var updatedRecord = (U)record.WithName("Some name"); + var updatedFile = file.WithUpdatedRecord(updatedRecord); - Assert.AreEqual(2, _baseFile.Length); - Assert.IsNull(_baseFile[3]); + Assert.That(updatedFile[1].Name, Is.EqualTo("Some name")); } [Test] - public void PubFile_Indexing_ReturnsExpectedItemWhenRequestedByID() + public void WithUpdatedRecord_IDOutOfRange_ThrowsArgumentException() { - 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); + 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); } - private byte[] MakeDummyFile(params DummyRecord[] records) + [Test] + public void WithRemovedRecord_RemovesRecord() { - var numberEncoderService = new NumberEncoderService(); + var record = (U)new U().WithID(1).WithName("My record"); + + var file = new T().WithAddedRecord(record); + var updatedFile = file.WithRemovedRecord(record); - var bytes = new List(); + Assert.That(updatedFile, Is.Empty); + Assert.That(file, Has.Length.EqualTo(1)); + } - bytes.Add((byte)records.Length); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); + [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); - return bytes.ToArray(); + Assert.That(() => file.WithRemovedRecord((U)record.WithID(2)), Throws.ArgumentException); } } } 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..88d0622bc 100644 --- a/EOLib.IO.Test/Pub/ECFFileTest.cs +++ b/EOLib.IO.Test/Pub/ECFFileTest.cs @@ -1,127 +1,16 @@ -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 EOLib.IO.Pub; using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; 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); - } - - [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" }); - - _classFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); - - var actualBytes = _classFile.SerializeToByteArray(new NumberEncoderService(), 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"} - }; - var bytes = MakeECFFile(55565554, records); - - _classFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _classFile.Data.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeECFFile(int checksum, params ECFRecord[] 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]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); - } - - private byte[] MakeECFFileWithWrongLength(int checksum, int length, params ECFRecord[] 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(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + Assert.That(new ECFFile().FileType, Is.EqualTo("ECF")); } } } diff --git a/EOLib.IO.Test/Pub/ECFRecordTest.cs b/EOLib.IO.Test/Pub/ECFRecordTest.cs index 3cf23216a..beb111628 100644 --- a/EOLib.IO.Test/Pub/ECFRecordTest.cs +++ b/EOLib.IO.Test/Pub/ECFRecordTest.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; +using EOLib.IO.Pub; +using NUnit.Framework; +using System; 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 { @@ -14,172 +10,25 @@ namespace EOLib.IO.Test.Pub 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() + public void ECFRecord_HasAllExpectedProperties() { - 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 expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Class)) + .Except(new[] { PubRecordProperty.Class }); - var properties = record.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); - 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); - } + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); } [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) + public void ECFRecord_HasExpectedDataSize() { - 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(); + const int ExpectedDataSize = 14; + Assert.That(new ECFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); } } } diff --git a/EOLib.IO.Test/Pub/EIFFileTest.cs b/EOLib.IO.Test/Pub/EIFFileTest.cs index 03dcd31dd..00c1209da 100644 --- a/EOLib.IO.Test/Pub/EIFFileTest.cs +++ b/EOLib.IO.Test/Pub/EIFFileTest.cs @@ -1,127 +1,16 @@ -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 EOLib.IO.Pub; using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; 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); - } - - [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"}); - - _itemFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); - - var actualBytes = _itemFile.SerializeToByteArray(new NumberEncoderService(), rewriteChecksum: false); - - CollectionAssert.AreEqual(expectedBytes, actualBytes); - } - - [Test] - 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"} - }; - var bytes = MakeEIFFile(55565554, records); - - _itemFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - CollectionAssert.AreEqual(records.Select(x => new {x.ID, x.Name}).ToList(), - _itemFile.Data.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) - { - 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]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); - } - - private byte[] MakeEIFFileWithWrongLength(int checksum, int length, params EIFRecord[] 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(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + Assert.That(new EIFFile().FileType, Is.EqualTo("EIF")); } } } diff --git a/EOLib.IO.Test/Pub/EIFRecordTest.cs b/EOLib.IO.Test/Pub/EIFRecordTest.cs index 3c51d803b..45710d4cd 100644 --- a/EOLib.IO.Test/Pub/EIFRecordTest.cs +++ b/EOLib.IO.Test/Pub/EIFRecordTest.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; +using EOLib.IO.Pub; +using NUnit.Framework; +using System; 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 { @@ -14,300 +10,25 @@ namespace EOLib.IO.Test.Pub 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() + public void EIFRecord_HasAllExpectedProperties() { - 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); + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Item)) + .Except(new[] { PubRecordProperty.Item }); - 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); + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); - value++; - } + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); } [Test] - public void EIFRecord_SharedValueSet2_HaveSameValue() + public void EIFRecord_HasExpectedDataSize() { - 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(); + const int ExpectedDataSize = 58; + Assert.That(new EIFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); } } } diff --git a/EOLib.IO.Test/Pub/ENFFileTest.cs b/EOLib.IO.Test/Pub/ENFFileTest.cs index c24c829e0..a9ba82954 100644 --- a/EOLib.IO.Test/Pub/ENFFileTest.cs +++ b/EOLib.IO.Test/Pub/ENFFileTest.cs @@ -1,127 +1,16 @@ -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 EOLib.IO.Pub; using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; 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); - } - - [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" }); - - _npcFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); - - var actualBytes = _npcFile.SerializeToByteArray(new NumberEncoderService(), 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"} - }; - var bytes = MakeENFFile(55565554, records); - - _npcFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _npcFile.Data.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeENFFile(int checksum, params ENFRecord[] 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]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); - } - - private byte[] MakeENFFileWithWrongLength(int checksum, int length, params ENFRecord[] 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(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + Assert.That(new ENFFile().FileType, Is.EqualTo("ENF")); } } } diff --git a/EOLib.IO.Test/Pub/ENFRecordTest.cs b/EOLib.IO.Test/Pub/ENFRecordTest.cs index 29fdb80f7..c15ba99c1 100644 --- a/EOLib.IO.Test/Pub/ENFRecordTest.cs +++ b/EOLib.IO.Test/Pub/ENFRecordTest.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; +using EOLib.IO.Pub; +using NUnit.Framework; +using System; 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 { @@ -14,189 +10,25 @@ namespace EOLib.IO.Test.Pub 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() + public void ENFRecord_HasAllExpectedProperties() { - 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); + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.NPC)) + .Except(new[] { PubRecordProperty.NPC }); - foreach (var property in properties) - { - var expectedValue = property.GetValue(sourceRecord); - var actualValue = property.GetValue(record); + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); - Assert.AreEqual(expectedValue, actualValue, "Property: {0}", property.Name); - } + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); } [Test] - public void ENFRecord_DeserializeFromByteArray_InvalidArrayLength_ThrowsException() + public void ENFRecord_HasExpectedDataSize() { - 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(); + const int ExpectedDataSize = 39; + Assert.That(new ENFRecord().DataSize, Is.EqualTo(ExpectedDataSize)); } } } diff --git a/EOLib.IO.Test/Pub/ESFFileTest.cs b/EOLib.IO.Test/Pub/ESFFileTest.cs index cb958441c..85715868f 100644 --- a/EOLib.IO.Test/Pub/ESFFileTest.cs +++ b/EOLib.IO.Test/Pub/ESFFileTest.cs @@ -1,127 +1,16 @@ -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 EOLib.IO.Pub; using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; 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); - } - - [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 = "-" }); - - _spellFile.DeserializeFromByteArray(expectedBytes, new NumberEncoderService()); - - var actualBytes = _spellFile.SerializeToByteArray(new NumberEncoderService(), 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 = ""} - }; - var bytes = MakeESFFile(55565554, records); - - _spellFile.DeserializeFromByteArray(bytes, new NumberEncoderService()); - - CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), - _spellFile.Data.Select(x => new { x.ID, x.Name }).ToList()); - } - - private byte[] MakeESFFile(int checksum, params ESFRecord[] 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]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); - } - - private byte[] MakeESFFileWithWrongLength(int checksum, int length, params ESFRecord[] 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(length, 2)); - bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]); - foreach (var record in records) - bytes.AddRange(record.SerializeToByteArray(numberEncoderService)); - - return bytes.ToArray(); + Assert.That(new ESFFile().FileType, Is.EqualTo("ESF")); } } } diff --git a/EOLib.IO.Test/Pub/ESFRecordTest.cs b/EOLib.IO.Test/Pub/ESFRecordTest.cs index 31206d294..aa2d1dc50 100644 --- a/EOLib.IO.Test/Pub/ESFRecordTest.cs +++ b/EOLib.IO.Test/Pub/ESFRecordTest.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; +using EOLib.IO.Pub; +using NUnit.Framework; +using System; 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 { @@ -14,202 +10,25 @@ namespace EOLib.IO.Test.Pub 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() + public void ESFRecord_HasAllExpectedProperties() { - 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)); - } + var expectedProperties = ((PubRecordProperty[])Enum.GetValues(typeof(PubRecordProperty))) + .Where(x => x.HasFlag(PubRecordProperty.Spell)) + .Except(new[] { PubRecordProperty.Spell }); - [Test] - public void ESFRecord_GetClassProperty_ThrowsArgumentOutOfRangeException() - { - const PubRecordProperty invalidProperty = PubRecordProperty.ClassAgi; + Assert.That(record.Bag.Count, Is.EqualTo(expectedProperties.Count())); - var record = new ESFRecord(); - - Assert.Throws(() => record.Get(invalidProperty)); + foreach (var p in expectedProperties) + Assert.That(record.Bag, Does.ContainKey(p)); } [Test] - public void ESFRecord_InvalidPropertyReturnType_ThrowsInvalidCastException() + public void ESFRecord_HasExpectedDataSize() { - 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(); + 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..4295c51c9 --- /dev/null +++ b/EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs @@ -0,0 +1,140 @@ +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; + +namespace EOLib.IO.Test.Services.Serializers +{ + [TestFixture] + public class PubFileSerializerTest_EIFImpl : PubFileSerializerTest { } + + [TestFixture] + public class PubFileSerializerTest_ENFImpl : PubFileSerializerTest { } + + [TestFixture] + public class PubFileSerializerTest_ESFImpl : PubFileSerializerTest { } + + [TestFixture] + public class PubFileSerializerTest_ECFImpl : PubFileSerializerTest { } + + [ExcludeFromCodeCoverage] + public abstract class PubFileSerializerTest + where T : IPubFile, new() + where U : class, IPubRecord, new() + { + [Test] + public void DeserializeFromByteArray_WrongLength_Throws() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + 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(ExpectedChecksum, ExpectedLength + 1, records); + var pubBytesShort = MakePubFileBytes(ExpectedChecksum, ExpectedLength - 1, records); + + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesLong, () => new T()), Throws.InstanceOf()); + Assert.That(() => CreateSerializer().DeserializeFromByteArray(pubBytesShort, () => new T()), Throws.InstanceOf()); + } + + [Test] + public void DeserializeFromByteArray_HasExpectedHeader() + { + const int ExpectedChecksum = 1234567890; + const int ExpectedLength = 4; + + var records = new[] + { + 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(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 SerializeToByteArray_ReturnsExpectedBytes() + { + 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 DeserializeFromByteArray_HasExpectedIDAndNames() + { + var records = new[] + { + 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 serializer = CreateSerializer(); + var file = serializer.DeserializeFromByteArray(bytes, () => new T()); + + CollectionAssert.AreEqual(records.Select(x => new { x.ID, x.Name }).ToList(), + file.Select(x => new { x.ID, x.Name }).ToList()); + } + + 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(new T().FileType)); + 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.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/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..09e6dd1a0 100644 --- a/EOLib.IO/Pub/BasePubFile.cs +++ b/EOLib.IO/Pub/BasePubFile.cs @@ -1,73 +1,112 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Text; -using EOLib.IO.Services; +using System.Linq; 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] + /// + public TRecord this[int id] => _data[id - 1]; + + protected BasePubFile() { - get - { - if (id < 1 || id > _data.Count) - return null; + _data = new List(); + } - return _data[id - 1]; - } + protected BasePubFile(int checksum, List data) + { + CheckSum = checksum; + _data = data; } - public IReadOnlyList Data => _data; + /// + public IPubFile WithCheckSum(int checksum) + { + var copy = MakeCopy(); + copy.CheckSum = checksum; + return copy; + } - protected BasePubFile() + /// + public IPubFile WithAddedRecord(TRecord record) { - _data = new List(); + 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 byte[] SerializeToByteArray(INumberEncoderService numberEncoderService, bool rewriteChecksum = true) + /// + public IPubFile WithInsertedRecord(TRecord record) { - 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; + 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", nameof(record)); + + var copy = MakeCopy(); + copy._data[record.ID - 1] = record; + return copy; } - public abstract void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService); + /// + 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(); + copy._data.Remove(record); + AdjustIDs(copy._data); + return copy; + } + + public IEnumerator GetEnumerator() + { + return _data.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _data.GetEnumerator(); + } + + 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 + 1); + } } } 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..d2a379756 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, 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, string shout) + : base(id, new List { name, shout }, 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..f304a84c1 100644 --- a/EOLib.IO/Pub/IPubFile.cs +++ b/EOLib.IO/Pub/IPubFile.cs @@ -1,26 +1,68 @@ 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() { + /// + /// 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; } - IReadOnlyList Data { 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; } - int CheckSum { get; set; } + /// + /// 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; } - byte[] SerializeToByteArray(INumberEncoderService numberEncoderService, bool rewriteChecksum = true); - - void DeserializeFromByteArray(byte[] bytes, INumberEncoderService numberEncoderService); + /// + /// 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/IPubRecord.cs b/EOLib.IO/Pub/IPubRecord.cs index f8d100cee..923f6f9fa 100644 --- a/EOLib.IO/Pub/IPubRecord.cs +++ b/EOLib.IO/Pub/IPubRecord.cs @@ -1,19 +1,41 @@ -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 WithName(string name); + + 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..a23f88d53 --- /dev/null +++ b/EOLib.IO/Pub/PubRecord.cs @@ -0,0 +1,160 @@ +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) + { + if (names.Count != NumberOfNames) + throw new ArgumentException($"Expected {NumberOfNames} names, but got {names.Count} instead", nameof(names)); + + 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) + { + if (id <= 0) + throw new ArgumentOutOfRangeException(nameof(id)); + + var copy = MakeCopy(_names, _propertyBag); + copy.ID = 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); + return copy; + } + + public IPubRecord WithProperty(PubRecordProperty type, int value) + { + var copy = MakeCopy(_names, _propertyBag); + var existing = copy._propertyBag[type]; + + 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; + } + + public T Get(PubRecordProperty property) => CastTo.From(Bag[property].Value); + + public override bool Equals(object obj) + { + var pr = obj as PubRecord; + if (pr == null) + return false; + + 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() + { + 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)); + } + + 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 new file mode 100644 index 000000000..47dfaaaa8 --- /dev/null +++ b/EOLib.IO/Pub/PubRecordProperty.cs @@ -0,0 +1,266 @@ +using System; + +namespace EOLib.IO.Pub +{ + /// + /// Enum representing the different properties that exist within the pub records + /// + [Flags] + public enum PubRecordProperty : uint + { + 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, + + NPC = 0x200, + + [RecordData(0, 2)] + NPCGraphic, + + [RecordData(2, 1)] + NPCUnkByte2, + + [RecordData(3, 2)] + NPCBoss, + [RecordData(5, 2)] + NPCChild, + [RecordData(7, 2)] + NPCType, + [RecordData(9, 2)] + NPCVendorID, + + [RecordData(11, 3)] + NPCHP, + + [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, + + [RecordData(36, 3)] + NPCExp, + + 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, + + 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 + } +} 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/PubRecordProperty.cs b/EOLib.IO/PubRecordProperty.cs deleted file mode 100644 index 76f63a802..000000000 --- a/EOLib.IO/PubRecordProperty.cs +++ /dev/null @@ -1,175 +0,0 @@ -namespace EOLib.IO -{ - /// - /// 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 - { - //Applicable to all records - GlobalID, - GlobalName, - - #region Item Specific - - ItemGraphic, - ItemType, - ItemSubType, - - ItemSpecial, - ItemHP, - ItemTP, - ItemMinDam, - ItemMaxDam, - ItemAccuracy, - ItemEvade, - ItemArmor, - - ItemUnkByte19, - - ItemStr, - ItemInt, - ItemWis, - ItemAgi, - ItemCon, - ItemCha, - - ItemLight, - ItemDark, - ItemEarth, - ItemAir, - ItemWater, - ItemFire, - - ItemScrollMap, - ItemDollGraphic, - ItemExpReward, - ItemHairColor, - ItemEffect, - ItemKey, - - ItemGender, - ItemScrollX, - - ItemScrollY, - ItemDualWieldDollGraphic, - - ItemLevelReq, - ItemClassReq, - ItemStrReq, - ItemIntReq, - ItemWisReq, - ItemAgiReq, - ItemConReq, - ItemChaReq, - - ItemElement, - ItemElementPower, - - ItemWeight, - - ItemUnkByte56, - - ItemSize, - - #endregion - - #region NPC Specific - - NPCGraphic, - - NPCUnkByte2, - - NPCBoss, - NPCChild, - NPCType, - - NPCUnkShort14, - - NPCVendorID, - - NPCHP, - NPCExp, - NPCMinDam, - NPCMaxDam, - - NPCAccuracy, - NPCEvade, - NPCArmor, - - NPCUnkByte26, - NPCUnkShort27, - NPCUnkShort29, - NPCElementWeak, - NPCElementWeakPower, - NPCUnkByte35, - - #endregion - - #region Spell Specific - - SpellShout, - - SpellIcon, - SpellGraphic, - - SpellTP, - SpellSP, - - SpellCastTime, - - SpellUnkByte9, - SpellUnkByte10, - - SpellType, - - SpellUnkByte14, - SpellUnkShort15, - - SpellTargetRestrict, - SpellTarget, - - SpellUnkByte19, - SpellUnkByte20, - SpellUnkShort21, - - SpellMinDam, - SpellMaxDam, - SpellAccuracy, - - SpellUnkShort29, - SpellUnkShort31, - SpellUnkByte33, - - SpellHP, - - SpellUnkShort36, - SpellUnkByte38, - SpellUnkShort39, - SpellUnkShort41, - SpellUnkShort43, - SpellUnkShort45, - SpellUnkShort47, - SpellUnkShort49, - - #endregion - - #region Class Specific - - ClassBase, - ClassType, - - ClassStr, - ClassInt, - ClassWis, - ClassAgi, - ClassCon, - ClassCha - - #endregion - } -} 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..44424185f --- /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 || mem.Position < mem.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()); } } } diff --git a/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs b/EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs index 9289f042c..8c3fa7fc6 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).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new EIFRecord().WithID(2).WithName("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).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new ENFRecord().WithID(2).WithName("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).WithName("Test1"))) + .AddBytes(rs.SerializeToByteArray(new ECFRecord().WithID(2).WithName("eof"))); break; } 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..573aae5a8 100644 --- a/EOLib/Net/FileTransfer/FileRequestService.cs +++ b/EOLib/Net/FileTransfer/FileRequestService.cs @@ -1,12 +1,12 @@ -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 { @@ -14,16 +14,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 +43,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 +62,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 +110,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..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 {Name = ""}; + var record = OldWorld.Instance.ECF[@class] ?? new ECFRecord().WithName(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)