diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index 10403a3..737b096 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -4,7 +4,7 @@ Exe enable enable - preview + 12 net6.0;net7.0;net8.0 diff --git a/Server/Server.csproj b/Server/Server.csproj index 05c9822..f0d9ddb 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -6,7 +6,7 @@ warnings Linux Server - default + 12 true net8.0;net7.0 true diff --git a/Server/Services/ItemObjectPoolService.cs b/Server/Services/ItemObjectPoolService.cs new file mode 100644 index 0000000..3a8ac45 --- /dev/null +++ b/Server/Services/ItemObjectPoolService.cs @@ -0,0 +1,51 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Wonderking.Game.Data; +using Wonderking.Game.Reader; + +namespace Server.Services; + +public class ItemObjectPoolService : IHostedService +{ + readonly ConcurrentDictionary _itemObjectPool = new(); + private readonly ItemReader _itemReader; + + public ItemObjectPoolService(IConfiguration configuration) + { + _itemReader = new ItemReader(configuration.GetSection("Game").GetSection("Data").GetValue("Path") ?? + string.Empty); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var amountOfEntries = _itemReader.GetAmountOfEntries(); + ParallelEnumerable.Range(0, (int)amountOfEntries).AsParallel().ForAll(i => + { + var itemObject = _itemReader.GetEntry((uint)i); + _itemObjectPool.TryAdd(itemObject.ItemID, itemObject); + }); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _itemReader.Dispose(); + return Task.CompletedTask; + } + + public ItemObject GetItem(ushort itemId) + { + return _itemObjectPool[itemId]; + } + + public bool ContainsItem(ushort itemId) + { + return _itemObjectPool.ContainsKey(itemId); + } + + public IQueryable QueryItems() + { + return _itemObjectPool.AsReadOnly().Values.AsQueryable(); + } +} diff --git a/Wonderking/Game/Data/Item/ContainedItem.cs b/Wonderking/Game/Data/Item/ContainedItem.cs new file mode 100644 index 0000000..3f819f4 --- /dev/null +++ b/Wonderking/Game/Data/Item/ContainedItem.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Data.Item; + +[StructLayout(LayoutKind.Sequential)] +public struct ContainedItem +{ + public short ID { get; internal set; } + public float ObtainChance { get; internal set; } +} diff --git a/Wonderking/Game/Data/Item/CraftMaterial.cs b/Wonderking/Game/Data/Item/CraftMaterial.cs new file mode 100644 index 0000000..b548e36 --- /dev/null +++ b/Wonderking/Game/Data/Item/CraftMaterial.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Data.Item; + +[StructLayout(LayoutKind.Sequential)] +public struct CraftMaterial +{ + public uint ID; + public uint Amount; +} + diff --git a/Wonderking/Game/Data/Item/ElementalStats.cs b/Wonderking/Game/Data/Item/ElementalStats.cs new file mode 100644 index 0000000..1599a5f --- /dev/null +++ b/Wonderking/Game/Data/Item/ElementalStats.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.GameData.Item; + +[StructLayout(LayoutKind.Explicit, Size = 64)] +public struct ElementalStats +{ + [FieldOffset(0), MarshalAs(UnmanagedType.I4)] + public int MinimumFireDamage; + + [FieldOffset(4), MarshalAs(UnmanagedType.I4)] + public int MinimumWaterDamage; + + [FieldOffset(8), MarshalAs(UnmanagedType.I4)] + public int MinimumDarkDamage; + + [FieldOffset(12), MarshalAs(UnmanagedType.I4)] + public int MinimumHolyDamage; + + [FieldOffset(16), MarshalAs(UnmanagedType.I4)] + public int MaximumFireDamage; + + [FieldOffset(20), MarshalAs(UnmanagedType.I4)] + public int MaximumWaterDamage; + + [FieldOffset(24), MarshalAs(UnmanagedType.I4)] + public int MaximumDarkDamage; + + [FieldOffset(28), MarshalAs(UnmanagedType.I4)] + public int MaximumHolyDamage; + + [FieldOffset(32), MarshalAs(UnmanagedType.U4)] + public uint ElementFire; + + [FieldOffset(36), MarshalAs(UnmanagedType.U4)] + public uint ElementWater; + + [FieldOffset(40), MarshalAs(UnmanagedType.U4)] + public uint ElementDark; + + [FieldOffset(44), MarshalAs(UnmanagedType.U4)] + public uint ElementHoly; + + [FieldOffset(48), MarshalAs(UnmanagedType.I4)] + public int FireResistance; + + [FieldOffset(52), MarshalAs(UnmanagedType.I4)] + public int WaterResistance; + + [FieldOffset(56), MarshalAs(UnmanagedType.I4)] + public int DarkResistance; + + [FieldOffset(60), MarshalAs(UnmanagedType.I4)] + public int HolyResistance; +} diff --git a/Wonderking/Game/Data/Item/ItemOptions.cs b/Wonderking/Game/Data/Item/ItemOptions.cs new file mode 100644 index 0000000..3b52fb1 --- /dev/null +++ b/Wonderking/Game/Data/Item/ItemOptions.cs @@ -0,0 +1,7 @@ +namespace Wonderking.Game.Data.Item; + +public struct ItemOptions +{ + public ICollection OptionIDs { get; internal set; } + public bool OptionAvailable { get; internal set; } +} diff --git a/Wonderking/Game/Data/Item/Stats.cs b/Wonderking/Game/Data/Item/Stats.cs new file mode 100644 index 0000000..ab60dd0 --- /dev/null +++ b/Wonderking/Game/Data/Item/Stats.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.GameData.Item; + +[StructLayout(LayoutKind.Explicit, Size = 24)] +public struct Stats +{ + [FieldOffset(0), MarshalAs(UnmanagedType.I4)] + public int Strength; + + [FieldOffset(4), MarshalAs(UnmanagedType.I4)] + public int Dexterity; + + [FieldOffset(8), MarshalAs(UnmanagedType.I4)] + public int Intelligence; + + [FieldOffset(12), MarshalAs(UnmanagedType.I4)] + public int Vitality; + + [FieldOffset(16), MarshalAs(UnmanagedType.I4)] + public int Luck; + + [FieldOffset(20), MarshalAs(UnmanagedType.I4)] + public int Wisdom; +} diff --git a/Wonderking/Game/Data/ItemObject.cs b/Wonderking/Game/Data/ItemObject.cs new file mode 100644 index 0000000..2f1bf2e --- /dev/null +++ b/Wonderking/Game/Data/ItemObject.cs @@ -0,0 +1,171 @@ +using System.Text.Json.Serialization; +using Wonderking.Game.Data.Item; +using Wonderking.GameData.Item; +using Wonderking.Utils; + +namespace Wonderking.Game.Data; + +[GameDataMetadata(932, "baseitemdata.dat", 197)] +public struct ItemObject +{ + public uint ItemID { get; set; } + public bool Disabled { get; set; } + public uint ItemType { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown3 { get; set; } + + public uint ClassNo1 { get; set; } + public uint ClassNo2 { get; set; } + public uint ClassNo3 { get; set; } + public uint ClassNo4 { get; set; } + public uint SlotNo1 { get; set; } + public uint SlotNo2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown4 { get; set; } + + public uint IsCash { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown5 { get; set; } + + public uint Price { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown7 { get; set; } + + public uint MaxNumber { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown17 { get; set; } + + public uint MaximumLevelRequirement { get; set; } + public uint SexNo { get; set; } + public uint WeaponSomething { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown8 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R2C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown9 { get; set; } + + public Stats Stats { get; set; } + public ElementalStats ElementalStats { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R7C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R8C { get; set; } + + public float Speed { get; set; } + + public float Jump { get; set; } + public int StatDefense { get; set; } + public uint MagicID { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown13 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown14 { get; set; } + + public int AdditionalHealthRecoveryVolume { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R9C_1 { get; set; } + + public int AdditionalManaRecoveryVolume { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R9C_2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R10C { get; set; } + + public int AdditionalHealthPoints { get; set; } + public int AdditionalManaPoints { get; set; } + public bool IsArrow { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown18 { get; set; } + + public int AdditionalEvasionRate { get; set; } + public int HitRate { get; set; } + + public int ChanceToHit { get; set; } + public int MagicalDamage { get; set; } + public int CriticalHitChance { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R12C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown16 { get; set; } + + public int MinimalAttackDamage { get; set; } + public int MaximalAttackDamage { get; set; } + public int PhysicalDamage { get; set; } + public CraftMaterial[] CraftMaterial { get; set; } + public uint CraftResultAmount { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R14C { get; set; } + + public uint CraftResultItem { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R15C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R16C { get; set; } + + public int InventoryX { get; set; } + public int InventoryY { get; set; } + public int InventoryWidth { get; set; } + public int InventoryHeight { get; set; } + public int SheetID { get; set; } + public string Name { get; set; } + public string Description { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown1 { get; set; } + + public bool IsEnchantable { get; set; } + + public uint SetID { get; set; } + public uint[] SetItems { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown1_2 { get; set; } + + public ItemOptions Options { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown19 { get; set; } + + public byte PetID { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown20 { get; set; } + + public byte HitBoxScaling { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown20_2 { get; set; } + + public ContainedItem[] ContainedItems { get; set; } + + public bool IsQuestItem { get; set; } + public byte MinimumLevelRequirement { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown21_2 { get; set; } +} diff --git a/Wonderking/Game/DataReader.cs b/Wonderking/Game/DataReader.cs new file mode 100644 index 0000000..e67c8a4 --- /dev/null +++ b/Wonderking/Game/DataReader.cs @@ -0,0 +1,50 @@ +using System.Reflection; + +namespace Wonderking.Game; + +public abstract class DataReader +{ + protected DataReader(string path) + { + Path = path; + DatFileContent = new(GetDatFileContent(path).ToArray()); + } + + private protected string Path { get; init; } + + public abstract uint GetAmountOfEntries(); + public abstract T GetEntry(uint entryId); + + protected ushort GetSizeOfEntry() + { + return typeof(T).GetCustomAttribute()?.DataEntrySize ?? + throw new NotSupportedException("DataEntrySize is null"); + } + + private static string GetDatFileName() + { + return typeof(T).GetCustomAttribute()?.DatFileName ?? + throw new NotSupportedException("DatFileName is null"); + } + + private static byte GetXorKey() + { + return typeof(T).GetCustomAttribute()?.XorKey ?? + throw new NotSupportedException("XorKey is null"); + } + + protected MemoryStream DatFileContent { get; } + + private static Span GetDatFileContent(string path) + { + var fileData = File.ReadAllBytes(path + GetDatFileName()); + var data = new byte[fileData.Length]; + + for (var i = 0; i < fileData.Length; i++) + { + data[i] = (byte)(fileData[i] ^ GetXorKey()); + } + + return data; + } +} diff --git a/Wonderking/Game/GameDataMetadataAttribute.cs b/Wonderking/Game/GameDataMetadataAttribute.cs new file mode 100644 index 0000000..b9db18c --- /dev/null +++ b/Wonderking/Game/GameDataMetadataAttribute.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace Wonderking.Game; + +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] +public class GameDataMetadataAttribute(ushort dataEntrySize, string datFileName, byte xorKey) : Attribute +{ + [UsedImplicitly] public byte XorKey { get; init; } = xorKey; + [UsedImplicitly] public ushort DataEntrySize { get; init; } = dataEntrySize; + + [UsedImplicitly] internal string DatFileName { get; init; } = datFileName; +} diff --git a/Wonderking/Game/Reader/BinaryReader.cs b/Wonderking/Game/Reader/BinaryReader.cs new file mode 100644 index 0000000..62c683d --- /dev/null +++ b/Wonderking/Game/Reader/BinaryReader.cs @@ -0,0 +1,75 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Reader; + +public static class BinaryReader where T : new() +{ + public static readonly Func Read; + + static BinaryReader() + { + var type = typeof(T); + + if (type == typeof(bool)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadBoolean()); + } + else if (type == typeof(char)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadChar()); + } + else if (type == typeof(string)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadString()); + } + else if (type == typeof(sbyte)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadSByte()); + } + else if (type == typeof(short)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt16()); + } + else if (type == typeof(int)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt32()); + } + else if (type == typeof(long)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt64()); + } + else if (type == typeof(byte)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadByte()); + } + else if (type == typeof(ushort)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt16()); + } + else if (type == typeof(uint)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt32()); + } + else if (type == typeof(ulong)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt64()); + } + else if (type == typeof(float)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadSingle()); + } + else if (type == typeof(double)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadDouble()); + } + else if (type == typeof(decimal)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadDecimal()); + } + else + { + Read = (Func)(p => + (T)(object)p.ReadBytes(Marshal.SizeOf(new T()))); + } + } +} diff --git a/Wonderking/Game/Reader/GenericReaderExtensions.cs b/Wonderking/Game/Reader/GenericReaderExtensions.cs new file mode 100644 index 0000000..17e888e --- /dev/null +++ b/Wonderking/Game/Reader/GenericReaderExtensions.cs @@ -0,0 +1,37 @@ + +/* Nicht gemergte Ă„nderung aus Projekt "Wonderking(net7.0)" +Vor: +using System.Runtime.InteropServices; +using System.Text; +Nach: +using System.Text; +*/ + +using System.Text; + +namespace Wonderking.Game.Reader; + +public static class GenericReaderExtensions +{ + public static string ReadString(this BinaryReader reader, int length) + { + var ret = Encoding.ASCII.GetString(reader.ReadBytes(length)).Replace("\0", ""); + return ret; + } + + public static T[] ReadArray(this BinaryReader pReader, int pLength) where T : new() + { + var array = new T[pLength]; + for (var index = 0; index < pLength; ++index) + { + array[index] = pReader.Read(); + } + + return array; + } + + public static T Read(this BinaryReader br) where T : new() + { + return BinaryReader.Read(br); + } +} diff --git a/Wonderking/Game/Reader/ItemReader.cs b/Wonderking/Game/Reader/ItemReader.cs new file mode 100644 index 0000000..38ae8a3 --- /dev/null +++ b/Wonderking/Game/Reader/ItemReader.cs @@ -0,0 +1,107 @@ +using Wonderking.Game.Data; +using Wonderking.Game.Data.Item; + +namespace Wonderking.Game.Reader; + +public class ItemReader(string path) : DataReader(path), IDisposable +{ + public override uint GetAmountOfEntries() + { + return (uint)((this.DatFileContent.Length - 9) / this.GetSizeOfEntry()); + } + + public override ItemObject GetEntry(uint entryId) + { + var item = new ItemObject(); + this.DatFileContent.Position = 9 + entryId * this.GetSizeOfEntry(); + var reader = new BinaryReader(this.DatFileContent); + item.ItemID = reader.ReadUInt32(); //9 + item.Disabled = reader.ReadUInt32() == 1; //13 + item.ItemType = reader.ReadUInt32(); //17 + item.Unknown2 = reader.ReadBytes(4); //21 + item.Unknown3 = reader.ReadBytes(4); //25 + item.ClassNo1 = reader.ReadUInt32(); //29 + item.ClassNo2 = reader.ReadUInt32(); //33 + item.ClassNo3 = reader.ReadUInt32(); //37 + item.ClassNo4 = reader.ReadUInt32(); //41 + item.SlotNo1 = reader.ReadUInt32(); //45 + item.SlotNo2 = reader.ReadUInt32(); //49 + item.Unknown4 = reader.ReadBytes(4); //53 + item.IsCash = reader.ReadUInt32(); //57 + item.Unknown5 = reader.ReadBytes(4); //61 + item.Price = reader.ReadUInt32(); //65 + item.Unknown7 = reader.ReadBytes(4); //69 + item.MaxNumber = reader.ReadUInt32(); //73 + item.Unknown17 = reader.ReadBytes(12); //77 + item.MaximumLevelRequirement = reader.ReadUInt32(); //89 + item.SexNo = reader.ReadUInt32(); //93 + item.WeaponSomething = reader.ReadUInt32(); //97 + item.Unknown8 = reader.ReadBytes(4); //101 + item.R2C = reader.ReadBytes(16); //105 + item.Unknown9 = reader.ReadBytes(4); //121 + item.Stats = reader.ReadStats(); //125 + item.ElementalStats = reader.ReadElementalStats(); //149 + item.R7C = reader.ReadBytes(4); //213 + item.R8C = reader.ReadBytes(8); //217 + item.Speed = reader.ReadSingle(); //225 + item.Jump = reader.ReadSingle(); //229 + item.StatDefense = reader.ReadInt32(); //233 + item.MagicID = reader.ReadUInt32(); //237 + item.Unknown13 = reader.ReadBytes(4); //241 + item.Unknown14 = reader.ReadBytes(4); //245 + item.AdditionalHealthRecoveryVolume = reader.ReadInt32(); //249 + item.R9C_1 = reader.ReadBytes(4); //253 + item.AdditionalManaRecoveryVolume = reader.ReadInt32(); //257 + item.R9C_2 = reader.ReadBytes(4); //261 + item.R10C = reader.ReadBytes(8); //265 + item.AdditionalHealthPoints = reader.ReadInt32(); //273 + item.AdditionalManaPoints = reader.ReadInt32(); //277 + item.IsArrow = reader.ReadBoolean(); //281 + item.Unknown18 = reader.ReadBytes(7); //282 + item.AdditionalEvasionRate = reader.ReadInt32(); //289 + item.HitRate = reader.ReadInt32(); //293 + item.ChanceToHit = reader.ReadInt32(); //297 + item.MagicalDamage = reader.ReadInt32(); //301 + item.CriticalHitChance = reader.ReadInt32(); //305 + item.R12C = reader.ReadBytes(4); //309 + item.Unknown16 = reader.ReadBytes(4); //313 + item.MinimalAttackDamage = reader.ReadInt32(); //317 + item.MaximalAttackDamage = reader.ReadInt32(); //321 + item.PhysicalDamage = reader.ReadInt32(); //325 + item.CraftMaterial = reader.ReadCraftMaterial(); //329 + item.CraftResultAmount = reader.ReadUInt32(); //361 + item.R14C = reader.ReadBytes(4); //365 + item.CraftResultItem = reader.ReadUInt32(); //369 + item.R15C = reader.ReadBytes(4); //373 + item.R16C = reader.ReadBytes(20); //377 + item.InventoryX = reader.ReadInt32(); //397 + item.InventoryY = reader.ReadInt32(); //401 + item.InventoryWidth = reader.ReadInt32(); //405 + item.InventoryHeight = reader.ReadInt32(); //409 + item.SheetID = reader.ReadInt32(); //413 + item.Name = reader.ReadString(20); //417 + item.Description = reader.ReadString(85); //427 + item.Unknown1 = reader.ReadBytes(175); //493 + item.IsEnchantable = reader.ReadUInt32() == 1; //687 + item.Unknown1_2 = reader.ReadBytes(104); //687 + item.SetItems = reader.ReadArray(5); + item.SetID = reader.ReadUInt32(); //691 + item.Options = reader.ReadItemOptions(); //819 + item.Unknown19 = reader.ReadBytes(23); //835 + item.PetID = reader.ReadByte(); //858 + item.Unknown20 = reader.ReadBytes(20); //859 + item.HitBoxScaling = reader.ReadByte(); //879 + item.Unknown20_2 = reader.ReadBytes(13); //880 + item.ContainedItems = reader.ReadContainedItems(); //893 + item.IsQuestItem = reader.ReadBoolean(); //923 + item.MinimumLevelRequirement = reader.ReadByte(); //924 + item.Unknown21_2 = reader.ReadBytes(6); //925 + reader.Dispose(); //931 + return item; + } + + public void Dispose() + { + this.DatFileContent.Dispose(); + } +} diff --git a/Wonderking/Game/Reader/ItemReaderExtensions.cs b/Wonderking/Game/Reader/ItemReaderExtensions.cs new file mode 100644 index 0000000..df9d71f --- /dev/null +++ b/Wonderking/Game/Reader/ItemReaderExtensions.cs @@ -0,0 +1,96 @@ +using Wonderking.GameData.Item; + +namespace Wonderking.Game.Data.Item; + +public static class ItemReaderExtensions +{ + public static Stats ReadStats(this BinaryReader reader) + { + return new Stats() + { + Strength = reader.ReadInt32(), //125 + Dexterity = reader.ReadInt32(), //129 + Intelligence = reader.ReadInt32(), //133 + Vitality = reader.ReadInt32(), //137 + Luck = reader.ReadInt32(), //141 + Wisdom = reader.ReadInt32(), //145 + }; + } + + public static ElementalStats ReadElementalStats(this BinaryReader reader) + { + return new ElementalStats() + { + MinimumFireDamage = reader.ReadInt32(), //149 + MinimumWaterDamage = reader.ReadInt32(), //153 + MinimumDarkDamage = reader.ReadInt32(), //157 + MinimumHolyDamage = reader.ReadInt32(), //161 + MaximumFireDamage = reader.ReadInt32(), //165 + MaximumWaterDamage = reader.ReadInt32(), //169 + MaximumDarkDamage = reader.ReadInt32(), //173 + MaximumHolyDamage = reader.ReadInt32(), //177 + ElementFire = reader.ReadUInt32(), //181 + ElementWater = reader.ReadUInt32(), //185 + ElementDark = reader.ReadUInt32(), //189 + ElementHoly = reader.ReadUInt32(), //193 + FireResistance = reader.ReadInt32(), //197 + WaterResistance = reader.ReadInt32(), //201 + DarkResistance = reader.ReadInt32(), //205 + HolyResistance = reader.ReadInt32(), //209 + }; + } + + public static ContainedItem[] ReadContainedItems(this BinaryReader reader) + { + var list = new ContainedItem[5]; + //893 + for (var i = 0; i < 5; i++) + { + list[i].ID = reader.ReadInt16(); + } + + //903 + for (var i = 0; i < 5; i++) + { + list[i].ObtainChance = reader.ReadSingle(); + } + + return list; + } + + public static CraftMaterial[] ReadCraftMaterial(this BinaryReader reader) + { + var mats = new CraftMaterial[4]; + //329 + for (var i = 0; i < 4; ++i) + { + mats[i].ID = reader.ReadUInt32(); + } + + //345 + for (var i = 0; i < 4; ++i) + { + mats[i].ID = reader.ReadUInt32(); + } + + return mats; + } + + public static ItemOptions ReadItemOptions(this BinaryReader reader) + { + var options = new ItemOptions(); + + options.OptionAvailable = reader.ReadInt32() == 1; //819 + + var optionIDs = new List(4); + //823 + for (var i = 0; i < 3; i++) + { + optionIDs.Add((byte)reader.ReadUInt32()); + } + + options.OptionIDs = optionIDs; + + return options; + } +} diff --git a/Wonderking/Utils/ByteArrayConverter.cs b/Wonderking/Utils/ByteArrayConverter.cs new file mode 100644 index 0000000..76259f5 --- /dev/null +++ b/Wonderking/Utils/ByteArrayConverter.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Wonderking.Utils; + +public class ByteArrayConverter : JsonConverter +{ + public override byte[] Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var hexData = reader.GetString(); + if (hexData != null) + { + return hexData.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray(); + } + throw new JsonException("Hex string is null."); + } + + public override void Write( + Utf8JsonWriter writer, + byte[]? value, + JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + var hexData = BitConverter.ToString(value).Replace("-", string.Empty); + writer.WriteStringValue(hexData); + } + } +} diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index 78f67e5..f5b8158 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -5,6 +5,7 @@ enable net8.0;net7.0 strict + 12 @@ -30,5 +31,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + + + True + \ + + + + +