diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index a222302..f3c4424 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -52,28 +52,28 @@ public PlayerHouse GetPlayerHouse(int index) { if ((uint)index >= MainSaveOffsets.PlayerCount) throw new ArgumentOutOfRangeException(nameof(index)); - return Data.Slice(Offsets.PlayerHouseList + (index * PlayerHouse.SIZE), PlayerHouse.SIZE).ToClass(); + return new PlayerHouse(Data.Slice(Offsets.PlayerHouseList + (index * PlayerHouse.SIZE), PlayerHouse.SIZE)); } public void SetPlayerHouse(PlayerHouse h, int index) { if ((uint)index >= MainSaveOffsets.PlayerCount) throw new ArgumentOutOfRangeException(nameof(index)); - h.ToBytesClass().CopyTo(Data, Offsets.PlayerHouseList + (index * PlayerHouse.SIZE)); + h.Data.CopyTo(Data, Offsets.PlayerHouseList + (index * PlayerHouse.SIZE)); } public VillagerHouse GetVillagerHouse(int index) { if ((uint)index >= MainSaveOffsets.VillagerCount) throw new ArgumentOutOfRangeException(nameof(index)); - return Data.Slice(Offsets.NpcHouseList + (index * VillagerHouse.SIZE), VillagerHouse.SIZE).ToClass(); + return new VillagerHouse(Data.Slice(Offsets.NpcHouseList + (index * VillagerHouse.SIZE), VillagerHouse.SIZE)); } public void SetVillagerHouse(VillagerHouse h, int index) { if ((uint)index >= MainSaveOffsets.VillagerCount) throw new ArgumentOutOfRangeException(nameof(index)); - h.ToBytesClass().CopyTo(Data, Offsets.NpcHouseList + (index * VillagerHouse.SIZE)); + h.Data.CopyTo(Data, Offsets.NpcHouseList + (index * VillagerHouse.SIZE)); } public VillagerHouse[] GetVillagerHouses() diff --git a/NHSE.Core/Structures/GameFileDumper.cs b/NHSE.Core/Structures/GameFileDumper.cs index 97635a8..5aeb1fe 100644 --- a/NHSE.Core/Structures/GameFileDumper.cs +++ b/NHSE.Core/Structures/GameFileDumper.cs @@ -89,7 +89,7 @@ public static void DumpPlayerHouses(this HorizonSave sav, string path) private static void Dump(this PlayerHouse h, string player, string path) { var dest = Path.Combine(path, $"{player}.nhph"); - var data = h.ToBytesClass(); + var data = h.Data; File.WriteAllBytes(dest, data); } @@ -112,7 +112,7 @@ private static void Dump(this VillagerHouse h, string path, Villager v) { var name = GameInfo.Strings.GetVillager(v.InternalName); var dest = Path.Combine(path, $"{name}.nhvh"); - var data = h.ToBytesClass(); + var data = h.Data; File.WriteAllBytes(dest, data); } diff --git a/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs b/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs new file mode 100644 index 0000000..4865826 --- /dev/null +++ b/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; + +namespace NHSE.Core +{ + public class RoomItemManager + { + // Layer 1: Base Layer Placed Items + // Layer 2: Supported Layer Placed Items + // Layer 3: North Wall + // Layer 4: ??? + // Layer 5: ??? + // Layer 6: ??? + // Layer 7: Rug + // Layer 8: ??? + + public readonly RoomItemLayer[] Layers; + + public readonly PlayerRoom Room; + + public RoomItemManager(PlayerRoom room) + { + Layers = room.GetItemLayers(); + Room = room; + Debug.Assert(Layers.Length == 8); + } + + public void Save() => Room.SetItemLayers(Layers); + + // public bool IsOccupied(int x, int y) => Layers.Any(z => !z.GetTile(x, y).IsNone); + } +} diff --git a/NHSE.Core/Structures/Misc/GSaveItemName.cs b/NHSE.Core/Structures/Misc/GSaveItemName.cs index 62bdf3a..d4e73d4 100644 --- a/NHSE.Core/Structures/Misc/GSaveItemName.cs +++ b/NHSE.Core/Structures/Misc/GSaveItemName.cs @@ -14,6 +14,16 @@ public struct GSaveItemName [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; } + [field: FieldOffset(0x04)] public int FreeParam { get; set; } + + public Item ToItem() => this.ToBytes().ToClass(); + + public void CopyFrom(Item item) + { + UniqueID = item.ItemId; + SystemParam = item.SystemParam; + AdditionalParam = item.AdditionalParam; + FreeParam = item.FreeParam; + } } } diff --git a/NHSE.Core/Structures/Villager/IHouseInfo.cs b/NHSE.Core/Structures/Villager/IHouseInfo.cs index 6eb23c9..de45f34 100644 --- a/NHSE.Core/Structures/Villager/IHouseInfo.cs +++ b/NHSE.Core/Structures/Villager/IHouseInfo.cs @@ -9,7 +9,7 @@ public interface IHouseInfo WallType OrderWallUniqueID { get; set; } RoofType OrderRoofUniqueID { get; set; } DoorKind OrderDoorUniqueID { get; set; } - GSaveItemName DoorDecoItemName { get; set; } + Item DoorDecoItemName { get; set; } } public static class HouseInfoExtensions diff --git a/NHSE.Core/Structures/Villager/PlayerHouse.cs b/NHSE.Core/Structures/Villager/PlayerHouse.cs index 0372d4f..de8d814 100644 --- a/NHSE.Core/Structures/Villager/PlayerHouse.cs +++ b/NHSE.Core/Structures/Villager/PlayerHouse.cs @@ -1,23 +1,88 @@ -using System.Runtime.InteropServices; +using System; namespace NHSE.Core { - [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 4)] public class PlayerHouse : IHouseInfo { public const int SIZE = 0x26400; - [field: FieldOffset(0x000)] public uint HouseLevel { get; set; } - [field: FieldOffset(0x004)] public WallType WallUniqueID { get; set; } - [field: FieldOffset(0x006)] public RoofType RoofUniqueID { get; set; } - [field: FieldOffset(0x008)] public DoorKind DoorUniqueID { get; set; } - [field: FieldOffset(0x00A)] public WallType OrderWallUniqueID { get; set; } - [field: FieldOffset(0x00C)] public RoofType OrderRoofUniqueID { get; set; } - [field: FieldOffset(0x00E)] public DoorKind OrderDoorUniqueID { get; set; } + public readonly byte[] Data; + public PlayerHouse(byte[] data) => Data = data; - [field: FieldOffset(0x263D4)] public GSaveItemName DoorDecoItemName { get; set; } + public uint HouseLevel { get => BitConverter.ToUInt32(Data, 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, 0x00); } + public WallType WallUniqueID { get => (WallType)BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x04); } + public RoofType RoofUniqueID { get => (RoofType)BitConverter.ToUInt16(Data, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x06); } + public DoorKind DoorUniqueID { get => (DoorKind)BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public WallType OrderWallUniqueID { get => (WallType)BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + public RoofType OrderRoofUniqueID { get => (RoofType)BitConverter.ToUInt16(Data, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C); } + public DoorKind OrderDoorUniqueID { get => (DoorKind)BitConverter.ToUInt16(Data, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0E); } - [field: FieldOffset(0x263E0)] public GSaveItemName PostItemName { get; set; } - [field: FieldOffset(0x263E8)] public GSaveItemName OrderPostItemName { get; set; } + public EncryptedInt32 Loan + { + get => EncryptedInt32.Read(Data, 0x10); + set => value.Write(Data, 0x10); + } + + public EncryptedInt32 Unknown + { + get => EncryptedInt32.Read(Data, 0x18); + set => value.Write(Data, 0x18); + } + + public short[] GetEventFlags() + { + var result = new short[0x100 / 2]; + Buffer.BlockCopy(Data, 0x20, result, 0, result.Length * sizeof(short)); + return result; + } + + public void SetEventFlags(short[] value) => Buffer.BlockCopy(value, 0, Data, 0x20, value.Length * sizeof(short)); + + public const int MaxRoom = 6; + private const int RoomStart = 0x120; + + public PlayerRoom GetRoom(int roomIndex) + { + if ((uint)roomIndex >= MaxRoom) + throw new ArgumentOutOfRangeException(nameof(roomIndex)); + + var data = Data.Slice(RoomStart + (roomIndex * PlayerRoom.SIZE), PlayerRoom.SIZE); + return new PlayerRoom(data); + } + + public void SetRoom(int roomIndex, PlayerRoom room) + { + if ((uint)roomIndex >= MaxRoom) + throw new ArgumentOutOfRangeException(nameof(roomIndex)); + + room.Data.CopyTo(Data, RoomStart + (roomIndex * PlayerRoom.SIZE)); + } + + public sbyte NPC1 { get => (sbyte)Data[0x263D0]; set => Data[0x263D0] = (byte)value; } + public sbyte NPC2 { get => (sbyte)Data[0x263D1]; set => Data[0x263D1] = (byte)value; } + + // 2bytes padding + + public Item DoorDecoItemName + { + get => Data.Slice(0x263D4, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x263D4); + } + + public bool PlayerHouseFlag { get => Data[0x263DC] != 0; set => Data[0x263DC] = (byte)(value ? 1 : 0); } + + public Item PostItemName + { + get => Data.Slice(0x263E0, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x263E0); + } + + public Item OrderPostItemName + { + get => Data.Slice(0x263E8, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x263E8); + } + + // cockroach @ 0x263f0 -- meh } } diff --git a/NHSE.Core/Structures/Villager/PlayerRoom.cs b/NHSE.Core/Structures/Villager/PlayerRoom.cs new file mode 100644 index 0000000..6a2b75c --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerRoom.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace NHSE.Core +{ + public class PlayerRoom + { + public const int SIZE = 0x65C8; + + public readonly byte[] Data; + public PlayerRoom(byte[] data) => Data = data; + + /* + s_d8bc748b ItemLayerList[8]; // @0x0 size 0xc80, align 4 + s_c20a4615 ItemSwitchList[8]; // @0x6400 size 0x34, align 4 + GSaveAudioInfo AudioInfo; // @0x65a0 size 0x4, align 2 + GSaveRoomFloorWall RoomFloorWall; // @0x65a4 size 0x24, align 4 + */ + + public const int LayerCount = 8; + public RoomItemLayer[] GetItemLayers() => RoomItemLayer.GetArray(Data.Slice(0, LayerCount * RoomItemLayer.SIZE)); + public void SetItemLayers(IReadOnlyList value) => RoomItemLayer.SetArray(value).CopyTo(Data, 0); + } +} diff --git a/NHSE.Core/Structures/Villager/VillagerHouse.cs b/NHSE.Core/Structures/Villager/VillagerHouse.cs index 714c87b..94cdf5c 100644 --- a/NHSE.Core/Structures/Villager/VillagerHouse.cs +++ b/NHSE.Core/Structures/Villager/VillagerHouse.cs @@ -1,26 +1,33 @@ -using System.Runtime.InteropServices; +using System; namespace NHSE.Core { - [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 4)] public class VillagerHouse : IHouseInfo { 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 WallType WallUniqueID { get; set; } - [field: FieldOffset(0x00A)] public RoofType RoofUniqueID { get; set; } - [field: FieldOffset(0x00C)] public DoorKind DoorUniqueID { get; set; } - [field: FieldOffset(0x00E)] public WallType OrderWallUniqueID { get; set; } - [field: FieldOffset(0x010)] public RoofType OrderRoofUniqueID { get; set; } - [field: FieldOffset(0x012)] public DoorKind OrderDoorUniqueID { get; set; } + public readonly byte[] Data; + public VillagerHouse(byte[] data) => Data = data; - [field: FieldOffset(0x1C4)] public sbyte NPC1 { get; set; } - [field: FieldOffset(0x1C5)] public sbyte NPC2 { get; set; } + public uint HouseLevel { get => BitConverter.ToUInt32(Data, 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, 0x00); } + public uint HouseStatus { get => BitConverter.ToUInt32(Data, 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, 0x04); } + public WallType WallUniqueID { get => (WallType)BitConverter.ToUInt16(Data, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x08); } + public RoofType RoofUniqueID { get => (RoofType)BitConverter.ToUInt16(Data, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0A); } + public DoorKind DoorUniqueID { get => (DoorKind)BitConverter.ToUInt16(Data, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0C); } + public WallType OrderWallUniqueID { get => (WallType)BitConverter.ToUInt16(Data, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x0E); } + public RoofType OrderRoofUniqueID { get => (RoofType)BitConverter.ToUInt16(Data, 0x10); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x10); } + public DoorKind OrderDoorUniqueID { get => (DoorKind)BitConverter.ToUInt16(Data, 0x12); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x12); } - [field: FieldOffset(0x1C8)] public GSaveItemName DoorDecoItemName { get; set; } - [field: FieldOffset(0x1D0)] public sbyte BuildPlayer { get; set; } + public Item DoorDecoItemName + { + get => Data.Slice(0x1C8, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x1C8); + } + + public sbyte NPC1 { get => (sbyte)Data[0x1C4]; set => Data[0x1C4] = (byte)value; } + public sbyte NPC2 { get => (sbyte)Data[0x1C5]; set => Data[0x1C5] = (byte)value; } + + public sbyte BuildPlayer { get => (sbyte)Data[0x1D0]; set => Data[0x1D0] = (byte)value; } } } diff --git a/NHSE.Tests/MarshalTests.cs b/NHSE.Tests/MarshalTests.cs index e2a41ff..f75b16e 100644 --- a/NHSE.Tests/MarshalTests.cs +++ b/NHSE.Tests/MarshalTests.cs @@ -11,8 +11,6 @@ public class MarshalTests [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 MarshalPlayerHouse() => MarshalTest(PlayerHouse.SIZE); [Fact] public void MarshalVillagerHouseItem() => MarshalTestS(VillagerHouseItem.SIZE); [Fact] public void MarshalGSaveItemName() => MarshalTestS(GSaveItemName.SIZE);