diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index 0d4471f..555b8b1 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -84,6 +84,18 @@ public void SetAcreBytes(byte[] data) public FieldItem[] GetFieldItems() => FieldItem.GetArray(Data.Slice(Offsets.FieldItem, MapGrid.MapTileCount32x32 * FieldItem.SIZE * 2)); public void SetFieldItems(IReadOnlyList array) => FieldItem.SetArray(array).CopyTo(Data, Offsets.FieldItem); + public ushort OutsideFieldTemplateUniqueId + { + get => BitConverter.ToUInt16(Data, Offsets.Acres + AcreSizeAll); + set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Acres + AcreSizeAll); + } + + public ushort MainFieldParamUniqueID + { + get => BitConverter.ToUInt16(Data, Offsets.Acres + AcreSizeAll + 2); + set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Acres + AcreSizeAll + 2); + } + public uint PlazaX { get => BitConverter.ToUInt32(Data, Offsets.Acres + AcreSizeAll + 4); diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs index c8ac014..3e9147e 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs @@ -15,7 +15,7 @@ public abstract class MainSaveOffsets public const int PatternCount = 50; public abstract int Buildings { get; } - public const int BuildingCount = 40; // actual count unknown, max may be 46 + public const int BuildingCount = 46; public abstract int RecycleBin { get; } public const int RecycleBinCount = 40; diff --git a/NHSE.Core/Structures/TurnipStonk.cs b/NHSE.Core/Structures/TurnipStonk.cs index 82ff497..2c4e404 100644 --- a/NHSE.Core/Structures/TurnipStonk.cs +++ b/NHSE.Core/Structures/TurnipStonk.cs @@ -2,12 +2,12 @@ namespace NHSE.Core { - [StructLayout(LayoutKind.Sequential)] - public class TurnipStonk + [StructLayout(LayoutKind.Sequential, Size = SIZE)] + public class TurnipStonk // GSaveShopKabu { - public const int SIZE = sizeof(uint) * (1 + (2 * 7)); + public const int SIZE = 0x44; - public uint BuyPrice { get; set; } + public uint BuyPrice { get; set; } // KaburibaKabuka public uint SellSundayAM { get; set; } public uint SellSundayPM { get; set; } @@ -29,5 +29,8 @@ public class TurnipStonk public uint SellSaturdayAM { get; set; } public uint SellSaturdayPM { get; set; } + + public uint Pattern { get; set; } // KabukaPattern + public uint Unk_6C4E0099 { get; set; } } } diff --git a/NHSE.Core/Structures/Villager/VillagerHouse.cs b/NHSE.Core/Structures/Villager/VillagerHouse.cs new file mode 100644 index 0000000..9d31450 --- /dev/null +++ b/NHSE.Core/Structures/Villager/VillagerHouse.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace NHSE.Core +{ + [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 4)] + public class VillagerHouse + { + public const int SIZE = 0x1D4; + public const int ItemCount = 36; + + [field: FieldOffset(0x000)] public uint HouseLevel { get; set; } + [field: FieldOffset(0x004)] public uint HouseStatus { get; set; } + [field: FieldOffset(0x008)] public ushort WallUniqueID { get; set; } + [field: FieldOffset(0x00A)] public ushort RoofUniqueID { get; set; } + [field: FieldOffset(0x00C)] public ushort DoorUniqueID { get; set; } + [field: FieldOffset(0x00E)] public ushort OrderWallUniqueID { get; set; } + [field: FieldOffset(0x010)] public ushort OrderRoofUniqueID { get; set; } + [field: FieldOffset(0x012)] public ushort OrderDoorUniqueID { get; set; } + + [field: FieldOffset(0x1C4)] public sbyte NPC1 { get; set; } + [field: FieldOffset(0x1C5)] public sbyte NPC2 { get; set; } + + [field: FieldOffset(0x1C8)] public GSaveItemName DoorDecoItemName { get; set; } + [field: FieldOffset(0x1D0)] public sbyte BuildPlayer { get; set; } + } + + [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)] + public struct GSaveItemName + { + public const int SIZE = 0x08; + [field: FieldOffset(0x00)] public ushort UniqueID { get; set; } + [field: FieldOffset(0x02)] public byte SystemParam { get; set; } + [field: FieldOffset(0x03)] public byte AdditionalParam { get; set; } + [field: FieldOffset(0x04)] public uint FreeParam { get; set; } + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => UniqueID; + public override bool Equals(object obj) => obj is GSaveItemName i && i.Equals(this); + public bool Equals(GSaveItemName obj) => obj.UniqueID == UniqueID && obj.SystemParam == SystemParam && obj.AdditionalParam == AdditionalParam; + public static bool operator ==(GSaveItemName left, GSaveItemName right) => left.Equals(right); + public static bool operator !=(GSaveItemName left, GSaveItemName right) => !(left == right); + } +} diff --git a/NHSE.Core/Structures/Villager/VillagerHouseItem.cs b/NHSE.Core/Structures/Villager/VillagerHouseItem.cs new file mode 100644 index 0000000..9b6625e --- /dev/null +++ b/NHSE.Core/Structures/Villager/VillagerHouseItem.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace NHSE.Core +{ + [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)] + public struct VillagerHouseItem // GFtrData + { + public const int SIZE = 0xC; + + [field: FieldOffset(0x0)] public ushort ItemId { get; set; } + [field: FieldOffset(0x2)] public byte Flags0 { get; set; } + [field: FieldOffset(0x3)] public byte Flags1 { get; set; } + [field: FieldOffset(0x4)] public ushort Count { get; set; } + [field: FieldOffset(0x6)] public ushort UseCount { get; set; } + + [field: FieldOffset(0x8)] public byte UnitX { get; set; } + [field: FieldOffset(0x9)] public byte UnitY { get; set; } + [field: FieldOffset(0xA)] public byte Direction { get; set; } + [field: FieldOffset(0xB)] public byte Layer { get; set; } + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => ItemId; + public override bool Equals(object obj) => obj is VillagerHouseItem i && i.Equals(this); + public bool Equals(VillagerHouseItem obj) => obj.ItemId == ItemId && obj.Flags0 == Flags0 && obj.Flags1 == Flags1; + public static bool operator ==(VillagerHouseItem left, VillagerHouseItem right) => left.Equals(right); + public static bool operator !=(VillagerHouseItem left, VillagerHouseItem right) => !(left == right); + } +} diff --git a/NHSE.Tests/MarshalTests.cs b/NHSE.Tests/MarshalTests.cs index f3d5cda..a434ac4 100644 --- a/NHSE.Tests/MarshalTests.cs +++ b/NHSE.Tests/MarshalTests.cs @@ -6,55 +6,34 @@ namespace NHSE.Tests { public class MarshalTests { - [Fact] - public void ItemMarshal() - { - var item = new Item(); - var bytes = item.ToBytesClass(); - bytes.Length.Should().Be(Item.SIZE); - } + [Fact] public void MarshalItem() => MarshalTest(Item.SIZE); + [Fact] public void MarshalVillagerItem() => MarshalTest(VillagerItem.SIZE); + [Fact] public void MarshalTerrainTile() => MarshalTest(TerrainTile.SIZE); + [Fact] public void MarshalTurnip() => MarshalTest(TurnipStonk.SIZE); + [Fact] public void MarshalBuilding() => MarshalTest(Building.SIZE); + [Fact] public void MarshalVillagerHouse() => MarshalTest(VillagerHouse.SIZE); - [Fact] - public void VillagerItemMarshal() - { - var item = new VillagerItem(); - var bytes = item.ToBytesClass(); - bytes.Length.Should().Be(VillagerItem.SIZE); - } + [Fact] public void MarshalVillagerHouseItem() => MarshalTestS(VillagerHouseItem.SIZE); + [Fact] public void MarshalGSaveItemName() => MarshalTestS(GSaveItemName.SIZE); - [Fact] - public void FieldItemMarshal() + private static void MarshalTest(int size) where T : class, new() { - var item = new FieldItem(); - var bytes = item.ToBytesClass(); - bytes.Length.Should().Be(Item.SIZE); - - var other = new FieldItem(); - item.CopyFrom(other); - } - - [Fact] - public void TerrainTileMarshal() - { - var obj = new TerrainTile(); + var obj = new T(); var bytes = obj.ToBytesClass(); - bytes.Length.Should().Be(TerrainTile.SIZE); + bytes.Length.Should().Be(size); + + var recomputed = bytes.ToClass(); + recomputed.Should().NotBeNull(); } - [Fact] - public void TurnipMarshal() + private static void MarshalTestS(int size) where T : struct { - var obj = new TurnipStonk(); - var bytes = obj.ToBytesClass(); - bytes.Length.Should().Be(TurnipStonk.SIZE); - } + var obj = new T(); + var bytes = obj.ToBytes(); + bytes.Length.Should().Be(size); - [Fact] - public void BuildingMarshal() - { - var obj = new Building(); - var bytes = obj.ToBytesClass(); - bytes.Length.Should().Be(Building.SIZE); + var recomputed = bytes.ToStructure(); + recomputed.Should().NotBeNull(); } } } diff --git a/NHSE.WinForms/Controls/VillagerEditor.cs b/NHSE.WinForms/Controls/VillagerEditor.cs index 196487d..4250ccb 100644 --- a/NHSE.WinForms/Controls/VillagerEditor.cs +++ b/NHSE.WinForms/Controls/VillagerEditor.cs @@ -123,8 +123,8 @@ private void B_LoadVillager_Click(object sender, EventArgs e) var player0 = Origin; if (!v.IsOriginatedFrom(player0)) { - var result = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, - $"Imported Villager did not originate from Resident Rep's data.", "Update values?"); + const string msg = "Imported Villager did not originate from Resident Rep's data."; + var result = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, msg, "Update values?"); if (result == DialogResult.Cancel) return; if (result == DialogResult.Yes)